Files
EventHubSpec/EventHubBackSpec.md
2026-04-25 19:39:46 +03:00

23 KiB
Raw Blame History

ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
Версия: 1.2 (расширенная гибридная модель + ролевая модель администрирования)

  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)

%% Событие (может быть мастером или экземпляром)
-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, причина)

2.8. Баг-трекер (автоматический)

  • При ошибке создаётся тикет, группировка по хэшу ошибки
  • Учёт количества повторений
  • Уведомление пользователя при создании и при закрытии тикета
  • Доступ только у администраторов

2.9. Платная подписка

  • Личное использование бесплатно (personal)
  • Коммерческое использование платно (commercial)
  • Пробный период 30 дней
  • Планы подписки: 1, 3, 6, 12 месяцев
  • Заглушка платежного шлюза (для тестирования)
  • Автоматическое истечение подписки

2.10. Административная панель

  • Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446)
  • Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами
  • Статистика, информация о нодах кластера
  • WebSocket-уведомления администраторам

2.10.1. Ролевая модель администраторов

Вводится три встроенные роли администраторов с различным объёмом прав:

Роль Идентификатор Права
Суперадмин superadmin Полный доступ ко всем модулям, управление ролями других админов, просмотр аудита, системные метрики.
Модератор moderator Модерация контента (события, жалобы, отзывы), блокировка/разблокировка пользователей, работа с баг-трекером.
Поддержка support Read-only доступ к пользователям и событиям, обработка жалоб, создание и обновление багов.

Примечание: На этапе MVP поддерживается только роль admin (эквивалент superadmin). Полноценная ролевая модель будет внедрена в ближайших итерациях.

2.10.2. Эндпоинты для управления ролями и аудитом

  • GET /admin/me (или GET /api/auth/me) — получение текущей роли и разрешений администратора.
  • GET /admin/admins — список всех администраторов с ролями (только для superadmin).
  • PUT /admin/admins/:id — изменение роли администратора (только для superadmin).
  • POST /admin/admins — приглашение нового администратора с назначением роли (только для superadmin).
  • GET /admin/audit — журнал действий администраторов с фильтрацией по дате, пользователю, действию (только для superadmin).

2.10.3. Статистика для дашборда с учётом ролей

  • superadmin — системные метрики: все пользователи, события, жалобы, баги за период, графики регистраций/событий по дням, активность администраторов.
  • moderator — собственные обработанные жалобы/события (количество, статусы, время реакции), общая статистика по модерации.
  • support — количество открытых багов и жалоб, назначенных на текущего сотрудника, персональные задачи.

Эндпоинт: GET /admin/stats (или /api/stats с авторизацией). Возвращает JSON, содержимое которого фильтруется в соответствии с ролью вызывающего.

2.11. Real-time уведомления (WebSocket)

  • Пользователи: подписка на календарь, получение обновлений
  • Администраторы: подписка на глобальные уведомления (жалобы, авто-заморозки)

3.1. Производительность и масштабирование

  • 100 000+ пользователей
  • Горизонтальное масштабирование (увеличение нод)
  • Mnesia с дисковыми копиями на нескольких нодах
  • Отдельные ноды для исторических данных (disc_only_copies)
  • Пагинация всех списков

3.2. Надёжность

  • Супервизорное дерево OTP
  • Let it crash быстрый перезапуск процессов
  • Автоматическое восстановление после падения ноды (Mnesia)

3.3. Безопасность

  • JWT с ролью (user, admin; в будущем — superadmin, moderator, support)
  • HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod)
  • Argon2 для хеширования паролей (erlang-argon2)
  • OAuth2 для Google (опционально)
  • Refresh token (запланирован)
  • При блокировке пользователя или отклонении сущности обязательно сохранять причину (поле reason)

