Files
scrabble-game/backend/internal/adminconsole/views.go
T
Ilia Denisov ab58062565 R3: backend rate-limit observability — ratewatch, auto-flag, admin throttled view
- accounts.flagged_high_rate_at baked into the R1 baseline (no prod data; the
  contour schema is wiped after merge); jet regenerated — the regen also picks
  up the previously missing game_drafts/game_hidden models.
- account.Store: FlagHighRate (set-once), ClearHighRateFlag, the flag in
  GetByID/ListUsers and a ListFlaggedHighRate review queue.
- New internal/ratewatch: ingests the gateway rejection reports, keeps a
  bounded in-memory episode window for the console and applies the
  conservative auto-flag (1000 rejected / 10 min, BACKEND_HIGHRATE_FLAG_*).
- POST /api/v1/internal/ratelimit/report (network-trusted, like
  sessions/resolve).
- Admin console: Throttled page (episodes + flagged accounts), a high-rate
  badge in the user list, the marker + operator clear action on the user card.
- Tests: ratewatch unit suite, report-route handler test, renderer cases,
  integration coverage for the store round-trip and the console flow.
2026-06-10 02:14:10 +02:00

290 lines
7.4 KiB
Go

package adminconsole
import "html/template"
// The *View types are the display models the gin handlers fill and the templates
// render. Time values are pre-formatted to strings by the handlers so the
// templates stay logic-free.
// Pager is the shared list pagination state.
type Pager struct {
Page int
PageSize int
Total int
HasPrev bool
HasNext bool
PrevPage int
NextPage int
}
// NewPager builds the pagination state for a 1-based page of pageSize over total
// items.
func NewPager(page, pageSize, total int) Pager {
if page < 1 {
page = 1
}
p := Pager{Page: page, PageSize: pageSize, Total: total, PrevPage: page - 1, NextPage: page + 1}
p.HasPrev = page > 1
p.HasNext = page*pageSize < total
return p
}
// VariantVersions lists the dictionary versions resident for one variant.
type VariantVersions struct {
Variant string
Latest string
Versions []string
}
// DashboardView is the landing-page summary.
type DashboardView struct {
Accounts int
Games int
ActiveGames int
OpenComplaints int
PendingChanges int
Variants []VariantVersions
}
// UsersView is the paginated account list.
type UsersView struct {
Items []UserRow
Pager Pager
// Robots is the active people/robots toggle; NameMask/ExternalIDMask are the current
// glob filters; FilterQuery is those encoded for pager/toggle links.
Robots bool
NameMask string
ExternalIDMask string
FilterQuery string
}
// UserRow is one account row in the list. MoveMin/Avg/Max are the account's
// pre-formatted move-duration summary (empty when it has no timed move);
// FlaggedHighRate marks the soft high-rate badge (R3).
type UserRow struct {
ID string
DisplayName string
Kind string
Language string
Guest bool
FlaggedHighRate bool
CreatedAt string
HasMoveStats bool
MoveMin string
MoveAvg string
MoveMax string
}
// MessagesView is the paginated chat-message moderation list. NameMask/ExtMask are the
// current sender glob filters; GameID/UserID pin the list to one game / sender (set from a
// game or user card); FilterQuery is the active filters encoded for the pager links.
type MessagesView struct {
Items []MessageRow
Pager Pager
NameMask string
ExtMask string
GameID string
UserID string
FilterQuery string
}
// MessageRow is one chat message in the moderation list: its sender (linked to the user
// card), source, IP, body, game (linked to the game card) and time.
type MessageRow struct {
ID string
SenderID string
SenderName string
Source string
IP string
Body string
GameID string
CreatedAt string
}
// UserDetailView is one account with its stats, identities and recent games.
type UserDetailView struct {
ID string
DisplayName string
Language string
TimeZone string
Guest bool
NotificationsInAppOnly bool
PaidAccount bool
// MergedInto is the primary account id when this account has been retired by a
// merge (Stage 11), or empty for a live account.
MergedInto string
// FlaggedHighRateAt is the pre-formatted soft high-rate marker timestamp,
// empty for an unflagged account; the card shows it with the Clear action (R3).
FlaggedHighRateAt string
HintBalance int
CreatedAt string
HasStats bool
Stats StatsRow
Identities []IdentityRow
Games []GameRow
TelegramID string
ConnectorEnabled bool
// MoveChart is the pre-rendered inline SVG of the account's per-move-number think
// time (min/mean/max), empty when the account has no timed move.
MoveChart template.HTML
}
// StatsRow is an account's lifetime statistics.
type StatsRow struct {
Wins int
Losses int
Draws int
MaxGamePoints int
MaxWordPoints int
}
// IdentityRow is one platform/email identity of an account.
type IdentityRow struct {
Kind string
ExternalID string
Confirmed bool
CreatedAt string
}
// GameRow is one game row in a list.
type GameRow struct {
ID string
Variant string
Status string
Players int
UpdatedAt string
}
// GamesView is the paginated games list, optionally filtered by status.
type GamesView struct {
Items []GameRow
Status string
Pager Pager
}
// GameDetailView is one game with its seats.
type GameDetailView struct {
ID string
Variant string
DictVersion string
Status string
Players int
ToMove int
EndReason string
MoveCount int
CreatedAt string
UpdatedAt string
FinishedAt string
Seats []SeatRow
// HasRobot is true when any seat is a robot, gating the robot-target caption;
// RobotTargetPct is the configured global play-to-win rate, in percent.
HasRobot bool
RobotTargetPct int
}
// SeatRow is one seat of a game. For a robot seat (IsRobot) RobotIntent is the game's
// deterministic play-to-win decision ("play to win"/"play to lose"), and NextMove is the
// scheduled next-move ETA shown only while it is that robot's turn in an active game.
type SeatRow struct {
Seat int
DisplayName string
AccountID string
Score int
HintsUsed int
Winner bool
IsRobot bool
RobotIntent string
NextMove string
}
// ComplaintsView is the paginated complaint review queue.
type ComplaintsView struct {
Items []ComplaintRow
Status string
Pager Pager
}
// ComplaintRow is one complaint row in the queue.
type ComplaintRow struct {
ID string
Word string
Variant string
WasValid bool
Status string
Disposition string
CreatedAt string
}
// ComplaintDetailView is one complaint with its resolution state and form.
type ComplaintDetailView struct {
ID string
Word string
Variant string
DictVersion string
WasValid bool
Note string
Status string
Disposition string
ResolutionNote string
CreatedAt string
ResolvedAt string
GameID string
Resolved bool
}
// DictionaryView lists the resident versions per variant and the pending
// wordlist changes from accepted complaints.
type DictionaryView struct {
Variants []VariantVersions
Changes []DictChangeRow
}
// DictChangeRow is one pending wordlist edit.
type DictChangeRow struct {
Variant string
Word string
Action string
ResolvedAt string
}
// BroadcastView is the operator-broadcast form page.
type BroadcastView struct {
ConnectorEnabled bool
}
// ThrottledView is the rate-limit observability page: the recent gateway-reported
// throttle episodes (in-memory, reset on restart) and the accounts currently
// carrying the high-rate flag. FlagThreshold and FlagWindow caption the active
// auto-flag tuning.
type ThrottledView struct {
Episodes []ThrottleEpisodeRow
Flagged []FlaggedAccountRow
FlagThreshold int
FlagWindow string
}
// ThrottleEpisodeRow is one recently throttled limiter key. UserID links to the
// user card and is set only for the user class (the other classes key by IP).
type ThrottleEpisodeRow struct {
Class string
Key string
UserID string
Rejected int
FirstSeen string
LastSeen string
}
// FlaggedAccountRow is one account carrying the high-rate flag.
type FlaggedAccountRow struct {
ID string
DisplayName string
FlaggedAt string
}
// MessageView is the result page shown after a POST action.
type MessageView struct {
Heading string
Body string
Back string
}