Skip to content

Commit

Permalink
Merge pull request #47 from rarimo/feat/one-time-qrcodes
Browse files Browse the repository at this point in the history
Feat/one time qrcodes
  • Loading branch information
Zaptoss authored Oct 8, 2024
2 parents 4829055 + fe442f5 commit 22d2d19
Show file tree
Hide file tree
Showing 25 changed files with 726 additions and 61 deletions.
25 changes: 25 additions & 0 deletions docs/spec/components/schemas/BonusCode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
allOf:
- $ref: '#/components/schemas/BonusCodeKey'
- type: object
x-go-is-request: true
required:
- attributes
properties:
attributes:
type: object
properties:
reward:
type: integer
format: int
description: Reward for this bonus code
default: 10
example: 10
usage_count:
type: integer
format: int
description: Specify how many times bonus code can be scaned. Omit if bonus code must have infinity usage count
example: 1
nullifier:
type: string
description: For creating personal bonus codes
example: "0xabc...123"
12 changes: 12 additions & 0 deletions docs/spec/components/schemas/BonusCodeKey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
type: object
required:
- id
- type
properties:
id:
type: string
description: Bonus code value
example: "one_time_abcdefg..xyz"
type:
type: string
enum: [ bonus_code ]
8 changes: 7 additions & 1 deletion docs/spec/components/schemas/EventClaimingState.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,15 @@ allOf:
attributes:
required:
- claimed
- reward
type: object
properties:
claimed:
type: bool
example: true
description: If passport scan event was automatically claimed
description: If passport scan event was automatically claimed
reward:
type: integer
format: int64
description: Reward amount in points
example: 50
37 changes: 37 additions & 0 deletions docs/spec/paths/integrations@geo-points-svc@v2@[email protected]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
post:
tags:
- Bonus Codes
summary: Createbonus code
description: Create custom bonus code
operationId: createCode
security:
- BearerAuth: []
requestBody:
required: true
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/BonusCode'
responses:
200:
description: Success
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/BonusCode'
400:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
500:
$ref: '#/components/responses/internalError'
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
post:
tags:
- Bonus Codes
summary: Send code
description: Send a code and get a reward
operationId: submitCode
security:
- BearerAuth: []
requestBody:
required: true
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/BonusCodeKey'
responses:
200:
description: Success
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/EventClaimingState'
400:
$ref: '#/components/responses/invalidParameter'
401:
$ref: '#/components/responses/invalidAuth'
403:
description: May be user haven't verified passport
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
404:
$ref: '#/components/responses/notFound'
409:
description: QR code already submited
content:
application/vnd.api+json:
schema:
$ref: '#/components/schemas/Errors'
500:
$ref: '#/components/responses/internalError'
27 changes: 27 additions & 0 deletions internal/assets/migrations/006_one_time_qr_code.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- +migrate Up
CREATE OR REPLACE FUNCTION trigger_set_updated_at_ts() RETURNS trigger
LANGUAGE plpgsql
AS $$ BEGIN NEW.updated_at = (NOW() AT TIME ZONE 'utc'); RETURN NEW; END; $$;

CREATE TABLE IF NOT EXISTS bonus_codes (
id TEXT PRIMARY KEY,
nullifier TEXT REFERENCES balances (nullifier),
reward BIGINT NOT NULL,
usage_count BIGINT NOT NULL DEFAULT 0,
infinity BOOLEAN NOT NULL DEFAULT FALSE,

updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
created_at TIMESTAMP NOT NULL DEFAULT NOW()
);

DROP TRIGGER IF EXISTS set_updated_at ON bonus_codes;
CREATE TRIGGER set_updated_at
BEFORE UPDATE
ON bonus_codes
FOR EACH ROW
EXECUTE FUNCTION trigger_set_updated_at_ts();


-- +migrate Down
DROP TABLE IF EXISTS bonus_codes;
DROP FUNCTION IF EXISTS trigger_set_updated_at_ts();
40 changes: 40 additions & 0 deletions internal/data/bonus_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package data

import (
"database/sql"
"time"

"gitlab.com/distributed_lab/kit/pgdb"
)

const (
ColNullifier = "nullifier"
ColUsageCount = "usage_count"
ColInfinity = "infinity"
)

type BonusCode struct {
ID string `db:"id"`
Nullifier sql.NullString `db:"nullifier"`
Reward int `db:"reward"`
UsageCount int `db:"usage_count"`
Infinity bool `db:"infinity"`

UpdatedAt time.Time `db:"updated_at"`
CreatedAt time.Time `db:"created_at"`
}

