Нужно добавить возможность показывать историю своих игр в профиле, с настройкой подробности отображения, отображением только лучшей партии или всех игр.
Нужно добавить возможность показывать историю своих игр в профиле, с настройкой подробности отображения, отображением только лучшей партии или всех игр.
Зависит от: #17
NotBigGhost
added this to the v1.35 - промежуточная полировка версии 1.3 milestone 2026-09-06 19:04:00 +03:00
В теле задачи не было критериев готовности, поэтому сперва уточнили три вещи: настройки хранятся в профиле (БД), «подробность» — это два режима карточки (компактно/подробно), «лучшая партия» выбирается по League Points, а история видна и в чужом профиле.
Сейчас в профиле списка партий нет вовсе, а выборка партий по игроку в бэкенде отсутствует — есть только список по группе.
Модель настроек. Две колонки в users с CheckConstraint по образцу role: history_mode (all | best, по умолчанию all) и history_detail (compact | full, по умолчанию compact). history_mode — витрина профиля: определяет, что видят и владелец, и гости, поэтому фильтрация на бэкенде, а не на клиенте. Миграция 0011 по идемпотентному шаблону 0010.
Бэкенд
stats_service.py — user_match_list(...). Сборку элементов списка из group_match_list вынесу в общий хелпер и переиспользую в обеих функциях, а не продублирую. Для режима best лучшая партия ищется через уже готовый SCORED_CTE: s.points — это League Points за партию; максимум, при равенстве — свежее. В историю идут только завершённые партии, как и во всей остальной статистике.
user_service.py — update_history_prefs рядом с update_favorite_faction, значения вне списка → ошибка валидации.
schemas/api.py — поля в UserRead/ProfileUpdate и новая схема MatchHistory (items/total/limit/offset + mode/detail): настройки едут вместе со списком, чтобы чужой профиль отрисовал историю в выбранном владельцем виде без второго запроса.
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`
Работа приостановлена: задача технически упирается в невлитый 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` без конфликтов, план готов к исполнению как есть.
Блокировка снята (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 «Ревью кода» (последняя в вехе).
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.
Нужно добавить возможность показывать историю своих игр в профиле, с настройкой подробности отображения, отображением только лучшей партии или всех игр.
Зависит от: #17
План выполнения
В теле задачи не было критериев готовности, поэтому сперва уточнили три вещи: настройки хранятся в профиле (БД), «подробность» — это два режима карточки (компактно/подробно), «лучшая партия» выбирается по League Points, а история видна и в чужом профиле.
Сейчас в профиле списка партий нет вовсе, а выборка партий по игроку в бэкенде отсутствует — есть только список по группе.
Модель настроек. Две колонки в
usersсCheckConstraintпо образцуrole:history_mode(all|best, по умолчаниюall) иhistory_detail(compact|full, по умолчаниюcompact).history_mode— витрина профиля: определяет, что видят и владелец, и гости, поэтому фильтрация на бэкенде, а не на клиенте. Миграция0011по идемпотентному шаблону0010.Бэкенд
stats_service.py—user_match_list(...). Сборку элементов списка изgroup_match_listвынесу в общий хелпер и переиспользую в обеих функциях, а не продублирую. Для режимаbestлучшая партия ищется через уже готовыйSCORED_CTE:s.points— это League Points за партию; максимум, при равенстве — свежее. В историю идут только завершённые партии, как и во всей остальной статистике.user_service.py—update_history_prefsрядом сupdate_favorite_faction, значения вне списка → ошибка валидации.schemas/api.py— поля вUserRead/ProfileUpdateи новая схемаMatchHistory(items/total/limit/offset+mode/detail): настройки едут вместе со списком, чтобы чужой профиль отрисовал историю в выбранном владельцем виде без второго запроса.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Работа приостановлена: задача технически упирается в невлитый 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 влит), работа доведена до конца. Открыт 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 «Ревью кода» (последняя в вехе).