Files
samreshu_backend/.cursor/plans/документация_vs_реализация_0932d1a0.plan.md
Anton 223feed0e0 feat: синхронизация бэкенда с документацией (AGENT_TASK_BACKEND_SYNC)
- Добвлен @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
- Обновлены тесты
2026-03-06 13:58:34 +03:00

17 KiB
Raw Permalink Blame History

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

Вывод: Разные имена полей в теле запроса.

Документация: 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 150
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) Процент (0100)
totalQuestions В ответе Нет в ответе
percentage В ответе Нет (score как процент)

Реализация (tests.service.ts 239241):

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_tokensverificationTokens.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. Сводка расхождений

Критические

  1. Отсутствие префикса /api/v1
  2. Auth: другой flow (verify-email до токенов, refresh/logout через body вместо cookie)
  3. Tests: score как процент вместо количества
  4. Отсутствует GET /tests/:id/results
  5. Формат ответов create/answer/finish/history не совпадает с документацией

Значительные

  1. Login lockout: 429 вместо 403 ACCOUNT_LOCKED
  2. Login: нет поля user в ответе
  3. Profile: нет role, plan; другая структура stats
  4. Admin: другой набор эндпоинтов и логика approve/reject
  5. Пагинация: offset вместо cursor

Незначительные

  1. Имена полей (testId vs id, newPassword vs password)
  2. verify-email: userId + code вместо только code
  3. Эндпоинт admin: /pending вместо /queue

Рекомендации

  1. Привести маршрутизацию к документации: добавить префикс /api/v1 при регистрации роутов.
  2. Унифицировать auth: реализовать cookie для refresh token и обновить logout/refresh под документацию, либо явно зафиксировать в документации текущий подход (body).
  3. Исправить score в тестах: хранить и возвращать количество правильных ответов, а процент считать отдельно.
  4. Реализовать GET /tests/:id/results по описанному в документации формату.
  5. Привести ответы create/answer/finish/history к формату из contracts.md.
  6. Обновить документацию под уже реализованные отличия (offset, admin approve/reject и т.д.), если менять реализацию не планируется.
  7. Расширить getPrivateProfile полями role и plan из subscription middleware.