feat(admin): manual account blocking (suspensions)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 12s
CI / ui (pull_request) Successful in 45s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m10s

Operator-driven hard block, the counterpart to the soft high-rate flag: permanent or until a date, with an optional reason chosen from an editable en+ru picklist (snapshotted onto the block). A block forfeits the player's active games (opponent wins, as a resignation) and cancels their open matchmaking games. A backend gate refuses a blocked account on every /api/v1/user/* route except the block-status probe with 403 account_blocked, which threads through the gateway as the Execute result_code; the UI surfaces it as a terminal blocked screen and stops all push/poll. Temporary blocks self-expire; the operator can unblock at any time (lost games stay lost). Sessions are not revoked, so the blocked client can still reach the exempt block-status endpoint.

Backend: migration 00003 (account_suspensions + suspension_reasons) + jet regen; account suspension store; game.ForfeitAllForAccount; requireNotSuspended gate + block-status endpoint; admin console block/unblock + Reasons CRUD. Wire: fbs BlockStatus + account.block_status gateway op. UI: blocked screen, app state, transport/codec, i18n. Docs: ARCHITECTURE, FUNCTIONAL(+ru), PRERELEASE (AB).
This commit is contained in:
Ilia Denisov
2026-06-14 21:55:59 +02:00
parent 9d85090075
commit d1ba666495
48 changed files with 2206 additions and 2 deletions
+36
View File
@@ -135,6 +135,29 @@ type UserDetailView struct {
// 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
// Suspension is the account's current manual-block state, shown with the block/unblock
// form; Reasons is the operator-editable reason picklist offered in the block form.
Suspension SuspensionView
Reasons []ReasonOption
}
// SuspensionView is an account's current manual-block state shown on the user card: whether it
// is blocked, whether the block is permanent, the pre-formatted expiry (empty when permanent),
// when it was applied, and the reason snapshot in both languages (empty when none was cited).
type SuspensionView struct {
Blocked bool
Permanent bool
Until string
BlockedAt string
ReasonEn string
ReasonRu string
}
// ReasonOption is one suspension-reason picklist entry offered in the block form's dropdown.
type ReasonOption struct {
ID string
TextEn string
TextRu string
}
// StatsRow is an account's lifetime statistics.
@@ -317,6 +340,19 @@ type FlaggedAccountRow struct {
FlaggedAt string
}
// ReasonsView is the suspension-reason picklist management page: every editable reason entry.
type ReasonsView struct {
Items []ReasonRow
}
// ReasonRow is one editable suspension-reason entry, with its English and Russian text.
type ReasonRow struct {
ID string
TextEn string
TextRu string
CreatedAt string
}
// MessageView is the result page shown after a POST action.
type MessageView struct {
Heading string