-- 0011_telegram_notifications — уведомления в Telegram о заявках, -- которые простояли в статусе 'new' дольше 24 часов. -- -- Куда слать — profiles.telegram_target, одно поле и два способа его -- заполнить: -- * числовой chat_id — записывает long-polling поллер, когда человек -- написал боту /start <код> из раздела «Настройки» (личная привязка); -- * @группа или @канал — вводит сам пользователь (бот обязан быть -- добавлен в этот чат; личный @ник боту недоступен, пока человек -- сам боту не напишет — поэтому личная привязка и через код). -- Пусто/NULL — уведомления этому пользователю не приходят. -- -- Дедуп — tickets.tg_notified_at: когда уходило последнее сообщение. -- Повторять можно, если поле NULL или старше порога (24 ч): напоминание -- уходит раз в сутки, пока заявка остаётся 'new'. Как только её взяли — -- условие отваливается само; поле не чистим, чтобы после возврата в -- 'new' отсчёт начался заново. -- -- Коды привязки — одноразовые и короткоживущие (15 минут). В таблице -- лежит только sha256 кода, как и токены сброса пароля (0007): утекшая -- база не даёт привязать чужой чат — по хешу код не восстановить. -- Выпуск нового кода гасит прежние неиспользованные (см. CreateBindCode). -- -- Применённый файл не редактировать (checksum в schema_migrations). ALTER TABLE profiles ADD COLUMN IF NOT EXISTS telegram_target text; ALTER TABLE tickets ADD COLUMN IF NOT EXISTS tg_notified_at timestamptz; CREATE TABLE IF NOT EXISTS telegram_bind_codes ( user_id uuid NOT NULL REFERENCES profiles (id) ON DELETE CASCADE, -- sha256 кода в hex; сам код показывается пользователю один раз code_hash text PRIMARY KEY, created_at timestamptz NOT NULL DEFAULT now(), expires_at timestamptz NOT NULL, used_at timestamptz, CONSTRAINT telegram_bind_codes_expiry CHECK (expires_at > created_at) ); -- У одного пользователя обычно один живой код; гашение прежних идёт -- по его строкам, чистка — по expires_at. CREATE INDEX IF NOT EXISTS telegram_bind_codes_user_idx ON telegram_bind_codes (user_id, created_at); -- Кандидаты на уведомление — все ещё не взятые заявки: отбор по -- created_at внутри статуса 'new' (частичный индекс держит запрос дешёвым). CREATE INDEX IF NOT EXISTS tickets_new_created_idx ON tickets (created_at) WHERE status = 'new';