Files
runners-calendar/docs/admin-dashboard-plan.md
admin dcf62c882f
CI / build-and-test (pull_request) Has been cancelled
feat(admin): add user management dashboard
2026-09-24 23:39:07 +03:00

14 KiB
Raw Permalink Blame History

План отдельной админки

Цель и границы

Добавить в существующее приложение отдельный раздел /admin, доступный только пользователю с нормализованным email av_sports@mail.ru. Раздел показывает всех пользователей и позволяет управлять их учётными записями.

Отдельный frontend, отдельный деплой, универсальная ролевая модель, массовые операции, фильтры и пагинация в первую версию не входят: для одного известного администратора они не нужны. Авторизация всегда проверяется backend; скрытие ссылки или маршрута во frontend не считается защитой.

Трактовки требований

  • «Первый пользователь» означает только учётную запись с LOWER(BTRIM(email)) = 'av_sports@mail.ru', а не первую строку по времени или UUID. Её email и саму запись запрещено изменять или удалять через админку, чтобы доступ нельзя было случайно передать или потерять.
  • Дата регистрации — users.created_at.
  • Дата последнего входа — отдельное nullable-поле users.last_login_at, которое обновляется только после успешной проверки пароля. sessions.last_seen_at не используется: это дата активности сессии, а не входа.
  • Количество введённых забегов — число всех races владельца; количество совершённых пробегов — число его стартов со статусом completed.
  • Редактируемые поля первой версии: email, публичное имя и два существующих флага видимости будущих и завершённых стартов. Системные даты, счётчики, хеш пароля и право администратора не редактируются.
  • При смене email он нормализуется и проверяется на уникальность, подтверждение сбрасывается, активные сессии отзываются, на новый адрес отправляется обычное письмо подтверждения. Это не позволяет сохранить подтверждённую сессию после назначения другого почтового адреса.
  • «Сбросить пароль» означает создать одноразовый токен штатным механизмом и отправить владельцу ссылку. Администратор не видит токен и не назначает пароль; повторный запрос делает прежнюю неиспользованную ссылку недействительной.
  • Удаление пользователя физическое. Существующие внешние ключи с ON DELETE CASCADE удалят его сессии, токены и забеги.

API и данные

  • Миграция добавляет users.last_login_at TIMESTAMPTZ; успешный login обновляет поле в той же операции, которая создаёт сессию.
  • Общий middleware requireAdmin сначала требует подтверждённую сессию, затем сравнивает нормализованный email с фиксированным адресом администратора.
  • GET /api/admin/users возвращает пользователей в стабильном порядке с датами, полями профиля и двумя агрегированными счётчиками без N+1 запросов.
  • PATCH /api/admin/users/:id строго принимает только заявленные поля.
  • POST /api/admin/users/:id/password-reset переиспользует создание токена и отправку письма из текущего auth flow.
  • DELETE /api/admin/users/:id удаляет неадминистративную учётную запись в транзакции. Все изменяющие запросы продолжают проходить существующую CSRF-защиту.
  • Ответы различают 401 (нет сессии), 403 (не администратор), 404 (пользователь отсутствует), 409 (занятый email/username или защищённая административная запись) и 400 (невалидные данные).

Спринты и параллельная работа

Спринт 1 — данные, защита и API

Поток backend:

  • миграция last_login_at, обновление успешного входа;
  • requireAdmin и административные endpoints списка, редактирования, сброса пароля и удаления;
  • тесты доступа, дат, счётчиков, валидации и каждой изменяющей операции.

Поток delivery/docs параллельно:

  • этот план и включение существующего CI для PR в интеграционную ветку;
  • сверка API-контракта, миграции и тестовых сценариев с реализацией backend.

Критерии приёмки:

  • неавторизованный запрос получает 401, любой подтверждённый пользователь, кроме фиксированного администратора, — 403 на каждом admin endpoint;
  • список содержит корректные createdAt, nullable lastLoginAt и оба счётчика;
  • сброс отправляет штатную ссылку, edit соблюдает границы полей и уникальность;
  • администратора нельзя удалить или лишить фиксированного email;
  • backend build, миграции и тесты, frontend build проходят в CI.

