23 KiB
ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
Версия: 1.2 (расширенная гибридная модель + ролевая модель администрирования)
-
ЦЕЛИ И НАЗНАЧЕНИЕ EventHub — платформа для управления событиями с поддержкой календарей, записи участников (включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, встроенного баг-трекера и платной подписки. Целевая аудитория: владельцы календарей (бизнес), участники (клиенты), администраторы.
-
ФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
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=...) система:
- Выбирает все одиночные события (event_type = single), попадающие в интервал.
- Выбирает все активные мастер-записи (event_type = recurring), у которых:
- дата первого вхождения ≤ конец интервала,
- правило повторения допускает вхождения внутри интервала (учёт until или ограничения по количеству повторений).
- Для каждой мастер-записи генерирует список вхождений в пределах запрошенного интервала (используя встроенную функцию разбора RRULE или собственный алгоритм).
- Из сгенерированного списка исключаются вхождения, присутствующие в таблице recurrence_exceptions с действием cancel.
- Если для какого-либо вхождения уже существует материализованная запись в таблице events (по совпадению master_id и start_time), то используются данные материализованного экземпляра (специалист, длительность, статус) вместо вычисленных по шаблону.
- Итоговый список сортируется по времени начала и возвращается с пагинацией.
Ограничение: глубина генерации вхождений для одного запроса ограничена 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/registerPOST /v1/loginGET /v1/user/mePOST /v1/calendars+ CRUDPOST /v1/calendars/:id/events+ CRUDPOST /v1/events/:id/joinPOST /v1/events/:id/confirm/:user_idPOST /v1/reviewsPOST /v1/reportsPOST /v1/subscription/activatePOST /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/nodesGET /admin/calendars,PUT /admin/calendars/:id/freeze,/unfreezeGET /admin/events,PUT /admin/events/:id/freeze,PATCH /admin/events/:id/approve(модерация)GET /admin/reviews,PUT /admin/reviews/:id/hideGET /admin/reports(complaints),PUT /admin/reports/:id/statusGET /admin/tickets(bugs),POST /admin/tickets,PUT /admin/tickets/:id/status,DELETE /admin/tickets/:idGET /admin/banned_words,POST /admin/banned_words,DELETE /admin/banned_wordsGET /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)