3.4. Наблюдаемость

  • Healthcheck эндпоинт (GET /health)
  • Логирование (JSON, ротация)
  • Prometheus метрики (запланированы на уровне приложения, в настоящее время доступны через балансировщик)
  • Аудит действий администраторов (обязателен, см. п. 2.10.2)

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 (реализовано)
  • Хеширование паролей: erlang-argon2 1.0.0
  • Тестирование: meck 0.9.2, gun 2.0.0
  • Нагрузочное тестирование: Tsung 1.8.0
  • CI/CD: Drone 2.0+
  • Контейнеризация: Docker 20.10+
  • База данных: Mnesia (встроенная, распределённая)

5. ИЕРАРХИЧЕСКАЯ СТРУКТУРА КОДА

src/
├── infra/ # инфраструктура (приложение, супервизоры, аутентификация, подписки)
├── core/ # слой доступа к данным (DAO) и модели
├── logic/ # бизнес-логика (независимая от HTTP/WS)
├── services/ # внешние сервисы (заглушки/интеграции)
└── handlers/ # обработчики HTTP/WebSocket

Правила:

  • Модули handlers/ не содержат бизнес-логики.
  • Модули logic/ не знают о HTTP/WS.
  • Модули core/ (DAO) работают только с Mnesia.
  • Модули services/ могут быть заменены на реальные реализации без изменения остального кода.
  • Заголовочный файл include/records.hrl содержит все записи таблиц Mnesia.

6. ОСНОВНЫЕ API (КРАТКО)

Пользовательские (порт 8080)

  • POST /v1/register
  • POST /v1/login
  • GET /v1/user/me
  • POST /v1/calendars + CRUD
  • POST /v1/calendars/:id/events + CRUD
  • POST /v1/events/:id/join
  • POST /v1/events/:id/confirm/:user_id
  • POST /v1/reviews
  • POST /v1/reports
  • POST /v1/subscription/activate
  • POST /v1/events/:id/export (Google)
  • GET /v1/events/:id/ical (Apple)
  • GET /health

WebSocket (порт 8081): ws://localhost:8081/ws?token=...

Административные (порт 8445)

  • GET /admin/stats/overview (или /admin/stats) — статистика с учётом роли
  • GET /admin/nodes
  • GET /admin/calendars, PUT /admin/calendars/:id/freeze, /unfreeze
  • GET /admin/events, PUT /admin/events/:id/freeze, PATCH /admin/events/:id/approve (модерация)
  • GET /admin/reviews, PUT /admin/reviews/:id/hide
  • GET /admin/reports (complaints), PUT /admin/reports/:id/status
  • GET /admin/tickets (bugs), POST /admin/tickets, PUT /admin/tickets/:id/status, DELETE /admin/tickets/:id
  • GET /admin/banned_words, POST /admin/banned_words, DELETE /admin/banned_words
  • GET /admin/admins (только superadmin)
  • PUT /admin/admins/:id (только superadmin)
  • POST /admin/admins (только superadmin)
  • GET /admin/audit (только superadmin)
  • GET /admin/me (текущий админ, его роль и права)

Административный WebSocket (порт 8446): wss://localhost:8446/admin/ws?token=...

7. ВЕРСИОНИРОВАНИЕ И СТАТУС

Текущая версия: 1.2 (MVP, альфа, включает гибридную модель повторяющихся событий и базовую ролевую модель администрирования).

8. ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ

  • В разработке используются самоподписанные SSL-сертификаты.
  • Для production требуется реальный сертификат и настройка OAuth2 для Google.
  • Заглушки внешних сервисов должны быть заменены перед запуском.
  • Полноценное разграничение прав администраторов (superadmin, moderator, support) ожидается в следующем релизе; пока используется универсальная роль admin.

9. ТРЕБОВАНИЯ К ОКРУЖЕНИЮ

  • Erlang/OTP 28.2
  • rebar3 3.27.0
  • openssl
  • Docker (опционально)
  • Drone (опционально, для CI/CD)