55ed87fb11
Store the sender's interface language (lang) and, for a message that arrived through an external connector (Telegram), the bot language (channel_lang) on the feedback row at submit time, so the operator console shows the state as it was rather than the account's current settings (same snapshot discipline as a suspension reason). Added additively in migration 00005. The console detail reads these columns instead of loading the account live.
56 lines
3.2 KiB
SQL
56 lines
3.2 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). The sender's interface/bot language snapshots
|
|
-- (lang, channel_lang) are added additively in 00005.
|
|
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;
|