docker compose определяет .env для подстановки ${VAR} по каталогу
compose-файла (deploy/), а не по текущей директории — repo-root .env,
который использует install.sh и вся документация, молча не подхватывался.
Это и была причина "WARN: LIVEKIT_API_KEY not set" на боевом сервере:
секреты были в .env, но compose их не видел и подставлял небезопасные
дефолты (см. таблицу разведки сервера).
Теперь ставшие обязательными ${VAR:?} в docker-compose.yml (см. предыдущий
коммит) без этого немедленно проваливали бы конфиг на любой из
документированных команд. Добавлен `--env-file .env`/"$ENV_FILE" ко всем
вызовам docker compose в install.sh и в командах из README/docs.
install.sh дополнительно: ensure_default (аналог ensure_secret без генерации
секрета) для новых не-секретных параметров nginx/coturn/livekit
(NGINX_SERVER_NAMES, NGINX_CERT_NAME, LIVEKIT_USE_EXTERNAL_IP,
LIVEKIT_NODE_IP, TURN_EXTERNAL_IP) — дефолты только для локальной
разработки, не перезаписывают значения, заданные вручную на боевом
сервере. Плюс вызов deploy/render-templates.sh перед сборкой/подъёмом
стека.
536 lines
32 KiB
Markdown
536 lines
32 KiB
Markdown
# Архитектура
|
||
|
||
Высокоуровневый обзор дизайна системы VidConf.
|
||
|
||
## Быстрые ссылки
|
||
|
||
- **[frontend-themes.md](./frontend-themes.md)** — архитектура темы оболочки: механизм `data-theme`, localStorage, React-hook, анти-FOUC, инвариант комнаты.
|
||
|
||
## Архитектура системы
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Web Browsers │
|
||
│ (React SPA + LiveKit JS SDK) │
|
||
└──────────────────────────┬──────────────────────────────────┘
|
||
│ HTTPS
|
||
┌──────▼───────┐
|
||
│ Nginx │ (TLS termination)
|
||
│ Reverse │
|
||
│ Proxy │
|
||
└──────┬───────┘
|
||
┌──────────────────┼──────────────────┐
|
||
│ │ │
|
||
┌────▼────┐ ┌──────▼──────┐ ┌─────▼─────┐
|
||
│ FastAPI │ │ LiveKit │ │ Coturn │
|
||
│ Backend │ │ SFU │ │ TURN │
|
||
└────┬────┘ └──────┬──────┘ └───────────┘
|
||
│ │
|
||
│ (TCP/Postgres) │ (RTP/SRTP WebRTC)
|
||
┌────▼──────────────────┘
|
||
│
|
||
├──► PostgreSQL 16
|
||
│ - 14 таблиц: users, teams, conferences, conference_sessions и т.д.
|
||
│
|
||
├──► Redis
|
||
│ - Celery broker
|
||
│ - Rate limiting, refresh-токены, pub/sub чата
|
||
│
|
||
└──► Celery воркеры (раздельные очереди)
|
||
├─► transcription (faster-whisper small/medium/large-v3 → фразы)
|
||
├─► summarize (Qwen3.5 4B/9B/35B-A3B → резюме, 3 уровня)
|
||
└─► notify (email + .ics → SMTP)
|
||
```
|
||
|
||
## Основные компоненты
|
||
|
||
### 1. Frontend (React SPA)
|
||
**Location:** `frontend/`
|
||
|
||
**Tech:** React 18 + TypeScript + Vite, Tailwind CSS 4 + @tailwindcss/vite, shadcn/ui, `@livekit/components-react`, FullCalendar
|
||
|
||
**Функциональность:**
|
||
- Аутентификация (вход, регистрация, верификация email), JWT (access — заголовок Authorization, refresh — httpOnly cookie)
|
||
- Лобби-хаб, календарь (создание/редактирование/отмена конференций, повторение), «мои конференции»
|
||
- Вход в конференцию по номеру/ссылке, гостевой вход
|
||
- Комната конференции: видео/аудио через LiveKit JS SDK, чат в реальном времени, настройки устройств, fullscreen, Picture-in-Picture, демонстрация экрана
|
||
- Профиль пользователя (ФИО, команда, аватар, смена пароля)
|
||
- Админ-панель (конференции, пользователи, команды, настройки инстанса)
|
||
- Светлая/тёмная тема оболочки (см. [frontend-themes.md](./frontend-themes.md))
|
||
|
||
**Коммуникация:**
|
||
- HTTP + WebSocket → FastAPI backend (`/api/v1/*`)
|
||
- WebRTC → LiveKit SFU
|
||
|
||
Подробнее: [frontend/README.md](../../frontend/README.md).
|
||
|
||
---
|
||
|
||
### 2. Backend (FastAPI)
|
||
**Location:** `backend/`
|
||
|
||
**Tech:** Python 3.12, FastAPI, SQLAlchemy 2.0 async, Alembic
|
||
|
||
**Реализовано:**
|
||
- Схема БД + миграции (14 таблиц + расширение `btree_gist`, установлено, но не используется текущими constraint'ами)
|
||
- Контракты плагинов (Transcriber, Summarizer) + factory с реестром (`@register_transcriber`, `@register_summarizer`)
|
||
- `NullTranscriber`, `NullSummarizer` (no-op), `FasterWhisperCPU`/`FasterWhisperGPU`, `QwenLocal`
|
||
- JWT аутентификация (вход, регистрация, верификация email, refresh, logout), OAuth2 password flow
|
||
- Генерация LiveKit токенов, приём и обработка webhook-событий LiveKit (room_started, participant_joined/left, room_finished)
|
||
- Динамические конференции: создание (мгновенное/плановое), календарь, вход по номеру/ссылке/паролю, гостевой вход, повторение
|
||
- Приглашения на конференцию (пользователь или внешний email), рассылка .ics
|
||
- Чат конференции (WebSocket, история, broadcast через Redis pub/sub)
|
||
- Администрирование: конференции, пользователи, команды, настройки инстанса
|
||
- Endpoint `GET /api/health` (проверка БД/Redis), `GET /metrics` (Prometheus)
|
||
|
||
**API структура:**
|
||
```
|
||
/api/v1/
|
||
├── auth/ register, verify-email, token, refresh, logout, registration-options
|
||
├── users/ me (GET/PATCH), me/avatar (POST/DELETE), me/password, поиск (GET ?q=)
|
||
├── teams/ справочник команд (GET)
|
||
├── conferences/ POST create, GET /my, GET /calendar, GET /resolve,
|
||
│ POST /{id}/join, POST /{id}/guest-join, GET/PATCH/DELETE /{id}
|
||
├── conferences/{id}/chat WebSocket-чат
|
||
├── admin/ conferences, users, teams, settings
|
||
└── livekit/ webhook приёмник
|
||
|
||
/api/health статус системы (БД, Redis)
|
||
/metrics метрики Prometheus
|
||
```
|
||
|
||
Полная спецификация: [docs/api/README.md](../api/README.md).
|
||
|
||
---
|
||
|
||
### 3. Database (PostgreSQL 16)
|
||
**Location:** миграции в `backend/alembic/versions/` (9 миграций)
|
||
|
||
**Основные таблицы:**
|
||
|
||
| Таблица | Назначение |
|
||
|---------|---------|
|
||
| `users` | Зарегистрированные пользователи (email, password_hash, role, is_blocked, team_id, avatar_path) |
|
||
| `teams` | Справочник команд |
|
||
| `email_verification_tokens` | Одноразовые токены верификации email |
|
||
| `conferences` | Постоянные сущности конференций (номер, slug, владелец, статус, recurrence, summary_recipients, ics_sequence) |
|
||
| `conference_invitees` | Приглашённые на конференцию (user_id ИЛИ email) |
|
||
| `guest_access` | Гости, представившиеся при входе (display_name, email) |
|
||
| `conference_sessions` | Один запуск конференции — единица AI-пайплайна (t_start, t_end, pipeline_status, summary_data) |
|
||
| `conference_participants` | Отслеживание участия в сеансе (user_id ИЛИ guest_id, session_id, joined_at, left_at) |
|
||
| `session_audio_tracks` | Аудиотреки сеанса (per-track запись, статус транскрибации, segments JSONB) |
|
||
| `phrases` | Текстовые сегменты транскрибации (participant_id, session_id, t_start, t_end) |
|
||
| `chat_messages` | Сообщения в сеансе (session_id, автор — user_id или guest_access_id, author_name) |
|
||
| `email_deliveries` | Журнал отправленных писем (саммари, приглашения) — идемпотентность рассылки |
|
||
| `instance_settings` | Настройки инстанса (key-value JSONB): AI-уровень, чат, таймзона, режим рассылки, опции регистрации |
|
||
| `livekit_webhook_events` | Журнал webhook-событий для идемпотентности (event_id, event_type, received_at) |
|
||
|
||
**Ключевые особенности:**
|
||
- **UUID первичные ключи** (кроме `phrases`/`chat_messages` → BIGINT Identity)
|
||
- **Все времена в UTC** (`timestamptz`)
|
||
- **Номер (9 цифр) и slug (base64url)** конференции — публичные идентификаторы доступа
|
||
- **Recurrence как JSONB** (не RRULE; 4 типа: weekly/biweekly/monthly/every_n_days)
|
||
- **Гости как полноценные участники** (`guest_id` в `conference_participants`)
|
||
|
||
См. [docs/db/schema.md](../db/schema.md) — полная ER-диаграмма и обоснования дизайна.
|
||
|
||
---
|
||
|
||
### 4. LiveKit SFU
|
||
**Profile compose:** `media`
|
||
|
||
**Tech:** LiveKit (Go-based SFU)
|
||
|
||
**Роль:**
|
||
- SFU (Selective Forwarding Unit) для WebRTC
|
||
- Per-track аудио (один трек = один спикер, исключает необходимость диаризации)
|
||
- Запись треков через Egress (`.ogg`, opus) для последующей транскрибации
|
||
- FastAPI генерирует LiveKit-токены (JWT, TTL 6 часов, per-room), браузер подключается через LiveKit JS SDK
|
||
|
||
---
|
||
|
||
### 5. Celery Workers
|
||
**Location:** `workers/`
|
||
|
||
**Tech:** Celery + Redis (message broker), раздельные очереди (`transcription`/`summarize`/`notify`)
|
||
|
||
**Задачи:**
|
||
|
||
| Задача | Вход | Процесс | Выход |
|
||
|--------|-------|---------|--------|
|
||
| `run_pipeline()` | session_id | Диспетчер + транскрибация (faster-whisper) + реконструкция фраз | `session_audio_tracks.segments`, `phrases` |
|
||
| `summarize_session()` | session_id | Map-reduce Qwen3.5 (уровень из instance_settings) | `conference_sessions.summary_data` |
|
||
| `notify_session()` | session_id | Генерация email + .ics, рассылка по режиму (all/owner) | `email_deliveries` |
|
||
| `send_invitations()` | conference_id, emails | Генерация .ics, рассылка приглашений | `email_deliveries` |
|
||
| `cleanup_conferences()` (beat) | — | Закрытие зависших сеансов, завершение просроченных конференций | — |
|
||
| `recover_stuck_summaries()` / `recover_stuck_notifications()` (beat) | — | Переставить зависшую `summarize_session`/`notify_session` в очередь | завершение пайплайна |
|
||
|
||
**State machine pipeline_status:**
|
||
```
|
||
recording (создана при t_start сеанса, LiveKit Egress записывает)
|
||
↓ (webhook room_finished → enqueue run_pipeline в очередь transcription)
|
||
transcribing (транскрибация faster-whisper + реконструкция фраз)
|
||
↓
|
||
summarizing (map-reduce Qwen3.5 → очередь summarize; summary_data записан, статус не меняется)
|
||
↓ (notify_session отправляет уведомления → очередь notify)
|
||
notified
|
||
↓ Успех: история сохранена в БД
|
||
|
||
↘ (при ошибке на любом шаге)
|
||
↘ failed
|
||
↘ Лог; beat-задачи recover_stuck_* могут переставить зависший шаг
|
||
```
|
||
|
||
**Переход в notified:**
|
||
- Собрать получателей по эффективному режиму рассылки (`conference.summary_recipients or cfg.summary_recipients`)
|
||
- Режим `'all'` → участники сеанса (users) + гости с email
|
||
- Режим `'owner'` → email владельца конференции
|
||
- Отправить письмо для каждого нового адреса (таблица `email_deliveries` обеспечивает идемпотентность)
|
||
- Переход в `notified` независимо от того, были ли получатели
|
||
|
||
**Идемпотентность:** каждый шаг проверяет `pipeline_status` перед началом и пропускает уже пройденные шаги.
|
||
|
||
Подробнее: [workers/README.md](../../workers/README.md).
|
||
|
||
---
|
||
|
||
### 6. AI Plugins (Strategy Pattern)
|
||
**Location:** `backend/core/plugins/`
|
||
|
||
**Interfaces:**
|
||
- `Transcriber.transcribe(audio_path, language) → list[Segment]`
|
||
- `Summarizer.summarize(transcript) → str`
|
||
|
||
**Factory:**
|
||
- Registry: `@register_transcriber`, `@register_summarizer`
|
||
- Configuration: `config/plugins.yaml`
|
||
- No-op defaults: `NullTranscriber`, `NullSummarizer`
|
||
|
||
**Расширяемость:**
|
||
- Добавить нового провайдера = новый класс + строка в конфиге
|
||
- Никаких изменений в основном коде
|
||
|
||
См. [docs/plugins/contracts.md](../plugins/contracts.md).
|
||
|
||
---
|
||
|
||
### 7. Nginx Reverse Proxy
|
||
**Конфиг:** `deploy/nginx.conf`
|
||
|
||
**Задачи:**
|
||
- Завершение TLS
|
||
- Обслуживание статических файлов (frontend SPA)
|
||
- Маршрутизация запросов API → FastAPI
|
||
- Проксирование WebSocket (чат конференции)
|
||
- Сжатие Gzip
|
||
|
||
---
|
||
|
||
## Поток данных
|
||
|
||
### 1. Пользователь входит в конференцию
|
||
|
||
```
|
||
Browser
|
||
│ 1. POST /api/v1/conferences/{id}/join (пароль опционально)
|
||
├─────────────────────────────────► FastAPI
|
||
│ │
|
||
│ 2. Проверка доступа, генерация LiveKit-токена
|
||
│ │
|
||
│ 3. {"token": "...", "url": "...", "chat_enabled": true}
|
||
◄─────────────────────────────────────┤
|
||
│
|
||
4. Подключение через LiveKit JS SDK
|
||
├────────────────► LiveKit SFU
|
||
│ │
|
||
│ 5. Запись аудио per-track через Egress (при завершении сеанса)
|
||
└ (video/audio stream) ──────────►
|
||
```
|
||
|
||
БД: INSERT `conference_participants` (user_id ИЛИ guest_id, session_id, joined_at).
|
||
|
||
---
|
||
|
||
### 2. Конференция заканчивается → пост-обработка
|
||
|
||
```
|
||
room_finished webhook
|
||
│
|
||
1. Backend помечает завершение записи, ожидает завершения egress
|
||
│
|
||
2. enqueue_pipeline(session_id) → Redis, очередь `transcription`
|
||
│
|
||
3. run_pipeline(session_id) — диспетчер пайплайна
|
||
├─► Ожидание завершения egress (retry с backoff)
|
||
├─► Для каждого трека:
|
||
│ ├─► Загрузить плагин transcriber (FasterWhisperCPU/GPU или null)
|
||
│ ├─► transcriber.transcribe(file_path, language='ru')
|
||
│ └─► Сохранить segments в session_audio_tracks.segments (JSONB)
|
||
├─► Собрать сегменты по участникам (с учётом смещений)
|
||
├─► build_phrases(segments_by_participant, track_offsets)
|
||
├─► DELETE phrases + INSERT новые → таблица phrases
|
||
├─► UPDATE pipeline_status = 'summarizing'
|
||
│
|
||
4. send_task_with_retry ставит summarize_session → очередь `summarize`
|
||
│
|
||
5. summarize_session
|
||
├─► Получить фразы сеанса, собрать транскрипт
|
||
├─► Разбить на чанки (20 мин по умолчанию, зависит от уровня AI)
|
||
├─► Qwen3.5 map-reduce (промпты в workers/summarizer/prompts/)
|
||
├─► UPDATE conference_sessions.summary_data = '{...}' (pipeline_status остаётся 'summarizing')
|
||
│
|
||
6. send_task_with_retry ставит notify_session → очередь `notify`
|
||
│
|
||
7. notify_session
|
||
├─► Собрать получателей (режим: all=участники+гости / owner=владелец)
|
||
├─► Для каждого получателя: проверить email_deliveries, отправить письмо
|
||
├─► Сгенерировать HTML + plaintext письмо (backend/services/email_templates.py)
|
||
├─► Отправить через email-бэкенд (console|smtp, backend/services/email.py)
|
||
├─► UPDATE email_deliveries (идемпотентность повторной отправки)
|
||
├─► UPDATE pipeline_status = 'notified' (когда все получатели обработаны)
|
||
│
|
||
8. Beat-задачи recover_stuck_summaries/recover_stuck_notifications (интервал 300 сек)
|
||
├─► Найти сеансы, зависшие в 'summarizing' (без summary_data или без уведомления)
|
||
├─► Переставить соответствующую задачу в очередь для восстановления после сбоя брокера
|
||
```
|
||
|
||
**Ключевые особенности:**
|
||
- **Диспетчер единый:** `run_pipeline()` управляет всеми шагами транскрибации
|
||
- **Per-track идемпотентность:** каждый трек коммитится отдельно (точка возобновления)
|
||
- **Реконструкция фраз:** группировка по спикеру, короткие вставки, перекрытия речи (`workers/transcription/phrases.py`)
|
||
- **Атрибуция гостям:** фразы и треки связаны с `conference_participants`, не `users` (ADR-002)
|
||
- **Проверка pipeline_status:** guard идемпотентности (повторный запуск пропустит готовые шаги)
|
||
|
||
---
|
||
|
||
## Обработка времени
|
||
|
||
**Инвариант:** все времена в БД в UTC (`timestamptz`).
|
||
|
||
### Server-side (PostgreSQL)
|
||
```sql
|
||
INSERT INTO conferences (scheduled_at, duration_minutes)
|
||
VALUES ('2026-07-15T14:30:00+00:00', 60);
|
||
```
|
||
|
||
### Client-side (JavaScript)
|
||
```typescript
|
||
const startUtc = new Date('2026-07-15T14:30:00Z');
|
||
// Браузер форматирует в локальный часовой пояс пользователя
|
||
```
|
||
|
||
### .ics export
|
||
```ics
|
||
BEGIN:VCALENDAR
|
||
VERSION:2.0
|
||
PRODID:-//VidConf//EN
|
||
BEGIN:VTIMEZONE
|
||
TZID:Europe/Moscow
|
||
...
|
||
END:VTIMEZONE
|
||
BEGIN:VEVENT
|
||
UID:...
|
||
DTSTART;TZID=Europe/Moscow:20260715T143000
|
||
DTEND;TZID=Europe/Moscow:20260715T153000
|
||
...
|
||
END:VEVENT
|
||
END:VCALENDAR
|
||
```
|
||
|
||
---
|
||
|
||
## Ключевые архитектурные решения
|
||
|
||
1. **Динамические конференции вместо бронирований** (ADR-001) — конференция как пользовательская сущность
|
||
- Номер (9 цифр) + slug (base64url) — постоянные идентификаторы доступа
|
||
- Статусы: scheduled/active/ended
|
||
- Повторение: RecurrenceRule (JSON, 4 типа)
|
||
- Гости — полноценные участники (`guest_access`)
|
||
|
||
2. **Номер и slug как идентификаторы доступа** (ADR-001, п.4) — публичные эндпоинты без auth
|
||
- **Номер:** 9 цифр, первая 1..9 (энтропия ≈2^29.75); перебор при rate limit ≈60+ дней
|
||
- **Slug:** base64url 8 байт → 11 символов (64 бита энтропии); имя LiveKit-комнаты
|
||
- **Резолв:** единообразный 404 (живая/мёртвая неразличимы для безопасности)
|
||
- **Rate limit:** 10 запросов в минуту на IP для `/resolve` и `/guest-join`
|
||
|
||
3. **Гости как полноценные участники** (ADR-001, п.6) — представление при входе
|
||
- `guest_access`: display_name (обязателен), email (факультативен)
|
||
- LiveKit identity: `guest:{guest_id}` (email не передаётся)
|
||
- Участвуют в пайплайне саммари (email в рассылке, если заполнен)
|
||
|
||
4. **Recurrence как собственная модель, не RRULE** (ADR-001, п.3) — 4 типа, привязаны к форме UI
|
||
- `type`: weekly | biweekly | monthly | every_n_days
|
||
- Хранение в JSONB (`conferences.recurrence`), развёртка occurrences в памяти
|
||
- Правило содержит локальное время + IANA-таймзону; развёртка — в UTC
|
||
|
||
5. **Паттерн Strategy для плагинов** — подключаемые AI-реализации без изменений ядра
|
||
- Интерфейсы Transcriber/Summarizer в `backend/core/plugins/`
|
||
- Паттерн Factory с реестром
|
||
- Конфиг через YAML
|
||
|
||
6. **Идемпотентный pipeline пост-обработки** — безопасно повторять любой шаг
|
||
- Каждый шаг проверяет `pipeline_status` перед продолжением
|
||
- Per-track коммит сегментов (точка возобновления в БД)
|
||
- Неудачные задачи можно повторить без дублирования (DELETE+INSERT фраз в одной транзакции)
|
||
|
||
7. **Атрибуция фраз и треков к участнику сеанса** (ADR-002) — поддержка гостей без user_id
|
||
- `phrases.participant_id` → FK `conference_participants.id` (вместо user_id)
|
||
- `session_audio_tracks.participant_id` → FK `conference_participants.id`
|
||
- Гость без `user_id` проходит пайплайн наравне с пользователем
|
||
|
||
8. **Per-track аудио в SFU** — исключает необходимость диаризации спикеров
|
||
- LiveKit предоставляет per-track recording (один трек = один микрофон = один спикер)
|
||
- Track SID прямо соответствует identity участника
|
||
- Запись через Track Egress в `.ogg` (opus)
|
||
|
||
9. **UUID первичные ключи** — поддержка распределённых систем и репликации
|
||
- Сгенерировано через `gen_random_uuid()`
|
||
- Исключения: таблицы высокой частоты (`phrases`, `chat_messages`) используют BIGINT Identity
|
||
|
||
10. **Настройки инстанса в БД** — бутстрап из `plugins.yaml`
|
||
- Таблица `instance_settings` (key-value JSONB) импортирует дефолты `config/plugins.yaml` при старте backend
|
||
- Однократно и идемпотентно (`INSERT ... ON CONFLICT DO NOTHING`)
|
||
- Воркеры читают эффективную конфигурацию на старте каждой задачи
|
||
- Административный интерфейс может менять настройки без рестарта
|
||
|
||
11. **Рассылка саммари с переопределением на уровне конференции** — гибкая конфигурация
|
||
- `instance_settings.summary_recipients` — дефолт инстанса (`'all'` или `'owner'`)
|
||
- `conferences.summary_recipients` — переопределение для конкретной конференции (nullable)
|
||
- Эффективный режим = `conference.summary_recipients or cfg.summary_recipients`
|
||
|
||
12. **Идемпотентная рассылка саммари** — без дублирования писем
|
||
- Таблица `email_deliveries` с уникальным частичным индексом `(session_id, recipient_email)` WHERE `kind='summary'`
|
||
- Повторный запуск уведомления отправляет письмо только адресатам, которых ещё нет в таблице
|
||
- Семантика at-least-once: редкий дубль письма возможен, потеря — нет
|
||
- Per-получательный commit обеспечивает точку возобновления при обрыве
|
||
|
||
13. **Email-транспорт только через `.env`** — секреты никогда не попадают в БД или API
|
||
- Переключатель бэкенда (`EMAIL_BACKEND=console|smtp`) — переменная окружения
|
||
- Все SMTP-реквизиты (хост, порт, пароль, from) — только в `.env`
|
||
- Секреты не логируются и не попадают в `SettingsOut` API
|
||
- `aiosmtplib` с обработкой ошибок (retryable vs. skip)
|
||
|
||
14. **Управление командами и опциями регистрации** — справочник команд и гибкий вход
|
||
- Таблица `teams` — справочник команд (id, name UNIQUE, created_at); админ-API CRUD
|
||
- `users.team_id` (nullable) → FK `teams.id` с `ON DELETE SET NULL`
|
||
- Настройка инстанса `registration_team_choice` (bool) — показ выбора команды при регистрации; `GET /auth/registration-options` возвращает список доступных команд
|
||
- Настройка инстанса `registration_email_domain` (bool, domain: str|null) — обязательное совпадение домена email при регистрации (иначе 400 `invalid_email_domain`)
|
||
|
||
---
|
||
|
||
## Безопасность
|
||
|
||
- **Пароли:** хэширование Argon2 (никогда не логируется и не раскрывается)
|
||
- **JWT access-токен:** HS256, TTL по умолчанию 15 минут (`ACCESS_TOKEN_TTL_MINUTES`), передаётся в заголовке `Authorization: Bearer`
|
||
- **Refresh-токен:** httpOnly SameSite=Strict cookie, TTL по умолчанию 14 дней; отзывается при logout
|
||
- **LiveKit токены:** ограничены по времени (TTL 6 часов), выдаются на конкретную комнату
|
||
- **HTTPS/TLS:** терминируется на Nginx; dev-окружение использует HTTP
|
||
- **CSRF:** митигируется `SameSite=Strict` на cookie с refresh-токеном (не отправляется с cross-site запросов); access-токен передаётся в заголовке, а не в cookie, и потому не подвержен CSRF
|
||
- **SQL injection:** SQLAlchemy ORM с параметризованными запросами
|
||
- **Rate limiting:** Redis-based, публичные эндпоинты (`/resolve`, `/guest-join`) — 10 запросов/мин на IP
|
||
|
||
---
|
||
|
||
## ADR (Записи архитектурных решений)
|
||
|
||
Подробные обоснования см. в папке [adr/](./adr/):
|
||
- [001-dynamic-conferences-pivot.md](./adr/001-dynamic-conferences-pivot.md) — динамические конференции вместо бронирований
|
||
- [002-phrase-attribution-session-participant.md](./adr/002-phrase-attribution-session-participant.md) — атрибуция фраз и треков к участнику сеанса
|
||
- [003-conference-invitees.md](./adr/003-conference-invitees.md) — приглашённые на конференцию
|
||
- [004-ai-tier-matrix.md](./adr/004-ai-tier-matrix.md) — матрица уровней AI
|
||
- [005-password-reset-deferred.md](./adr/005-password-reset-deferred.md) — сброс пароля по email отложен
|
||
- [000-template.md](./adr/000-template.md) — шаблон ADR
|
||
|
||
---
|
||
|
||
## Производительность и масштабируемость
|
||
|
||
### Уровни AI качества (ADR-004)
|
||
|
||
**Пресет 1–2 (без AI):**
|
||
- 10–20 одновременных конференций
|
||
- 4 vCPU, 8 GB RAM, 40 GB диск
|
||
|
||
**Пресет 3: AI min** (faster-whisper small + Qwen3.5-4B)
|
||
- 10–20 одновременных конференций
|
||
- 8 vCPU, 16 GB RAM, 100 GB диск
|
||
- Транскрибация: ~2x realtime (10мин → 5мин на 8-ядерном CPU)
|
||
|
||
**Пресет 4: AI medium** (faster-whisper medium + Qwen3.5-9B; GPU опционально)
|
||
- 15–30 одновременных конференций (CPU) или 30–50 (GPU)
|
||
- 12–16 vCPU, 32 GB RAM, 150 GB диск
|
||
- CPU: ~3x realtime; GPU (≥8 GB): ~1.5x realtime
|
||
|
||
**Пресет 5: AI max** (faster-whisper large-v3 + Qwen3.5-35B-A3B; GPU обязателен)
|
||
- 50–100+ одновременных конференций
|
||
- 16+ vCPU, 64 GB RAM, 250 GB диск, GPU ≥16 GB VRAM
|
||
- Транскрибация: ~0.5x realtime (10мин → 20сек на V100)
|
||
|
||
См. [docs/deploy/hardware-profiles.md](../deploy/hardware-profiles.md) и [ADR-004](./adr/004-ai-tier-matrix.md).
|
||
|
||
---
|
||
|
||
## Развёртывание
|
||
|
||
### Установка
|
||
|
||
```bash
|
||
./install.sh # интерактивный опросник (автодетект железа)
|
||
./install.sh --preset 3 # неинтерактивно (пресет 3 = AI min)
|
||
```
|
||
|
||
Инсталлятор автоматически:
|
||
1. Детектирует CPU/RAM/GPU
|
||
2. Рекомендует пресет
|
||
3. Генерирует `.env` с секретами
|
||
4. Запускает `docker compose` с нужными профилями
|
||
5. Выполняет миграции и seed
|
||
|
||
### Локальная разработка (вручную)
|
||
```bash
|
||
docker compose -f deploy/docker-compose.yml --env-file .env up -d # пресет 1 (без AI)
|
||
docker compose -f deploy/docker-compose.yml --env-file .env \
|
||
--profile media --profile transcribe --profile llm up -d # пресет 3 (с AI min)
|
||
```
|
||
|
||
### Продакшен
|
||
- Docker Compose на одном хосте (текущий целевой сценарий)
|
||
- Nginx для TLS + статические файлы
|
||
- Переменные окружения для секретов (`.env`)
|
||
- Резервные копии БД (PostgreSQL dump)
|
||
- Мониторинг (Prometheus + Grafana, `--profile monitoring`)
|
||
|
||
См. [docs/deploy/dev-setup.md](../deploy/dev-setup.md).
|
||
|
||
---
|
||
|
||
## Мониторинг и наблюдаемость
|
||
|
||
- **Логи:** Docker-логи + логирование приложений
|
||
- **Метрики:** `GET /metrics` — `vidconf_http_request_duration_seconds`, `vidconf_pipeline_sessions`, `vidconf_celery_queue_depth` (см. [workers/README.md](../../workers/README.md#метрики))
|
||
- **Визуализация:** Grafana (compose-профиль `monitoring`)
|
||
- **БД:** журнал медленных запросов PostgreSQL
|
||
|
||
См. [docs/deploy/monitoring.md](../deploy/monitoring.md).
|
||
|
||
---
|
||
|
||
## Стратегия тестирования
|
||
|
||
- **Backend:** pytest (unit + интеграционные тесты)
|
||
- **Frontend:** ESLint + TypeScript (`tsc -b`); автоматических unit/E2E тестов пока нет
|
||
- **БД:** проверка версии Alembic (`alembic current`), автогенерация миграций
|
||
|
||
---
|
||
|
||
## Ссылки
|
||
|
||
- [Корневой README](../../README.md)
|
||
- [Схема БД](../db/schema.md)
|
||
- [Справка API](../api/README.md)
|
||
- [Архитектура плагинов](../plugins/contracts.md)
|
||
- [Профили развёртывания](../deploy/hardware-profiles.md)
|
||
- [Backend README](../../backend/README.md)
|
||
- [Frontend README](../../frontend/README.md)
|
||
- [Workers README](../../workers/README.md)
|