Skip to content

Commit

Permalink
Add infinity referrals logic. DevOps can specified if on specific lvl…
Browse files Browse the repository at this point in the history
… user can get infinity referral code
  • Loading branch information
Zaptoss committed Jul 8, 2024
1 parent 948a60d commit 118e727
Show file tree
Hide file tree
Showing 16 changed files with 303 additions and 48 deletions.
3 changes: 3 additions & 0 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ levels:
- lvl: 1
threshold: 0
referrals: 1
- lvl: 2
threshold: 10
infinity: true

auth:
addr: http://rarime-auth
Expand Down
42 changes: 42 additions & 0 deletions configs/geo-auth-svc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
log:
level: debug
disable_sentry: true

db:
url: postgres://auth:auth@geo-auth-db:5432/auth?sslmode=disable

listener:
addr: :80

jwt:
secret_key: 0xbcb14d08dc1472b80f49eaf72f0873ac314a7146083bbf3694df8c3238949f2ec0971427f5509382a2812c700e541e328cd6ec65a1383c3ca435cdf55862dc4b
access_expiration_time: 1h
refresh_expiration_time: 72h

cookies:
domain: "*"
secure: true
same_site: 4

auth_verifier:
verification_key_path: "./auth_verification_key.json"
disabled: true

passport_verifier:
verification_key_path: "./passport_verification_key.json"
allowed_age: 18
allowed_identity_timestamp: 1715698750

root_verifier:
rpc: https://rpc.evm.node1.mainnet-beta.rarimo.com:443
contract: 0x59123B0D0f95EB23183879f476B6939cEFd2cF3B
request_timeout: 10s

sig_verifier:
verification_key: "bcb14d08dc1472b80f49eaf72f0873ac314a7146083bbf3694df8c3238949f2ec0971427f5509382a2812c700e541e328cd6ec65a1383c3ca435cdf55862dc4b"

points:
admin: 0x8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918
url: http://geo-points
default_verified: true
disabled: true
81 changes: 81 additions & 0 deletions configs/geo-points-svc.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
log:
level: debug
disable_sentry: true

db:
url: postgres://points:points@geo-points-db:5432/points?sslmode=disable

listener:
addr: :80

event_types:
types:
- name: passport_scan
title: Points for passport scan
reward: 10
description: Get points for scan passport and share data
short_description: Short description
frequency: one-time
action_url: https://...
logo: https://...
auto_claim: true
- name: free_weekly
title: Free weekly points
reward: 1
frequency: weekly
description: Get free points every week by visiting the platform and claiming your reward
short_description: Short description
- name: be_referred
title: Referral welcome bonus
reward: 1
frequency: one-time
description: Be referred by a friend and get a welcome bonus
short_description: Short description
no_auto_open: true
- name: referral_common
reward: 1
frequency: one-time
title: Refer new users
short_description: Refer friends and get a reward for each friend who verifies the passport
description: Refer friends and get a reward for each friend who verifies the passport
- name: referral_specific
title: Refer user {:did}
reward: 1
frequency: unlimited
description: The user {:did} has verified the passport. Claim the reward!
short_description: Short description
no_auto_open: true
auto_claim: true
- name: meetup_participation
title: Prove your participation by scanning QR code
reward: 5
frequency: unlimited
description: Prove your participation by scanning QR code
short_description: Short description
auto_claim: true
qr_code_value: "aawdiojawndgawjhkdla"

levels:
levels:
- lvl: 1
threshold: 0
referrals: 10
- lvl: 2
threshold: 10
infinity: true

auth:
addr: http://geo-auth

verifier:
verification_key_path: "./verification_key.json"
allowed_age: 18
allowed_identity_timestamp: 1715698750

root_verifier:
rpc: https://rpc.evm.node1.mainnet-beta.rarimo.com:443
contract: 0x59123B0D0f95EB23183879f476B6939cEFd2cF3B
request_timeout: 10s

