From 03f35fb27919c071b3a0d68dd85c5864347f0b8e Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Fri, 5 Jul 2024 13:27:33 +0300 Subject: [PATCH 1/4] Add total_count field in meta for leaderboard endpoint --- internal/data/balances.go | 2 ++ internal/data/pg/balances.go | 15 +++++++++++++++ internal/service/handlers/leaderboard.go | 21 ++++++++++++++++++--- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/internal/data/balances.go b/internal/data/balances.go index 84ef5a6..04a61e7 100644 --- a/internal/data/balances.go +++ b/internal/data/balances.go @@ -39,6 +39,8 @@ type BalancesQ interface { GetWithRank(nullifier string) (*Balance, error) SelectWithRank() ([]Balance, error) + Count() (int64, error) + // WithoutPassportEvent returns balances which already // have scanned passport, but there no claimed events // for this. Filters are not applied. diff --git a/internal/data/pg/balances.go b/internal/data/pg/balances.go index 479969c..584edbc 100644 --- a/internal/data/pg/balances.go +++ b/internal/data/pg/balances.go @@ -17,6 +17,7 @@ type balances struct { selector squirrel.SelectBuilder updater squirrel.UpdateBuilder rank squirrel.SelectBuilder + counter squirrel.SelectBuilder } func NewBalances(db *pgdb.DB) data.BalancesQ { @@ -25,6 +26,7 @@ func NewBalances(db *pgdb.DB) data.BalancesQ { selector: squirrel.Select("*").From(balancesTable), updater: squirrel.Update(balancesTable), rank: squirrel.Select("*, ROW_NUMBER() OVER (ORDER BY amount DESC, updated_at ASC) AS rank").From(balancesTable), + counter: squirrel.Select("COUNT(*) as count").From(balancesTable), } } @@ -124,6 +126,18 @@ func (q *balances) Get() (*data.Balance, error) { return &res, nil } +func (q *balances) Count() (int64, error) { + res := struct { + Count int64 `db:"count"` + }{} + + if err := q.db.Get(&res, q.counter); err != nil { + return 0, fmt.Errorf("get balance: %w", err) + } + + return res.Count, nil +} + func (q *balances) GetWithRank(nullifier string) (*data.Balance, error) { var res data.Balance stmt := fmt.Sprintf(` @@ -205,5 +219,6 @@ func (q *balances) applyCondition(cond squirrel.Sqlizer) data.BalancesQ { q.selector = q.selector.Where(cond) q.updater = q.updater.Where(cond) q.rank = q.rank.Where(cond) + q.counter = q.counter.Where(cond) return q } diff --git a/internal/service/handlers/leaderboard.go b/internal/service/handlers/leaderboard.go index 6d0f81b..18546ee 100644 --- a/internal/service/handlers/leaderboard.go +++ b/internal/service/handlers/leaderboard.go @@ -1,6 +1,7 @@ package handlers import ( + "encoding/json" "net/http" "github.com/rarimo/rarime-points-svc/internal/data" @@ -24,16 +25,30 @@ func Leaderboard(w http.ResponseWriter, r *http.Request) { return } - resp := newLeaderboardResponse(leaders) + leadersCount, err := BalancesQ(r).FilterDisabled().Count() + if err != nil { + Log(r).WithError(err).Error("Failed to leaders count") + ape.RenderErr(w, problems.InternalError()) + return + } + + resp := newLeaderboardResponse(leaders, leadersCount) resp.Links = req.GetLinks(r) ape.Render(w, resp) } -func newLeaderboardResponse(balances []data.Balance) resources.BalanceListResponse { +func newLeaderboardResponse(balances []data.Balance, totalLeaders int64) resources.BalanceListResponse { list := make([]resources.Balance, len(balances)) for i, balance := range balances { list[i] = newBalanceModel(balance) } - return resources.BalanceListResponse{Data: list} + // must never panic + serMeta, _ := json.Marshal(struct { + TotalCount int64 `json:"total_count"` + }{ + TotalCount: totalLeaders, + }) + + return resources.BalanceListResponse{Data: list, Meta: serMeta} } From b39b40060ca374feec3e33a10e5bea52731efb6e Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Fri, 5 Jul 2024 14:34:41 +0300 Subject: [PATCH 2/4] Small refactoring. Docs --- ...@rarime-points-svc@v1@public@balances.yaml | 16 ++++++++++++++++ internal/service/handlers/leaderboard.go | 19 ++++++++----------- internal/service/requests/leaderboard.go | 1 + 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/docs/spec/paths/integrations@rarime-points-svc@v1@public@balances.yaml b/docs/spec/paths/integrations@rarime-points-svc@v1@public@balances.yaml index 1c5ea99..136e588 100644 --- a/docs/spec/paths/integrations@rarime-points-svc@v1@public@balances.yaml +++ b/docs/spec/paths/integrations@rarime-points-svc@v1@public@balances.yaml @@ -57,6 +57,13 @@ get: - $ref: '#/components/parameters/pageLimit' - $ref: '#/components/parameters/pageNumber' - $ref: '#/components/parameters/pageOrder' + - in: query + name: count + description: Count total number of users. + required: false + schema: + type: boolean + example: true responses: 200: description: Success @@ -71,6 +78,15 @@ get: type: array items: $ref: '#/components/schemas/Balance' + meta: + type: object + required: + - total_count + properties: + total_count: + type: integer + description: Appears when `count=true` is specified + example: 18 400: $ref: '#/components/responses/invalidParameter' 409: diff --git a/internal/service/handlers/leaderboard.go b/internal/service/handlers/leaderboard.go index 18546ee..e1585cb 100644 --- a/internal/service/handlers/leaderboard.go +++ b/internal/service/handlers/leaderboard.go @@ -1,7 +1,6 @@ package handlers import ( - "encoding/json" "net/http" "github.com/rarimo/rarime-points-svc/internal/data" @@ -32,23 +31,21 @@ func Leaderboard(w http.ResponseWriter, r *http.Request) { return } - resp := newLeaderboardResponse(leaders, leadersCount) + resp := newLeaderboardResponse(leaders) resp.Links = req.GetLinks(r) + if req.Count { + _ = resp.PutMeta(struct { + EventsCount int64 `json:"events_count"` + }{leadersCount}) + } ape.Render(w, resp) } -func newLeaderboardResponse(balances []data.Balance, totalLeaders int64) resources.BalanceListResponse { +func newLeaderboardResponse(balances []data.Balance) resources.BalanceListResponse { list := make([]resources.Balance, len(balances)) for i, balance := range balances { list[i] = newBalanceModel(balance) } - // must never panic - serMeta, _ := json.Marshal(struct { - TotalCount int64 `json:"total_count"` - }{ - TotalCount: totalLeaders, - }) - - return resources.BalanceListResponse{Data: list, Meta: serMeta} + return resources.BalanceListResponse{Data: list} } diff --git a/internal/service/requests/leaderboard.go b/internal/service/requests/leaderboard.go index 29a2207..ded8abf 100644 --- a/internal/service/requests/leaderboard.go +++ b/internal/service/requests/leaderboard.go @@ -9,6 +9,7 @@ import ( type Leaderboard struct { page.OffsetParams + Count bool `url:"count"` } func NewLeaderboard(r *http.Request) (req Leaderboard, err error) { From 53d53f3556cb100b543b510b73f02d1795db02b3 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Fri, 5 Jul 2024 14:35:56 +0300 Subject: [PATCH 3/4] Comments --- internal/service/handlers/leaderboard.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/handlers/leaderboard.go b/internal/service/handlers/leaderboard.go index e1585cb..2e1ab83 100644 --- a/internal/service/handlers/leaderboard.go +++ b/internal/service/handlers/leaderboard.go @@ -26,7 +26,7 @@ func Leaderboard(w http.ResponseWriter, r *http.Request) { leadersCount, err := BalancesQ(r).FilterDisabled().Count() if err != nil { - Log(r).WithError(err).Error("Failed to leaders count") + Log(r).WithError(err).Error("Failed to count balances") ape.RenderErr(w, problems.InternalError()) return } From ba97db278dd5e6f870d56cd59ceb11207b1a4185 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Fri, 5 Jul 2024 14:38:58 +0300 Subject: [PATCH 4/4] Calculate balances count if count parameter specified --- internal/service/handlers/leaderboard.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/internal/service/handlers/leaderboard.go b/internal/service/handlers/leaderboard.go index 2e1ab83..d134c2a 100644 --- a/internal/service/handlers/leaderboard.go +++ b/internal/service/handlers/leaderboard.go @@ -24,16 +24,16 @@ func Leaderboard(w http.ResponseWriter, r *http.Request) { return } - leadersCount, err := BalancesQ(r).FilterDisabled().Count() - if err != nil { - Log(r).WithError(err).Error("Failed to count balances") - ape.RenderErr(w, problems.InternalError()) - return - } - resp := newLeaderboardResponse(leaders) resp.Links = req.GetLinks(r) if req.Count { + leadersCount, err := BalancesQ(r).FilterDisabled().Count() + if err != nil { + Log(r).WithError(err).Error("Failed to count balances") + ape.RenderErr(w, problems.InternalError()) + return + } + _ = resp.PutMeta(struct { EventsCount int64 `json:"events_count"` }{leadersCount})