-- 186_tg_support.sql -- Telegram support bridge: @MERAsupport_bot mirrors client DMs into a -- support-group topic; an operator replies in-thread; the bot relays the -- reply back to the client's private chat. -- -- WHY: -- No durable state existed for this flow. Two things are required to make -- it work reliably: -- 1. A mapping from "message mirrored into the support topic" back to -- "which client chat_id it came from" — this is how an operator's -- reply (a Telegram reply-to a topic message) gets routed to the -- right client. `topic_message_id` on tg_support_messages is that -- routing key. -- 2. A durable long-polling offset (`tg_support_state`) so a worker -- restart does not replay already-processed Telegram updates. -- -- WHAT: -- - tg_support_users — one row per client Telegram private chat -- (chat_id is the Telegram chat id, stable per -- client, used directly as PK — no surrogate key -- needed). -- - tg_support_messages — full conversation log, both directions. -- - tg_support_state — singleton key/value store for worker offsets -- (e.g. key='last_update_id'). -- -- 152-FZ: -- tg_support_users / tg_support_messages hold personal data (Telegram -- username/name + free-text conversation content). ON DELETE CASCADE from -- tg_support_users -> tg_support_messages makes client erasure a single -- `DELETE FROM tg_support_users WHERE chat_id = :chat_id` statement, no -- separate cleanup pass needed. -- -- IDEMPOTENCY / SAFETY: -- CREATE TABLE IF NOT EXISTS + CREATE INDEX IF NOT EXISTS throughout — -- safe re-run. Purely additive: no existing table/view/column touched. -- -- Dependencies: none (new standalone tables). Auto-applied on deploy via -- _schema_migrations tracking (tradein-mvp/backend/data/sql convention). BEGIN; CREATE TABLE IF NOT EXISTS tg_support_users ( chat_id bigint PRIMARY KEY, username text, first_name text, last_name text, language_code text, created_at timestamptz NOT NULL DEFAULT now(), last_seen_at timestamptz NOT NULL DEFAULT now(), is_blocked boolean NOT NULL DEFAULT false ); COMMENT ON TABLE tg_support_users IS '152-ФЗ: ПДн клиентов Telegram-поддержки (@MERAsupport_bot). Удаление клиента — DELETE FROM tg_support_users WHERE chat_id=...; ON DELETE CASCADE в tg_support_messages подчищает переписку одной операцией.'; COMMENT ON COLUMN tg_support_users.chat_id IS 'Telegram private chat id клиента (стабильный, используется как PK напрямую).'; COMMENT ON COLUMN tg_support_users.is_blocked IS 'true, если клиент заблокировал бота (Telegram 403 на отправку) — бот перестаёт пытаться слать сообщения.'; CREATE TABLE IF NOT EXISTS tg_support_messages ( id bigserial PRIMARY KEY, chat_id bigint NOT NULL REFERENCES tg_support_users (chat_id) ON DELETE CASCADE, direction text NOT NULL CHECK (direction IN ('in', 'out')), tg_message_id bigint, topic_message_id bigint, kind text NOT NULL, text_body text, operator_tg_id bigint, created_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE tg_support_messages IS '152-ФЗ: полный лог переписки Telegram-поддержки (ПДн, содержимое сообщений). Каскадно удаляется вместе с tg_support_users по chat_id.'; COMMENT ON COLUMN tg_support_messages.direction IS '''in'' — сообщение от клиента боту; ''out'' — ответ бота/оператора клиенту.'; COMMENT ON COLUMN tg_support_messages.tg_message_id IS 'id сообщения в личном чате с клиентом (Telegram message_id в chat_id).'; COMMENT ON COLUMN tg_support_messages.topic_message_id IS 'id зеркала сообщения в support-топике группы — ключ маршрутизации: реплай оператора на это сообщение адресуется данному chat_id.'; COMMENT ON COLUMN tg_support_messages.kind IS 'text | photo | document | video | voice | other.'; COMMENT ON COLUMN tg_support_messages.text_body IS 'Текст сообщения или caption медиа; NULL для медиа без подписи.'; COMMENT ON COLUMN tg_support_messages.operator_tg_id IS 'Telegram user id оператора, ответившего в топике; заполняется только для direction=''out''.'; CREATE UNIQUE INDEX IF NOT EXISTS tg_support_messages_topic_message_id_uq ON tg_support_messages (topic_message_id) WHERE topic_message_id IS NOT NULL; CREATE INDEX IF NOT EXISTS tg_support_messages_chat_id_created_at_idx ON tg_support_messages (chat_id, created_at DESC); CREATE TABLE IF NOT EXISTS tg_support_state ( key text PRIMARY KEY, value text NOT NULL, updated_at timestamptz NOT NULL DEFAULT now() ); COMMENT ON TABLE tg_support_state IS 'Singleton key/value store для состояния Telegram-поддержки (например last_update_id для long-polling), переживает рестарт воркера.'; COMMENT ON COLUMN tg_support_state.key IS 'e.g. ''last_update_id''.'; COMMIT;