From 403fef624abb4ef911641e6d2f77a17dec9601ad Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 6 Feb 2024 15:48:33 +0200 Subject: [PATCH 1/4] Withdrawal history DB migration and interface --- internal/assets/migrations/001_initial.sql | 14 +++++++ internal/data/balances.go | 26 +++++++++++++ internal/data/{main.go => events.go} | 43 ++++++---------------- internal/data/withdrawals.go | 23 ++++++++++++ 4 files changed, 74 insertions(+), 32 deletions(-) create mode 100644 internal/data/balances.go rename internal/data/{main.go => events.go} (72%) create mode 100644 internal/data/withdrawals.go diff --git a/internal/assets/migrations/001_initial.sql b/internal/assets/migrations/001_initial.sql index 439d801..4a4de96 100644 --- a/internal/assets/migrations/001_initial.sql +++ b/internal/assets/migrations/001_initial.sql @@ -43,7 +43,21 @@ CREATE TRIGGER set_updated_at FOR EACH ROW 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()) +); + +CREATE INDEX IF NOT EXISTS withdrawals_user_did_index ON withdrawals using btree (user_did); + -- +migrate Down +DROP INDEX IF EXISTS withdrawals_user_did_index; +DROP TABLE IF EXISTS withdrawals; + DROP TRIGGER IF EXISTS set_updated_at ON events; DROP INDEX IF EXISTS events_type_index; DROP INDEX IF EXISTS events_user_did_index; diff --git a/internal/data/balances.go b/internal/data/balances.go new file mode 100644 index 0000000..e7f3ed4 --- /dev/null +++ b/internal/data/balances.go @@ -0,0 +1,26 @@ +package data + +import ( + "gitlab.com/distributed_lab/kit/pgdb" +) + +type Balance struct { + DID string `db:"did"` + Amount int32 `db:"amount"` + CreatedAt int32 `db:"created_at"` + UpdatedAt int32 `db:"updated_at"` + Rank *int `db:"rank"` +} + +type BalancesQ interface { + New() BalancesQ + Insert(did string) error + UpdateAmountBy(points int32) error + + Page(*pgdb.OffsetPageParams) BalancesQ + Select() ([]Balance, error) + Get() (*Balance, error) + WithRank() BalancesQ + + FilterByDID(string) BalancesQ +} diff --git a/internal/data/main.go b/internal/data/events.go similarity index 72% rename from internal/data/main.go rename to internal/data/events.go index 7854b79..51f8601 100644 --- a/internal/data/main.go +++ b/internal/data/events.go @@ -19,6 +19,17 @@ func (s EventStatus) String() string { return string(s) } +type Event struct { + ID string `db:"id"` + UserDID string `db:"user_did"` + Type string `db:"type"` + Status EventStatus `db:"status"` + CreatedAt int32 `db:"created_at"` + UpdatedAt int32 `db:"updated_at"` + Meta Jsonb `db:"meta"` + PointsAmount sql.NullInt32 `db:"points_amount"` +} + type EventsQ interface { New() EventsQ Insert(...Event) error @@ -37,35 +48,3 @@ type EventsQ interface { FilterByType(...string) EventsQ FilterByUpdatedAtBefore(int64) EventsQ } - -type BalancesQ interface { - New() BalancesQ - Insert(did string) error - UpdateAmountBy(points int32) error - - Page(*pgdb.OffsetPageParams) BalancesQ - Select() ([]Balance, error) - Get() (*Balance, error) - WithRank() BalancesQ - - FilterByDID(string) BalancesQ -} - -type Event struct { - ID string `db:"id"` - UserDID string `db:"user_did"` - Type string `db:"type"` - Status EventStatus `db:"status"` - CreatedAt int32 `db:"created_at"` - UpdatedAt int32 `db:"updated_at"` - Meta Jsonb `db:"meta"` - PointsAmount sql.NullInt32 `db:"points_amount"` -} - -type Balance struct { - DID string `db:"did"` - Amount int32 `db:"amount"` - CreatedAt int32 `db:"created_at"` - UpdatedAt int32 `db:"updated_at"` - Rank *int `db:"rank"` -} diff --git a/internal/data/withdrawals.go b/internal/data/withdrawals.go new file mode 100644 index 0000000..61fcf55 --- /dev/null +++ b/internal/data/withdrawals.go @@ -0,0 +1,23 @@ +package data + +import ( + "gitlab.com/distributed_lab/kit/pgdb" +) + +type Withdrawal struct { + ID string `db:"id"` + UserDID string `db:"user_did"` + Amount int32 `db:"amount"` + Address string `db:"address"` + CreatedAt int32 `db:"created_at"` +} + +type WithdrawalsQ interface { + New() WithdrawalsQ + Insert(Withdrawal) error + + Page(*pgdb.CursorPageParams) WithdrawalsQ + Select() ([]Withdrawal, error) + + FilterByUserDID(string) WithdrawalsQ +} From c34ffabdd542916fd2fa985f9a4340895c05b034 Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 6 Feb 2024 16:05:34 +0200 Subject: [PATCH 2/4] Withdrawal history handler --- internal/data/pg/withdrawals.go | 61 +++++++++++++++++++ internal/service/handlers/ctx.go | 11 ++++ internal/service/handlers/list_withdrawals.go | 58 ++++++++++++++++++ internal/service/requests/list_withdrawals.go | 27 ++++++++ internal/service/router.go | 19 +++--- 5 files changed, 169 insertions(+), 7 deletions(-) create mode 100644 internal/data/pg/withdrawals.go create mode 100644 internal/service/handlers/list_withdrawals.go create mode 100644 internal/service/requests/list_withdrawals.go diff --git a/internal/data/pg/withdrawals.go b/internal/data/pg/withdrawals.go new file mode 100644 index 0000000..63d1ae0 --- /dev/null +++ b/internal/data/pg/withdrawals.go @@ -0,0 +1,61 @@ +package pg + +import ( + "fmt" + + "github.com/Masterminds/squirrel" + "github.com/rarimo/rarime-points-svc/internal/data" + "gitlab.com/distributed_lab/kit/pgdb" +) + +const withdrawalsTable = "withdrawals" + +type withdrawals struct { + db *pgdb.DB + selector squirrel.SelectBuilder +} + +func NewWithdrawals(db *pgdb.DB) data.WithdrawalsQ { + return &withdrawals{ + db: db, + selector: squirrel.Select("*").From(withdrawalsTable), + } +} + +func (q *withdrawals) New() data.WithdrawalsQ { + return NewWithdrawals(q.db.Clone()) +} + +func (q *withdrawals) Insert(w data.Withdrawal) error { + stmt := squirrel.Insert(withdrawalsTable).SetMap(map[string]interface{}{ + "user_did": w.UserDID, + "amount": w.Amount, + "address": w.Address, + }) + + if err := q.db.Exec(stmt); err != nil { + return fmt.Errorf("insert withdrawal [%+v]: %w", w, err) + } + + return nil +} + +func (q *withdrawals) Page(page *pgdb.CursorPageParams) data.WithdrawalsQ { + q.selector = page.ApplyTo(q.selector, "created_at") + return q +} + +func (q *withdrawals) Select() ([]data.Withdrawal, error) { + var res []data.Withdrawal + + if err := q.db.Select(&res, q.selector); err != nil { + return nil, fmt.Errorf("select withdrawals: %w", err) + } + + return res, nil +} + +func (q *withdrawals) FilterByUserDID(did string) data.WithdrawalsQ { + q.selector = q.selector.Where(squirrel.Eq{"user_did": did}) + return q +} diff --git a/internal/service/handlers/ctx.go b/internal/service/handlers/ctx.go index 14c6ac2..2604717 100644 --- a/internal/service/handlers/ctx.go +++ b/internal/service/handlers/ctx.go @@ -17,6 +17,7 @@ const ( logCtxKey ctxKey = iota eventsQCtxKey balancesQCtxKey + withdrawalsQCtxKey eventTypesCtxKey userClaimsCtxKey broadcasterCtxKey @@ -53,6 +54,16 @@ func BalancesQ(r *http.Request) data.BalancesQ { return r.Context().Value(balancesQCtxKey).(data.BalancesQ).New() } +func CtxWithdrawalsQ(q data.WithdrawalsQ) func(context.Context) context.Context { + return func(ctx context.Context) context.Context { + return context.WithValue(ctx, withdrawalsQCtxKey, q) + } +} + +func WithdrawalsQ(r *http.Request) data.WithdrawalsQ { + return r.Context().Value(withdrawalsQCtxKey).(data.WithdrawalsQ).New() +} + func CtxEventTypes(types evtypes.Types) func(context.Context) context.Context { return func(ctx context.Context) context.Context { return context.WithValue(ctx, eventTypesCtxKey, types) diff --git a/internal/service/handlers/list_withdrawals.go b/internal/service/handlers/list_withdrawals.go new file mode 100644 index 0000000..9d288e2 --- /dev/null +++ b/internal/service/handlers/list_withdrawals.go @@ -0,0 +1,58 @@ +package handlers + +import ( + "net/http" + + "github.com/rarimo/rarime-auth-svc/pkg/auth" + "github.com/rarimo/rarime-points-svc/internal/data" + "github.com/rarimo/rarime-points-svc/internal/service/requests" + "github.com/rarimo/rarime-points-svc/resources" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func ListWithdrawals(w http.ResponseWriter, r *http.Request) { + req, err := requests.NewListWithdrawals(r) + if err != nil { + ape.RenderErr(w, problems.BadRequest(err)...) + return + } + + if !auth.Authenticates(UserClaims(r), auth.UserGrant(req.DID)) { + ape.RenderErr(w, problems.Unauthorized()) + return + } + + withdrawals, err := WithdrawalsQ(r).FilterByUserDID(req.DID).Page(&req.CursorPageParams).Select() + if err != nil { + Log(r).WithError(err).Error("Failed to get withdrawal list") + ape.RenderErr(w, problems.InternalError()) + return + } + + var last string + if len(withdrawals) > 0 { + last = withdrawals[len(withdrawals)-1].ID + } + + resp := newWithdrawalsResponse(withdrawals) + resp.Links = req.CursorParams.GetLinks(r, last) +} + +func newWithdrawalsResponse(withdrawals []data.Withdrawal) resources.WithdrawalListResponse { + list := make([]resources.Withdrawal, len(withdrawals)) + for i, w := range withdrawals { + list[i] = resources.Withdrawal{ + Key: resources.Key{ + ID: w.ID, + Type: resources.WITHDRAWAL, + }, + Attributes: resources.WithdrawalAttributes{ + Amount: w.Amount, + Address: w.Address, + CreatedAt: w.CreatedAt, + }, + } + } + return resources.WithdrawalListResponse{Data: list} +} diff --git a/internal/service/requests/list_withdrawals.go b/internal/service/requests/list_withdrawals.go new file mode 100644 index 0000000..774836e --- /dev/null +++ b/internal/service/requests/list_withdrawals.go @@ -0,0 +1,27 @@ +package requests + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi" + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/rarimo/rarime-points-svc/internal/service/page" + "gitlab.com/distributed_lab/urlval/v4" +) + +type ListWithdrawals struct { + DID string + page.CursorParams +} + +func NewListWithdrawals(r *http.Request) (req ListWithdrawals, err error) { + if err = urlval.Decode(r.URL.Query(), &req); err != nil { + return req, validation.Errors{ + "query": fmt.Errorf("failed to decode query: %w", err), + } + } + + req.DID = chi.URLParam(r, "did") + return req, req.Validate() +} diff --git a/internal/service/router.go b/internal/service/router.go index 01cfdbb..7c535b1 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -17,21 +17,26 @@ func (s *service) router() chi.Router { handlers.CtxLog(s.log), handlers.CtxEventsQ(pg.NewEvents(s.cfg.DB())), handlers.CtxBalancesQ(pg.NewBalances(s.cfg.DB())), + handlers.CtxWithdrawalsQ(pg.NewWithdrawals(s.cfg.DB())), handlers.CtxEventTypes(s.cfg.EventTypes()), handlers.CtxBroadcaster(s.cfg.Broadcaster()), handlers.CtxPointPrice(s.cfg.PointPrice()), ), ) r.Route("/integrations/rarime-points-svc/v1", func(r chi.Router) { - r.Group(func(r chi.Router) { + r.Route("/balances", func(r chi.Router) { r.Use(handlers.AuthMiddleware(s.cfg.Auth(), s.log)) - r.Route("/balances", func(r chi.Router) { - r.Post("/", handlers.CreateBalance) - r.Get("/{did}", handlers.GetBalance) - r.Patch("/{did}", handlers.Withdraw) + r.Post("/", handlers.CreateBalance) + r.Route("/{did}", func(r chi.Router) { + r.Get("/", handlers.GetBalance) + r.Get("/withdrawals", handlers.ListWithdrawals) + r.Post("/withdrawals", handlers.Withdraw) }) - r.Get("/events", handlers.ListEvents) - r.Patch("/events/{id}", handlers.ClaimEvent) + }) + r.Route("/events", func(r chi.Router) { + r.Use(handlers.AuthMiddleware(s.cfg.Auth(), s.log)) + r.Get("/", handlers.ListEvents) + r.Patch("/{id}", handlers.ClaimEvent) }) r.Get("/balances", handlers.Leaderboard) }) From f7f14813200804c3ca6199b6169acf52813a12da Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 6 Feb 2024 17:33:48 +0200 Subject: [PATCH 3/4] Return withdrawal and balance on withdrawing, fix small issues --- docs/spec/components/schemas/Withdrawal.yaml | 13 +++++++ ...nts-svc@v1@balances@{did}@withdrawals.yaml | 4 ++- internal/data/pg/withdrawals.go | 11 +++--- internal/data/withdrawals.go | 2 +- internal/service/handlers/create_balance.go | 5 +++ internal/service/handlers/list_withdrawals.go | 26 ++++++++------ internal/service/handlers/withdraw.go | 34 +++++++++++++++++-- internal/service/requests/list_events.go | 3 ++ resources/model_withdrawal.go | 3 +- resources/model_withdrawal_relationships.go | 9 +++++ 10 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 resources/model_withdrawal_relationships.go diff --git a/docs/spec/components/schemas/Withdrawal.yaml b/docs/spec/components/schemas/Withdrawal.yaml index 9079c08..ef4951f 100644 --- a/docs/spec/components/schemas/Withdrawal.yaml +++ b/docs/spec/components/schemas/Withdrawal.yaml @@ -23,3 +23,16 @@ allOf: type: integer description: Unix timestamp of withdrawal creation example: 1706531218 + relationships: + type: object + required: + - balance + properties: + balance: + type: object + description: # hack for openapi-go-generator + required: + - data + properties: + data: + $ref: '#/components/schemas/BalanceKey' diff --git a/docs/spec/paths/integrations@rarime-points-svc@v1@balances@{did}@withdrawals.yaml b/docs/spec/paths/integrations@rarime-points-svc@v1@balances@{did}@withdrawals.yaml index 5d1b27d..ea53b11 100644 --- a/docs/spec/paths/integrations@rarime-points-svc@v1@balances@{did}@withdrawals.yaml +++ b/docs/spec/paths/integrations@rarime-points-svc@v1@balances@{did}@withdrawals.yaml @@ -34,7 +34,9 @@ post: tags: - Points balance summary: Withdraw points - description: Convert points to RMO by exchange rate and withdraw to user wallet + description: | + Convert points to RMO by exchange rate and withdraw to user wallet. + Updated balance with new rank is returned. operationId: withdrawPoints parameters: - $ref: '#/components/parameters/pathDID' diff --git a/internal/data/pg/withdrawals.go b/internal/data/pg/withdrawals.go index 63d1ae0..461ca49 100644 --- a/internal/data/pg/withdrawals.go +++ b/internal/data/pg/withdrawals.go @@ -26,18 +26,19 @@ func (q *withdrawals) New() data.WithdrawalsQ { return NewWithdrawals(q.db.Clone()) } -func (q *withdrawals) Insert(w data.Withdrawal) error { +func (q *withdrawals) Insert(w data.Withdrawal) (*data.Withdrawal, error) { + var res data.Withdrawal stmt := squirrel.Insert(withdrawalsTable).SetMap(map[string]interface{}{ "user_did": w.UserDID, "amount": w.Amount, "address": w.Address, - }) + }).Suffix("RETURNING *") - if err := q.db.Exec(stmt); err != nil { - return fmt.Errorf("insert withdrawal [%+v]: %w", w, err) + if err := q.db.Get(&res, stmt); err != nil { + return nil, fmt.Errorf("insert withdrawal [%+v]: %w", w, err) } - return nil + return &res, nil } func (q *withdrawals) Page(page *pgdb.CursorPageParams) data.WithdrawalsQ { diff --git a/internal/data/withdrawals.go b/internal/data/withdrawals.go index 61fcf55..722237e 100644 --- a/internal/data/withdrawals.go +++ b/internal/data/withdrawals.go @@ -14,7 +14,7 @@ type Withdrawal struct { type WithdrawalsQ interface { New() WithdrawalsQ - Insert(Withdrawal) error + Insert(Withdrawal) (*Withdrawal, error) Page(*pgdb.CursorPageParams) WithdrawalsQ Select() ([]Withdrawal, error) diff --git a/internal/service/handlers/create_balance.go b/internal/service/handlers/create_balance.go index 6709887..7149b5a 100644 --- a/internal/service/handlers/create_balance.go +++ b/internal/service/handlers/create_balance.go @@ -33,6 +33,11 @@ func CreateBalance(w http.ResponseWriter, r *http.Request) { } return nil }) + if err != nil { + Log(r).WithError(err).Error("Failed to add balance with open events") + ape.RenderErr(w, problems.InternalError()) + return + } // We can't return inserted balance in a single query, because we can't calculate // rank in transaction: RANK() is a window function allowed on a set of rows, diff --git a/internal/service/handlers/list_withdrawals.go b/internal/service/handlers/list_withdrawals.go index 9d288e2..37d2693 100644 --- a/internal/service/handlers/list_withdrawals.go +++ b/internal/service/handlers/list_withdrawals.go @@ -42,17 +42,21 @@ func ListWithdrawals(w http.ResponseWriter, r *http.Request) { func newWithdrawalsResponse(withdrawals []data.Withdrawal) resources.WithdrawalListResponse { list := make([]resources.Withdrawal, len(withdrawals)) for i, w := range withdrawals { - list[i] = resources.Withdrawal{ - Key: resources.Key{ - ID: w.ID, - Type: resources.WITHDRAWAL, - }, - Attributes: resources.WithdrawalAttributes{ - Amount: w.Amount, - Address: w.Address, - CreatedAt: w.CreatedAt, - }, - } + list[i] = newWithdrawalModel(w) } return resources.WithdrawalListResponse{Data: list} } + +func newWithdrawalModel(w data.Withdrawal) resources.Withdrawal { + return resources.Withdrawal{ + Key: resources.Key{ + ID: w.ID, + Type: resources.WITHDRAWAL, + }, + Attributes: resources.WithdrawalAttributes{ + Amount: w.Amount, + Address: w.Address, + CreatedAt: w.CreatedAt, + }, + } +} diff --git a/internal/service/handlers/withdraw.go b/internal/service/handlers/withdraw.go index b2828ef..dab2a58 100644 --- a/internal/service/handlers/withdraw.go +++ b/internal/service/handlers/withdraw.go @@ -7,6 +7,7 @@ import ( cosmos "github.com/cosmos/cosmos-sdk/types" bank "github.com/cosmos/cosmos-sdk/x/bank/types" validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/rarimo/rarime-points-svc/internal/data" "github.com/rarimo/rarime-points-svc/internal/service/requests" "github.com/rarimo/rarime-points-svc/resources" "gitlab.com/distributed_lab/ape" @@ -24,11 +25,22 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { return } + var withdrawal *data.Withdrawal err = EventsQ(r).Transaction(func() error { err = BalancesQ(r).FilterByDID(req.Data.ID).UpdateAmountBy(-req.Data.Attributes.Amount) if err != nil { return fmt.Errorf("decrease points amount: %w", err) } + + withdrawal, err = WithdrawalsQ(r).Insert(data.Withdrawal{ + UserDID: req.Data.ID, + Amount: req.Data.Attributes.Amount, + Address: req.Data.Attributes.Address, + }) + if err != nil { + return fmt.Errorf("add withdrawal entry: %w", err) + } + if err = broadcastWithdrawalTx(req, r); err != nil { return fmt.Errorf("broadcast transfer tx: %w", err) } @@ -36,7 +48,7 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { }) if err != nil { - Log(r).WithError(err).Error("Failed to decrease balance and broadcast tx") + Log(r).WithError(err).Error("Failed to perform withdrawal") ape.RenderErr(w, problems.InternalError()) return } @@ -47,7 +59,25 @@ func Withdraw(w http.ResponseWriter, r *http.Request) { return } - ape.Render(w, newBalanceModel(*balance)) + ape.Render(w, newWithdrawResponse(*withdrawal, *balance)) +} + +func newWithdrawResponse(w data.Withdrawal, balance data.Balance) *resources.WithdrawalResponse { + wm := newWithdrawalModel(w) + wm.Relationships = &resources.WithdrawalRelationships{ + Balance: resources.Relation{ + Data: &resources.Key{ + ID: balance.DID, + Type: resources.BALANCE, + }, + }, + } + + resp := resources.WithdrawalResponse{Data: wm} + bm := newBalanceModel(balance) + resp.Included.Add(&bm) + + return &resp } func isEnoughPoints(req resources.WithdrawRequest, w http.ResponseWriter, r *http.Request) bool { diff --git a/internal/service/requests/list_events.go b/internal/service/requests/list_events.go index 5faa580..0805855 100644 --- a/internal/service/requests/list_events.go +++ b/internal/service/requests/list_events.go @@ -24,6 +24,9 @@ func NewListEvents(r *http.Request) (req ListEvents, err error) { "query": fmt.Errorf("failed to decode query: %w", err), } } + if err = req.CursorParams.Validate(); err != nil { + return + } err = validation.Errors{ "filter[did]": validation.Validate(req.FilterDID, validation.Required), diff --git a/resources/model_withdrawal.go b/resources/model_withdrawal.go index dfc8561..0b60594 100644 --- a/resources/model_withdrawal.go +++ b/resources/model_withdrawal.go @@ -8,7 +8,8 @@ import "encoding/json" type Withdrawal struct { Key - Attributes WithdrawalAttributes `json:"attributes"` + Attributes WithdrawalAttributes `json:"attributes"` + Relationships *WithdrawalRelationships `json:"relationships,omitempty"` } type WithdrawalResponse struct { Data Withdrawal `json:"data"` diff --git a/resources/model_withdrawal_relationships.go b/resources/model_withdrawal_relationships.go new file mode 100644 index 0000000..199b465 --- /dev/null +++ b/resources/model_withdrawal_relationships.go @@ -0,0 +1,9 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +type WithdrawalRelationships struct { + Balance Relation `json:"balance"` +} From 4da13d821b042654fb714d75c9828b9c414b17b4 Mon Sep 17 00:00:00 2001 From: violog <51th.apprent1ce.f0rce@gmail.com> Date: Tue, 6 Feb 2024 18:47:21 +0200 Subject: [PATCH 4/4] Get point price in urmo --- docs/spec/components/schemas/PointPrice.yaml | 18 ++++++++ .../components/schemas/PointPriceKey.yaml | 7 +++ ...ions@rarime-points-svc@v1@point_price.yaml | 20 +++++++++ internal/service/handlers/point_price.go | 21 +++++++++ internal/service/router.go | 1 + resources/model_point_price.go | 43 +++++++++++++++++++ resources/model_point_price_attributes.go | 10 +++++ resources/model_resource_type.go | 1 + 8 files changed, 121 insertions(+) create mode 100644 docs/spec/components/schemas/PointPrice.yaml create mode 100644 docs/spec/components/schemas/PointPriceKey.yaml create mode 100644 docs/spec/paths/integrations@rarime-points-svc@v1@point_price.yaml create mode 100644 internal/service/handlers/point_price.go create mode 100644 resources/model_point_price.go create mode 100644 resources/model_point_price_attributes.go diff --git a/docs/spec/components/schemas/PointPrice.yaml b/docs/spec/components/schemas/PointPrice.yaml new file mode 100644 index 0000000..e6a5a33 --- /dev/null +++ b/docs/spec/components/schemas/PointPrice.yaml @@ -0,0 +1,18 @@ +allOf: + - $ref: '#/components/schemas/PointPriceKey' + - type: object + required: + - attributes + properties: + type: + type: string + enum: [point_price] + attributes: + type: object + required: + - urmo + properties: + urmo: + type: integer + description: Amount of `urmo` tokens for one point + example: 1000 diff --git a/docs/spec/components/schemas/PointPriceKey.yaml b/docs/spec/components/schemas/PointPriceKey.yaml new file mode 100644 index 0000000..fab5f9e --- /dev/null +++ b/docs/spec/components/schemas/PointPriceKey.yaml @@ -0,0 +1,7 @@ +type: object +required: + - type +properties: + type: + type: string + enum: [point_price] diff --git a/docs/spec/paths/integrations@rarime-points-svc@v1@point_price.yaml b/docs/spec/paths/integrations@rarime-points-svc@v1@point_price.yaml new file mode 100644 index 0000000..676445d --- /dev/null +++ b/docs/spec/paths/integrations@rarime-points-svc@v1@point_price.yaml @@ -0,0 +1,20 @@ +get: + tags: + - Points balance + summary: Get point price + description: How many `urmo` tokens cost one point. + operationId: getPointPrice + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/PointPrice' + 500: + $ref: '#/components/responses/internalError' diff --git a/internal/service/handlers/point_price.go b/internal/service/handlers/point_price.go new file mode 100644 index 0000000..4bea765 --- /dev/null +++ b/internal/service/handlers/point_price.go @@ -0,0 +1,21 @@ +package handlers + +import ( + "net/http" + + "github.com/rarimo/rarime-points-svc/resources" + "gitlab.com/distributed_lab/ape" +) + +func GetPointPrice(w http.ResponseWriter, r *http.Request) { + ape.Render(w, resources.PointPriceResponse{ + Data: resources.PointPrice{ + Key: resources.Key{ + Type: resources.POINT_PRICE, + }, + Attributes: resources.PointPriceAttributes{ + Urmo: PointPrice(r), + }, + }, + }) +} diff --git a/internal/service/router.go b/internal/service/router.go index 7c535b1..1f02539 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -39,6 +39,7 @@ func (s *service) router() chi.Router { r.Patch("/{id}", handlers.ClaimEvent) }) r.Get("/balances", handlers.Leaderboard) + r.Get("/point_price", handlers.GetPointPrice) }) return r diff --git a/resources/model_point_price.go b/resources/model_point_price.go new file mode 100644 index 0000000..3047fb1 --- /dev/null +++ b/resources/model_point_price.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type PointPrice struct { + Key + Attributes PointPriceAttributes `json:"attributes"` +} +type PointPriceResponse struct { + Data PointPrice `json:"data"` + Included Included `json:"included"` +} + +type PointPriceListResponse struct { + Data []PointPrice `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *PointPriceListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *PointPriceListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustPointPrice - returns PointPrice from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustPointPrice(key Key) *PointPrice { + var pointPrice PointPrice + if c.tryFindEntry(key, &pointPrice) { + return &pointPrice + } + return nil +} diff --git a/resources/model_point_price_attributes.go b/resources/model_point_price_attributes.go new file mode 100644 index 0000000..e653436 --- /dev/null +++ b/resources/model_point_price_attributes.go @@ -0,0 +1,10 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +type PointPriceAttributes struct { + // Amount of `urmo` tokens for one point + Urmo int32 `json:"urmo"` +} diff --git a/resources/model_resource_type.go b/resources/model_resource_type.go index fc73c4a..a236d96 100644 --- a/resources/model_resource_type.go +++ b/resources/model_resource_type.go @@ -12,6 +12,7 @@ const ( CLAIM_EVENT ResourceType = "claim_event" CREATE_BALANCE ResourceType = "create_balance" EVENT ResourceType = "event" + POINT_PRICE ResourceType = "point_price" WITHDRAW ResourceType = "withdraw" WITHDRAWAL ResourceType = "withdrawal" )