sig_verifier:
verification_key: "bcb14d08dc1472b80f49eaf72f0873ac314a7146083bbf3694df8c3238949f2ec0971427f5509382a2812c700e541e328cd6ec65a1383c3ca435cdf55862dc4b"
9 changes: 5 additions & 4 deletions docs/spec/components/schemas/Balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ allOf:
format: int
description: Rank of the user in the full leaderboard. Returned only for the single user.
example: 294
referral_code:
type: string
description: User referral code. Returned only for the single user.
example: "6xM70VgX4eh"
referral_codes:
type: array
description: Referral codes. Returned only for the single user.
items:
$ref: '#/components/schemas/ReferralCode'
level:
type: integer
format: int
Expand Down
28 changes: 28 additions & 0 deletions docs/spec/components/schemas/ReferralCode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
type: object
required:
- id
- status
properties:
id:
type: string
description: Referral code itself, unique identifier
example: "bDSCcQB8Hhk"
status:
type: string
description: |
Status of the code, belonging to this user (referrer):
1. infinity: the code have unlimited usage count and user can get points for each user who scanned passport
2. active: the code is not used yet by another user (referee)
3. awaiting: the code is used by referee who has scanned passport, but the referrer hasn't yet
4. rewarded: the code is used, both referee and referrer have scanned passports
5. consumed: the code is used by referee who has not scanned passport yet
The list is sorted by priority. E.g. if the referee has scanned passport,
but referrer not, the status would be `consumed`. If both not scann passport yet
status would be `awaiting`.
enum:
- infinity
- active
- awaiting
- rewarded
- consumed
3 changes: 2 additions & 1 deletion internal/assets/migrations/001_initial.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ CREATE TABLE IF NOT EXISTS referrals
(
id text PRIMARY KEY,
nullifier TEXT NOT NULL REFERENCES balances (nullifier),
usage_left INTEGER NOT NULL DEFAULT 1
usage_left INTEGER NOT NULL DEFAULT 1,
infinity BOOLEAN NOT NULL DEFAULT FALSE
);

CREATE INDEX IF NOT EXISTS referrals_nullifier_index ON referrals (nullifier);
Expand Down
10 changes: 7 additions & 3 deletions internal/config/levels.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
)

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

type Levels map[int]Level
Expand Down Expand Up @@ -63,6 +64,9 @@ func (l Levels) LvlUp(currentLevel int, totalAmount int64) (refCoundToAdd int, n
}

newLevel = slices.Max(lvls)
if l[newLevel].Infinity {
refCoundToAdd = -1
}
return
}

Expand Down
49 changes: 45 additions & 4 deletions internal/data/pg/referrals.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ func (q *referrals) Insert(referrals ...data.Referral) error {
return nil
}

stmt := squirrel.Insert(referralsTable).Columns("id", "nullifier", "usage_left")
stmt := squirrel.Insert(referralsTable).Columns("id", "nullifier", "usage_left", "infinity")
for _, ref := range referrals {
stmt = stmt.Values(ref.ID, ref.Nullifier, ref.UsageLeft)
stmt = stmt.Values(ref.ID, ref.Nullifier, ref.UsageLeft, ref.Infinity)
}

