База данных и миграции¶
Схема PostgreSQL изменяется только новыми миграциями Goose из каталога migrations/.
Имя файла и версию создаёт Goose; уже применённые миграции не редактируются.
Предметы роста¶
Миграция 20261020101748_add_growth_items_and_journal.sql добавляет growth_item_settings, growth_item_inventory, growth_item_players, growth_item_events. Ключи дневных событий учитывают сезон. Индексы обслуживают инвентарь, входящую/исходящую историю, журнал канала и очередь объявлений. Backfill не нужен: механика выключена по умолчанию, исторические предметы не выдаются.
Сначала примените миграцию штатным Goose, затем обновите Public API, OwnAdmin API, twitch-chat-execution, frontend и edge-конфигурацию Nginx: /api/v1/growth/player/ обслуживается Public API. Для legacy runtime обновляется основной бот. Затем владелец может включить предметы. Исходники доставляются через локальный коммит, push и git pull --ff-only на production. Down удаляет таблицы с игровыми данными: для отключения используйте настройку вместо отката. Правила и API.
Быстрая загрузка рейтинга роста¶
Миграция 20261005125445_index_growth_leaderboard_lookups.sql создаёт индексы
sm_measurements_growth_leaderboard_idx на (channel_id, current_value DESC, updated_date DESC)
и users_stats_channel_user_created_idx на (channel_id, user_id, created_date DESC) INCLUDE (name).
Запрос ListTopGrowthByChannel сначала ограничивает участников в материализованном CTE,
затем находит последнее имя только у выбранных лидеров. Сортировка и fallback имён сохраняются.
Колонка users_stats.name есть в схеме и коде, но 20250104175654_initial.sql её не создаёт: на production она
появилась вне миграций. Поэтому эта миграция сначала выполняет ALTER TABLE users_stats ADD COLUMN IF NOT EXISTS name:
на базах, где колонка есть, это ничего не меняет, а на базе, поднятой только миграциями, без этого индекс не создавался.
Миграция использует NO TRANSACTION и CREATE INDEX CONCURRENTLY: её нельзя оборачивать
во внешнюю транзакцию. Backfill не требуется. Примените миграцию штатным способом при развёртывании.
Если создание прервётся, проверьте pg_index.indisvalid: индекс с таким же именем и indisvalid = false
нужно удалить через DROP INDEX CONCURRENTLY и повторить миграцию; IF NOT EXISTS сам его не исправит.
На локальном PostgreSQL 16 с 2 000 участниками и 100 000 записей истории исходный запрос занял 5 661 мс, запрос с предварительным LIMIT — 17,2 мс, с новыми индексами — 0,22 мс. Это синтетический замер, время ответа production API отдельно не измерялось.
Настройки базовых команд¶
Таблица channel_base_command_settings хранит отдельные переключатели встроенных
команд канала. Миграция 20260911064303_add_follow_command_toggle.sql добавляет
cmd_follow_enabled boolean NOT NULL DEFAULT false. При применении миграции существующие
каналы наследуют состояние !стата, чтобы не потерять уже доступную команду; у новых
каналов владельцев !фоллоу / !follow остаётся выключенной до явного включения.
При развёртывании сначала примените миграции обычной production-командой проекта, затем перезапустите сервисы, которые читают или изменяют общие настройки и исполняют Twitch-команды.
Антиспам команд¶
Миграция 20260923090220_add_channel_command_spam_settings.sql добавляет в channels
настройки антиспама команд:
| Колонка | Тип | По умолчанию | Смысл |
|---|---|---|---|
command_quiet_cooldown_enabled |
boolean |
true |
предупреждать о кулдауне один раз за окно |
command_spam_timeout_enabled |
boolean |
false |
таймаут за продолжение спама после предупреждения |
command_spam_timeout_after |
smallint |
3 |
сколько попыток после предупреждения прощается; CHECK 1..10 |
Переносить данные не нужно: существующие каналы получают дефолты, то есть тихий
кулдаун включён, таймауты выключены. При подключении нового владельца
command_spam_timeout_enabled сбрасывается в false вместе с остальными
поведенческими флагами (src/twitch/owner_defaults.go).
Колонки читаются и пишутся запросами GetChannelGeneralSettings /
UpdateChannelGeneralSettings в src/pg/query.sql. Состояние «кто сколько раз
упёрся в кулдаун» в базе не хранится: оно живёт в памяти Twitch-процесса ровно
столько, сколько длится кулдаун.
После миграции перезапустите API-сервер (общие настройки) и Twitch-процесс, который исполняет команды и правила.
Запрещённые слова канала¶
Миграция 20260923113508_add_channel_forbidden_words.sql создаёт таблицу
channel_forbidden_words: слова, которые канал запрещает боту писать в Twitch,
в дополнение к встроенному списку (описание).
| Колонка | Тип | Смысл |
|---|---|---|
channel_id |
int |
канал, ON DELETE CASCADE |
word |
varchar(40) |
нормализованная запись: нижний регистр, ё→е; * в конце — основа |
created_by_user_id |
int |
кто добавил, ON DELETE SET NULL |
created_at |
timestamptz |
когда добавлено |
Первичный ключ (channel_id, word) покрывает единственный запрос чтения по каналу.
Сохранение (PUT /api/v1/channels/{id}/forbidden-words) заменяет список целиком в одной
транзакции: удаляет убранные записи и добавляет новые, не трогая автора и дату уже
существующих. Данные переносить не нужно, таблица стартует пустой.
Таймер счётчиков¶
Миграция 20260925124402_add_counter_streak_timer.sql добавляет в channel_counters
таймер «без события» (описание):
| Колонка | Тип | По умолчанию | Смысл |
|---|---|---|---|
counting_since |
timestamptz |
CURRENT_TIMESTAMP |
с какой даты ведём счёт: {since}, {counter.since:…} |
streak_started_at |
timestamptz |
CURRENT_TIMESTAMP |
последний сброс таймера, от него идёт {timer} |
previous_streak_seconds |
bigint |
0 |
сколько длился отрезок до последнего сброса: {previous} |
Ручной шаг не нужен: миграция сама ставит обе даты существующих счётчиков в created_date,
то есть старые счётчики считают с момента создания. Строки при этом обновляются, поэтому у них
сдвинется updated_date, а открытые OBS-страницы один раз получат уведомление об изменении
с тем же значением.
Сброс таймера — один UPDATE без гонок: IncrementChannelCounterRestartStreakByID (действие правила
с restart_timer) и RestartChannelCounterStreakByIDForChannel (кнопка на странице счётчиков)
в одной строке записывают previous_streak_seconds = now() - streak_started_at и ставят
streak_started_at = now().
После миграции перезапустите API-сервер (страница счётчиков), исполнение правил и config-guru:
флаг действия restart_timer передаётся в снапшоте правил.
Резерв классической логики правил¶
Миграция 20260926032301_add_channel_rule_legacy_backups.sql создаёт таблицу
channel_rule_legacy_backups. Когда классическое правило (logic_version=1) переводится в сценарий
(logic_version=2), logic_v2 заменяет условия, действия и наборы действий. Прежняя логика сохраняется здесь,
чтобы к ней можно было вернуться.
| Колонка | Тип | Смысл |
|---|---|---|
rule_id |
int |
правило, ON DELETE CASCADE |
channel_id |
int |
канал правила на момент резерва |
trigger_type, match_mode |
varchar |
триггер и режим совпадения условий |
conditions, actions, trigger_config |
jsonb |
классические условия, действия и настройки, в том числе наборы действий |
reason |
varchar(32) |
почему сохранили: switch_to_flow — правило сохранили в новом конструкторе, bulk_convert — перевела утилита convert-legacy-rules |
created_date |
timestamptz |
когда сохранили |
Резерв пишет PUT /api/v1/channels/{id}/rules/{ruleId} в той же транзакции, что и само изменение,
когда logic_version меняется с 1 на 2. Утилита массового перевода пишет резерв так же, в транзакции
каждого правила, — см. «Массовый перевод» во внутреннем docs/rules-and-triggers.md. Индекс
(rule_id, created_date DESC) нужен для ListChannelRuleLegacyBackups. Ручной шаг не нужен, таблица стартует
пустой.
Розыгрыши¶
Миграция 20261004104818_add_channel_giveaways.sql добавляет channel_giveaways, giveaway_entries, giveaway_ticket_ledger, giveaway_draws, giveaway_events, giveaway_partners, giveaway_message_receipts. Новые данные не требуют backfill; существующие каналы не получают открытых розыгрышей или партнёрского согласия.
PostgreSQL хранит настройки и состояния в JSONB, а ключи канала, цикла, Twitch ID, события и запроса — в индексируемых колонках. Уникальные ключи защищают начисления, вход и повторный запрос выбора. Переходы выполняются под блокировкой розыгрыша; открытие дополнительно блокирует канал advisory-lock для проверки конфликтующих команд. Пул, исходные веса, кандидат и результат сохраняются до анимации. Сброс меняет цикл и не удаляет историю.
SQL принадлежит src/pg/giveaways.sql; sqlc generate обновляет giveaways.sql.go и models.go. giveaway_identity_schema.sql — проекция уже существующих таблиц идентичности для sqlc, не новая схема этих таблиц. Триггер журнала публикует только channel_id через pulsecaster_giveaway_changes.
Сначала примените миграцию, затем обновите API и исполнение правил и запустите отдельный worker по инструкции эксплуатации docs/operations/giveaways.md в репозитории. Откат Goose удаляет все данные розыгрышей; выполнять его можно только с отдельным разрешением и резервной копией.
Связи Twitch ↔ Telegram и слияние аккаунтов¶
Вход в кабинет — это строка users. user_twitch_links (user_id → twitch_identity_id) и user_telegram_links (user_id → telegram_user_id) связаны с аккаунтом 1:1 и уникальны по внешнему идентификатору; JWT хранит внешний id, аккаунт вычисляется по этим таблицам на каждом запросе. Вход через Twitch без привязанного Telegram создаёт «Twitch-native» аккаунт (users.type='twitch'), он же остаётся персоной зрителя для чат-функций, поэтому строки users не удаляются.
user_twitch_links.linked_via: oauth (кнопка «Подключить Twitch»/вход), code (!code в чате), backfill, broadcaster (вход вещателя автоматически привязан к аккаунту, который уже владеет его каналом). user_telegram_links.linked_via: native, oauth, backfill. Миграция 20261005091839_merge_twitch_native_accounts.sql создаёт self-link для type='telegram' без связи (аккаунты, заведённые ботом) и добавляет broadcaster в CHECK; откат переводит broadcaster в oauth. Ручных шагов нет.
Если человек подтверждает Twitch и Telegram, которые уже живут в разных аккаунтах, internal/accountmerge объединяет их, а не отказывает. Выживший всегда Telegram-аккаунт (type='telegram' или с Telegram-связью); поглощается только Twitch-native аккаунт без Telegram. Одной транзакцией с блокировкой строк users по возрастанию id переносятся: связь Twitch (и запись city_user_twitch_links), channels.owner_user_id, moderation_trust_memberships.owner_id, channel_moderators (запись выжившего побеждает; granted_via/verified_at сохраняются), subscriber/subscription, content_mirrors, tg_bots, media_timing_moderator_access. Одноразовые коды и OAuth-состояния поглощённого удаляются. Не переносятся City-роль и персонаж, VIP, рост, статистика и поля аудита *_by: они остаются на поглощённой строке. Отказ без изменений: у аккаунта уже другой Twitch/Telegram, идентичность принадлежит аккаунту, который нельзя поглотить, либо Telegram-id занят строкой другого типа (users.user_id общий для обоих пространств id). Каждое слияние пишется в лог (accountmerge: absorbed twitch-native account) с id обоих аккаунтов и источником.
Привязка через /twitch использует существующие таблицы идентичностей и кодов; запросы принадлежат src/pg/account_links.sql. Проекция для sqlc включает существующие twitch_identity_link_codes и city_user_twitch_link_codes. Прогресс присутствия и новые настройки активности сохраняются в JSON розыгрыша/заявки. Дополнительная миграция и backfill для этого расширения не нужны; старый источник активности остаётся «сообщения».
Миграция 20261004175920_extend_giveaways_access_rewards_and_elimination.sql добавляет отдельные viewer credentials и giveaway_redemptions, уникальную привязку награды к неархивному розыгрышу и разрешает отрицательные, ненулевые суммы журнала билетов для отмен покупок. Для существующих розыгрышей миграция создаёт непредсказуемые ссылки участников через два PostgreSQL UUID; OBS-ссылки сохраняются. Прежние числовые ссылки нужно заменить ссылками из кабинета. В новых розыгрышах оба ключа генерируются независимо через crypto/rand.
Goose Up/Down проверяются на PostgreSQL 16, sqlc-проекция содержит существующую channel_managed_rewards. Откат этой миграции удаляет очередь покупок, отрицательные записи журнала и viewer credentials, но не пересчитывает JSON-счётчики билетов. Поэтому Down не является безопасным рабочим откатом после покупок: предварительно остановите обработку, завершите очередь и сохраните резервную копию; требуется отдельное разрешение. Данные последовательности исключений остаются в JSON выбора.
Миграция 20261004213028_add_giveaway_targeted_grants_and_worker_status.sql добавляет giveaway_partner_grants с уникальной парой проверяемого и получающего доступ каналов, индексом по получателю и запретом выдачи доступа самому себе, а также giveaway_worker_status для диагностики worker. Старые широкие согласия отключаются; адресные разрешения выдаются владельцами заново в кабинете. Автоматического backfill в доступ всем каналам нет. Примените миграцию до обновления API/worker. Down удаляет адресные разрешения и heartbeat и не восстанавливает широкие согласия.
Общая база ответов ИИ¶
Миграция 20261005095422_add_ai_answer_library.sql создаёт ai_answer_settings
и ai_answers. Записи общие для всех каналов; kind отделяет тип генерации
(первым подключён superpower). Новые типы должны явно подключить сервис
internal/aianswers и собственную валидацию; предсказания пока используют прежнюю логику.
input_key — способность в нижнем регистре с нормализованными пробелами.
Пустой ключ обозначает полный ответ для команды без аргумента. Для непустого
ключа хранится только подвох к этой способности. Статусы: pending, approved,
rejected; бот выбирает исключительно approved. Источник: ai или manual.
Результаты ИИ автоматически сохраняются как pending, повторная генерация
не сбрасывает модерацию или счётчик существующей записи. Очередь сгенерированных
кандидатов автоматически сохраняет 1000 последних записей; одобренные,
отклонённые и ручные записи не удаляются при этой очистке. Удалённый вариант
может появиться снова при новой генерации; чтобы запретить его повторную модерацию,
используйте rejected.
Вес ответа равен 1 / (1 + usage_count). Выбор и увеличение счётчика выполняются
одним SQL-запросом с FOR UPDATE SKIP LOCKED, без потерянных обновлений между
процессами. Записи, занятые конкурентным запросом, пропускаются; если все подходящие
ответы заняты, запрос возвращается к ИИ. Максимальный счётчик — 9007199254740991
(точное целое в JavaScript); достигшие его ответы не выбираются до ручной правки.
При редактировании поле usage_count можно опустить, чтобы сохранить текущий счётчик.
database_chance_percent — общий шанс обращения к базе (0–100, начальное значение 50).
Он читается при каждом запросе и меняется без перезапуска. При пустой базе,
отсутствии совпадающего ключа или ошибке хранилища бот обращается к ИИ.
Готовый ответ не расходует квоту ИИ; суточный лимит команды и лимит действия
правила на зрителя в стриме сохраняются.
API доступен только администраторам платформы под /api/v1/admin/ai-answers/{kind}:
| Метод и путь | Назначение |
|---|---|
GET /superpower?status=pending&before_id=123 |
список до 50 записей, has_more, next_cursor; без status — все |
POST /superpower |
ручной вариант: input, answer, status, необязательный usage_count; статус по умолчанию pending |
PUT /superpower/{id} |
редактирование текста, статуса и необязательного счётчика |
DELETE /superpower/{id} |
удаление варианта |
GET /superpower/settings |
текущая вероятность |
PUT /superpower/settings |
{"database_chance_percent": 50} |
Страница админки: /admin/ai-answers. Текст способности и ответа ограничен
180 символами; управляющие символы запрещены. Новые генерации содержат
пользовательский текст способности: список доступен только администраторам,
эти тексты не выводятся в служебные логи.
Миграция стартует с пустой базой ответов; backfill не требуется. При развёртывании
сначала примените миграцию штатной командой проекта, затем пересоберите и
перезапустите ownadmin-api и процесс, исполняющий Twitch-команды и правила
(decision-engine; при embedded-режиме — основной процесс), и опубликуйте новую
сборку Mini App. Скриншоты-референсы используются в промпте; ответы из них
автоматически в одобренную базу не добавляются.
Для проверки SQL на изолированной PostgreSQL:
PULSECASTER_AI_ANSWERS_TEST_DATABASE_URL=... go test ./internal/aianswers -run TestPostgresAnswerPool.
Тест создаёт собственную схему, проверяет Goose Up/Down, модерацию, веса и конкурентные счётчики,
затем удаляет схему.
VIP за серию просмотров и сроки VIP¶
Миграция 20261005125535_vip_streak_tiers_and_grant_details.sql:
| Объект | Назначение |
|---|---|
channel_vip_settings.streak_enabled |
включён ли VIP за серию просмотров |
channel_vip_streak_tiers |
ступени канала: min_streak (1–10000 стримов подряд), duration_days (0–3650, 0 — навсегда); пара (channel_id, min_streak) уникальна |
channel_vip_streak_state |
по зрителю (viewer_id — числовой Twitch ID, если его нет — логин): last_streak для обнаружения обрыва серии и claimed_streak — уже выданная ступень |
channel_vips.source_detail |
пояснение выдачи для списка VIP: название награды, число стримов серии, кто выдал вручную |
Источник VIP в channel_vips.source теперь также принимает streak. Срок VIP живёт в channel_vips.expires_at
и применяется всегда, когда он задан: фоновая проверка снимает любого VIP с истёкшим сроком, кроме людей
из белого списка. Поэтому миграция обнуляет сроки у каналов с default_duration_days = 0, где они раньше
не применялись. Подробности выпуска — в docs/operations/vip-manager-rollout.md.
Доска /wall: перемещение, закрепление и восстановление¶
Миграция 20261007134135_add_wall_card_controls.sql добавляет в wall_messages
pinned (по умолчанию false) и visible_since. Backfill сохраняет исходный срок
видимости, учитывая последнее восстановление из wall_moderation_events.
Дата created_at остаётся датой публикации. Срок видимости считается от
visible_since: 30 дней для анонимных записей, 90 для Twitch. Действие restore
обновляет visible_since, поэтому возвращённая старая записка снова появляется.
Если до обновления запись после восстановления уже была автоматически скрыта,
нажмите «Вернуть» ещё раз после выпуска.
Администратор может двигать любую карточку на /wall и закреплять/откреплять её
на доске или в /admin/wall. Закрепление сохраняет карточку после истечения срока
и защищает от скрытия автором, скрытия/блокировки и безвозвратного удаления через
админку: сначала требуется открепить карточку. Само закрепление скрытую запись
не восстанавливает. После открепления применяется прежний срок видимости.
Действия администратора move, pin, unpin записываются в журнал модерации.
PATCH /api/v1/wall/messages/{id} принимает x и y вместе и/или body.
Только автор, вошедший через Twitch, может перемещать свои опубликованные записи
и править текст (1–180 символов) первые 30 минут с created_at. Анонимным авторам
остаётся скрытие собственных записей. Координаты ограничены 0–7000 и 0–4250.
Публичная карточка содержит pinned, can_move, can_edit, can_delete;
для Twitch-автора — edit_until. Авторизация, срок и видимость проверяются сервером.
Восстановление и перемещение не продлевают время редактирования. Дополнительные
OAuth scopes Twitch не нужны: используется существующая личность из OIDC-сессии.
PATCH /api/v1/admin/wall/messages/{id} дополнительно поддерживает
action: move с x, y, а также pin и unpin. Ответ сохраняет поле status
и добавляет pinned и актуальную карточку message.
Для выпуска сначала примените миграцию, затем пересоберите и перезапустите
ownadmin-api и опубликуйте новую сборку frontend. Другие процессы не затронуты.
Down удаляет новые поля и события move/pin/unpin; исходные даты публикации сохранены.
Кадрирование фона уведомлений¶
Миграция 20261007231302_add_subscription_background_crop.sql добавляет четыре поля в img_type_2: background_crop_enabled boolean NOT NULL DEFAULT false, background_crop_x и background_crop_y integer NOT NULL DEFAULT 500 с CHECK 0–1000, background_crop_zoom integer NOT NULL DEFAULT 100 с CHECK 100–400. X/Y выбирают относительное смещение внутри доступной области обрезки; zoom задаёт масштаб в процентах. Эти поля независимы от рамки фона и расположения области градиента.
Отдельный backfill не нужен: существующие строки получают false, центр 500/500 и масштаб 100. Прежний способ растяжения сохраняется до явного включения кадрирования. sqlc-схема и UpdateSubscriptionBackgroundCrop находятся в src/pg/query.sql; после изменения выполнен sqlc generate. Сохранение кадрирования выполняется в транзакции image-config вместе с параметрами и режимом обложки.
Для выпуска сначала примените миграцию через штатный db-migrate, затем обновите ownadmin-api, генератор уведомлений (event-hub в разделённом runtime) и frontend Mini App. При Down удаляются параметры кадрирования; перед откатом схемы нужно вернуть backend, который не читает новые поля. Исходные изображения библиотеки не изменяются.
Композиции обложек со слоями¶
Миграция 20261008164752_add_subscription_cover_scene.sql добавляет nullable settings.cover_scene jsonb. Документ имеет version: 1 и хранит холст и слои; img_type = 3 включает новый генератор. Размер холста — 320–2560 px по каждой стороне; до 24 слоёв, до 12 файлов в медиаслое. Координаты измеряются в пикселях холста. Параметры и режим записывает sqlc-запрос SetSubscriptionCoverScene в транзакции image-config под блокировкой строки settings. При выключении изображения (img_type = 0) документ можно сохранить вместе с режимом: повторное включение не теряет композицию.
Backfill не нужен. Существующие режимы 0/½ не меняются при миграции; переход на 3 выполняется явным сохранением в новом редакторе. Сначала примените миграцию штатным db-migrate, затем обновите ownadmin-api, процессы генерации уведомлений (event-hub в разделённом runtime) и frontend. WASM собирается из того же исходного кода, что и backend; команды и API описаны в интеграциях.
Down выключает подписки с режимом 3 и удаляет документы композиции; исходные медиафайлы и параметры старых режимов сохраняются. Перед откатом схемы нужно вернуть backend, который не читает cover_scene. Чтобы восстановить композиции после Down, необходима их резервная копия.
Сцены роста и ограниченные награды Качка¶
Миграция 20261009153002_add_growth_overlays_and_collective_rewards.sql добавляет:
channel_growth_overlays: две независимые настройки на канал (eventsиresults), переключатель, JSON оформления и хеш секретного токена. Открытый токен возвращается только при создании/замене ссылки.channel_growth_overlay_state: последний снимок ивента с сроком действия и возрастающей ревизией. Twitch-исполнение публикует его, Public.API читает; запоздавший снимок не заменяет новый.growth_collective_rewards: день последней выдачи по МСК и ожидающий бонус пользователя на канале. Ограничение базы — 0–4 см. Выдача участникам транзакционная, повтор в тот же день запрещён; расходование бонуса и сохранение результата роста выполняются одной транзакцией.growth_check_events.display_nameиachievement: текст новых уведомлений, без изменения старой истории. Индексы по(channel_id, id)и(channel_id, user_id, current_value DESC)ускоряют очередь и поиск рекорда.
Backfill не требуется. Настройки сцен создаются при первом сохранении или создании ссылки. Сброс роста очищает ожидающую награду, сохраняя день выдачи.
Индексы создаются CONCURRENTLY, поэтому миграция выполняется без общей транзакции.
При прерванном построении проверьте pg_index.indisvalid; невалидный индекс с одним из указанных имён
нужно удалить DROP INDEX CONCURRENTLY перед повторным запуском миграции.
Операции добавления столбцов требуют краткой блокировки таблицы; выбирайте время низкой активности.
Сначала примените миграцию, затем пересоберите Public.API, OwnAdmin.API и twitch-chat-execution, а также frontend.
Down удаляет настройки, ссылки и ожидающие награды; перед откатом нужна резервная копия и отдельное разрешение.
Счётчики по категориям¶
Миграция 20261011175054_add_category_counters.sql добавляет к channel_counters режим
scope (global по умолчанию, category для родительского счётчика), parent_counter_id,
category_id, category_name, category_key_prefix. Значение и таймер каждой категории
хранятся в отдельной строке; уникальный индекс (parent_counter_id, category_id) защищает
от дублирования при параллельных первых изменениях. Префикс уникален среди счётчиков режима
category одного канала. Общие счётчики не меняют поведения.
Триггеры уведомляют OBS как при изменении значения, так и при изменении категории в
stream_current_states. Значения выбираются по ID Twitch, без нового OAuth-доступа.
Автоматического массового backfill нет: старые ключи вида death:<название> можно подключить
через префикс в форме создания. Связь с ID категории появляется при первом изменении,
существующее число и таймер сохраняются. До подключения переименование категории не может
быть восстановлено по одному старому текстовому ключу — такой ключ нужно сопоставить вручную.
Сначала примените миграцию, затем пересоберите и перезапустите ownadmin-api, public-api
и twitch-chat-execution (и другие процессы, если они исполняют правила в вашей конфигурации); обновите frontend. Down сохраняет строки и
числа, но снимает связи категорий: это технический откат схемы, а не восстановление прежнего UI.
API создания принимает scope и необязательный category_key_prefix. Режим и префикс
неизменяемы после создания. Списки возвращают только родительские/общие счётчики, а для
режима по категориям — выбранные value, category_id, category_name и categories
на endpoint списка OBS-счётчиков. PUT и POST mutate принимают необязательный category_id
как защиту от устаревшей формы: несовпадение с текущей категорией даёт 409 без изменения числа.
Чат и Stream Deck без этого параметра используют текущую категорию на момент запроса.
Шатауты: объявление к шатауту и пауза по кулдауну Twitch¶
Миграция 20261020100011_add_shoutout_first_message_announcement.sql добавляет в channel_shoutout_settings
настройки объявления к шатауту по первому сообщению: first_message_announce_enabled,
first_message_announce_color (с той же проверкой цвета, что у объявления при рейде),
first_message_announce_template и first_message_announce_fallback_template.
Она же поднимает queue_pause_seconds до 120 у каналов, где пауза была меньше, и меняет значение
по умолчанию на 120: Twitch разрешает каналу один шатаут в 2 минуты, с паузой 60 секунд второй шатаут
получал отказ и пропадал. Других ручных шагов нет; Down возвращает значение по умолчанию 60, но уже
поднятые паузы не опускает.
Список каналов (channel_shoutout_targets) теперь меняется точечно (POST/DELETE
/channels/{id}/shoutout-settings/targets): строки не пересоздаются, поэтому created_by_login и created_at
сохраняются. Журнал страницы читает channel_activity_events категории shoutout
(shoutout.*, raid_announcement.*, first_message_announcement.*) по индексу
channel_activity_events_filter_idx.
Настройки канала без своего столбца¶
Миграция 20261011190512_add_channel_setting_overrides.sql создаёт channel_setting_overrides
(channel_id, key, value jsonb, updated_at) с первичным ключом (channel_id, key) и удалением вместе с каналом.
Сейчас здесь лежат только выключенные переключатели алиасов команд (ключ commands.completed:!играл, значение
false); включённый алиас строки не имеет. Таблица общая для настроек, которым не нужен отдельный столбец.
Down удаляет таблицу, и все алиасы снова включаются. Сначала примените миграцию, затем перезапустите сервис с API кабинета
(/channels/{id}/settings) и twitch-chat-execution, потом frontend. Без миграции бот
продолжает работать: ошибка отсутствующей таблицы трактуется как «все алиасы включены».
Отдельные переключатели для команд, которые делили общий¶
Миграция 20261011190600_split_shared_command_switches.sql даёт свои переключатели командам, которые раньше
включались вместе с соседней: !тайминги и !можностримить (с !медиа), !казино (с !рост), !статус
(с !прошел) и !отметить (с !эпизод). Их значения лежат в channel_setting_overrides под ключами
commands.media_timings, commands.media_streamability, commands.casino, admin_commands.media_status,
admin_commands.media_progress. Бэкфилл: если общий столбец канала выключен, новая команда записывается
выключенной; выключенные «алиасы» !тайминги, !можностримить, !казино, !статус, !отметить становятся
выключенными командами, !риск и !чепомедиа переезжают к !можностримить. Удаляются ключи слов, которых
больше нет (!помошь, !смотела, !d, !d20, !д20 — бот их не обрабатывал или узнаёт как опечатку сам),
и бывших алиасов, ставших основной командой (!чтобыло, !статистика, !шатаут). Новый владелец получает
эти команды выключенными вместе с остальными (ConfirmChannelOwnerWithDefaults).
Down убирает переключатели отделённых команд; прежние выключенные алиасы не восстанавливаются.
Версия миграции поставлена сразу после 20261011190512: та получила версию по местному времени и опережает
сгенерированную goose (UTC), а эта должна выполниться после неё. Порядок применения: миграция, затем бэкенд
(сервис с API кабинета и twitch-chat-execution), затем frontend.
Один выключатель у !можностримить¶
Миграция 20261011190700_single_streamability_switch.sql: у !можностримить был второй выключатель —
channel_media_streamability_settings.enabled («Команда проверки» на странице кинопросмотра). Каналы, где он выключен,
получают выключенный переключатель commands.media_streamability в channel_setting_overrides; бот больше не смотрит на
enabled, столбец остаётся и не меняется. Down ничего не делает: выключенная команда остаётся выключенной. Номер
поставлен после 20261011190600 по той же причине, что и у неё.
DonateX OAuth и повторы донатных запросов¶
Миграция 20261020083405_add_donation_oauth_recovery_and_donatex.sql добавляет в
donation_platform_connections поля last_error_code и next_retry_at, а в
donation_platform_oauth_sessions — зашифрованный code_verifier и провайдера donate_x.
Версия сгенерирована Goose и новее предыдущей миграции 20261011190700.
Перед запуском обновлённого ownadmin-api примените миграцию; существующие credentials не меняются,
backfill не требуется. Затем обновите frontend и edge (маршруты callback без access-log).
Для DonateX заполните ключи и включите OAuth по инструкции в docs/configuration.md.
Откат схемы удаляет незавершённые DonateX OAuth-сессии; активные подключения и токены остаются
в общей таблице. Старый runtime не умеет продлевать DonateX OAuth: перед его возвратом отключите
такие подключения либо явно переподключите их по API-ключу.