- Добвлен @fastify/cookie и настройку httpOnly cookie для refresh token
- Добавлен префикс /api/v1 для auth, profile, tests, admin
- Скорректировано в Login: возвращать user (id, nickname, avatarUrl, role, emailVerified),
ставить refreshToken в Set-Cookie
- Скорректировано в Logout: Bearer + cookie, пустое тело, 200 + { message }, очищать cookie
- Скорректировано в Refresh: token из cookie, пустое тело, 200 + { accessToken }, Set-Cookie
- Добавлено в getPrivateProfile: поля role и plan
- Скорректировано в Tests: score = количество правильных, ответ { score, totalQuestions, percentage }
- Добавлено в question_cache_meta: поля valid, retryCount, questionsGenerated
- Обновлены тесты
17 KiB
name, overview, todos, isProject
| name | overview | todos | isProject |
|---|---|---|---|
| Документация vs Реализация | Подробный отчёт о соответствии бэкенда документации samreshu_docs: выявлены расхождения в API контрактах, схеме БД, аутентификации, тестах и других компонентах. | false |
Отчёт: Соответствие бэкенда документации Samreshu
1. Базовый URL и структура API
Документация (contracts.md):
- Базовый URL:
/api/v1 - Все эндпоинты:
/api/v1/auth/,/api/v1/profile/,/api/v1/tests/*,/api/v1/admin/*
Реализация (app.ts):
- Префикс
/api/v1отсутствует. Маршруты зарегистрированы как/auth,/profile,/tests,/admin - Фактические URL:
/auth/,/profile/,/tests/*,/admin/*
Вывод: Критическое расхождение. Клиент, следующий документации, будет отправлять запросы на несуществующие эндпоинты.
2. Аутентификация (Auth)
2.1 POST /auth/register
| Аспект | Документация | Реализация |
|---|---|---|
| Response 201 | { user, accessToken } + Set-Cookie refreshToken |
{ userId, message, verificationCode } |
| Логика | Сразу выдаёт токены, отправляет письмо | Требует верификацию email; не выдаёт токены; возвращает код (для dev) |
| NICKNAME_TAKEN | — | Есть в коде; в документации не описан |
Вывод: Разный flow. Документация описывает выдачу токенов сразу после регистрации; реализация ожидает верификацию email.
2.2 POST /auth/login
| Аспект | Документация | Реализация |
|---|---|---|
| Response | { user, accessToken } + Set-Cookie |
{ accessToken, refreshToken, expiresIn } — без user |
| Lockout при brute force | 403 ACCOUNT_LOCKED |
429 RATE_LIMIT_EXCEEDED |
| Ошибка неверных данных | 401 INVALID_CREDENTIALS |
401 UNAUTHORIZED (общий код) |
Вывод: Расхождения в формате ответа, коде ошибки и семантике блокировки (403 vs 429).
2.3 POST /auth/logout
| Аспект | Документация | Реализация |
|---|---|---|
| Авторизация | Bearer token | refreshToken в теле |
| Request body | Пустое | { refreshToken: string } |
| Response | 200 { message: "..." } |
204 (без тела) |
Вывод: Другой механизм авторизации и формат ответа.
2.4 POST /auth/refresh
| Аспект | Документация | Реализация |
|---|---|---|
| Токен | httpOnly cookie | { refreshToken } в body |
| Request body | Пустое | { refreshToken: string } |
Вывод: Документация предполагает cookie; реализация использует body.
2.5 POST /auth/verify-email
| Аспект | Документация | Реализация |
|---|---|---|
| Request | { code } |
{ userId, code } |
| Авторизация | Bearer token | Не требуется |
Вывод: Документация — только code с Bearer; реализация — userId и code без Bearer.
2.6 POST /auth/reset-password
| Аспект | Документация | Реализация |
|---|---|---|
| Поле пароля | password |
newPassword |
Вывод: Разные имена полей в теле запроса.
2.7 Set-Cookie для refresh token
Документация: RefreshToken в httpOnly cookie (Path=/api/v1/auth, Max-Age=604800).
Реализация: Cookie не устанавливаются; refresh token возвращается только в JSON.
3. Profile
3.1 GET /profile
Документация: Ответ включает role, plan (из subscriptions).
Реализация (user.service.ts): getPrivateProfile возвращает id, nickname, avatarUrl, country, city, selfLevel, isPublic, email, emailVerifiedAt, createdAt, updatedAt, stats. Поля role и plan отсутствуют.
Вывод: В ответе нет полей, указанных в документации.
3.2 GET /profile/:username
Документация: stats: { testsCompleted, averageScore }.
Реализация: stats: { byStack, totalTestsTaken, totalQuestions, correctAnswers, accuracy } — другая структура.
Вывод: Формат статистики не совпадает.
3.3 PATCH /profile
Документация: Поле avatarUrl не описано.
Реализация: Поддерживается avatarUrl.
Вывод: Документация неполная (расхождение в пользу реализации).
4. Tests
4.1 POST /tests (создание)
| Аспект | Документация | Реализация |
|---|---|---|
| questionCount | enum: 10 / 20 | integer 1–50 |
| stack/level (MVP 0) | html, css / basic, beginner | Все значения enum |
| Response структура | { id, stack, level, questionCount, status, currentQuestion, startedAt, timeLimitSeconds, question: {...} } — только текущий вопрос |
{ ...test, questions: [...] } — все вопросы |
Вывод: Разная структура ответа и валидация параметров.
4.2 POST /tests/:id/answer
Документация: { answered: {...}, progress: {...}, nextQuestion: {...} }.
Реализация: Возвращает полный TestSnapshot отвеченного вопроса, без progress и nextQuestion.
Вывод: Формат ответа не совпадает с документацией.
4.3 POST /tests/:id/finish
| Аспект | Документация | Реализация |
|---|---|---|
| score | Количество правильных ответов (8 из 10) | Процент (0–100) |
| totalQuestions | В ответе | Нет в ответе |
| percentage | В ответе | Нет (score как процент) |
Реализация (tests.service.ts 239–241):
const score = Math.round((correctCount / questions.length) * 100);
Вывод: Критическое расхождение: score в документации — количество, в реализации — процент.
4.4 GET /tests/:id/results
Документация: Детальные результаты с questions, userAnswer, correctAnswer, isCorrect, explanation.
Реализация: Эндпоинт отсутствует.
Вывод: Критическое расхождение: описанный эндпоинт не реализован.
4.5 GET /tests/history
| Аспект | Документация | Реализация |
|---|---|---|
| Путь | GET /tests/history | GET /tests (то же для истории) |
| Пагинация | cursor-based (limit, cursor) | offset-based (limit, offset) |
| Формат ответа | { data: [...], pagination: { nextCursor, hasMore } } |
{ tests: [...], total } |
| Параметры | stack, status фильтры | Нет фильтров |
Вывод: Другая схема пагинации и структура ответа.
4.6 Параметр теста
Документация: :id.
Реализация: :testId в params.
Вывод: Незначительное расхождение в именовании.
5. Admin
5.1 Список вопросов
| Аспект | Документация | Реализация |
|---|---|---|
| Эндпоинт | GET /admin/questions/queue | GET /admin/questions/pending |
| Пагинация | cursor (limit, cursor) | offset (limit, offset) |
| status в query | pending / approved / rejected | — |
5.2 Редактирование
Документация: PATCH /admin/questions/:id с status: approved | rejected и полями для правок.
Реализация:
- POST /admin/questions/:questionId/approve
- POST /admin/questions/:questionId/reject
- PATCH /admin/questions/:questionId — для редактирования
Вывод: Другая схема: отдельные approve/reject вместо смены статуса через PATCH.
6. База данных
6.1 Таблицы
Документация (schema.md): verification_tokens (обобщённо).
Реализация: Отдельные таблицы email_verification_codes и password_reset_tokens (в verificationTokens.ts).
Вывод: Расхождение в модели хранения токенов.
6.2 Отсутствующие таблицы (Phase 2+)
В документации есть таблицы, которых нет в текущем коде: oauth_accounts, totp_secrets, payments, payment_events, notifications_log, promo_codes, user_achievements. Это ожидаемо для Phase 2+.
7. Безопасность
7.1 Argon2
Документация: argon2id, 19 MiB, 2 iterations.
Реализация (password.ts): memoryCost: 19456 (KiB), timeCost: 2.
Вывод: Совпадение.
7.2 JWT
Документация: Access 15 мин, Refresh 7 дней, HS256.
Реализация: JWT_ACCESS_TTL=15m, JWT_REFRESH_TTL=7d.
Вывод: Совпадение.
7.3 Login lockout
Документация: 5/15 мин, 10/1 ч, 20/24 ч.
Реализация (loginLockout.ts): Те же пороги (5, 10, 20).
Вывод: Совпадение.
7.4 Rate limits
Документация: RATE_LIMIT_LOGIN, RATE_LIMIT_REGISTER, RATE_LIMIT_FORGOT_PASSWORD, RATE_LIMIT_VERIFY_EMAIL, RATE_LIMIT_API_AUTHED, RATE_LIMIT_API_GUEST.
Реализация (env.ts): RATE_LIMIT_LOGIN отсутствует (используется progressive lockout). Остальные переменные есть.
Вывод: Незначительное расхождение; security.md упоминает RATE_LIMIT_LOGIN, но логика lockout иная.
7.5 CORS
Документация: http://localhost:5173, https://samreshu.ru, credentials: true, методы GET, POST, PATCH, DELETE.
Реализация: Origins из CORS_ORIGINS, credentials: true, методы включают PUT и OPTIONS.
Вывод: Реализация шире, расхождение несущественное.
7.6 Helmet
Документация: Полный набор заголовков, включая CSP.
Реализация: contentSecurityPolicy: false, crossOriginEmbedderPolicy: false.
Вывод: CSP и COEP отключены.
8. LLM
8.1 Конфигурация
Документация: LLM_BASE_URL, LLM_MODEL, LLM_API_KEY, LLM_TIMEOUT_MS, LLM_MAX_RETRIES, LLM_TEMPERATURE, LLM_MAX_TOKENS.
Реализация: Дополнительно LLM_FALLBACK_MODEL, LLM_RETRY_DELAY_MS.
Вывод: Реализация расширяет документацию.
8.2 question_cache_meta
Документация: model, generation_time_ms, prompt_hash. Также упоминаются valid, retry_count, questions_generated.
Реализация: Нужно проверить сохранение этих полей в questionCacheMeta и связанных сервисах.
9. Код и инфраструктура
9.1 Onboarding / setup
Документация: docker-compose.dev.yml с postgres и redis.
Реализация: Соответствует.
9.2 .env.example
Документация: Полный перечень переменных.
Реализация: Совпадает, включая rate limits и LLM. JWT_SECRET требует не менее 32 символов — в примере выполнено.
10. Сводка расхождений
Критические
- Отсутствие префикса
/api/v1 - Auth: другой flow (verify-email до токенов, refresh/logout через body вместо cookie)
- Tests:
scoreкак процент вместо количества - Отсутствует GET /tests/:id/results
- Формат ответов create/answer/finish/history не совпадает с документацией
Значительные
- Login lockout: 429 вместо 403 ACCOUNT_LOCKED
- Login: нет поля
userв ответе - Profile: нет
role,plan; другая структураstats - Admin: другой набор эндпоинтов и логика approve/reject
- Пагинация: offset вместо cursor
Незначительные
- Имена полей (testId vs id, newPassword vs password)
- verify-email: userId + code вместо только code
- Эндпоинт admin: /pending вместо /queue
Рекомендации
- Привести маршрутизацию к документации: добавить префикс
/api/v1при регистрации роутов. - Унифицировать auth: реализовать cookie для refresh token и обновить logout/refresh под документацию, либо явно зафиксировать в документации текущий подход (body).
- Исправить score в тестах: хранить и возвращать количество правильных ответов, а процент считать отдельно.
- Реализовать GET /tests/:id/results по описанному в документации формату.
- Привести ответы create/answer/finish/history к формату из contracts.md.
- Обновить документацию под уже реализованные отличия (offset, admin approve/reject и т.д.), если менять реализацию не планируется.
- Расширить getPrivateProfile полями
roleиplanиз subscription middleware.