Спринт 2 — интерфейс администратора

Поток frontend:

  • типизированный API-клиент, защищённый маршрут /admin, ссылка только для администратора и таблица всех пользователей;
  • формы редактирования, подтверждения сброса пароля и удаления, состояния загрузки/ошибки/пустого списка и обновление данных после операции.

Поток проверки параллельно:

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

Критерии приёмки:

  • обычный пользователь не видит вход в админку, а прямой переход не раскрывает данные и завершается отказом backend;
  • таблица показывает email, регистрацию, последний вход, оба счётчика и действия;
  • действия имеют понятное подтверждение, блокируются на время запроса и показывают результат без перезагрузки страницы;
  • интерфейс пригоден для клавиатуры и узкого экрана; frontend и backend checks проходят.

Спринт 3 — сквозная проверка и выпуск

Поток качества:

  • регрессия регистрации, входа, CSRF, сброса пароля и CRUD забегов;
  • сквозная проверка admin list/edit/reset/delete на отдельной тестовой записи;
  • проверка миграции на копии схемы и поведения удаления с каскадами.

Поток выпуска параллельно:

  • актуализация API-документации и операционной памятки;
  • minor bump затронутых backend/frontend после стабилизации всего функционала;
  • после зелёных проверок и устранения замечаний — запись пользовательски значимых изменений в CHANGELOG.md и финальное независимое ревью.

Критерии приёмки:

  • все требования этого документа подтверждены тестом или зафиксированным ручным сценарием;
  • backend tests/build и frontend production build проходят, блокирующих замечаний независимого ревью нет;
  • версии видны в футере, changelog соответствует фактическому составу выпуска;
  • результат оставлен в рабочем дереве; PR в локальном режиме не создаётся.

Ветки, PR и проверки

Первоначально поставка планировалась через feature/admin-dashboard-integration, PR каждого спринта в неё и финальный PR в main. По последующему указанию владельца эта работа выполняется локально: без commit, push и PR. Независимое ревью каждого блока и все проверки сохраняются; итоговые изменения остаются в рабочем дереве для последующей поставки владельцем.

Если владелец возобновит Git-доставку, применяется первоначально согласованная схема:

  1. От актуального origin/main создаётся feature/admin-dashboard-integration; напрямую функциональный код в неё не вносится.
  2. Последовательно создаются feature/admin-dashboard-s1-backend, feature/admin-dashboard-s2-frontend и feature/admin-dashboard-s3-hardening, каждая от актуальной интеграционной ветки. Параллельные агенты работают внутри границ текущего спринта, результат собирается в одну sprint-ветку и один PR.
  3. Каждый sprint PR направляется в feature/admin-dashboard-integration. После каждого push отдельный reviewer проверяет полный diff и оставляет PR-комментарий. Замечания исправляются в том же PR, checks и независимое ревью повторяются до отсутствия блокирующих замечаний.
  4. Слияние sprint PR выполняет сопровождающий вручную только после зелёных checks и ревью. Автоматическое слияние запрещено. Следующий спринт начинается от обновлённой интеграционной ветки.
  5. После трёх спринтов открывается единственный финальный PR feature/admin-dashboard-integration → main. Он снова проходит полный CI и независимое ревью и остаётся готовым к ручному слиянию. Этот шаг сейчас отложен по явному указанию работать локально.

Для каждого блока обязательны backend build и tests на PostgreSQL 16, а также frontend production build. В локальном режиме эти проверки запускаются вручную.

Риски удаления

  • Удаление необратимо и каскадно уничтожает историю забегов, сессии и auth-токены. UI должен требовать явного подтверждения с email удаляемого пользователя и повторно показывать последствия.
  • Перед первым production-использованием удаления нужна проверенная резервная копия PostgreSQL и известная процедура восстановления.
  • Backend запрещает self-delete администратора независимо от состояния UI и проверяет целевую запись непосредственно перед DELETE в транзакции.
  • Ответ об успехе показывается только после commit; при ошибке ни одна часть учётной записи не считается удалённой.