Files
samreshu_docs/agents/backend.md
Anton 99cd8ae727 docs: add full project documentation
- Architecture: overview, 7 ADR, tech stack
- Principles: code-style, git-workflow, security
- API contracts: auth, profile, tests, admin endpoints
- Database schema: tables, relationships, indexes
- LLM strategy: prompts, fallback, validation, Qwen 2.5 14B
- Onboarding: setup, Docker, .env template
- Progress: roadmap, changelog
- Agents: context, backend instructions

Made-with: Cursor
2026-03-04 12:07:17 +03:00

9.8 KiB
Raw Permalink Blame History

Инструкции для AI-агента: Backend

Документ для агентов при создании и доработке samreshu-backend. Общий контекст проекта — в context.md.

Ссылки на документацию

Документ Что взять
api/contracts.md Все endpoints, request/response, коды ошибок
database/schema.md Таблицы, колонки, связи, индексы
principles/security.md Auth (argon2, JWT, refresh), rate limiting, Helmet, CORS
llm/strategy.md Промпты, JSON Schema валидации, fallback, LlmService интерфейс
principles/code-style.md ESLint, Prettier, TS config, Vitest
onboarding/setup.md Docker Compose, .env шаблон

Структура репозитория

samreshu-backend/
├── src/
│   ├── index.ts                 # Entry point
│   ├── app.ts                   # Fastify app, регистрация плагинов
│   ├── config/
│   │   └── env.ts               # Валидация и экспорт env переменных
│   ├── db/
│   │   ├── index.ts             # Drizzle client, экспорт drizzle
│   │   ├── schema/              # Drizzle schema (users, sessions, tests, ...)
│   │   └── migrations/         # SQL миграции (drizzle-kit)
│   ├── plugins/
│   │   ├── database.ts          # Подключение к PostgreSQL
│   │   ├── redis.ts             # Подключение к Redis
│   │   ├── auth.ts              # JWT verify, decode, refreshToken logic
│   │   ├── subscription.ts      # Загрузка user.plan из subscriptions
│   │   ├── rateLimit.ts         # @fastify/rate-limit + Redis
│   │   ├── helmet.ts            # @fastify/helmet
│   │   └── cors.ts              # @fastify/cors
│   ├── routes/
│   │   ├── auth.ts              # POST /auth/register, login, logout, refresh, ...
│   │   ├── profile.ts           # GET/PATCH /profile, GET /profile/:username
│   │   ├── tests.ts             # POST/GET /tests, answer, finish, results, history
│   │   └── admin/
│   │       └── questions.ts     # GET queue, PATCH /admin/questions/:id
│   ├── services/
│   │   ├── auth.service.ts      # register, login, verifyPassword, createSession
│   │   ├── user.service.ts      # getProfile, updateProfile
│   │   ├── test.service.ts      # createTest, getTest, answerQuestion, finishTest
│   │   ├── question.service.ts  # getFromBank, fallback logic
│   │   └── llm.service.ts       # generateQuestions, вызов Ollama API, валидация
│   ├── lib/
│   │   ├── errors.ts            # Кастомные ошибки (AppError)
│   │   ├── pagination.ts        # Cursor-based helper
│   │   └── uuid.ts              # UUID v7 генерация (если нет встроенной)
│   └── types/
│       └── index.ts             # Общие типы (Stack, Level, QuestionType)
├── docker-compose.dev.yml       # PostgreSQL + Redis + Ollama
├── .env.example
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── drizzle.config.ts

Префикс /api/v1 задаётся при регистрации роутов: fastify.register(routes, { prefix: '/api/v1' }).


Порядок разработки

Рекомендуемая последовательность (каждый шаг — отдельный PR):

1. Каркас

  • Инициализация проекта (npm init, зависимости)
  • config/env.ts — загрузка и валидация env (zod или аналог)
  • app.ts — Fastify, Pino logger, базовые плагины (helmet, cors)
  • db/schema — Drizzle schema для MVP 0: users, sessions, subscriptions, tests, test_questions, question_bank
  • db/index.ts — Drizzle client
  • Миграции: drizzle-kit generate + migrate
  • Docker Compose (PostgreSQL, Redis)

2. Auth

  • plugins/auth.ts — JWT verify, refresh token из cookie
  • services/auth.service.ts — argon2, создание sessions, ротация refresh
  • routes/auth.ts — register, login, logout, refresh, verify-email, forgot-password, reset-password
  • plugins/rateLimit.ts — прогрессивный lockout на login
  • Email-мок для dev (например, вывод в консоль или mailpit)

