Создание системы объявлений (#84) #85

Merged
NotBigGhost merged 4 commits from issue-84-announcements into dev 2026-09-18 22:06:42 +03:00
Collaborator

Что сделано

Объявления администрации по выбранным макетам: окно E у игрока и редактор R1 в админке.

Игрок. Окно всплывает на любой странице, но только после окна установки пароля. Заголовок в плашке-шапке, ниже текст, точки очереди и «Понятно»; крестика нет. Объявления с пересекающимися периодами идут от старого к новому, следующее — после закрытия предыдущего. Закрытым считается по «Понятно»; отметка хранится на сервере (announcement_views), так что между устройствами объявление не повторяется.

Админка. Вкладка «Объявления»: заголовок, текст с панелью (жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления), период показа по МСК, переключатель «Показывать новым игрокам» (выключен — не видят зарегистрировавшиеся после начала показа), при правке — «Показать заново тем, кто уже закрыл» (они увидят объявление снова с красной мелкой пометкой «обновлено» под заголовком). Предпросмотр — тем же окном, что у игрока. Список со статусом (запланировано / идёт / завершено), счётчиком «закрыли N из M» и действиями: изменить, снять с показа, дублировать, удалить. Выделение текста в форме — полупрозрачный оранжевый (--accent-2) вместо синего.

Безопасность. Текст приходит HTML-ом из contenteditable и сохраняется только после очистки на сервере: HTML пересобирается из разобранных токенов, остаются b / em / mark (+ class="red") / p / br без атрибутов, script / style / svg / iframe … выбрасываются вместе с содержимым, весь текст экранируется заново. Фронт вставляет в окно игрока только этот HTML.

Коммиты

  • 2e496a3 Гитигнор: локальный AGENTS.md не идёт в репозиторий
  • a0a0e52 Объявления: модель, API игрока и админки, очистка HTML
  • ab1fc3f Объявления: окно у игрока и редактор в админке
  • ec0445f Миграции: объявления отходят от 0013, слияние с рейтингом в 0016

Миграции

Объявления выпускаются в main раньше рейтинга (PR #86 — те же изменения на main без рейтинга), а там последняя миграция — 0013. Поэтому 0015_announcements отходит от 0013, как и рейтинговая 0014, а пустая 0016 сводит обе ветки в одну голову. Базы на 0014 (dev) получат 0015 и 0016; прод, стоящий на 0015 после релиза объявлений, при переходе на dev получит 0014 и 0016. Базы, уже прогнанные первой версией этого PR (где 0015 шла после 0014), повторно получат идемпотентную 0014 — проверено.

Проверки

  • pytest — 208 passed (было 194; +14 в tests/test_announcements.py: очистка HTML и попытки XSS, права, период и порядок, «новые игроки», повторный показ с пометкой, снятие с показа, удаление, валидация).
  • npm run build (typecheck + сборка) — зелёный.
  • alembic upgrade head — проверено на копии dev-БД (на 0014 и на 0015 из первой версии PR), на пустой базе и по пути прода 0013 → 0015 (main) → 0014 + 0016 (dev).
  • npm run gen:api — schema.d.ts пересобран из живого OpenAPI (только добавления).
  • /security-review по диффу dev...issue-84-announcements — уязвимостей HIGH/MEDIUM не найдено. Очистка HTML дополнительно проверена ~40 вредоносными вставками (<img onerror>, xmp, noscript, svg/style-мутации, CDATA, битые теги, инъекция атрибутов в mark) — на выходе только разрешённые теги и экранированный текст.
  • В браузере не проверялось: расширение Chrome в сессии не подключено. Поведение редактора (execCommand) и окна стоит прокликать вручную.

Модель угрозы. Писать объявления может только админ (отдельный вход, get_current_admin, CSRF double-submit на всех изменяющих запросах). Игрок только читает активные объявления и отмечает закрытие — user_id берётся из сессии, чужую отметку поставить нельзя. Главный риск — хранимый XSS через текст объявления, который видят все игроки; его закрывает серверная очистка (и тесты на неё).

Отклонения от плана

  • Черновиков нет (в макете R1 был статус «Черновик»): период показа обязателен, вместо черновика — «Дублировать».
  • «Новые игроки» — зарегистрированные после начала показа; пометку «обновлено» видят только те, кто закрывал прежнюю версию.
  • Объявление с отложенным началом появляется у открытой вкладки при перезапросе раз в 5 минут (или при возврате во вкладку) — наступление времени не событие в данных, SSE его не шлёт.
  • Добавлен PR #86 в main и миграция-слияние 0016 — по просьбе владельца выпустить объявления раньше рейтинга.

После мёржа

Перед запуском dev нужны миграции (run.ps1 делает alembic upgrade head сам); в проде — entrypoint.sh.

Closes #84

🤖 Generated with Claude Code

## Что сделано Объявления администрации по выбранным макетам: окно **E** у игрока и редактор **R1** в админке. **Игрок.** Окно всплывает на любой странице, но только после окна установки пароля. Заголовок в плашке-шапке, ниже текст, точки очереди и «Понятно»; крестика нет. Объявления с пересекающимися периодами идут от старого к новому, следующее — после закрытия предыдущего. Закрытым считается по «Понятно»; отметка хранится на сервере (`announcement_views`), так что между устройствами объявление не повторяется. **Админка.** Вкладка «Объявления»: заголовок, текст с панелью (жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления), период показа по МСК, переключатель «Показывать новым игрокам» (выключен — не видят зарегистрировавшиеся после начала показа), при правке — «Показать заново тем, кто уже закрыл» (они увидят объявление снова с красной мелкой пометкой «обновлено» под заголовком). Предпросмотр — тем же окном, что у игрока. Список со статусом (запланировано / идёт / завершено), счётчиком «закрыли N из M» и действиями: изменить, снять с показа, дублировать, удалить. Выделение текста в форме — полупрозрачный оранжевый (`--accent-2`) вместо синего. **Безопасность.** Текст приходит HTML-ом из `contenteditable` и сохраняется только после очистки на сервере: HTML пересобирается из разобранных токенов, остаются `b / em / mark (+ class="red") / p / br` без атрибутов, `script / style / svg / iframe …` выбрасываются вместе с содержимым, весь текст экранируется заново. Фронт вставляет в окно игрока только этот HTML. ## Коммиты - `2e496a3` Гитигнор: локальный AGENTS.md не идёт в репозиторий - `a0a0e52` Объявления: модель, API игрока и админки, очистка HTML - `ab1fc3f` Объявления: окно у игрока и редактор в админке - `ec0445f` Миграции: объявления отходят от 0013, слияние с рейтингом в 0016 ## Миграции Объявления выпускаются в `main` раньше рейтинга (PR #86 — те же изменения на `main` без рейтинга), а там последняя миграция — `0013`. Поэтому `0015_announcements` отходит от `0013`, как и рейтинговая `0014`, а пустая `0016` сводит обе ветки в одну голову. Базы на `0014` (dev) получат `0015` и `0016`; прод, стоящий на `0015` после релиза объявлений, при переходе на `dev` получит `0014` и `0016`. Базы, уже прогнанные первой версией этого PR (где `0015` шла после `0014`), повторно получат идемпотентную `0014` — проверено. ## Проверки - `pytest` — **208 passed** (было 194; +14 в `tests/test_announcements.py`: очистка HTML и попытки XSS, права, период и порядок, «новые игроки», повторный показ с пометкой, снятие с показа, удаление, валидация). - `npm run build` (typecheck + сборка) — зелёный. - `alembic upgrade head` — проверено на копии dev-БД (на `0014` и на `0015` из первой версии PR), на пустой базе и по пути прода `0013 → 0015 (main) → 0014 + 0016 (dev)`. - `npm run gen:api` — `schema.d.ts` пересобран из живого OpenAPI (только добавления). - `/security-review` по диффу `dev...issue-84-announcements` — уязвимостей HIGH/MEDIUM не найдено. Очистка HTML дополнительно проверена ~40 вредоносными вставками (`<img onerror>`, `xmp`, `noscript`, `svg/style`-мутации, CDATA, битые теги, инъекция атрибутов в `mark`) — на выходе только разрешённые теги и экранированный текст. - **В браузере не проверялось**: расширение Chrome в сессии не подключено. Поведение редактора (`execCommand`) и окна стоит прокликать вручную. **Модель угрозы.** Писать объявления может только админ (отдельный вход, `get_current_admin`, CSRF double-submit на всех изменяющих запросах). Игрок только читает активные объявления и отмечает закрытие — `user_id` берётся из сессии, чужую отметку поставить нельзя. Главный риск — хранимый XSS через текст объявления, который видят все игроки; его закрывает серверная очистка (и тесты на неё). ## Отклонения от плана - **Черновиков нет** (в макете R1 был статус «Черновик»): период показа обязателен, вместо черновика — «Дублировать». - «Новые игроки» — зарегистрированные **после начала показа**; пометку «обновлено» видят только те, кто закрывал прежнюю версию. - Объявление с отложенным началом появляется у открытой вкладки при перезапросе раз в 5 минут (или при возврате во вкладку) — наступление времени не событие в данных, SSE его не шлёт. - Добавлен PR #86 в `main` и миграция-слияние `0016` — по просьбе владельца выпустить объявления раньше рейтинга. ## После мёржа Перед запуском dev нужны миграции (`run.ps1` делает `alembic upgrade head` сам); в проде — `entrypoint.sh`. Closes #84 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Agent added 3 commits 2026-09-18 21:29:39 +03:00
AGENTS.md — такой же локальный гайд для ассистента, как CLAUDE.md, только
для другого инструмента. Он лежал в рабочем дереве незакоммиченным и висел
в git status; теперь он в блоке «AI-ассистенты» рядом с CLAUDE.md
и .claude/, так что случайный `git add .` его не подхватит. #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Объявление администрации показывается игроку окном в свой период, пока игрок
не закроет его («Понятно»). Отметка о закрытии хранится на сервере с номером
версии: правка с «показать заново» поднимает версию, и закрывшие прежнюю
увидят объявление снова — ответ помечен updated («обновлено»). Флаг
show_to_new_players=false прячет объявление от зарегистрировавшихся после
начала показа. Пересекающиеся объявления идут от старого к новому.

Текст приходит HTML-ом из редактора админки и сохраняется только после
очистки по белому списку (b, em, mark и mark.red, p, br): атрибуты
отбрасываются, script/style/svg — вместе с содержимым, текст экранируется
заново. Фронт вставляет только этот HTML.

API: GET /api/announcements/pending, POST /api/announcements/{id}/ack;
админка — список со статусом и счётчиком «закрыли N из M», создание, правка,
«снять с показа», удаление, всё в аудит. SSE-событие announcements активным
игрокам. Миграция 0015 идемпотентная.

Тесты: очистка (XSS-попытки, вложенные div), права, период и порядок,
«новые игроки», повторный показ, снятие, удаление, валидация. #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Окно — вариант E из макетов: заголовок в плашке-шапке, текст, точки очереди
и «Понятно», крестика нет. Очередь идёт от старого к новому; закрытое сразу
уходит из кэша, следующее открывается без ожидания сети. У повторно
показанного объявления под заголовком — красная пометка «обновлено».
AppShell показывает окно на любой странице и только когда пароль задан:
обязательное окно пароля всегда первое. Новые и изменённые объявления
приходят SSE-событием announcements, отложенное начало показа — перезапросом
раз в 5 минут.

Админка — вкладка «Объявления» с редактором R1: contenteditable и панель
(жирный, курсив, золотой и красный акцент, эмодзи, снятие оформления),
вставка и перетаскивание только простым текстом, счётчик символов, период
показа по МСК, переключатели «показывать новым игрокам» и «показать заново».
Предпросмотр — тем же окном, что у игрока. Список со статусом, счётчиком
«закрыли N из M» и действиями: изменить, снять с показа, дублировать,
удалить. Выделение текста в форме — полупрозрачный оранжевый вместо синего.

schema.d.ts пересобран из OpenAPI. #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NotBigGhost added 1 commit 2026-09-18 21:55:46 +03:00
Объявления выпускаются в main раньше рейтинга, а в main последняя миграция —
0013. Если бы 0015 оставалась после 0014, прод, получивший объявления,
встал бы на 0015, и при будущем переходе на dev alembic счёл бы базу
актуальной — рейтинговая 0014 не применилась бы никогда.

Теперь 0015 отходит от 0013 (как и 0014), а пустая 0016 сводит обе ветки
в одну голову: прод на 0015 докатит 0014 и 0016, база на 0014 — 0015 и 0016.
Базы, уже прогнанные прежней версией (0015 после 0014), повторно получат
идемпотентную 0014 — проверено на копии dev-БД, на пустой базе и на такой
базе. #84

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NotBigGhost merged commit 7829a49e9d into dev 2026-09-18 22:06:42 +03:00
NotBigGhost deleted branch issue-84-announcements 2026-09-18 22:06:43 +03:00
Sign in to join this conversation.