История игр в профиле игрока #1

Closed
opened 2026-09-01 10:55:17 +03:00 by NotBigGhost · 3 comments
Owner

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

Зависит от: #17

Нужно добавить возможность показывать историю своих игр в профиле, с настройкой подробности отображения, отображением только лучшей партии или всех игр. Зависит от: #17
NotBigGhost added this to the v1.35 - промежуточная полировка версии 1.3 milestone 2026-09-06 19:04:00 +03:00
NotBigGhost added the Kind/Feature label 2026-09-06 21:35:04 +03:00
Agent self-assigned this 2026-09-07 16:19:34 +03:00
Agent added the
Reviewed
Confirmed
1
label 2026-09-07 16:19:35 +03:00
Collaborator

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

В теле задачи не было критериев готовности, поэтому сперва уточнили три вещи: настройки хранятся в профиле (БД), «подробность» — это два режима карточки (компактно/подробно), «лучшая партия» выбирается по League Points, а история видна и в чужом профиле.

Сейчас в профиле списка партий нет вовсе, а выборка партий по игроку в бэкенде отсутствует — есть только список по группе.

Модель настроек. Две колонки в users с CheckConstraint по образцу role: history_mode (all | best, по умолчанию all) и history_detail (compact | full, по умолчанию compact). history_mode — витрина профиля: определяет, что видят и владелец, и гости, поэтому фильтрация на бэкенде, а не на клиенте. Миграция 0011 по идемпотентному шаблону 0010.

Бэкенд

  1. stats_service.py — user_match_list(...). Сборку элементов списка из group_match_list вынесу в общий хелпер и переиспользую в обеих функциях, а не продублирую. Для режима best лучшая партия ищется через уже готовый SCORED_CTE: s.points — это League Points за партию; максимум, при равенстве — свежее. В историю идут только завершённые партии, как и во всей остальной статистике.
  2. user_service.py — update_history_prefs рядом с update_favorite_faction, значения вне списка → ошибка валидации.
  3. schemas/api.py — поля в UserRead/ProfileUpdate и новая схема MatchHistory (items/total/limit/offset + mode/detail): настройки едут вместе со списком, чтобы чужой профиль отрисовал историю в выбранном владельцем виде без второго запроса.
  4. routers/users.py — GET /{user_id}/matches; update_my_profile уже переведён на exclude_unset в #17, достаточно дописать две ветки.

Фронтенд
5. hooks/users.ts — useUserMatches; hooks/auth.ts — useUpdateHistoryPrefs.
6. Новый components/MatchHistory.tsx: подробный режим переиспользует готовый MatchListView (он уже рисует всех участников с местами и фракциями), компактный — своя строка с датой, моим местом, моей фракцией и длительностью.
7. AccountPage.tsx — блок «История игр» с двумя тумблерами на готовом Switch; PublicProfilePage.tsx — та же история, но без контролов (страница намеренно read-only).
8. npm run gen:api — контракт меняется.

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

  • В своём профиле виден список партий; переключатели меняют режим и подробность, выбор сохраняется между сессиями и устройствами.
  • «Только лучшая партия» оставляет одну партию — с максимальными очками, а не просто свежую победу; в чужом профиле применяется настройка владельца.
  • Подробный режим выглядит как список партий группы, компактный — строкой с моим результатом.
  • Незавершённые партии в историю не попадают.
  • alembic upgrade head, pytest, npm run build — зелёные.

Осознанно не делаю: отдельного выключателя «скрыть историю целиком» — задача его не просит. Если понадобится, это третье значение history_mode, а не новая колонка.

Ветка: issue-1-profile-match-history от dev

