- 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
9.8 KiB
9.8 KiB
Инструкции для 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_bankdb/index.ts— Drizzle client- Миграции:
drizzle-kit generate+migrate - Docker Compose (PostgreSQL, Redis)
2. Auth
plugins/auth.ts— JWT verify, refresh token из cookieservices/auth.service.ts— argon2, создание sessions, ротация refreshroutes/auth.ts— register, login, logout, refresh, verify-email, forgot-password, reset-passwordplugins/rateLimit.ts— прогрессивный lockout на login- Email-мок для dev (например, вывод в консоль или mailpit)
3. Profile
plugins/subscription.ts— middleware, подгружающий user.planservices/user.service.tsroutes/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, finishroutes/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