if err := q.db.Exec(stmt); err != nil {
Expand All @@ -51,10 +51,13 @@ func (q *referrals) Insert(referrals ...data.Referral) error {
return nil
}

func (q *referrals) Update(usageLeft int) (*data.Referral, error) {
func (q *referrals) Update(usageLeft int, infinity bool) (*data.Referral, error) {
var res data.Referral

if err := q.db.Get(&res, q.updater.Set("usage_left", usageLeft).Suffix("RETURNING *")); err != nil {
if err := q.db.Get(&res, q.updater.SetMap(map[string]interface{}{
"usage_left": usageLeft,
"infinity": infinity,
}).Suffix("RETURNING *")); err != nil {
return nil, fmt.Errorf("update referral: %w", err)
}

Expand Down Expand Up @@ -96,10 +99,48 @@ func (q *referrals) Count() (uint64, error) {
return res.Count, nil
}

func (q *referrals) WithStatus() data.ReferralsQ {
var (
joinReferrer = fmt.Sprintf("JOIN %s rr ON %s.nullifier = rr.nullifier", balancesTable, referralsTable)
joinReferee = fmt.Sprintf("LEFT JOIN %s re ON %s.id = re.referred_by", balancesTable, referralsTable)

status = fmt.Sprintf(`CASE
WHEN infinity = TRUE THEN '%s'
WHEN usage_left > 0 THEN '%s'
WHEN rr.is_verified = FALSE AND re.is_verified = TRUE THEN '%s'
WHEN rr.is_verified = TRUE AND re.is_verified = TRUE THEN '%s'
ELSE '%s'
END AS status`,
data.StatusInfinity, data.StatusActive, data.StatusAwaiting,
data.StatusRewarded, data.StatusConsumed,
)
)

q.selector = q.selector.Column(status).
JoinClause(joinReferrer).
JoinClause(joinReferee)

return q
}

func (q *referrals) Consume(id string) error {
stmt := q.consumer.Where(squirrel.Eq{"id": id})

if err := q.db.Exec(stmt); err != nil {
return fmt.Errorf("consume referral [%v]: %w", id, err)
}

return nil
}

func (q *referrals) FilterByNullifier(nullifier string) data.ReferralsQ {
return q.applyCondition(squirrel.Eq{fmt.Sprintf("%s.nullifier", referralsTable): nullifier})
}

func (q *referrals) FilterInactive() data.ReferralsQ {
return q.applyCondition(squirrel.Or{squirrel.Gt{fmt.Sprintf("%s.usage_left", referralsTable): 0}, squirrel.Eq{fmt.Sprintf("%s.infinity", referralsTable): true}})
}

func (q *referrals) applyCondition(cond squirrel.Sqlizer) data.ReferralsQ {
q.selector = q.selector.Where(cond)
q.consumer = q.consumer.Where(cond)
Expand Down
15 changes: 14 additions & 1 deletion internal/data/referrals.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,19 @@
package data

const (
StatusInfinity = "infinity"
StatusActive = "active"
StatusAwaiting = "awaiting"
StatusRewarded = "rewarded"
StatusConsumed = "consumed"
)

type Referral struct {
ID string `db:"id"`
Nullifier string `db:"nullifier"`
UsageLeft int32 `db:"usage_left"`
Infinity bool `db:"infinity"`
Status string `db:"status"`
}

type ReferralsQ interface {
Expand All @@ -13,8 +23,11 @@ type ReferralsQ interface {
Select() ([]Referral, error)
Get(id string) (*Referral, error)
Count() (uint64, error)
Consume(id string) error

Update(usageLeft int) (*Referral, error)
WithStatus() ReferralsQ
Update(usageLeft int, infinity bool) (*Referral, error)

FilterByNullifier(string) ReferralsQ
FilterInactive() ReferralsQ
}
29 changes: 20 additions & 9 deletions internal/service/handlers/claim_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/rarimo/geo-points-svc/internal/data"
"github.com/rarimo/geo-points-svc/internal/data/evtypes"
"github.com/rarimo/geo-points-svc/internal/data/pg"
"github.com/rarimo/geo-points-svc/internal/service/referralid"
"github.com/rarimo/geo-points-svc/internal/service/requests"
"github.com/rarimo/geo-points-svc/resources"
"gitlab.com/distributed_lab/ape"
Expand Down Expand Up @@ -141,16 +142,26 @@ func DoClaimEventUpdates(

func doLvlUpAndReferralsUpdate(levels config.Levels, referralsQ data.ReferralsQ, balance data.Balance, reward int64) (level int, err error) {
refsCount, level := levels.LvlUp(balance.Level, reward+balance.Amount)
if refsCount > 0 {
count, err := referralsQ.New().FilterByNullifier(balance.Nullifier).Count()
if err != nil {
return 0, fmt.Errorf("failed to get referral count: %w", err)
}
// we need +2 because refsCount can be -1
referrals := make([]data.Referral, 0, refsCount+2)

refToAdd := prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count)
if err = referralsQ.New().Insert(refToAdd...); err != nil {
return 0, fmt.Errorf("failed to insert referrals: %w", err)
}
// count used to calculate ref code
count, err := referralsQ.New().FilterByNullifier(balance.Nullifier).Count()
if err != nil {
return 0, fmt.Errorf("failed to get referral count: %w", err)
}
switch {
case refsCount > 0:
referrals = append(referrals, prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count)...)
case refsCount == -1:
referrals = append(referrals, data.Referral{
ID: referralid.New(balance.Nullifier, count),
Nullifier: balance.Nullifier,
Infinity: true,
})
}
if err = referralsQ.New().Insert(referrals...); err != nil {
return 0, fmt.Errorf("failed to insert referrals: %w", err)
}

return level, nil
Expand Down
Loading

0 comments on commit 118e727

Please sign in to comment.