## План выполнения В теле задачи не было критериев готовности, поэтому сперва уточнили три вещи: настройки хранятся **в профиле (БД)**, «подробность» — это **два режима карточки** (компактно/подробно), «лучшая партия» выбирается **по League Points**, а история **видна и в чужом профиле**. Сейчас в профиле списка партий нет вовсе, а выборка партий по игроку в бэкенде отсутствует — есть только список по группе. **Модель настроек.** Две колонки в `users` с `CheckConstraint` по образцу `role`: `history_mode` (`all` | `best`, по умолчанию `all`) и `history_detail` (`compact` | `full`, по умолчанию `compact`). `history_mode` — витрина профиля: определяет, что видят и владелец, и гости, поэтому фильтрация на бэкенде, а не на клиенте. Миграция `0011` по идемпотентному шаблону `0010`. **Бэкенд** 1. `stats_service.py` — `user_match_list(...)`. Сборку элементов списка из `group_match_list` вынесу в общий хелпер и переиспользую в обеих функциях, а не продублирую. Для режима `best` лучшая партия ищется через уже готовый `SCORED_CTE`: `s.points` — это League Points за партию; максимум, при равенстве — свежее. В историю идут только завершённые партии, как и во всей остальной статистике. 2. `user_service.py` — `update_history_prefs` рядом с `update_favorite_faction`, значения вне списка → ошибка валидации. 3. `schemas/api.py` — поля в `UserRead`/`ProfileUpdate` и новая схема `MatchHistory` (`items`/`total`/`limit`/`offset` + `mode`/`detail`): настройки едут вместе со списком, чтобы чужой профиль отрисовал историю в выбранном владельцем виде без второго запроса. 4. `routers/users.py` — `GET /{user_id}/matches`; `update_my_profile` уже переведён на `exclude_unset` в #17, достаточно дописать две ветки. **Фронтенд** 5. `hooks/users.ts` — `useUserMatches`; `hooks/auth.ts` — `useUpdateHistoryPrefs`. 6. Новый `components/MatchHistory.tsx`: подробный режим **переиспользует** готовый `MatchListView` (он уже рисует всех участников с местами и фракциями), компактный — своя строка с датой, моим местом, моей фракцией и длительностью. 7. `AccountPage.tsx` — блок «История игр» с двумя тумблерами на готовом `Switch`; `PublicProfilePage.tsx` — та же история, но без контролов (страница намеренно read-only). 8. `npm run gen:api` — контракт меняется. **Критерии готовности** - В своём профиле виден список партий; переключатели меняют режим и подробность, выбор сохраняется между сессиями и устройствами. - «Только лучшая партия» оставляет одну партию — с максимальными очками, а не просто свежую победу; в чужом профиле применяется настройка владельца. - Подробный режим выглядит как список партий группы, компактный — строкой с моим результатом. - Незавершённые партии в историю не попадают. - `alembic upgrade head`, `pytest`, `npm run build` — зелёные. **Осознанно не делаю:** отдельного выключателя «скрыть историю целиком» — задача его не просит. Если понадобится, это третье значение `history_mode`, а не новая колонка. **Ветка:** `issue-1-profile-match-history` от `dev`
Agent added the
Status
Need More Info
2
label 2026-09-07 16:32:15 +03:00
Agent added
Status
Blocked
1
and removed
Status
Need More Info
2
labels 2026-09-07 16:33:32 +03:00
Collaborator

Работа приостановлена: задача технически упирается в невлитый PR #20 (задача #17).

Что мешает. #1 правит ровно те же файлы, что и #17: models.py (колонки в users), routers/users.py (build_me и update_my_profile), schemas/api.py (UserRead / ProfileUpdate), services/user_service.py, services/stats_service.py, tests/test_profile.py, а на фронте — hooks/auth.ts, AccountPage.tsx и сгенерированный schema.d.ts.

Решающий аргумент — цепочка миграций. Миграция истории (0011) должна следовать за 0010_user_favorite_faction из #17. Если ветвиться от текущего dev, где 0010 ещё нет, пришлось бы цеплять 0011 к 0009 — и после мёржа обоих PR у Alembic оказались бы две ревизии с общим предком, то есть разветвлённая история и падение alembic upgrade head.

Кроме того, план #1 опирается на перевод update_my_profile на exclude_unset, сделанный в #17: без него добавление настроек истории в тот же PATCH /me/profile снова ломало бы соседние поля.

Что сделано до остановки. Требования уточнены и зафиксированы планом в комментарии выше (хранение настроек в БД, режимы all/best и compact/full, выбор лучшей партии по League Points, история видна и гостям профиля). Код не писался: ветка issue-1-profile-match-history была создана пустой и удалена, чтобы не висела недоделанной.

Как разблокировать. Влить PR #20 в dev — после этого задача берётся от свежего dev без конфликтов, план готов к исполнению как есть.

