Skip to content

Commit

Permalink
Merge pull request #5 from rarimo/feature/referral-events
Browse files Browse the repository at this point in the history
Feature: referral events
  • Loading branch information
violog authored Feb 19, 2024
2 parents 65cd4f2 + 1af7d1a commit 0c0deea
Show file tree
Hide file tree
Showing 36 changed files with 712 additions and 247 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM golang:1.21-alpine as buildbase
FROM golang:1.22-alpine as buildbase

RUN apk add git build-base

Expand Down
26 changes: 19 additions & 7 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,36 @@ event_types:
- name: get_poh
title: Get PoH credential
reward: 50
description: Lorem ipsum dolor sit amet
description: Prove that you are human
frequency: one-time
expires_at: 2020-01-01T00:00:00Z
- name: free_weekly
title: Free weekly points
reward: 100
frequency: weekly
description: Lorem ipsum dolor sit amet
description: Get free points every week by visiting the platform and claiming your reward
- name: daily_login
title: Daily login
reward: 5
frequency: daily
description: Lorem ipsum dolor sit amet
- name: verify_proof
title: Verify proof {:id}
reward: 10
description: Login every day
disabled: true
- name: be_referred
title: Referral welcome bonus
reward: 5
frequency: one-time
description: Be referred by a friend and get a welcome bonus
no_auto_open: true
- name: referral_common
title: Refer new users
reward: 25
frequency: one-time
description: Refer friends and get a reward for each friend who verifies the passport
- name: referral_specific
title: Refer user {:did}
reward: 25
frequency: unlimited
description: Lorem ipsum dolor sit amet
description: The user {:did} has verified the passport. Claim the reward!
no_auto_open: true

auth:
Expand Down
5 changes: 5 additions & 0 deletions docs/spec/components/schemas/Balance.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ allOf:
type: object
required:
- amount
- referral_id
- is_verified
- created_at
- updated_at
Expand All @@ -17,6 +18,10 @@ allOf:
format: int64
description: Amount of points
example: 580
referral_id:
type: string
description: Referral ID used to build a referral link and send it to friends
example: "zgsScguZ"
is_verified:
type: boolean
description: Whether the user has scanned passport
Expand Down
14 changes: 14 additions & 0 deletions docs/spec/components/schemas/CreateBalance.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
allOf:
- $ref: '#/components/schemas/CreateBalanceKey'
- type: object
x-go-is-request: true
properties:
attributes:
type: object
required:
- referred_by
properties:
referred_by:
type: string
description: ID of the referrer from the link
example: "rCx18MZ4"
8 changes: 1 addition & 7 deletions docs/spec/components/schemas/EventStaticMeta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@ properties:
description: |
Event frequency, which means how often you can fulfill
certain task and claim the reward.
enum: [one-time, daily, weekly, unlimited, custom]
no_auto_open:
type: boolean
description: |
If true, the event will not be created with `open` status automatically
when user creates the balance.
example: true
enum: [one-time, daily, weekly, unlimited]
expires_at:
type: string
format: time.Time
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ post:
- data
properties:
data:
$ref: '#/components/schemas/CreateBalanceKey'
$ref: '#/components/schemas/CreateBalance'
responses:
201:
description: Created
Expand All @@ -31,6 +31,8 @@ post:
$ref: '#/components/schemas/Balance'
401:
$ref: '#/components/responses/invalidAuth'
404:
$ref: '#/components/responses/notFound'
409:
description: Balance already exists for provided DID
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ patch:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
403:
description: This event type was disabled and cannot be claimed
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
404:
$ref: '#/components/responses/notFound'
500:
Expand Down
28 changes: 15 additions & 13 deletions internal/assets/migrations/001_initial.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ AS $$ BEGIN NEW.updated_at = EXTRACT('EPOCH' FROM NOW()); RETURN NEW; END; $$;
CREATE TABLE IF NOT EXISTS balances
(
did text PRIMARY KEY,
amount bigint not null default 0,
created_at integer not null default EXTRACT('EPOCH' FROM NOW()),
updated_at integer not null default EXTRACT('EPOCH' FROM NOW()),
amount bigint NOT NULL default 0,
created_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
updated_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
referral_id text UNIQUE NOT NULL,
referred_by text REFERENCES balances (referral_id),
passport_hash text UNIQUE,
passport_expires timestamp without time zone
);
Expand All @@ -25,12 +27,12 @@ CREATE TYPE event_status AS ENUM ('open', 'fulfilled', 'claimed');

CREATE TABLE IF NOT EXISTS events
(
id uuid PRIMARY KEY not null default gen_random_uuid(),
user_did text not null REFERENCES balances (did),
type text not null,
status event_status not null,
created_at integer not null default EXTRACT('EPOCH' FROM NOW()),
updated_at integer not null default EXTRACT('EPOCH' FROM NOW()),
id uuid PRIMARY KEY NOT NULL default gen_random_uuid(),
user_did text NOT NULL REFERENCES balances (did),
type text NOT NULL,
status event_status NOT NULL,
created_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
updated_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW()),
meta jsonb,
points_amount integer
);
Expand All @@ -48,10 +50,10 @@ EXECUTE FUNCTION trigger_set_updated_at();
CREATE TABLE IF NOT EXISTS withdrawals
(
id uuid PRIMARY KEY default gen_random_uuid(),
user_did text not null REFERENCES balances (did),
amount integer not null,
address text not null,
created_at integer not null default EXTRACT('EPOCH' FROM NOW())
user_did text NOT NULL REFERENCES balances (did),
amount integer NOT NULL,
address text NOT NULL,
created_at integer NOT NULL default EXTRACT('EPOCH' FROM NOW())
);

