Skip to content

Commit

Permalink
Add levels to balances. Level specify which possibilities user have
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaptoss committed May 23, 2024
1 parent 0a48336 commit 14feaf9
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 25 deletions.
15 changes: 15 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,21 @@ event_types:
description: Event that become fulfilled when another user verify you proof nationality (user that verify must have verified passport)
short_description: Short description

levels:
levels:
- lvl: 1
threshold: 0
referrals: 5
withdrawal_allowed: false
- lvl: 2
threshold: 20
referrals: 5
withdrawal_allowed: true
- lvl: 3
threshold: 40
referrals: 5
withdrawal_allowed: true

auth:
addr: http://rarime-auth

Expand Down
15 changes: 6 additions & 9 deletions docs/spec/components/schemas/Balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,16 @@ allOf:
type: object
required:
- amount
- is_verified
- is_disabled
- created_at
- updated_at
- level
properties:
amount:
type: integer
format: int64
description: Amount of points
example: 580
is_verified:
type: boolean
description: Whether the user has scanned passport
example: true
is_disabled:
type: boolean
description: |
Expand Down Expand Up @@ -56,7 +52,8 @@ allOf:
example: ["73k3bdYaFWM", "9csIL7dW65m"]
items:
type: string
is_withdrawal_allowed:
type: boolean
description: Whether the user can withdraw tokens. Returned only for the single user.
example: true
level:
type: integer
format: int
description: The level indicates how many possibilities the user has
example: 2
1 change: 1 addition & 0 deletions internal/assets/migrations/001_initial.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS balances
created_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
updated_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
referred_by text UNIQUE,
level INT NOT NULL DEFAULT 1
);

CREATE INDEX IF NOT EXISTS balances_page_index ON balances (amount, updated_at) WHERE referred_by IS NOT NULL;
Expand Down
38 changes: 38 additions & 0 deletions internal/config/levels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package config

import (
"gitlab.com/distributed_lab/figure/v3"
"gitlab.com/distributed_lab/kit/kv"
"gitlab.com/distributed_lab/logan/v3/errors"
)

type Level struct {
Level int `fig:"lvl,required"`
Threshold int `fig:"threshold,required"`
Referrals int `fig:"referrals,required"`
WithdrawalAllowed bool `fig:"withdrawal_allowed"`
}

type Levels map[int]Level

