Files
scrabble-game/backend/internal/postgres/migrations/00004_feedback.sql
T
Ilia Denisov 419ea11b14
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 47s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
feat(feedback): in-app user feedback with admin review and account roles
User-facing Feedback screen (Settings -> Info, registered accounts only): a
message (<=1024 runes) plus one optional attachment, an anti-spam gate (one
unreviewed message at a time), and the operator's inline reply with a
Settings/Info badge. Server-rendered admin console section (/_gm/feedback):
unread/read/archived queue with per-user search, detail with read/reply/
archive/delete/delete-all, safe attachment serving (nosniff, images inline via
<img>, others download-only). Introduces account_roles, the first per-account
role table; feedback_banned blocks only feedback submission, granted/revoked
from /users and the delete-with-block action.

- migration 00004_feedback (feedback_messages + account_roles) + jetgen
- backend internal/feedback (store+service), internal/account/roles.go
- wire: FlatBuffers feedback.submit/get/unread; gateway guest gate (Op.NonGuest,
  is_guest via session resolve) -> guest_forbidden before any backend call
- reply push reuses NotificationEvent with a new admin_reply sub-kind
- UI: /feedback route + screen, attachment picker, badge, channel detection, i18n
- tests: feedback unit (Go+UI), gateway guest-gate, inttest lifecycle, e2e
- docs: PLAN stage 19, ARCHITECTURE s15, FUNCTIONAL(+ru), TESTING, READMEs
2026-06-15 12:23:10 +02:00

55 lines
3.1 KiB
SQL

-- +goose Up
-- User feedback ("Обратная связь"): a flat list of messages a registered player sends to the
-- operators from Settings -> Info, each with an optional single attachment, plus the operator's
-- inline reply shown back to the player. Distinct from the in-game chat_messages (peer-to-peer)
-- and from the out-of-app Telegram messages. Also introduces account_roles, the project's first
-- per-account role table, replacing per-feature boolean flags. See docs/ARCHITECTURE.md.
SET search_path = backend, pg_catalog;
-- One feedback message. read_at marks "the operator has dealt with this" (set by every console
-- action: read / reply / archive; never by merely opening the detail). The anti-spam gate forbids
-- a new submission while the player has any read_at IS NULL message. archived_at files a handled
-- message away. The reply lives on the same row: reply_body/replied_at are stamped when the
-- operator answers; reply_read_at is stamped when the player's app first fetches the reply for
-- display ("delivered = read"), and the reply is hidden from the player one week after that.
-- attachment is the raw bytes (capped at 1,000,000 in Go); attachment_name carries the original
-- file name (its extension drives the safe content-type at serve time). sender_ip is the
-- gateway-forwarded client IP (validated, like chat); channel is the submitting platform
-- (telegram/ios/android/web, validated in Go).
CREATE TABLE feedback_messages (
message_id uuid PRIMARY KEY,
account_id uuid NOT NULL REFERENCES accounts (account_id) ON DELETE CASCADE,
body text NOT NULL,
attachment bytea,
attachment_name text,
sender_ip text,
channel text NOT NULL,
read_at timestamptz,
archived_at timestamptz,
reply_body text,
replied_at timestamptz,
reply_read_at timestamptz,
created_at timestamptz NOT NULL DEFAULT now()
);
-- The per-player probes (anti-spam "has unread", "latest message", "latest reply") and the
-- console's user-scoped search all look an account up newest-first.
CREATE INDEX feedback_messages_account_idx ON feedback_messages (account_id, created_at DESC);
-- The console queue lists messages newest-first across all accounts.
CREATE INDEX feedback_messages_created_idx ON feedback_messages (created_at DESC);
-- Named per-account roles. A reusable replacement for per-feature boolean flags: the first role,
-- 'feedback_banned', forbids only feedback submission (not the whole account, unlike a
-- suspension). Roles are validated in Go (no CHECK here) so new ones need no migration. Granted
-- from the feedback console (the "block" checkbox on delete) and granted/revoked from /users.
CREATE TABLE account_roles (
account_id uuid NOT NULL REFERENCES accounts (account_id) ON DELETE CASCADE,
role text NOT NULL,
granted_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (account_id, role)
);
-- +goose Down
SET search_path = backend, pg_catalog;
DROP TABLE IF EXISTS account_roles;
DROP TABLE IF EXISTS feedback_messages;