14 KiB
План отдельной админки
Цель и границы
Добавить в существующее приложение отдельный раздел /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, nullablelastLoginAtи оба счётчика; - сброс отправляет штатную ссылку, 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-доставку, применяется первоначально согласованная схема:
- От актуального
origin/mainсоздаётсяfeature/admin-dashboard-integration; напрямую функциональный код в неё не вносится. - Последовательно создаются
feature/admin-dashboard-s1-backend,feature/admin-dashboard-s2-frontendиfeature/admin-dashboard-s3-hardening, каждая от актуальной интеграционной ветки. Параллельные агенты работают внутри границ текущего спринта, результат собирается в одну sprint-ветку и один PR. - Каждый sprint PR направляется в
feature/admin-dashboard-integration. После каждого push отдельный reviewer проверяет полный diff и оставляет PR-комментарий. Замечания исправляются в том же PR, checks и независимое ревью повторяются до отсутствия блокирующих замечаний. - Слияние sprint PR выполняет сопровождающий вручную только после зелёных checks и ревью. Автоматическое слияние запрещено. Следующий спринт начинается от обновлённой интеграционной ветки.
- После трёх спринтов открывается единственный финальный 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; при ошибке ни одна часть учётной записи не считается удалённой.