- 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
310 lines
10 KiB
Markdown
310 lines
10 KiB
Markdown
# Схема базы данных
|
||
|
||
Описание таблиц и связей. Фактическая Drizzle-схема создаётся в `samreshu-backend`, здесь — справочник.
|
||
|
||
## Диаграмма связей
|
||
|
||
```mermaid
|
||
erDiagram
|
||
users ||--o{ subscriptions : has
|
||
users ||--o{ sessions : has
|
||
users ||--o{ oauth_accounts : has
|
||
users ||--o| totp_secrets : has
|
||
users ||--o{ tests : takes
|
||
users ||--o{ user_stats : has
|
||
users ||--o{ user_achievements : earns
|
||
users ||--o{ user_question_log : tracks
|
||
|
||
tests ||--o{ test_questions : contains
|
||
|
||
question_bank ||--o{ question_cache_meta : has
|
||
question_bank ||--o{ question_reports : receives
|
||
```
|
||
|
||
## Таблицы
|
||
|
||
### users
|
||
|
||
Основная таблица пользователей.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| email | varchar, unique | |
|
||
| password_hash | varchar | bcrypt/argon2 |
|
||
| nickname | varchar | Отображаемое имя |
|
||
| avatar_url | varchar, nullable | |
|
||
| country | varchar, nullable | |
|
||
| city | varchar, nullable | |
|
||
| self_level | enum, nullable | jun / mid / sen |
|
||
| is_public | boolean, default true | Публичный профиль |
|
||
| role | enum, default 'free' | guest / free / pro / admin |
|
||
| email_verified_at | timestamptz, nullable | |
|
||
| created_at | timestamptz | |
|
||
| updated_at | timestamptz | |
|
||
|
||
### subscriptions
|
||
|
||
Подписки пользователей. Существует с первого дня (даже для Free — с plan='free').
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| plan | enum | free / pro |
|
||
| status | enum | active / trialing / cancelled / expired |
|
||
| started_at | timestamptz | |
|
||
| expires_at | timestamptz, nullable | |
|
||
| cancelled_at | timestamptz, nullable | |
|
||
| payment_provider | varchar, nullable | yukassa / cloudpayments |
|
||
| external_id | varchar, nullable | ID подписки у провайдера |
|
||
|
||
### sessions
|
||
|
||
Активные сессии пользователя (устройства).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| refresh_token_hash | varchar | |
|
||
| user_agent | varchar | |
|
||
| ip_address | varchar | |
|
||
| last_active_at | timestamptz | |
|
||
| expires_at | timestamptz | |
|
||
| created_at | timestamptz | |
|
||
|
||
### oauth_accounts
|
||
|
||
Привязанные OAuth-провайдеры (Phase 2).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| provider | enum | github / google |
|
||
| provider_user_id | varchar | |
|
||
| created_at | timestamptz | |
|
||
|
||
### totp_secrets
|
||
|
||
2FA через TOTP (Phase 2).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users, unique | |
|
||
| secret | varchar | Зашифрованный TOTP-секрет |
|
||
| enabled | boolean, default false | |
|
||
| created_at | timestamptz | |
|
||
|
||
### tests
|
||
|
||
Пройденные тесты.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| stack | enum | html / css / js / ts / react / vue / nodejs / git / web_basics |
|
||
| level | enum | basic / beginner / intermediate / advanced / expert |
|
||
| question_count | integer | 10 / 20 / 30 |
|
||
| mode | enum | fixed / infinite / marathon |
|
||
| status | enum | in_progress / completed / abandoned |
|
||
| score | integer, nullable | Количество правильных |
|
||
| started_at | timestamptz | |
|
||
| finished_at | timestamptz, nullable | |
|
||
| time_limit_seconds | integer, nullable | |
|
||
|
||
### test_questions
|
||
|
||
Снепшот вопросов для конкретного теста. При старте теста вопросы копируются сюда из `question_bank` или генерируются LLM.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| test_id | uuid, FK → tests | |
|
||
| question_bank_id | uuid, FK → question_bank, nullable | Если из банка |
|
||
| order_number | integer | Порядок в тесте |
|
||
| type | enum | single_choice / multiple_select / true_false / short_text |
|
||
| question_text | text | |
|
||
| options | jsonb, nullable | Варианты ответов |
|
||
| correct_answer | jsonb | Правильный ответ |
|
||
| explanation | text | Объяснение |
|
||
| user_answer | jsonb, nullable | Ответ пользователя |
|
||
| is_correct | boolean, nullable | |
|
||
| answered_at | timestamptz, nullable | |
|
||
|
||
### question_bank
|
||
|
||
Провалидированные вопросы для переиспользования.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| stack | enum | |
|
||
| level | enum | |
|
||
| type | enum | |
|
||
| question_text | text | |
|
||
| options | jsonb, nullable | |
|
||
| correct_answer | jsonb | |
|
||
| explanation | text | |
|
||
| status | enum | pending / approved / rejected |
|
||
| source | enum | llm_generated / manual |
|
||
| usage_count | integer, default 0 | Сколько раз использован |
|
||
| created_at | timestamptz | |
|
||
| approved_at | timestamptz, nullable | |
|
||
|
||
### question_cache_meta
|
||
|
||
Метаданные генерации вопросов через LLM.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| question_bank_id | uuid, FK → question_bank | |
|
||
| llm_model | varchar | Модель, сгенерировавшая вопрос |
|
||
| prompt_hash | varchar | Хеш промпта |
|
||
| generation_time_ms | integer | Время генерации |
|
||
| created_at | timestamptz | |
|
||
|
||
### question_reports
|
||
|
||
Жалобы пользователей на вопросы.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| question_bank_id | uuid, FK → question_bank | |
|
||
| user_id | uuid, FK → users | |
|
||
| reason | text | |
|
||
| status | enum | open / resolved / dismissed |
|
||
| created_at | timestamptz | |
|
||
|
||
### user_stats
|
||
|
||
Агрегированная статистика по темам (обновляется после каждого теста).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| stack | enum | |
|
||
| level | enum | |
|
||
| total_questions | integer | |
|
||
| correct_answers | integer | |
|
||
| tests_taken | integer | |
|
||
| last_test_at | timestamptz | |
|
||
|
||
Unique constraint: `(user_id, stack, level)`
|
||
|
||
### user_achievements
|
||
|
||
Бейджи и достижения (Phase 3).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| achievement_code | varchar | Код достижения |
|
||
| earned_at | timestamptz | |
|
||
|
||
### user_question_log
|
||
|
||
Лог: какие вопросы пользователь видел (для дедупликации).
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| question_bank_id | uuid, FK → question_bank | |
|
||
| seen_at | timestamptz | |
|
||
|
||
### payments
|
||
|
||
Платежи.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| subscription_id | uuid, FK → subscriptions | |
|
||
| amount | decimal | |
|
||
| currency | varchar, default 'RUB' | |
|
||
| status | enum | pending / succeeded / failed / refunded |
|
||
| provider | enum | yukassa / cloudpayments |
|
||
| external_id | varchar | ID у провайдера |
|
||
| created_at | timestamptz | |
|
||
|
||
### payment_events
|
||
|
||
Лог webhook-событий от платёжных систем.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| payment_id | uuid, FK → payments, nullable | |
|
||
| provider | enum | |
|
||
| event_type | varchar | |
|
||
| payload | jsonb | Полный JSON от провайдера |
|
||
| processed | boolean, default false | |
|
||
| created_at | timestamptz | |
|
||
|
||
### audit_logs
|
||
|
||
Действия админов.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| admin_id | uuid, FK → users | |
|
||
| action | varchar | ban_user / approve_question / ... |
|
||
| target_type | varchar | user / question / promo_code |
|
||
| target_id | uuid | |
|
||
| details | jsonb, nullable | |
|
||
| created_at | timestamptz | |
|
||
|
||
### notifications_log
|
||
|
||
История отправленных уведомлений.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| user_id | uuid, FK → users | |
|
||
| channel | enum | email / in_app / telegram / push |
|
||
| template | varchar | verify_email / reset_password / trial_ending / ... |
|
||
| status | enum | sent / failed |
|
||
| created_at | timestamptz | |
|
||
|
||
### promo_codes
|
||
|
||
Промокоды.
|
||
|
||
| Колонка | Тип | Описание |
|
||
|---------|-----|----------|
|
||
| id | uuid, PK | |
|
||
| code | varchar, unique | |
|
||
| discount_percent | integer | |
|
||
| max_uses | integer, nullable | |
|
||
| used_count | integer, default 0 | |
|
||
| valid_from | timestamptz | |
|
||
| valid_until | timestamptz | |
|
||
| created_at | timestamptz | |
|
||
|
||
## Индексы (ключевые)
|
||
|
||
- `users.email` — unique
|
||
- `sessions.user_id` — для списка устройств
|
||
- `tests.user_id` + `tests.created_at` — для истории
|
||
- `question_bank.stack` + `question_bank.level` + `question_bank.status` — для выборки вопросов
|
||
- `user_question_log.user_id` + `user_question_log.question_bank_id` — для дедупликации
|
||
- `payments.user_id` — для истории платежей
|
||
|
||
## Примечания
|
||
|
||
- Все PK — UUID v7 (сортируемые по времени)
|
||
- Все timestamps — `timestamptz` (UTC)
|
||
- JSONB используется для вариантов ответов и webhook payload
|
||
- Enum-значения для stack/level определяются один раз и используются во всех таблицах
|