Правка идущей партии проставляет места, не завершая её #28

Closed
opened 2026-09-09 15:26:58 +03:00 by Agent · 4 comments
Collaborator

PATCH /api/matches/{id} (и админский PATCH /api/admin/matches/{id}) принимает participants и win_reason независимо от статуса партии. Докстрока match_service.update_match говорит «правка завершённой партии», но проверки статуса нет.

Что получается: партии в статусе in_progress можно проставить места и причину победы. finished_at и duration_minutes остаются NULL, статус — in_progress. Партия выглядит решённой в карточке, но в статистику не попадает: SCORED_CTE и все витрины считают только status='finished', League Points за неё никто не получает.

Почему не поправил в рамках ревью #8: нужен ваш выбор поведения, и он влияет на админку:

  1. запретить правку участников у незавершённой партии (409), оставив ей только finish;
  2. разрешить, но автоматически доводить партию до finished (проставлять finished_at, считать длительность);
  3. разрешить админу, запретить игроку.

Фронт игроцкий PATCH /api/matches/{id} сейчас не вызывает вовсе (правка партии идёт только через админский эндпойнт в AdminMatchEdit), так что вариант 1 малозаметен для пользователей, но админка сейчас открывает на правку в том числе идущие партии.

Обнаружено при работе над #8.

`PATCH /api/matches/{id}` (и админский `PATCH /api/admin/matches/{id}`) принимает `participants` и `win_reason` независимо от статуса партии. Докстрока `match_service.update_match` говорит «правка завершённой партии», но проверки статуса нет. **Что получается:** партии в статусе `in_progress` можно проставить места и причину победы. `finished_at` и `duration_minutes` остаются NULL, статус — `in_progress`. Партия выглядит решённой в карточке, но в статистику не попадает: `SCORED_CTE` и все витрины считают только `status='finished'`, League Points за неё никто не получает. **Почему не поправил в рамках ревью #8:** нужен ваш выбор поведения, и он влияет на админку: 1. запретить правку участников у незавершённой партии (`409`), оставив ей только `finish`; 2. разрешить, но автоматически доводить партию до `finished` (проставлять `finished_at`, считать длительность); 3. разрешить админу, запретить игроку. Фронт игроцкий `PATCH /api/matches/{id}` сейчас не вызывает вовсе (правка партии идёт только через админский эндпойнт в `AdminMatchEdit`), так что вариант 1 малозаметен для пользователей, но админка сейчас открывает на правку в том числе идущие партии. Обнаружено при работе над #8.
Agent added this to the v1.35 - промежуточная полировка версии 1.3 milestone 2026-09-09 15:26:58 +03:00
Owner

При взятии задачи в работу сначала объясни, что текущее поведение значит для пользователя, как он это видит.

При взятии задачи в работу сначала объясни, что текущее поведение значит для пользователя, как он это видит.
NotBigGhost added the Kind/Bug label 2026-09-09 19:08:51 +03:00
Owner

Также, если одновременно партию завершают два человека, они не видят, что вводит другой человек, что не очень хорошо. Особенно если один начал вносить изменения, а другой продолжил, в этом случае второму нужно вносить все данные заново. Нужно синхронизировать эту форму.

Также, если одновременно партию завершают два человека, они не видят, что вводит другой человек, что не очень хорошо. Особенно если один начал вносить изменения, а другой продолжил, в этом случае второму нужно вносить все данные заново. Нужно синхронизировать эту форму.
Agent self-assigned this 2026-09-09 19:20:14 +03:00
Agent added the
Reviewed
Confirmed
1
label 2026-09-09 19:20:15 +03:00
Author
Collaborator

Как это выглядит сейчас для пользователя

Партия с местами, но незавершённая. Админ в «Правке партии» открывает идущую партию, расставляет места, сохраняет. После этого партия по-прежнему висит в блоке «Незавершённые партии» с кнопкой «Завершить», при открытии показывает пустую форму завершения (места в неё не подставляются), в статистику не попадает совсем — ни в счётчик сыгранных, ни в League Points, ни в процент побед, — а длительность пустая. Победитель определён, но игры для приложения как бы не было.

