Роль Support + тикет-система техподдержки через бота

Единственный канал создания тикета — кнопка "Поддержка" в Telegram-боте:
переводит юзера в разовый режим ожидания (awaiting_support_message),
следующее свободное сообщение становится тикетом/ответом в уже открытый
(случайный текст мимо кнопки по-прежнему игнорируется бота). Саппорты
отвечают из веб-админки, ответ уходит юзеру обратно в тот же
Telegram-чат. Новая роль support видит только раздел тикетов
(support_access_guard) — создаётся только Owner'ом.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-09 23:20:09 +07:00
parent 3feb2f431c
commit d543fb5585
12 changed files with 832 additions and 24 deletions
+41
View File
@@ -0,0 +1,41 @@
-- =====================================================================
-- ТЕХПОДДЕРЖКА: роль Support + тикеты через Telegram-бота
-- =====================================================================
-- Единственный канал связи с поддержкой — бот (см. src/bot.rs). Кнопка
-- "Поддержка" переводит юзера в разовый режим ожидания сообщения
-- (awaiting_support_message), сбрасывается сразу после получения текста —
-- случайное сообщение мимо кнопки тикет не создаёт.
ALTER TABLE users DROP CONSTRAINT IF EXISTS users_role_check;
ALTER TABLE users
ADD CONSTRAINT users_role_check
CHECK (role IN ('user', 'owner', 'moderator', 'partner', 'support', 'admin'));
ALTER TABLE users ADD COLUMN IF NOT EXISTS awaiting_support_message BOOLEAN NOT NULL DEFAULT FALSE;
CREATE TABLE IF NOT EXISTS support_tickets (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'closed')),
-- Информационно: кто последним ответил. Не enforced-назначение — любой
-- staff/support видит и отвечает на любой тикет.
assigned_to UUID REFERENCES users(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_support_tickets_status ON support_tickets(status, updated_at DESC);
-- Быстро найти "есть ли уже открытый тикет у юзера" — частичный индекс,
-- т.к. это единственный поиск по user_id, который реально нужен в горячем пути.
CREATE INDEX IF NOT EXISTS idx_support_tickets_user_open ON support_tickets(user_id)
WHERE status = 'open';
CREATE TABLE IF NOT EXISTS support_messages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
ticket_id UUID NOT NULL REFERENCES support_tickets(id) ON DELETE CASCADE,
sender_role VARCHAR(10) NOT NULL CHECK (sender_role IN ('user', 'staff')),
sender_user_id UUID REFERENCES users(id),
body TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_support_messages_ticket ON support_messages(ticket_id, created_at);