CREATE INDEX IF NOT EXISTS withdrawals_user_did_index ON withdrawals using btree (user_did);
Expand Down
2 changes: 2 additions & 0 deletions internal/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/alecthomas/kingpin"
"github.com/rarimo/rarime-points-svc/internal/config"
"github.com/rarimo/rarime-points-svc/internal/service"
"github.com/rarimo/rarime-points-svc/internal/service/workers/expirywatch"
"github.com/rarimo/rarime-points-svc/internal/service/workers/reopener"
"github.com/rarimo/rarime-points-svc/internal/service/workers/sbtcheck"
"gitlab.com/distributed_lab/kit/kv"
Expand Down Expand Up @@ -57,6 +58,7 @@ func Run(args []string) bool {
run(func(context.Context, config.Config) { sbtcheck.Run(ctx, cfg) })
run(service.Run)
run(reopener.Run)
run(expirywatch.Run)
case migrateUpCmd.FullCommand():
err = MigrateUp(cfg)
case migrateDownCmd.FullCommand():
Expand Down
7 changes: 5 additions & 2 deletions internal/data/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ type Balance struct {
Amount int64 `db:"amount"`
CreatedAt int32 `db:"created_at"`
UpdatedAt int32 `db:"updated_at"`
ReferralID string `db:"referral_id"`
ReferredBy sql.NullString `db:"referred_by"`
PassportHash sql.NullString `db:"passport_hash"`
PassportExpires sql.NullTime `db:"passport_expires"`
Rank *int `db:"rank"`
}

type BalancesQ interface {
New() BalancesQ
Insert(did string) error
Insert(Balance) error
UpdateAmountBy(points int64) error
SetPassport(hash string, exp time.Time) error

Page(*pgdb.OffsetPageParams) BalancesQ
Select() ([]Balance, error)
Get() (*Balance, error)
WithRank() BalancesQ

WithRank() BalancesQ
FilterByDID(string) BalancesQ
FilterByReferralID(string) BalancesQ
}
1 change: 1 addition & 0 deletions internal/data/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type EventsQ interface {
New() EventsQ
Insert(...Event) error
Update(status EventStatus, meta json.RawMessage, points *int64) (*Event, error)
Delete() (rowsAffected int64, err error)
Transaction(func() error) error

Page(*pgdb.CursorPageParams) EventsQ
Expand Down
33 changes: 5 additions & 28 deletions internal/data/evtypes/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package evtypes

import (
"fmt"
"time"

"github.com/rarimo/rarime-points-svc/resources"
"gitlab.com/distributed_lab/figure/v3"
"gitlab.com/distributed_lab/kit/comfig"
"gitlab.com/distributed_lab/kit/kv"
Expand All @@ -26,15 +24,7 @@ func NewConfig(getter kv.Getter) EventTypeser {
func (c *config) EventTypes() Types {
return c.once.Do(func() interface{} {
var raw struct {
Types []struct {
Name string `fig:"name,required"`
Description string `fig:"description,required"`
Reward int64 `fig:"reward,required"`
Title string `fig:"title,required"`
Frequency Frequency `fig:"frequency,required"`
ExpiresAt *time.Time `fig:"expires_at"`
NoAutoOpen bool `fig:"no_auto_open"`
} `fig:"types,required"`
Types []EventConfig `fig:"types,required"`
}

err := figure.Out(&raw).
Expand All @@ -44,34 +34,21 @@ func (c *config) EventTypes() Types {
panic(fmt.Errorf("failed to figure out event_types: %s", err))
}

inner := make(map[string]resources.EventStaticMeta, len(raw.Types))
m := make(map[string]EventConfig, len(raw.Types))
for _, t := range raw.Types {
if !checkFreqValue(t.Frequency) {
panic(fmt.Errorf("invalid frequency: %s", t.Frequency))
}

inner[t.Name] = resources.EventStaticMeta{
Name: t.Name,
Description: t.Description,
Reward: t.Reward,
Title: t.Title,
Frequency: t.Frequency.String(),
ExpiresAt: t.ExpiresAt,
NoAutoOpen: t.NoAutoOpen,
}
}

if _, ok := inner[TypeGetPoH]; !ok {
panic(fmt.Errorf("event_types: missing %s entry", TypeGetPoH))
m[t.Name] = t
}

return Types{inner}
return Types{m, raw.Types}
}).(Types)
}

func checkFreqValue(f Frequency) bool {
switch f {
case OneTime, Daily, Weekly, Unlimited, Custom:
case OneTime, Daily, Weekly, Unlimited:
return true
}
return false
Expand Down
34 changes: 34 additions & 0 deletions internal/data/evtypes/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package evtypes

import (
"time"
)

type filter func(EventConfig) bool

func FilterExpired(ev EventConfig) bool {
return ev.ExpiresAt != nil && ev.ExpiresAt.Before(time.Now().UTC())
}

func FilterInactive(ev EventConfig) bool {
return ev.Disabled || FilterExpired(ev)
}

func FilterNotOpenable(ev EventConfig) bool {
return FilterInactive(ev) || ev.NoAutoOpen
}

func FilterByFrequency(f Frequency) func(EventConfig) bool {
return func(ev EventConfig) bool {
return ev.Frequency != f
}
}

func isFiltered(ev EventConfig, filters ...filter) bool {
for _, f := range filters {
if f(ev) {
return true
}
}
return false
}
Loading

0 comments on commit 0c0deea

Please sign in to comment.