Двое заполняют форму — второй теряет всё. Раскладка мест, ничьи, выбывшие, комментарии об игроках и причина победы живут только в браузере. Двое после партии открывают её на своих телефонах, оба расставляют места, друг друга не видят. Первый жмёт «Завершить» — у второго форма молча исчезает и подменяется чужими результатами, весь его ввод пропадает без сообщения. Если он нажал одновременно, получает тост «Партия изменилась на другом устройстве» с тем же итогом. Заново ввести свой вариант он уже не может: завершённую партию игрок не правит (это #29).

План выполнения

Ваши решения: правку идущей партии через PATCH запретить, форму синхронизировать общим черновиком на сервере.

  1. Новая таблица match_finish_drafts (match_id PK/FK CASCADE, data JSON, updated_by, updated_at) + идемпотентная миграция 0012. Именно отдельная таблица, а не колонки в matches: запись в строку партии дёрнула бы onupdate у updated_at, а это версия для оптимистичной блокировки — «Завершить» у второго начало бы ловить 409 каждые полсекунды.
  2. match_service: save/get/clear_finish_draft с проверками (партия идёт, состав совпадает с участниками, причина победы из списка). Черновик удаляется при завершении в той же транзакции и уходит каскадом при удалении партии.
  3. Guard в update_match: не finished → 409. Закрывает оба роутера, игроцкий и админский.
  4. PUT /api/matches/{id}/finish-draft; сам черновик приезжает в составе MatchRead — второй запрос клиенту не нужен.
  5. Новый тип SSE-события match_draft — участникам группы, кроме автора правки. Инвалидирует только карточку партии, чтобы движение тайла не перезапрашивало лидерборд всей группе.
  6. Фронт: запись черновика с задержкой ~600 мс; чужой черновик применяется, только если с момента моего последнего действия прошло больше ~1.5 с (иначе перетирало бы то, что человек тянет прямо сейчас); перед «Завершить» отложенная запись дожимается, чтобы оба видели ровно то, что записывается; строка «Результаты заполняет также <Ник>»; тост «Партию завершил <Ник>» вместо молча исчезающей формы.
  7. Админка: у идущей партии вместо формы правки — пояснение и ссылка на страницу партии.

Критерии готовности

  • Места и причину победы у идущей партии через PATCH записать нельзя (409 в обоих роутерах), в админке форма для неё не открывается.
  • Двое с открытой формой видят правки друг друга без перезагрузки, ввод не теряется.
  • Запись черновика не меняет version партии — «Завершить» после чужих правок проходит без STALE_WRITE.
  • После завершения черновик удалён; посторонний (не из группы) записать его не может.
  • pytest и npm run build зелёные, schema.d.ts перегенерирован (API меняется).

Ветка: issue-28-finish-form-sync от dev

## Как это выглядит сейчас для пользователя **Партия с местами, но незавершённая.** Админ в «Правке партии» открывает идущую партию, расставляет места, сохраняет. После этого партия по-прежнему висит в блоке «Незавершённые партии» с кнопкой «Завершить», при открытии показывает пустую форму завершения (места в неё не подставляются), в статистику не попадает совсем — ни в счётчик сыгранных, ни в League Points, ни в процент побед, — а длительность пустая. Победитель определён, но игры для приложения как бы не было. **Двое заполняют форму — второй теряет всё.** Раскладка мест, ничьи, выбывшие, комментарии об игроках и причина победы живут только в браузере. Двое после партии открывают её на своих телефонах, оба расставляют места, друг друга не видят. Первый жмёт «Завершить» — у второго форма молча исчезает и подменяется чужими результатами, весь его ввод пропадает без сообщения. Если он нажал одновременно, получает тост «Партия изменилась на другом устройстве» с тем же итогом. Заново ввести свой вариант он уже не может: завершённую партию игрок не правит (это #29). ## План выполнения Ваши решения: правку идущей партии через `PATCH` **запретить**, форму синхронизировать **общим черновиком на сервере**. 1. Новая таблица `match_finish_drafts` (`match_id` PK/FK CASCADE, `data` JSON, `updated_by`, `updated_at`) + идемпотентная миграция `0012`. Именно отдельная таблица, а не колонки в `matches`: запись в строку партии дёрнула бы `onupdate` у `updated_at`, а это версия для оптимистичной блокировки — «Завершить» у второго начало бы ловить 409 каждые полсекунды. 2. `match_service`: `save/get/clear_finish_draft` с проверками (партия идёт, состав совпадает с участниками, причина победы из списка). Черновик удаляется при завершении в той же транзакции и уходит каскадом при удалении партии. 3. Guard в `update_match`: не `finished` → 409. Закрывает оба роутера, игроцкий и админский. 4. `PUT /api/matches/{id}/finish-draft`; сам черновик приезжает в составе `MatchRead` — второй запрос клиенту не нужен. 5. Новый тип SSE-события `match_draft` — участникам группы, кроме автора правки. Инвалидирует только карточку партии, чтобы движение тайла не перезапрашивало лидерборд всей группе. 6. Фронт: запись черновика с задержкой ~600 мс; чужой черновик применяется, только если с момента моего последнего действия прошло больше ~1.5 с (иначе перетирало бы то, что человек тянет прямо сейчас); перед «Завершить» отложенная запись дожимается, чтобы оба видели ровно то, что записывается; строка «Результаты заполняет также <Ник>»; тост «Партию завершил <Ник>» вместо молча исчезающей формы. 7. Админка: у идущей партии вместо формы правки — пояснение и ссылка на страницу партии. **Критерии готовности** - Места и причину победы у идущей партии через `PATCH` записать нельзя (409 в обоих роутерах), в админке форма для неё не открывается. - Двое с открытой формой видят правки друг друга без перезагрузки, ввод не теряется. - Запись черновика не меняет `version` партии — «Завершить» после чужих правок проходит без `STALE_WRITE`. - После завершения черновик удалён; посторонний (не из группы) записать его не может. - `pytest` и `npm run build` зелёные, `schema.d.ts` перегенерирован (API меняется). **Ветка:** `issue-28-finish-form-sync` от `dev`
Agent added the
Status
In Review
label 2026-09-09 19:40:14 +03:00
Author
Collaborator

Работа выполнена, открыт PR: #35

Итог: результаты идущей партии больше не записать (409 в обоих роутерах, в админке вместо формы — пояснение и переход на страницу партии); форма завершения стала общей — раскладка, ничьи, выбывшие, комментарии, причина победы и комментарий о партии видны всем заполняющим и приезжают по SSE. Второй участник продолжает с того же места, а не вводит всё заново.

Черновик живёт в отдельной таблице: запись в строку партии двигала бы её версию для оптимистичной блокировки, и «Завершить» ловил бы STALE_WRITE на каждую чужую правку. На это есть отдельный тест.

Проверки: pytest — 87 passed (6 новых тестов); миграция 0012 проверена на копии dev-базы, повторный прогон no-op; npm run gen:api + npm run build — зелёные.

Отклонение от плана: запрет сужен с «весь PATCH» до «только результаты» — целиком он ломал правку общего комментария по ходу партии, что ловил существующий тест. Плюс в черновик добавлен overall_comment, иначе часть формы осталась бы несинхронизированной.

Статус: Status/In Review
Осталось за вами: ревью и мёрж PR — задача закроется автоматически. Дальше по вехе v1.35 беру #29.

Работа выполнена, открыт PR: https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/pulls/35 **Итог:** результаты идущей партии больше не записать (409 в обоих роутерах, в админке вместо формы — пояснение и переход на страницу партии); форма завершения стала общей — раскладка, ничьи, выбывшие, комментарии, причина победы и комментарий о партии видны всем заполняющим и приезжают по SSE. Второй участник продолжает с того же места, а не вводит всё заново. Черновик живёт в отдельной таблице: запись в строку партии двигала бы её версию для оптимистичной блокировки, и «Завершить» ловил бы `STALE_WRITE` на каждую чужую правку. На это есть отдельный тест. **Проверки:** `pytest` — 87 passed (6 новых тестов); миграция `0012` проверена на копии dev-базы, повторный прогон no-op; `npm run gen:api` + `npm run build` — зелёные. **Отклонение от плана:** запрет сужен с «весь PATCH» до «только результаты» — целиком он ломал правку общего комментария по ходу партии, что ловил существующий тест. Плюс в черновик добавлен `overall_comment`, иначе часть формы осталась бы несинхронизированной. **Статус:** `Status/In Review` **Осталось за вами:** ревью и мёрж PR — задача закроется автоматически. Дальше по вехе v1.35 беру #29.
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NotBigGhost/ForbiddenStarsApp#28