325 lines
27 KiB
Markdown
325 lines
27 KiB
Markdown
ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
|
||
Версия: 1.4 (актуальная реализация: раздельная JWT-аутентификация, версионированное админ-API, расширенная инфраструктура, аудит администраторов, расширенная статистика дашборда, улучшенная обработка ошибок)
|
||
|
||
## 1. ЦЕЛИ И НАЗНАЧЕНИЕ
|
||
EventHub — платформа для управления событиями с поддержкой календарей, записи участников (включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, встроенного баг-трекера и платной подписки.
|
||
|
||
Целевая аудитория: владельцы календарей (бизнес), участники (клиенты), администраторы.
|
||
|
||
## 2. ФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
|
||
|
||
### 2.1. Календари
|
||
- CRUD календаря (название, описание, теги, владелец)
|
||
- Расшаривание по ссылке (публичная/приватная)
|
||
- Приглашение пользователей с правами "запись" или "администрирование"
|
||
- Типы календарей: personal (бесплатный, без записи), commercial (платный, запись клиентов, специалисты)
|
||
- Гибкое подтверждение заявок: auto (автоматически), manual (вручную), timeout (авто через N секунд)
|
||
- Теги календаря, рейтинг (средняя оценка, количество голосов)
|
||
|
||
### 2.2. События (расширенная версия с повторяющимися событиями)
|
||
|
||
#### 2.2.1. Типы событий и модель хранения
|
||
События делятся на два типа:
|
||
- single — одиночное событие, имеет конкретные дату и время начала, длительность.
|
||
- recurring — повторяющееся событие (серия), определяется мастер-записью и правилом повторения.
|
||
|
||
Для эффективного хранения и быстрого поиска применяется гибридная модель с отложенной материализацией вхождений:
|
||
- Каждая повторяющаяся серия представлена одной мастер-записью в таблице events с полем event_type = recurring и полем recurrence_rule, содержащим правило повторения в формате, совместимом с iCalendar RRULE (или упрощённый JSON-представление: freq, interval, byday, until и т.д.).
|
||
- Конкретные вхождения (экземпляры) серии материализуются в таблице events только при необходимости:
|
||
* когда на данное вхождение записался хотя бы один участник;
|
||
* когда администратор вручную редактирует это вхождение (перенос времени, отмена, смена специалиста);
|
||
* (опционально) при наступлении даты вхождения для архивных целей.
|
||
- Материализованное вхождение имеет:
|
||
* event_type = single (или специальный флаг is_instance = true),
|
||
* ссылку master_id на родительскую мастер-запись,
|
||
* собственные start_time и duration, которые могут отличаться от вычисленных по правилу (например, при переносе конкретного дня).
|
||
- Отменённые вхождения из серии (исключения) хранятся в отдельной таблице recurrence_exceptions с указанием master_id, original_start_time и action = cancel | reschedule.
|
||
|
||
#### 2.2.2. Правила генерации вхождений при поиске
|
||
При поиске событий на заданный интервал дат (например, /v1/events?from=...&to=...) система:
|
||
1. Выбирает все одиночные события (event_type = single), попадающие в интервал.
|
||
2. Выбирает все активные мастер-записи (event_type = recurring), у которых:
|
||
- дата первого вхождения ≤ конец интервала,
|
||
- правило повторения допускает вхождения внутри интервала (учёт until или ограничения по количеству повторений).
|
||
3. Для каждой мастер-записи генерирует список вхождений в пределах запрошенного интервала (используя встроенную функцию разбора RRULE или собственный алгоритм).
|
||
4. Из сгенерированного списка исключаются вхождения, присутствующие в таблице recurrence_exceptions с действием cancel.
|
||
5. Если для какого-либо вхождения уже существует материализованная запись в таблице events (по совпадению master_id и start_time), то используются данные материализованного экземпляра (специалист, длительность, статус) вместо вычисленных по шаблону.
|
||
6. Итоговый список сортируется по времени начала и возвращается с пагинацией.
|
||
|
||
Ограничение: глубина генерации вхождений для одного запроса ограничена 1000 элементов; для длительных серий применяется пагинация по датам.
|
||
|
||
#### 2.2.3. Материализация при записи участника
|
||
При попытке записаться на вхождение повторяющегося события (POST /v1/events/:event_id/join):
|
||
- Если event_id ссылается на мастер-запись (event_type = recurring), то в теле запроса обязательно передаётся occurrence_start — конкретное время вхождения, на которое производится запись.
|
||
- Система в одной транзакции Mnesia:
|
||
* Проверяет, существует ли материализованное вхождение с master_id = EventId и start_time = OccurrenceStart.
|
||
* Если нет — создаёт новую запись в таблице events (тип single, master_id = EventId), копируя общие атрибуты мастер-записи (название, описание, календарь, специалист).
|
||
* Создаёт бронирование (booking), привязанное к идентификатору материализованного вхождения.
|
||
- Дальнейшие действия по подтверждению заявки выполняются уже с материализованным экземпляром.
|
||
|
||
#### 2.2.4. Изменение и удаление серий
|
||
- При редактировании мастер-записи (изменение названия, описания, правила повторения) обновляется только сама мастер-запись. Уже существующие материализованные вхождения остаются неизменными (сохраняют старые значения атрибутов). Это поведение может быть изменено администратором через специальный флаг «применить ко всем будущим вхождениям».
|
||
- При удалении мастер-записи все связанные материализованные вхождения также помечаются удалёнными (или удаляются каскадно), а бронирования на будущие вхождения аннулируются с уведомлением участников.
|
||
|
||
#### 2.2.5. Структура записей (records.hrl)
|
||
```erlang
|
||
-record(event, {
|
||
id :: binary(),
|
||
calendar_id :: binary(),
|
||
title :: binary(),
|
||
description :: binary(),
|
||
event_type :: single | recurring,
|
||
start_time :: calendar:datetime(),
|
||
duration :: integer(), %% минуты
|
||
recurrence_rule :: binary() | undefined,
|
||
master_id :: binary() | undefined,
|
||
is_instance :: boolean(), %% true для материализованных вхождений
|
||
specialist_id :: binary() | undefined,
|
||
location :: #location{} | undefined,
|
||
tags :: [binary()],
|
||
status :: active | cancelled | completed,
|
||
created_at :: calendar:datetime(),
|
||
updated_at :: calendar:datetime()
|
||
}).
|
||
|
||
-record(recurrence_exception, {
|
||
master_id :: binary(),
|
||
original_start :: calendar:datetime(),
|
||
action :: cancel | reschedule,
|
||
new_start :: calendar:datetime() | undefined
|
||
}).
|
||
```
|
||
#### 2.2.6. Требования к реализации
|
||
- Разбор и генерация RRULE должны быть реализованы в отдельном модуле logic_recurrence без внешних зависимостей (чистый Erlang).
|
||
- Все операции с материализацией и записью участников должны выполняться в транзакциях Mnesia для обеспечения консистентности.
|
||
- Для ускорения поиска материализованных вхождений по мастеру и дате создаётся составной индекс в Mnesia: {master_id, start_time} (через mnesia:add_table_index/2 или хранение в ets с ключом {master_id, start_time}).
|
||
|
||
### 2.3. Запись участников и подтверждение
|
||
- Запись через календарь
|
||
- Подтверждение согласно политике календаря
|
||
- Уведомления участника и владельца
|
||
- Привязка события к специалисту (отдельная запись в календаре специалиста)
|
||
|
||
### 2.4. Отзывы и рейтинги
|
||
- Только участники событий могут оставлять отзывы
|
||
- Оценка 1-5, текстовый комментарий
|
||
- Отзывы на событие или на календарь
|
||
- Модерация отзывов (администраторы могут скрывать)
|
||
|
||
### 2.5. Поиск и фильтрация
|
||
- По тексту, по тегам, по гео-позиции (радиус), по дате/времени
|
||
|
||
### 2.6. Расширенные возможности
|
||
- Экспорт в Google Calendar (через OAuth2)
|
||
- Экспорт в Apple Calendar (ICS-файл)
|
||
- Геокодирование (адрес -> координаты) через внешний API (заглушка / Nominatim)
|
||
|
||
### 2.7. Модерация и безопасность
|
||
- Жалобы на календари, события, отзывы
|
||
- Автоматическая модерация по ключевым словам и по порогу жалоб
|
||
- Ручная заморозка / разморозка администратором
|
||
- Бан-лист слов
|
||
- **Аудит действий администраторов:** Ведётся детальный журнал действий всех администраторов (кто, что, когда, IP, причина). Модель аудита (admin_audit) включает:
|
||
- admin_id — идентификатор администратора,
|
||
- email — email администратора,
|
||
- role — роль (superadmin, moderator, support),
|
||
- action — действие (например, add_banned_word, delete_banned_word, update_role),
|
||
- entity_type — тип сущности (banned_word, admin и т.д.),
|
||
- entity_id — идентификатор сущности,
|
||
- ip — IP-адрес,
|
||
- reason — причина действия (опционально),
|
||
- timestamp — время действия.
|
||
|
||
### 2.8. Баг-трекер (автоматический)
|
||
- При ошибке создаётся тикет, группировка по хэшу ошибки
|
||
- Учёт количества повторений
|
||
- Уведомление пользователя при создании и при закрытии тикета
|
||
- Доступ только у администраторов
|
||
|
||
### 2.9. Платная подписка
|
||
- Личное использование бесплатно (personal), коммерческое — платно (commercial)
|
||
- Пробный период 30 дней
|
||
- Планы подписки: monthly, quarterly, biannual, annual
|
||
- Заглушка платежного шлюза (для тестирования)
|
||
- Автоматическое истечение подписки
|
||
- Автоматическое понижение календарей до personal при истечении
|
||
|
||
### 2.10. Административная панель
|
||
- Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446)
|
||
- Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами
|
||
- Статистика, информация о нодах кластера
|
||
- WebSocket-уведомления администраторам
|
||
|
||
#### 2.10.1. Ролевая модель администраторов (реализована)
|
||
Реализована трехуровневая ролевая модель: superadmin, moderator, support. Проверка ролей выполняется в каждом административном обработчике через handler_auth:authenticate/1 и вспомогательную функцию is_admin/1.
|
||
|
||
#### 2.10.2. Эндпоинты для управления ролями и аудитом
|
||
- GET /v1/admin/me — получение текущей роли и разрешений администратора.
|
||
- GET /v1/admin/admins — список всех администраторов с ролями (только для superadmin).
|
||
- PUT /v1/admin/admins/:id — изменение роли администратора (только для superadmin).
|
||
- POST /v1/admin/admins — приглашение нового администратора с назначением роли (только для superadmin).
|
||
- GET /v1/admin/audit — журнал действий администраторов с фильтрацией по дате, пользователю, действию (только для superadmin).
|
||
|
||
#### 2.10.3. Статистика для дашборда с учётом ролей
|
||
Эндпоинт: GET /v1/admin/stats. Возвращает JSON, содержимое которого фильтруется в соответствии с ролью вызывающего:
|
||
- superadmin — системные метрики: все пользователи, события, жалобы, баги за период, графики регистраций/событий по дням, активность администраторов.
|
||
- moderator — собственные обработанные жалобы/события (количество, статусы, время реакции), общая статистика по модерации.
|
||
- support — количество открытых багов и жалоб, назначенных на текущего сотрудника, персональные задачи.
|
||
|
||
**Расширенная статистика (дополнение):**
|
||
- GET /v1/admin/statistics — агрегированная статистика по администраторам: общее количество действий за период, распределение по типам действий.
|
||
- Поддержка фильтрации: admin_id, action, date_from, date_to.
|
||
|
||
### 2.11. Real-time уведомления (WebSocket)
|
||
- Пользователи: порт 8081, маршрут /ws, подписка на календарь, получение обновлений
|
||
- Администраторы: порт 8446, маршрут /admin/ws, подписка на глобальные уведомления (жалобы, авто-заморозки)
|
||
|
||
### 2.12. Инфраструктура развертывания (Docker Compose)
|
||
- Балансировщик Traefik с поддержкой HTTPS/WSS, WAF (Coraza), Rate Limiting и Failover
|
||
- Кластер из трех нод Erlang с автоматическим обнаружением (DNS-лукап)
|
||
- Мониторинг: Prometheus (метрики), Grafana (дашборды), LogLynx (аналитика логов)
|
||
- Ротация логов (logrotate)
|
||
- Административный SPA (EventHubFrontAdmin) как отдельный сервис
|
||
- Сервис-заглушка (Fallback) для отказоустойчивости
|
||
|
||
## 3. НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
|
||
|
||
### 3.1. Производительность и масштабирование
|
||
- 100 000+ пользователей
|
||
- Горизонтальное масштабирование (увеличение нод)
|
||
- Mnesia с дисковыми копиями на нескольких нодах
|
||
- Отдельные ноды для исторических данных (disc_only_copies)
|
||
- Пагинация всех списков
|
||
- Автоматическое обнаружение узлов через DNS-лукап (libcluster в перспективе)
|
||
|
||
### 3.2. Надёжность
|
||
- Супервизорное дерево OTP
|
||
- Let it crash – быстрый перезапуск процессов
|
||
- Автоматическое восстановление после падения ноды (Mnesia)
|
||
- **Улучшенная обработка ошибок:**
|
||
- Валидация входных данных на всех критических эндпоинтах административного API.
|
||
- Единый формат ошибок: {"error": "код", "message": "описание"}.
|
||
|
||
### 3.3. Безопасность
|
||
- Раздельная JWT-аутентификация:
|
||
- Пользовательские токены: секрет JWT_SECRET, audience = <<"user">>, эндпоинт /v1/login
|
||
- Административные токены: секрет ADMIN_JWT_SECRET, audience = <<"admin">>, эндпоинт /v1/admin/login
|
||
- Проверка ролей: superadmin, moderator, support
|
||
- HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod)
|
||
- Argon2 для хеширования паролей (erlang-argon2)
|
||
- Refresh token реализован (хранение в Mnesia, сессии)
|
||
- OAuth2 для Google (опционально)
|
||
- При блокировке пользователя или отклонении сущности обязательно сохранять причину (поле reason)
|
||
- **CORS:** Все ответы API включают заголовки:
|
||
- access-control-allow-origin: *
|
||
- access-control-expose-headers: Content-Range
|
||
|
||
### 3.4. Наблюдаемость
|
||
- Healthcheck эндпоинт (GET /health)
|
||
- Логирование (JSON, ротация)
|
||
- Prometheus метрики (реализованы на уровне приложения через prometheus_cowboy)
|
||
- Аудит действий администраторов (запись admin_audit)
|
||
|
||
### 3.5. CI/CD
|
||
- Drone CI (или GitLab CI / GitHub Actions)
|
||
- EUnit + Common Test + Tsung
|
||
- Сборка релиза
|
||
- Docker-образ
|
||
- Горячее обновление (rolling upgrade) кластера (3+ нод)
|
||
- Миграции Mnesia
|
||
|
||
## 4. СТЕК ТЕХНОЛОГИЙ (С ВЕРСИЯМИ)
|
||
- Бэкенд: Erlang/OTP 28.2
|
||
- Сборка: rebar3 3.27.0
|
||
- HTTP/WebSocket: Cowboy 2.10.0
|
||
- JSON: jsx 3.1.0
|
||
- JWT: jose 1.11.10
|
||
- Хеширование паролей: erlang-argon2 1.2.0
|
||
- Тестирование: meck 0.9.2, gun 2.0.0
|
||
- Мониторинг: prometheus_cowboy 0.2.0
|
||
- Нагрузочное тестирование: Tsung 1.8.0
|
||
- CI/CD: Drone 2.0+
|
||
- Контейнеризация: Docker 20.10+
|
||
- База данных: Mnesia (встроенная, распределённая)
|
||
|
||
## 5. ИЕРАРХИЧЕСКАЯ СТРУКТУРА КОДА
|
||
```
|
||
src/
|
||
test/
|
||
├── infra/ ├── unit/ # EUnit тесты
|
||
│ ├── eventhub_auth.erl # JWT └── api/ # Common Test интеграционные тесты
|
||
│ └── ...
|
||
├── core/ # слой доступа к данным (DAO)
|
||
├── logic/ # бизнес-логика
|
||
├── services/ # внешние сервисы
|
||
├── handlers/ # обработчики HTTP/WebSocket
|
||
│ ├── admin/ # административные обработчики
|
||
│ └── ...
|
||
└── middlewares/ # промежуточные слои (CORS, аутентификация)
|
||
```
|
||
**Правила:**
|
||
- Модули handlers/ не содержат бизнес-логики.
|
||
- Модули logic/ не знают о HTTP/WS.
|
||
- Модули core/ (DAO) работают только с Mnesia.
|
||
- Модуль infra/eventhub_auth.erl реализует раздельную JWT-аутентификацию (пользователи и администраторы).
|
||
- Модули services/ могут быть заменены на реальные реализации без изменения остального кода.
|
||
- Заголовочный файл include/records.hrl содержит все записи таблиц Mnesia.
|
||
|
||
## 6. ОСНОВНЫЕ API (КРАТКО)
|
||
|
||
### Пользовательские (порт 8080)
|
||
- **Публичные:** GET /health, POST /v1/register, POST /v1/login, POST /v1/refresh
|
||
- **Авторизованные:** GET /v1/user/me, GET /v1/user/bookings, GET /v1/user/reviews, GET /v1/search
|
||
- **Календари:** POST /v1/calendars + CRUD, GET /v1/calendars/:id, PUT /v1/calendars/:id, DELETE /v1/calendars/:id
|
||
- **События:** POST /v1/calendars/:calendar_id/events, GET /v1/events/:id, PUT /v1/events/:id, DELETE /v1/events/:id, GET /v1/events/:id/occurrences, POST /v1/events/:id/join, POST /v1/events/:id/confirm/:user_id, GET /v1/events/:id/bookings
|
||
- **Бронирования:** GET /v1/bookings/:id, PUT /v1/bookings/:id, DELETE /v1/bookings/:id
|
||
- **Отзывы:** POST /v1/reviews, GET /v1/reviews/:id, PUT /v1/reviews/:id, DELETE /v1/reviews/:id
|
||
- **Жалобы:** POST /v1/reports
|
||
- **Тикеты:** POST /v1/tickets, GET /v1/tickets, GET /v1/tickets/:id, PUT /v1/tickets/:id, DELETE /v1/tickets/:id
|
||
- **Подписки:** GET /v1/subscription, POST /v1/subscription
|
||
- **Метрики:** GET /metrics/[:registry]
|
||
|
||
WebSocket (порт 8081): ws://localhost:8081/ws?token=
|
||
|
||
### Административные (порт 8445)
|
||
- **Версионирование:** Все административные эндпоинты имеют префикс /v1/admin/.
|
||
- **Базовые:** GET /v1/admin/health, GET /v1/admin/stats, POST /v1/admin/login
|
||
- **Пользователи:** GET /v1/admin/users, GET /v1/admin/users/:id, PUT /v1/admin/users/:id, DELETE /v1/admin/users/:id
|
||
- **Отчёты (жалобы):** GET /v1/admin/reports, GET /v1/admin/reports/:id, PUT /v1/admin/reports/:id
|
||
- **Отзывы:** GET /v1/admin/reviews/:id, PUT /v1/admin/reviews/:id (hide/show)
|
||
- **Бан-слова:** GET /v1/admin/banned-words, POST /v1/admin/banned-words, DELETE /v1/admin/banned-words/:word
|
||
- **Тикеты:** GET /v1/admin/tickets, POST /v1/admin/tickets, GET /v1/admin/tickets/:id, PUT /v1/admin/tickets/:id, DELETE /v1/admin/tickets/:id, GET /v1/admin/tickets/stats
|
||
- **Подписки:** GET /v1/admin/subscriptions, POST /v1/admin/subscriptions, GET /v1/admin/subscriptions/:id, PUT /v1/admin/subscriptions/:id, DELETE /v1/admin/subscriptions/:id
|
||
*- *Модерация:** PUT /v1/admin/:target_type/:id (target_type: calendar, event, review, user; action: freeze/unfreeze, hide/show, block/unblock)
|
||
|
||
Административный WebSocket (порт 8446): wss://localhost:8446/admin/ws?token=
|
||
|
||
## 6.1. Аутентификация и авторизация
|
||
Модуль infra/eventhub_auth.erl реализует раздельную JWT-аутентификацию:
|
||
- Пользовательские токены выпускаются через POST /v1/login с audience <<"user">> и секретом JWT_SECRET. Проверяются обработчиками через handler_auth:authenticate/1 → logic_auth:verify_jwt/1.
|
||
- Административные токены выпускаются через POST /v1/admin/login с audience <<"admin">> и секретом ADMIN_JWT_SECRET. Проверяются обработчиками через handler_auth:authenticate/1 с последующей проверкой роли is_admin/1.
|
||
Обработчики порта 8445 используют единый middleware handler_auth:authenticate/1, который извлекает токен из заголовка Authorization: Bearer ..., верифицирует его через logic_auth:verify_jwt/1 и возвращает {ok, UserId, Req} или {error, Code, Message, Req}.
|
||
|
||
## 7. ВЕРСИОНИРОВАНИЕ И СТАТУС
|
||
Текущая версия: 1.4 (MVP, альфа). Включает:
|
||
- Гибридную модель повторяющихся событий
|
||
- Раздельную JWT-аутентификацию (пользователи/администраторы)
|
||
- Полноценную ролевую модель администрирования
|
||
- Версионированное админ-API (/v1/admin/...)
|
||
- Расширенную инфраструктуру (Traefik, WAF, мониторинг, failover)
|
||
- Аудит действий администраторов
|
||
- Расширенную статистику для дашборда
|
||
- Улучшенную обработку ошибок и валидацию
|
||
|
||
## 8. ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ
|
||
- В разработке используются самоподписанные SSL-сертификаты.
|
||
- Для production требуется реальный сертификат и настройка OAuth2 для Google.
|
||
- Заглушки внешних сервисов должны быть заменены перед запуском.
|
||
- Автоматическое обнаружение узлов через libcluster находится в перспективе; текущая реализация использует статический JOIN_NODES с возможностью подключения через DNS-лукап.
|
||
|
||
## 9. ТРЕБОВАНИЯ К ОКРУЖЕНИЮ
|
||
- Erlang/OTP 28.2
|
||
- rebar3 3.27.0
|
||
- openssl
|
||
- Docker (опционально)
|
||
- Drone (опционально, для CI/CD) |