Перейти к содержанию

Архитектура

Монолит из двух частей в одном репозитории: frontend/ и backend/. Общий .env, общий CI. Общего JS-кода между ними нет.

flowchart LR
  browser[React SPA] --> api["FastAPI /api/v1"]
  api --> pg[(PostgreSQL)]
  api --> es[Elasticsearch]
  api --> mail[Resend]
  api --> llm[RouterAI]

Поиск кандидатов идёт в Elasticsearch, если задан ELASTICSEARCH_URL, иначе по базе. Письма — Resend, без ключа в не-production код подтверждения пишется в лог. Распознавание резюме и ИИ-собеседование — RouterAI, только если задан ROUTERAI_API_KEY.

Frontend

Слои Feature-Sliced: app → pages → widgets → features → entities → shared. Слой не импортирует верхний. Публичный API слайса — index.ts. Сущности ссылаются друг на друга через @x.

Маршруты заданы в frontend/src/shared/config/routes.ts. Среди них: /, /auth, кабинеты /profile/*, лента /feed, /vacancies, /responses, /calendar, /matching, /testing, /testing/builder, /admin, публичные /companies/:id и /candidates/:id, возврат FSP /profile/fsp/callback.

Серверное состояние — TanStack Query. Сессия — внешний store. Доступ на клиенте — RequireAuth, настоящая проверка на бэкенде.

Backend

Слой Что делает
api/routes Пути, статусы, зависимости. Без правил предметной области
dependencies Сессия БД, текущий пользователь, require_roles
services Сценарии
repositories Запросы
models SQLAlchemy 2, наружу не отдаются
schemas Pydantic, camelCase на проводе
core Настройки, Argon2id, JWT, ошибки
admin Отдельный пакет админки: банк, аналитика, банлист, параметры, верификация
integrations/fsp Провайдер FSP ID и клиент реестра

Ошибка API: {"error": {"code", "message", "details"}}. Несделанный сценарий отвечает 501 с кодом not_implemented, без фиктивной логики.

Пароли — Argon2id. Неизвестный email и неверный пароль дают один ответ, для выравнивания времени считается пустой хэш. JWT HS256, claims в форме, близкой к Keycloak (sub, email, realm_access.roles), чтобы позже принять RS256 через JWKS. Минимальная длина пароля в схеме регистрации — 8 символов (PASSWORD_MIN_LENGTH в schemas/auth.py). Это короче NFR-04.