feat(enakgame): weekly leaderboard

GET /customer/enakgame/leaderboard ranks the customers of an organization by
the EnakCoin they won from games in one week, Monday to Sunday in
Asia/Jakarta. Only completed, unflagged sessions count, by the week they
started; entry cost is not subtracted. Equal totals rank by who reached
theirs first, so no two customers share a rank.

Optional date (any day of the week, this week by default), game_id and limit
(10, at most 100). The response holds the top ranks and, under me, the
customer asking wherever they rank. Other customers are shown by a masked
name: the first word whole, the others by their first letter; a word with a
digit keeps only its first character.

The EnakGame web game shows it: integration-enakgame.md §10 (checklist now
§11) and RFC §11. The DB test needs TEST_DATABASE_URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
efrilm
2026-10-12 16:15:02 +07:00
co-authored by Claude Opus 5.5
parent c43baa53e1
commit 3af16a1fbc
10 changed files with 398 additions and 4 deletions
+28
View File
@@ -199,6 +199,34 @@ type GameSessionListQuery struct {
Limit int `form:"limit"`
}
// EnakGameLeaderboardQuery picks a week of the leaderboard, and optionally one game.
type EnakGameLeaderboardQuery struct {
// Any day of the week, YYYY-MM-DD in Asia/Jakarta; this week when empty.
Date string `form:"date"`
GameID string `form:"game_id"`
Limit int `form:"limit"`
}
// EnakGameLeaderboard ranks the customers of an organization by the EnakCoin they won
// from games in one week, Monday to Sunday in Asia/Jakarta.
type EnakGameLeaderboard struct {
WeekStart string `json:"week_start"`
WeekEnd string `json:"week_end"`
GameID *uuid.UUID `json:"game_id"`
Entries []EnakGameLeaderboardEntry `json:"entries"`
// The customer asking, wherever they rank; null when they won nothing that week.
Me *EnakGameLeaderboardEntry `json:"me"`
}
// EnakGameLeaderboardEntry is one customer on the leaderboard. Other customers are
// shown by a masked name only.
type EnakGameLeaderboardEntry struct {
Rank int `json:"rank"`
Name string `json:"name"`
Coins int64 `json:"coins"`
IsMe bool `json:"is_me"`
}
// GameSessionCompleteInput is what the client reports at the end of a play (§7.2): data
// only. Anything else it sends, a reward amount above all, is ignored (P1).
type GameSessionCompleteInput struct {