Skip to content

Commit

Permalink
v2 qrcodes logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Zaptoss committed Oct 8, 2024
1 parent 5a50b2b commit 704b0c1
Show file tree
Hide file tree
Showing 15 changed files with 580 additions and 49 deletions.
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 qr_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 qr_codes;
CREATE TRIGGER set_updated_at
BEFORE UPDATE
ON qr_codes
FOR EACH ROW
EXECUTE FUNCTION trigger_set_updated_at_ts();


-- +migrate Down
DROP TABLE IF EXISTS qr_codes;
DROP FUNCTION IF EXISTS trigger_set_updated_at_ts();
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
FilterByQRCode(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"
TypeQRCode = "qr_code"
)

const (
Expand Down
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) FilterByQRCode(qrCode string) data.EventsQ {
return q.applyCondition(squirrel.Eq{"meta->>'qr_code'": qrCode})
}

func (q *events) FilterInactiveNotClaimed(types ...string) data.EventsQ {
if len(types) == 0 {
return q
Expand Down
114 changes: 114 additions & 0 deletions internal/data/pg/qr_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 qrCodesTable = "qr_codes"

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

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

func (q *qrCodesQ) New() data.QRCodesQ {
return NewQRCodesQ(q.db)
}

func (q *qrCodesQ) Insert(qrCodes ...data.QRCode) error {
if len(qrCodes) == 0 {
return nil
}

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

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

return nil
}

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

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

return nil
}

func (q *qrCodesQ) Select() ([]data.QRCode, error) {
var res []data.QRCode

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

return res, nil
}

func (q *qrCodesQ) Get() (*data.QRCode, error) {
var res data.QRCode

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

return &res, nil
}

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

func (q *qrCodesQ) 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 qrCodes: %w", err)
}

return res.Count, nil
}

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

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

func (q *qrCodesQ) applyCondition(cond squirrel.Sqlizer) data.QRCodesQ {
q.selector = q.selector.Where(cond)
q.updater = q.updater.Where(cond)
q.counter = q.counter.Where(cond)
return q
}
40 changes: 40 additions & 0 deletions internal/data/qr_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 QRCode 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 QRCodesQ interface {
New() QRCodesQ
Insert(...QRCode) error
Update(map[string]any) error

Page(*pgdb.OffsetPageParams) QRCodesQ

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

FilterByID(...string) QRCodesQ
FilterByNullifier(...string) QRCodesQ
}
79 changes: 79 additions & 0 deletions internal/service/handlers/create_qr_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package handlers

import (
"crypto/rand"
"database/sql"
"encoding/hex"
"net/http"

"github.com/rarimo/geo-auth-svc/pkg/auth"
"github.com/rarimo/geo-points-svc/internal/data"
"github.com/rarimo/geo-points-svc/internal/service/requests"
"github.com/rarimo/geo-points-svc/resources"
"gitlab.com/distributed_lab/ape"
"gitlab.com/distributed_lab/ape/problems"
)

func CreateQRCode(w http.ResponseWriter, r *http.Request) {
req, err := requests.NewCreateQRCode(r)
if err != nil {
ape.RenderErr(w, problems.BadRequest(err)...)
return
}

if !auth.Authenticates(UserClaims(r), auth.AdminGrant) {
ape.RenderErr(w, problems.Unauthorized())
return
}

var (
_ = req.Data.Attributes.Nullifier
dataUsageCount = req.Data.Attributes.UsageCount
dataReward = req.Data.Attributes.Reward
usageCount = 1
reward = 10
)

qrValue := make([]byte, 10)
_, err = rand.Read(qrValue[:])
if err != nil {
Log(r).WithError(err).Error("Failed to get rand bytes")
ape.RenderErr(w, problems.InternalError())
return
}

if dataUsageCount != nil {
usageCount = *dataUsageCount
}

if dataReward != nil {
reward = *dataReward
}

qr := data.QRCode{
ID: "one_time_" + hex.EncodeToString(qrValue),
Nullifier: sql.NullString{},
UsageCount: usageCount,
Reward: reward,
}

if err = QRCodesQ(r).Insert(qr); err != nil {
Log(r).WithError(err).Error("Failed to insert qr code")
ape.RenderErr(w, problems.InternalError())
return
}

ape.Render(w, resources.QrCodeRequest{
Data: resources.QrCode{
Key: resources.Key{
ID: qr.ID,
Type: resources.QR_CODE,
},
Attributes: resources.QrCodeAttributes{
Reward: &reward,
UsageCount: &usageCount,
},
},
})

}
11 changes: 11 additions & 0 deletions internal/service/handlers/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const (
dailyQuestionsCtxKey
dailyQuestionsCfgCtxKey
abstractionCtxKey
qrCodesQCtxKey
)

func CtxLog(entry *logan.Entry) func(context.Context) context.Context {
Expand Down Expand Up @@ -112,6 +113,16 @@ func WithdrawalsQ(r *http.Request) data.WithdrawalsQ {
return r.Context().Value(withdrawalsQCtxKey).(data.WithdrawalsQ).New()
}

func CtxQRCodesQ(q data.QRCodesQ) func(context.Context) context.Context {
return func(ctx context.Context) context.Context {
return context.WithValue(ctx, qrCodesQCtxKey, q)
}
}

func QRCodesQ(r *http.Request) data.QRCodesQ {
return r.Context().Value(qrCodesQCtxKey).(data.QRCodesQ).New()
}

func CtxUserClaims(claim []resources.Claim) func(context.Context) context.Context {
return func(ctx context.Context) context.Context {
return context.WithValue(ctx, userClaimsCtxKey, claim)
Expand Down
1 change: 1 addition & 0 deletions internal/service/handlers/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func DBCloneMiddleware(db *pgdb.DB) func(http.Handler) http.Handler {
CtxEventTypesQ(pg.NewEventTypes(clone)),
CtxDailyQuestionsQ(pg.NewDailyQuestionsQ(clone)),
CtxWithdrawalsQ(pg.NewWithdrawals(clone)),
CtxQRCodesQ(pg.NewQRCodesQ(clone)),
}

for _, extender := range extenders {
Expand Down
Loading

0 comments on commit 704b0c1

Please sign in to comment.