func (c *config) Leveler() Levels {
return c.leveler.Do(func() interface{} {
var cfg struct {
Lvls []Level `fig:"levels,required"`
}

err := figure.Out(&cfg).
From(kv.MustGetStringMap(c.getter, "levels")).
Please()
if err != nil {
panic(errors.Wrap(err, "failed to figure out levels config"))
}

res := make(Levels)
for _, v := range cfg.Lvls {
res[v.Level] = v
}

return res
}).(Levels)
}
2 changes: 2 additions & 0 deletions internal/config/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Config interface {
evtypes.EventTypeser
sbtcheck.SbtChecker

Leveler() Levels
Verifier() *zk.Verifier
PointPrice() PointsPrice
}
Expand All @@ -33,6 +34,7 @@ type config struct {
evtypes.EventTypeser
sbtcheck.SbtChecker

leveler comfig.Once
verifier comfig.Once
pointPrice comfig.Once
getter kv.Getter
Expand Down
2 changes: 2 additions & 0 deletions internal/data/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ type Balance struct {
UpdatedAt int32 `db:"updated_at"`
ReferredBy sql.NullString `db:"referred_by"`
Rank *int `db:"rank"`
Level int `db:"level"`
}

type BalancesQ interface {
New() BalancesQ
Insert(Balance) error
UpdateAmountBy(points int64) error
SetReferredBy(referralCode string) error
SetLevel(level int) error

Page(*pgdb.OffsetPageParams) BalancesQ
Select() ([]Balance, error)
Expand Down
11 changes: 11 additions & 0 deletions internal/data/pg/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,17 @@ func (q *balances) SetReferredBy(referralCode string) error {
return nil
}

func (q *balances) SetLevel(level int) error {
stmt := q.updater.
Set("level", level)

if err := q.db.Exec(stmt); err != nil {
return fmt.Errorf("set level: %w", err)
}

return nil
}

func (q *balances) Page(page *pgdb.OffsetPageParams) data.BalancesQ {
q.selector = page.ApplyTo(q.selector, "amount", "updated_at")
return q
Expand Down
52 changes: 40 additions & 12 deletions internal/service/handlers/claim_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@ package handlers
import (
"fmt"
"net/http"
"slices"

"github.com/rarimo/decentralized-auth-svc/pkg/auth"
"github.com/rarimo/rarime-points-svc/internal/data"
"github.com/rarimo/rarime-points-svc/internal/data/evtypes"
"github.com/rarimo/rarime-points-svc/internal/service/requests"
"github.com/rarimo/rarime-points-svc/resources"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
"gitlab.com/distributed_lab/logan/v3/errors"
)

func ClaimEvent(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -48,15 +49,6 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
ape.RenderErr(w, problems.Forbidden())
return
}
if event.Type == evtypes.TypePassportScan {
if event.PointsAmount == nil {
Log(r).WithError(err).Errorf("PointsAmount can't be nil for event %s",
event.Type)
ape.RenderErr(w, problems.InternalError())
return
}
evType.Reward = *event.PointsAmount
}

balance, err := BalancesQ(r).FilterByNullifier(event.Nullifier).FilterDisabled().Get()
if err != nil {
Expand All @@ -70,7 +62,7 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
return
}

event, err = claimEventWithPoints(*event, evType.Reward, r)
event, err = claimEventWithPoints(r, *event, evType.Reward, balance)
if err != nil {
Log(r).WithError(err).Errorf("Failed to claim event %s and accrue %d points to the balance %s",
event.ID, evType.Reward, event.Nullifier)
Expand All @@ -90,8 +82,25 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
}

// requires: event exist
func claimEventWithPoints(event data.Event, reward int64, r *http.Request) (claimed *data.Event, err error) {
func claimEventWithPoints(r *http.Request, event data.Event, reward int64, balance *data.Balance) (claimed *data.Event, err error) {
err = EventsQ(r).Transaction(func() error {
refsCount, level := levelsToUp(r, balance.Level, reward+balance.Amount)
if level != 0 {
count, err := ReferralsQ(r).FilterByNullifier(event.Nullifier).Count()
if err != nil {
return errors.Wrap(err, "failed to get referral count")
}

refToAdd := prepareReferralsToAdd(event.Nullifier, uint64(refsCount), count)
if err = ReferralsQ(r).Insert(refToAdd...); err != nil {
return errors.Wrap(err, "failed to insert referrals")
}

if err = BalancesQ(r).FilterByNullifier(event.Nullifier).SetLevel(level); err != nil {
return errors.Wrap(err, "failed to update level")
}
}

updated, err := EventsQ(r).FilterByID(event.ID).Update(data.EventClaimed, nil, &reward)
if err != nil {
return fmt.Errorf("update event status: %w", err)
Expand All @@ -108,6 +117,25 @@ func claimEventWithPoints(event data.Event, reward int64, r *http.Request) (clai
return
}

// Take summary points after claim and calculate new lvls
func levelsToUp(r *http.Request, level int, points int64) (referralsToAdd int, newLevel int) {
var lvls []int
for k, v := range Levels(r) {
if k <= level {
continue
}
if int64(v.Threshold) > points {
break
}

referralsToAdd += v.Referrals
lvls = append(lvls, k)
}

newLevel = slices.Max(lvls)
return
}

func newClaimEventResponse(
event data.Event,
meta resources.EventStaticMeta,
Expand Down
11 changes: 11 additions & 0 deletions internal/service/handlers/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const (
broadcasterCtxKey
pointPriceCtxKey
verifierCtxKey
levelsCtxKey
)

func CtxLog(entry *logan.Entry) func(context.Context) context.Context {
Expand Down Expand Up @@ -127,3 +128,13 @@ func CtxVerifier(verifier *zk.Verifier) func(context.Context) context.Context {
func Verifier(r *http.Request) *zk.Verifier {
return r.Context().Value(verifierCtxKey).(*zk.Verifier)
}

func CtxLevels(levels config.Levels) func(context.Context) context.Context {
return func(ctx context.Context) context.Context {
return context.WithValue(ctx, levelsCtxKey, levels)
}
}

func Levels(r *http.Request) config.Levels {
return r.Context().Value(levelsCtxKey).(config.Levels)
}
1 change: 1 addition & 0 deletions internal/service/handlers/get_balance.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func newBalanceModel(balance data.Balance) resources.Balance {
CreatedAt: balance.CreatedAt,
UpdatedAt: balance.UpdatedAt,
Rank: balance.Rank,
Level: balance.Level,
},
}
}
Expand Down
1 change: 1 addition & 0 deletions internal/service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func Run(ctx context.Context, cfg config.Config) {
handlers.CtxEventTypes(cfg.EventTypes()),
handlers.CtxBroadcaster(cfg.Broadcaster()),
handlers.CtxPointPrice(cfg.PointPrice()),
handlers.CtxLevels(cfg.Leveler()),
),
handlers.DBCloneMiddleware(cfg.DB()),
)
Expand Down
6 changes: 2 additions & 4 deletions resources/model_balance_attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ type BalanceAttributes struct {
CreatedAt int32 `json:"created_at"`
// Whether the user was not referred by anybody, but the balance with some events was reserved. It happens when the user fulfills some event before the balance creation.
IsDisabled bool `json:"is_disabled"`
// Whether the user has scanned passport
IsVerified bool `json:"is_verified"`
// Whether the user can withdraw tokens. Returned only for the single user.
IsWithdrawalAllowed *bool `json:"is_withdrawal_allowed,omitempty"`
// The level indicates how many possibilities the user has
Level int `json:"level"`
// Rank of the user in the full leaderboard. Returned only for the single user.
Rank *int `json:"rank,omitempty"`
// Unix timestamp of the last points accruing
Expand Down

0 comments on commit 14feaf9

Please sign in to comment.