-- +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;