type BonusCodesQ interface {
New() BonusCodesQ
Insert(...BonusCode) error
Update(map[string]any) error

Page(*pgdb.OffsetPageParams) BonusCodesQ

Get() (*BonusCode, error)
Select() ([]BonusCode, error)
Count() (uint64, error)

FilterByID(...string) BonusCodesQ
FilterByNullifier(...string) BonusCodesQ
}
1 change: 1 addition & 0 deletions internal/data/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ type EventsQ interface {
FilterByType(...string) EventsQ
FilterByNotType(types ...string) EventsQ
FilterByUpdatedAtBefore(int64) EventsQ
FilterByBonusCode(string) EventsQ

FilterTodayEvents(offset int) EventsQ

Expand Down
1 change: 1 addition & 0 deletions internal/data/evtypes/models/extra.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
TypePollParticipation = "poll_participation"
TypeEarlyTest = "early_test"
TypeDailyQuestion = "daily_question"
TypeBonusCode = "bonus_code"
)

const (
Expand Down
114 changes: 114 additions & 0 deletions internal/data/pg/bonus_codes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package pg

import (
"database/sql"
"errors"
"fmt"

"github.com/Masterminds/squirrel"
"github.com/rarimo/geo-points-svc/internal/data"
"gitlab.com/distributed_lab/kit/pgdb"
)

const bonusCodesTable = "bonus_codes"

type bonusCodesQ struct {
db *pgdb.DB
selector squirrel.SelectBuilder
updater squirrel.UpdateBuilder
counter squirrel.SelectBuilder
}

func NewBonusCodesQ(db *pgdb.DB) data.BonusCodesQ {
return &bonusCodesQ{
db: db,
selector: squirrel.Select("id", bonusCodesTable+".nullifier AS nullifier", "usage_count", "infinity", "reward").From(bonusCodesTable),
updater: squirrel.Update(bonusCodesTable),
counter: squirrel.Select("COUNT(*) as count").From(bonusCodesTable),
}
}

func (q *bonusCodesQ) New() data.BonusCodesQ {
return NewBonusCodesQ(q.db)
}

func (q *bonusCodesQ) Insert(bonusCodes ...data.BonusCode) error {
if len(bonusCodes) == 0 {
return nil
}

stmt := squirrel.Insert(bonusCodesTable).Columns("id", "nullifier", "reward", "usage_count", "infinity")
for _, bonusCode := range bonusCodes {
stmt = stmt.Values(bonusCode.ID, bonusCode.Nullifier, bonusCode.Reward, bonusCode.UsageCount, bonusCode.Infinity)
}

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

return nil
}

func (q *bonusCodesQ) Update(values map[string]any) error {

if err := q.db.Exec(q.updater.SetMap(values)); err != nil {
return fmt.Errorf("update bonusCode: %w", err)
}

return nil
}

func (q *bonusCodesQ) Select() ([]data.BonusCode, error) {
var res []data.BonusCode

if err := q.db.Select(&res, q.selector); err != nil {
return nil, fmt.Errorf("select bonusCodes: %w", err)
}

return res, nil
}

func (q *bonusCodesQ) Get() (*data.BonusCode, error) {
var res data.BonusCode

if err := q.db.Get(&res, q.selector); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
return nil, fmt.Errorf("get bonusCode: %w", err)
}

return &res, nil
}

func (q *bonusCodesQ) Page(page *pgdb.OffsetPageParams) data.BonusCodesQ {
q.selector = page.ApplyTo(q.selector, "updated_at")
return q
}

func (q *bonusCodesQ) Count() (uint64, error) {
var res struct {
Count uint64 `db:"count"`
}

if err := q.db.Get(&res, q.counter); err != nil {
return 0, fmt.Errorf("count bonusCodes: %w", err)
}

return res.Count, nil
}

func (q *bonusCodesQ) FilterByNullifier(nullifiers ...string) data.BonusCodesQ {
return q.applyCondition(squirrel.Eq{fmt.Sprintf("%s.nullifier", bonusCodesTable): nullifiers})
}

func (q *bonusCodesQ) FilterByID(ids ...string) data.BonusCodesQ {
return q.applyCondition(squirrel.Eq{fmt.Sprintf("%s.id", bonusCodesTable): ids})
}

func (q *bonusCodesQ) applyCondition(cond squirrel.Sqlizer) data.BonusCodesQ {
q.selector = q.selector.Where(cond)
q.updater = q.updater.Where(cond)
q.counter = q.counter.Where(cond)
return q
}
4 changes: 4 additions & 0 deletions internal/data/pg/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ func (q *events) FilterByQuestionID(id int) data.EventsQ {
return q.applyCondition(squirrel.Eq{"meta->>'question_id'": id})
}

func (q *events) FilterByBonusCode(bonusCode string) data.EventsQ {
return q.applyCondition(squirrel.Eq{"meta->>'bonus_code'": bonusCode})
}

func (q *events) FilterInactiveNotClaimed(types ...string) data.EventsQ {
if len(types) == 0 {
return q
Expand Down
Loading

0 comments on commit 22d2d19

Please sign in to comment.