Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/one time qrcodes #47

Merged
merged 6 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ]
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
Loading