Stage 10: admin console & dictionary ops (complaint review, hot-reload, broadcasts)
Server-rendered admin console in the backend at /_gm (internal/adminconsole), fronted on the gateway's public listener by Basic-Auth + a verbatim reverse proxy (mounted on the edge mux below the h2c wrap). A same-origin check guards its POSTs; no operator identity is tracked. This supersedes the Stage 6 gateway-fronts- /api/v1/admin model: GATEWAY_ADMIN_ADDR and the backend /api/v1/admin ping are dropped and gateway/internal/admin is repurposed to the verbatim proxy. - Complaints: migration 00008 (+ jetgen) adds disposition/resolution_note/ resolved_at/applied_in_version + the deferred status CHECK; resolution feeds a query-derived pending dictionary-change pipeline (marked applied after a reload). - Dictionary hot-reload: per-version subdir BACKEND_DICT_DIR/<version>/ via the new Registry.LoadAvailable; engine.OpenWithVersions restores resident versions on restart. Partially addresses TODO-2. - Broadcasts: a backend Telegram-connector client (internal/connector, BACKEND_CONNECTOR_ADDR) for SendToUser / SendToGameChannel (discharges the Stage 9 forward-note). - Admin reads: account.ListAccounts/CountAccounts/Identities and game.ListGames/CountGames/GameByID/ListComplaints/GetComplaint/CountComplaints/ ResolveComplaint/DictionaryChanges/MarkChangesApplied. - Tests: adminconsole render, engine reload, same-origin guard, gateway verbatim proxy + h2c console mount, inttest complaint pipeline + list/count + /_gm console. - Docs: PLAN (Stage 10 done + refinements + TODO-2), ARCHITECTURE §1/§5/§6/§12/§13, FUNCTIONAL (+_ru), TESTING, backend/gateway READMEs.
This commit is contained in:
@@ -13,14 +13,18 @@ import (
|
||||
)
|
||||
|
||||
type Complaints struct {
|
||||
ComplaintID uuid.UUID `sql:"primary_key"`
|
||||
ComplainantID uuid.UUID
|
||||
GameID uuid.UUID
|
||||
Variant string
|
||||
DictVersion string
|
||||
Word string
|
||||
WasValid bool
|
||||
Note string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
ComplaintID uuid.UUID `sql:"primary_key"`
|
||||
ComplainantID uuid.UUID
|
||||
GameID uuid.UUID
|
||||
Variant string
|
||||
DictVersion string
|
||||
Word string
|
||||
WasValid bool
|
||||
Note string
|
||||
Status string
|
||||
CreatedAt time.Time
|
||||
Disposition string
|
||||
ResolutionNote string
|
||||
ResolvedAt *time.Time
|
||||
AppliedInVersion string
|
||||
}
|
||||
|
||||
@@ -17,16 +17,20 @@ type complaintsTable struct {
|
||||
postgres.Table
|
||||
|
||||
// Columns
|
||||
ComplaintID postgres.ColumnString
|
||||
ComplainantID postgres.ColumnString
|
||||
GameID postgres.ColumnString
|
||||
Variant postgres.ColumnString
|
||||
DictVersion postgres.ColumnString
|
||||
Word postgres.ColumnString
|
||||
WasValid postgres.ColumnBool
|
||||
Note postgres.ColumnString
|
||||
Status postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
ComplaintID postgres.ColumnString
|
||||
ComplainantID postgres.ColumnString
|
||||
GameID postgres.ColumnString
|
||||
Variant postgres.ColumnString
|
||||
DictVersion postgres.ColumnString
|
||||
Word postgres.ColumnString
|
||||
WasValid postgres.ColumnBool
|
||||
Note postgres.ColumnString
|
||||
Status postgres.ColumnString
|
||||
CreatedAt postgres.ColumnTimestampz
|
||||
Disposition postgres.ColumnString
|
||||
ResolutionNote postgres.ColumnString
|
||||
ResolvedAt postgres.ColumnTimestampz
|
||||
AppliedInVersion postgres.ColumnString
|
||||
|
||||
AllColumns postgres.ColumnList
|
||||
MutableColumns postgres.ColumnList
|
||||
@@ -68,35 +72,43 @@ func newComplaintsTable(schemaName, tableName, alias string) *ComplaintsTable {
|
||||
|
||||
func newComplaintsTableImpl(schemaName, tableName, alias string) complaintsTable {
|
||||
var (
|
||||
ComplaintIDColumn = postgres.StringColumn("complaint_id")
|
||||
ComplainantIDColumn = postgres.StringColumn("complainant_id")
|
||||
GameIDColumn = postgres.StringColumn("game_id")
|
||||
VariantColumn = postgres.StringColumn("variant")
|
||||
DictVersionColumn = postgres.StringColumn("dict_version")
|
||||
WordColumn = postgres.StringColumn("word")
|
||||
WasValidColumn = postgres.BoolColumn("was_valid")
|
||||
NoteColumn = postgres.StringColumn("note")
|
||||
StatusColumn = postgres.StringColumn("status")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
allColumns = postgres.ColumnList{ComplaintIDColumn, ComplainantIDColumn, GameIDColumn, VariantColumn, DictVersionColumn, WordColumn, WasValidColumn, NoteColumn, StatusColumn, CreatedAtColumn}
|
||||
mutableColumns = postgres.ColumnList{ComplainantIDColumn, GameIDColumn, VariantColumn, DictVersionColumn, WordColumn, WasValidColumn, NoteColumn, StatusColumn, CreatedAtColumn}
|
||||
defaultColumns = postgres.ColumnList{NoteColumn, StatusColumn, CreatedAtColumn}
|
||||
ComplaintIDColumn = postgres.StringColumn("complaint_id")
|
||||
ComplainantIDColumn = postgres.StringColumn("complainant_id")
|
||||
GameIDColumn = postgres.StringColumn("game_id")
|
||||
VariantColumn = postgres.StringColumn("variant")
|
||||
DictVersionColumn = postgres.StringColumn("dict_version")
|
||||
WordColumn = postgres.StringColumn("word")
|
||||
WasValidColumn = postgres.BoolColumn("was_valid")
|
||||
NoteColumn = postgres.StringColumn("note")
|
||||
StatusColumn = postgres.StringColumn("status")
|
||||
CreatedAtColumn = postgres.TimestampzColumn("created_at")
|
||||
DispositionColumn = postgres.StringColumn("disposition")
|
||||
ResolutionNoteColumn = postgres.StringColumn("resolution_note")
|
||||
ResolvedAtColumn = postgres.TimestampzColumn("resolved_at")
|
||||
AppliedInVersionColumn = postgres.StringColumn("applied_in_version")
|
||||
allColumns = postgres.ColumnList{ComplaintIDColumn, ComplainantIDColumn, GameIDColumn, VariantColumn, DictVersionColumn, WordColumn, WasValidColumn, NoteColumn, StatusColumn, CreatedAtColumn, DispositionColumn, ResolutionNoteColumn, ResolvedAtColumn, AppliedInVersionColumn}
|
||||
mutableColumns = postgres.ColumnList{ComplainantIDColumn, GameIDColumn, VariantColumn, DictVersionColumn, WordColumn, WasValidColumn, NoteColumn, StatusColumn, CreatedAtColumn, DispositionColumn, ResolutionNoteColumn, ResolvedAtColumn, AppliedInVersionColumn}
|
||||
defaultColumns = postgres.ColumnList{NoteColumn, StatusColumn, CreatedAtColumn, DispositionColumn, ResolutionNoteColumn, AppliedInVersionColumn}
|
||||
)
|
||||
|
||||
return complaintsTable{
|
||||
Table: postgres.NewTable(schemaName, tableName, alias, allColumns...),
|
||||
|
||||
//Columns
|
||||
ComplaintID: ComplaintIDColumn,
|
||||
ComplainantID: ComplainantIDColumn,
|
||||
GameID: GameIDColumn,
|
||||
Variant: VariantColumn,
|
||||
DictVersion: DictVersionColumn,
|
||||
Word: WordColumn,
|
||||
WasValid: WasValidColumn,
|
||||
Note: NoteColumn,
|
||||
Status: StatusColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
ComplaintID: ComplaintIDColumn,
|
||||
ComplainantID: ComplainantIDColumn,
|
||||
GameID: GameIDColumn,
|
||||
Variant: VariantColumn,
|
||||
DictVersion: DictVersionColumn,
|
||||
Word: WordColumn,
|
||||
WasValid: WasValidColumn,
|
||||
Note: NoteColumn,
|
||||
Status: StatusColumn,
|
||||
CreatedAt: CreatedAtColumn,
|
||||
Disposition: DispositionColumn,
|
||||
ResolutionNote: ResolutionNoteColumn,
|
||||
ResolvedAt: ResolvedAtColumn,
|
||||
AppliedInVersion: AppliedInVersionColumn,
|
||||
|
||||
AllColumns: allColumns,
|
||||
MutableColumns: mutableColumns,
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
-- +goose Up
|
||||
-- Stage 10 admin & dictionary ops: the word-check complaint resolution lifecycle.
|
||||
-- Stage 3 created complaints with a free-form status (only ever 'open'); the admin
|
||||
-- review queue (this stage) resolves them with a disposition that also feeds the
|
||||
-- offline dictionary-rebuild pipeline: an accepted complaint records whether the
|
||||
-- word should be added or removed, and is marked applied once a rebuilt dictionary
|
||||
-- version is hot-reloaded. No operator identity is recorded (the gateway gates the
|
||||
-- console behind Basic-Auth; the backend keeps no admin principal). Adds columns, so
|
||||
-- the generated jet code is regenerated (cmd/jetgen).
|
||||
SET search_path = backend, pg_catalog;
|
||||
|
||||
ALTER TABLE complaints
|
||||
ADD COLUMN disposition text NOT NULL DEFAULT '',
|
||||
ADD COLUMN resolution_note text NOT NULL DEFAULT '',
|
||||
ADD COLUMN resolved_at timestamptz,
|
||||
ADD COLUMN applied_in_version text NOT NULL DEFAULT '',
|
||||
ADD CONSTRAINT complaints_status_chk CHECK (status IN ('open', 'resolved')),
|
||||
ADD CONSTRAINT complaints_disposition_chk
|
||||
CHECK (disposition IN ('', 'reject', 'accept_add', 'accept_remove'));
|
||||
|
||||
-- +goose Down
|
||||
SET search_path = backend, pg_catalog;
|
||||
|
||||
ALTER TABLE complaints
|
||||
DROP CONSTRAINT complaints_disposition_chk,
|
||||
DROP CONSTRAINT complaints_status_chk,
|
||||
DROP COLUMN applied_in_version,
|
||||
DROP COLUMN resolved_at,
|
||||
DROP COLUMN resolution_note,
|
||||
DROP COLUMN disposition;
|
||||
Reference in New Issue
Block a user