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: нужен ваш выбор поведения, и он влияет на админку:
запретить правку участников у незавершённой партии (409), оставив ей только finish;
разрешить, но автоматически доводить партию до finished (проставлять finished_at, считать длительность);
разрешить админу, запретить игроку.
Фронт игроцкий PATCH /api/matches/{id} сейчас не вызывает вовсе (правка партии идёт только через админский эндпойнт в AdminMatchEdit), так что вариант 1 малозаметен для пользователей, но админка сейчас открывает на правку в том числе идущие партии.
`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
Также, если одновременно партию завершают два человека, они не видят, что вводит другой человек, что не очень хорошо. Особенно если один начал вносить изменения, а другой продолжил, в этом случае второму нужно вносить все данные заново. Нужно синхронизировать эту форму.
Также, если одновременно партию завершают два человека, они не видят, что вводит другой человек, что не очень хорошо. Особенно если один начал вносить изменения, а другой продолжил, в этом случае второму нужно вносить все данные заново. Нужно синхронизировать эту форму.
Agent
self-assigned this 2026-09-09 19:20:14 +03:00
Партия с местами, но незавершённая. Админ в «Правке партии» открывает идущую партию, расставляет места, сохраняет. После этого партия по-прежнему висит в блоке «Незавершённые партии» с кнопкой «Завершить», при открытии показывает пустую форму завершения (места в неё не подставляются), в статистику не попадает совсем — ни в счётчик сыгранных, ни в League Points, ни в процент побед, — а длительность пустая. Победитель определён, но игры для приложения как бы не было.
Двое заполняют форму — второй теряет всё. Раскладка мест, ничьи, выбывшие, комментарии об игроках и причина победы живут только в браузере. Двое после партии открывают её на своих телефонах, оба расставляют места, друг друга не видят. Первый жмёт «Завершить» — у второго форма молча исчезает и подменяется чужими результатами, весь его ввод пропадает без сообщения. Если он нажал одновременно, получает тост «Партия изменилась на другом устройстве» с тем же итогом. Заново ввести свой вариант он уже не может: завершённую партию игрок не правит (это #29).
План выполнения
Ваши решения: правку идущей партии через PATCHзапретить, форму синхронизировать общим черновиком на сервере.
Новая таблица match_finish_drafts (match_id PK/FK CASCADE, data JSON, updated_by, updated_at) + идемпотентная миграция 0012. Именно отдельная таблица, а не колонки в matches: запись в строку партии дёрнула бы onupdate у updated_at, а это версия для оптимистичной блокировки — «Завершить» у второго начало бы ловить 409 каждые полсекунды.
match_service: save/get/clear_finish_draft с проверками (партия идёт, состав совпадает с участниками, причина победы из списка). Черновик удаляется при завершении в той же транзакции и уходит каскадом при удалении партии.
Guard в update_match: не finished → 409. Закрывает оба роутера, игроцкий и админский.
PUT /api/matches/{id}/finish-draft; сам черновик приезжает в составе MatchRead — второй запрос клиенту не нужен.
Новый тип SSE-события match_draft — участникам группы, кроме автора правки. Инвалидирует только карточку партии, чтобы движение тайла не перезапрашивало лидерборд всей группе.
Фронт: запись черновика с задержкой ~600 мс; чужой черновик применяется, только если с момента моего последнего действия прошло больше ~1.5 с (иначе перетирало бы то, что человек тянет прямо сейчас); перед «Завершить» отложенная запись дожимается, чтобы оба видели ровно то, что записывается; строка «Результаты заполняет также <Ник>»; тост «Партию завершил <Ник>» вместо молча исчезающей формы.
Админка: у идущей партии вместо формы правки — пояснение и ссылка на страницу партии.
Критерии готовности
Места и причину победы у идущей партии через 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`
Итог: результаты идущей партии больше не записать (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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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: нужен ваш выбор поведения, и он влияет на админку:
409), оставив ей толькоfinish;finished(проставлятьfinished_at, считать длительность);Фронт игроцкий
PATCH /api/matches/{id}сейчас не вызывает вовсе (правка партии идёт только через админский эндпойнт вAdminMatchEdit), так что вариант 1 малозаметен для пользователей, но админка сейчас открывает на правку в том числе идущие партии.Обнаружено при работе над #8.
При взятии задачи в работу сначала объясни, что текущее поведение значит для пользователя, как он это видит.
Также, если одновременно партию завершают два человека, они не видят, что вводит другой человек, что не очень хорошо. Особенно если один начал вносить изменения, а другой продолжил, в этом случае второму нужно вносить все данные заново. Нужно синхронизировать эту форму.
Как это выглядит сейчас для пользователя
Партия с местами, но незавершённая. Админ в «Правке партии» открывает идущую партию, расставляет места, сохраняет. После этого партия по-прежнему висит в блоке «Незавершённые партии» с кнопкой «Завершить», при открытии показывает пустую форму завершения (места в неё не подставляются), в статистику не попадает совсем — ни в счётчик сыгранных, ни в League Points, ни в процент побед, — а длительность пустая. Победитель определён, но игры для приложения как бы не было.
Двое заполняют форму — второй теряет всё. Раскладка мест, ничьи, выбывшие, комментарии об игроках и причина победы живут только в браузере. Двое после партии открывают её на своих телефонах, оба расставляют места, друг друга не видят. Первый жмёт «Завершить» — у второго форма молча исчезает и подменяется чужими результатами, весь его ввод пропадает без сообщения. Если он нажал одновременно, получает тост «Партия изменилась на другом устройстве» с тем же итогом. Заново ввести свой вариант он уже не может: завершённую партию игрок не правит (это #29).
План выполнения
Ваши решения: правку идущей партии через
PATCHзапретить, форму синхронизировать общим черновиком на сервере.match_finish_drafts(match_idPK/FK CASCADE,dataJSON,updated_by,updated_at) + идемпотентная миграция0012. Именно отдельная таблица, а не колонки вmatches: запись в строку партии дёрнула быonupdateуupdated_at, а это версия для оптимистичной блокировки — «Завершить» у второго начало бы ловить 409 каждые полсекунды.match_service:save/get/clear_finish_draftс проверками (партия идёт, состав совпадает с участниками, причина победы из списка). Черновик удаляется при завершении в той же транзакции и уходит каскадом при удалении партии.update_match: неfinished→ 409. Закрывает оба роутера, игроцкий и админский.PUT /api/matches/{id}/finish-draft; сам черновик приезжает в составеMatchRead— второй запрос клиенту не нужен.match_draft— участникам группы, кроме автора правки. Инвалидирует только карточку партии, чтобы движение тайла не перезапрашивало лидерборд всей группе.Критерии готовности
PATCHзаписать нельзя (409 в обоих роутерах), в админке форма для неё не открывается.versionпартии — «Завершить» после чужих правок проходит безSTALE_WRITE.pytestиnpm run buildзелёные,schema.d.tsперегенерирован (API меняется).Ветка:
issue-28-finish-form-syncотdevРабота выполнена, открыт 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.