Работа приостановлена: задача технически упирается в невлитый PR #20 (задача #17). **Что мешает.** #1 правит ровно те же файлы, что и #17: `models.py` (колонки в `users`), `routers/users.py` (`build_me` и `update_my_profile`), `schemas/api.py` (`UserRead` / `ProfileUpdate`), `services/user_service.py`, `services/stats_service.py`, `tests/test_profile.py`, а на фронте — `hooks/auth.ts`, `AccountPage.tsx` и сгенерированный `schema.d.ts`. Решающий аргумент — цепочка миграций. Миграция истории (`0011`) должна следовать за `0010_user_favorite_faction` из #17. Если ветвиться от текущего `dev`, где `0010` ещё нет, пришлось бы цеплять `0011` к `0009` — и после мёржа обоих PR у Alembic оказались бы две ревизии с общим предком, то есть разветвлённая история и падение `alembic upgrade head`. Кроме того, план #1 опирается на перевод `update_my_profile` на `exclude_unset`, сделанный в #17: без него добавление настроек истории в тот же `PATCH /me/profile` снова ломало бы соседние поля. **Что сделано до остановки.** Требования уточнены и зафиксированы планом в комментарии выше (хранение настроек в БД, режимы `all`/`best` и `compact`/`full`, выбор лучшей партии по League Points, история видна и гостям профиля). Код не писался: ветка `issue-1-profile-match-history` была создана пустой и удалена, чтобы не висела недоделанной. **Как разблокировать.** Влить PR #20 в `dev` — после этого задача берётся от свежего `dev` без конфликтов, план готов к исполнению как есть.
Agent removed the
Status
Blocked
1
label 2026-09-07 19:51:47 +03:00
Agent added the
Status
In Review
label 2026-09-07 19:56:43 +03:00
Collaborator

Блокировка снята (PR #20 влит), работа доведена до конца. Открыт PR: #21

Итог: в профиле появился список партий — его там не было вовсе, как и выборки партий по игроку в бэкенде. Добавлен user_match_list (сборка элементов вынесена из group_match_list в общий хелпер), витрину задают history_mode и history_detail в users (миграция 0011). Режим применяется на бэкенде: гости видят профиль ровно так, как настроил владелец. На фронте — блок с двумя тумблерами в «Аккаунте» и компонент MatchHistory, где подробный режим переиспользует список партий группы.

Поправка к плану: при написании тестов выяснилось, что первое место даёт 1.0 очка независимо от размера стола, поэтому «победа на четверых весомее победы на двоих» — неверно. Разница возникает на непризовых местах (второе из четырёх ≈ 0.67 против второго из двух = 0), и тест на режим best построен именно на этом.

Проверки: alembic upgrade head — 0011 легла поверх 0010, цепочка линейна; pytest — 74 passed (было 69, добавлено пять); npm run gen:api и npm run build — зелёные. Визуальная проверка не выполнена: расширение Claude in Chrome к сессии не подключено.

Статус: Status/In Review
Осталось за вами: ревью и мёрж PR — задача закроется автоматически, и вместе с ней разблокируется #8 «Ревью кода» (последняя в вехе).

Блокировка снята (PR #20 влит), работа доведена до конца. Открыт PR: https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/pulls/21 **Итог:** в профиле появился список партий — его там не было вовсе, как и выборки партий по игроку в бэкенде. Добавлен `user_match_list` (сборка элементов вынесена из `group_match_list` в общий хелпер), витрину задают `history_mode` и `history_detail` в `users` (миграция `0011`). Режим применяется на бэкенде: гости видят профиль ровно так, как настроил владелец. На фронте — блок с двумя тумблерами в «Аккаунте» и компонент `MatchHistory`, где подробный режим переиспользует список партий группы. **Поправка к плану:** при написании тестов выяснилось, что первое место даёт 1.0 очка независимо от размера стола, поэтому «победа на четверых весомее победы на двоих» — неверно. Разница возникает на непризовых местах (второе из четырёх ≈ 0.67 против второго из двух = 0), и тест на режим `best` построен именно на этом. **Проверки:** `alembic upgrade head` — `0011` легла поверх `0010`, цепочка линейна; `pytest` — 74 passed (было 69, добавлено пять); `npm run gen:api` и `npm run build` — зелёные. Визуальная проверка не выполнена: расширение Claude in Chrome к сессии не подключено. **Статус:** `Status/In Review` **Осталось за вами:** ревью и мёрж PR — задача закроется автоматически, и вместе с ней разблокируется #8 «Ревью кода» (последняя в вехе).
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NotBigGhost/ForbiddenStarsApp#1