3. Profile

  • plugins/subscription.ts — middleware, подгружающий user.plan
  • services/user.service.ts
  • routes/profile.ts — GET/PATCH /profile, GET /profile/:username
  • Защита роутов: preHandler с проверкой JWT

4. Tests (ядро)

  • services/llm.service.ts — вызов Ollama, JSON Schema валидация, fallback на банк
  • services/question.service.ts — выбор из банка, дедупликация
  • services/test.service.ts — создание теста, снепшот в test_questions, answer, finish
  • routes/tests.ts — все endpoints для тестов
  • Проверка лимита Free (5 тестов/день) в subscription middleware

5. Admin

  • routes/admin/questions.ts — GET queue, PATCH approve/reject
  • Проверка user.role === 'admin' в preHandler

6. Seed и прогон

  • Скрипт npm run seed:questions — наполнение question_bank через LLM
  • Скрипт npm run db:seed — тестовый пользователь (опционально)
  • Проверка полного flow вручную

Ключевые паттерны

Fastify plugins

Каждый плагин — отдельный файл, регистрируется через fastify.register(plugin):

// plugins/auth.ts
import type { FastifyPluginAsync } from 'fastify'

const authPlugin: FastifyPluginAsync = async (fastify) => {
  fastify.decorate('verifyJwt', async (request, reply) => { ... })
}

export default authPlugin

Декорators: fastify.verifyJwt, fastify.db, fastify.redis, fastify.llm — доступны после регистрации плагинов.

Обработка ошибок

Единый setErrorHandler:

fastify.setErrorHandler((error, request, reply) => {
  if (error instanceof AppError) {
    return reply.status(error.statusCode).send({
      error: { code: error.code, message: error.message }
    })
  }
  fastify.log.error(error)
  return reply.status(500).send({
    error: { code: 'INTERNAL_ERROR', message: 'Internal server error' }
  })
})

AppError — класс с полями statusCode, code, message. Коды ошибок — из api/contracts.md.

Drizzle

  • Схема в db/schema/ — отдельный файл на домен (users.ts, tests.ts, question_bank.ts)
  • Все запросы через db.select(), db.insert(), db.update() — параметризованные
  • Даты: new Date() в UTC, в БД — timestamptz
  • UUID: uuidv7() или аналог для PK

LlmService

  • Один класс/объект в services/llm.service.ts
  • Конфиг из env: LLM_BASE_URL, LLM_MODEL, LLM_TIMEOUT_MS
  • Вызов: POST ${baseUrl}/chat/completions с телом OpenAI-формата
  • Ответ: извлечь content из choices[0].message, парсить JSON (см. llm/strategy.md — извлечение из markdown)
  • Валидация: JSON Schema (ajv или zod) перед использованием
  • Retry: 1 раз при таймауте или невалидном ответе
  • Fallback: question.service.getFromBank() если LLM недоступен

Валидация request body

Fastify schema на каждом route:

schema: {
  body: {
    type: 'object',
    required: ['email', 'password'],
    properties: {
      email: { type: 'string', format: 'email' },
      password: { type: 'string', minLength: 8 }
    },
    additionalProperties: false
  }
}

Пагинация cursor-based

Использовать limit и cursor (UUID). Запрос:

SELECT * FROM tests
WHERE user_id = :userId
  AND (cursor IS NULL OR id < :cursor)
ORDER BY id DESC
LIMIT :limit + 1

Если вернулось limit + 1 записей — hasMore = true, nextCursor = last.id. Иначе hasMore = false, nextCursor = null.


Тестирование

  • Vitest для unit и integration тестов
  • Покрытие: минимум 70% на сервисном слое (см. code-style.md)
  • Моки:
    • LlmService — не вызывать реальный Ollama в тестах, возвращать фиксированный JSON
    • Email — мок, не отправлять реальные письма
    • Redis — можно in-memory или реальный Redis в Docker (integration)
  • Тесты сервисов: мок Drizzle (или тестовая БД с migrater)
  • Тесты роутов: fastify.inject() с моками сервисов

Чеклист перед коммитом

  • ESLint и Prettier проходят
  • npm run test — все тесты зелёные
  • Нет console.log (только fastify.log / logger)
  • Нет any без явного обоснования
  • Секреты не в коде (только env)
  • Новые endpoints соответствуют api/contracts.md