30 KiB
ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
Версия: 1.5 (актуальная реализация: выполнены задачи #12–#17)
1. ЦЕЛИ И НАЗНАЧЕНИЕ
EventHub — платформа для управления событиями с поддержкой календарей, записи участников (включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, встроенного баг-трекера и платной подписки.
Целевая аудитория: владельцы календарей (бизнес), участники (клиенты), администраторы.
2. ФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
2.1. Календари
- CRUD календаря (название, описание, теги, владелец)
- Расшаривание по ссылке (публичная/приватная)
- Приглашение пользователей с правами "запись" или "администрирование"
- Типы календарей: personal (бесплатный, без записи), commercial (платный, запись клиентов, специалисты)
- Гибкое подтверждение заявок: auto (автоматически), manual (вручную), timeout (авто через N секунд)
- Теги календаря, рейтинг (средняя оценка, количество голосов)
Новые поля (задача #12):
short_name— короткое уникальное имя для API и поискаcategory— категория (enum)color— цвет отображенияimage_url— изображение календаряsettings— дополнительные настройки (map)
2.1.1. Специалисты календаря (задача #12)
Реализована отдельная таблица calendar_specialist, связывающая пользователя (специалиста) с
календарём. Специалист может иметь отображаемое имя (name) и список специализаций
(specialization). Статус специалиста: active | inactive.
2.2. События (расширенная версия с повторяющимися событиями)
2.2.1. Типы событий и модель хранения
События могут быть одиночными (event_type = single) или повторяющимися (event_type = recurring).
Для повторяющихся событий:
- Мастер-событие содержит правило повторения (
recurrence_rule) и является шаблоном. - При создании повторяющегося события генерируются экземпляры (instances) на определённый
период (например, на месяц вперёд), которые хранятся как отдельные записи с полем
is_instance = trueи ссылкой на мастер (master_id). - При изменении мастера можно выбрать обновление всех будущих экземпляров или только мастер-записи.
- При удалении мастера удаляются все связанные экземпляры.
- Для поддержки исключений (отмена отдельного вхождения) используется таблица
recurrence_exception.
2.2.2. Правила генерации вхождений при поиске
При поиске событий на заданный диапазон дат система должна:
- Включать все одиночные события, попадающие в диапазон.
- Для повторяющихся событий генерировать виртуальные вхождения на основе
recurrence_rule, исключая те, что помечены как исключения. - Возвращать как одиночные, так и сгенерированные вхождения в едином списке.
2.2.3. Материализация при записи участника
При записи участника на конкретное вхождение повторяющегося события:
- Система материализует (создаёт) физическую запись события для этого вхождения, если оно ещё не было материализовано (например, для хранения количества записавшихся).
- Материализованное событие имеет
is_instance = trueи ссылается наmaster_id. - Запись участника (
booking) всегда привязывается к конкретному экземпляру (материализованному или одиночному событию).
2.2.4. Изменение и удаление серий
- При редактировании мастера можно применить изменения ко всем будущим экземплярам или создать новый мастер с отдельной серией.
- При удалении мастера удаляются все связанные экземпляры, если на них нет активных записей.
Если есть активные записи, мастер-событие помечается как
cancelled, а существующие записи остаются.
2.2.5. Структура записей (records.hrl)
Актуальная структура записей включает дополнительные поля, добавленные в рамках задачи #12:
event— добавленыattachments :: [binary()] | undefined,edit_history :: [map()] | undefinedbooking— добавленыnotes :: binary() | undefined,reminder_sent :: boolean()review— добавленыlikes :: non_neg_integer(),dislikes :: non_neg_integer(),edited_at :: calendar:datetime() | undefined
2.2.6. Требования к реализации
- Все операции с событиями должны быть транзакционными.
- Генерация вхождений должна быть эффективной (использовать
calendar:datetime_to_gregorian_secondsи кэширование). - При поиске событий для календаря учитывать права доступа пользователя.
2.3. Запись участников и подтверждение
- Пользователь может отправить заявку на участие в событии.
- В зависимости от
confirmationкалендаря заявка либо подтверждается автоматически, либо ожидает ручного подтверждения владельцем, либо подтверждается по таймауту. - Бронирование имеет статусы:
pending,confirmed,cancelled. - Пользователь может отменить свою запись.
- Владелец календаря может подтвердить или отклонить заявку.
- При подтверждении фиксируется время (
confirmed_at). - Вместимость события (
capacity) ограничивает количество подтверждённых записей.
2.4. Отзывы и рейтинги
- Пользователи могут оставлять отзывы (рейтинг 1–5 и комментарий) на события или календари.
- Отзыв можно редактировать (сохраняется
edited_at). - Отзывы могут быть скрыты модератором.
- При добавлении/изменении/удалении отзыва пересчитывается средний рейтинг события
(
rating_avg,rating_count). - Реализованы лайки/дизлайки отзывов (
likes,dislikes). - Возможность пожаловаться на отзыв (создание
report).
2.5. Поиск и фильтрация
- Полнотекстовый поиск по названиям событий, календарей, тегам.
- Фильтрация по дате, категории, местоположению, рейтингу.
- Пагинация результатов.
- Поиск должен учитывать права доступа (не показывать скрытые/заблокированные календари).
2.6. Расширенные возможности
- Локация события (
locationзапись с адресом, широтой, долготой). - Онлайн-ссылка (
online_link). - Вложения к событию (
attachments). - История изменений события (
edit_history). - Заметки пользователя к бронированию (
notes). - Напоминания о событии (поле
reminder_sentв бронировании, логика отправки не реализована).
2.7. Модерация и безопасность
- Пользователи могут отправлять жалобы (
report) на календари, события, отзывы. - Модераторы могут просматривать жалобы и принимать меры (скрывать контент, блокировать).
- Список запрещённых слов (
banned_word) для фильтрации контента. - Автомодерация (
logic_automoderation):- Настройки (
automod_settings, singleton):keyword_action(reject|flag|censor, defaultflag),match_mode(word_boundary|substring, defaultword_boundary),report_threshold(integer ≥ 1, default3). - При create/update calendar (
title,description), event (title,description), review (comment) выполняется scan бан-слов. reject— сохранение отклоняется (400, content banned);censor— маскирование***+ hit в очередь;flag— сохранение + soft-hide + hit (review→hidden,event/calendar→frozen, reasonauto:keyword).- Порог жалоб: считаются только
pending; при достижении порога — soft-hide (auto:report_threshold) + hit; dismissed/reviewed не учитываются. - Очередь
automod_hit:open|approved|rejected; approve восстанавливает сущность, reject оставляет/усиливает блокировку. - WS админам:
automod_hit; аудит от системного актораsystem.
- Настройки (
- Аудит действий администраторов (
admin_audit).
2.8. Баг-трекер (автоматический)
- Источники тикетов (
source):backend(HTTP 500 / внутренние ошибки),frontend(клиентский crash),manual(ручной репорт). - При HTTP 500 сервер асинхронно регистрирует тикет через
logic_ticket:report_error(ответы по путям/ticketsне репортятся — защита от рекурсии). - Клиент и ручной репорт:
POST /v1/ticketsсerror_message, опциональноstacktrace,context,source(frontend|manual). Ручнойmanual— сценарий end-user клиентов, не Admin SPA. - Тикет содержит
error_hash(sha256 от source + сообщение + fingerprint стека; дляmanual— source + user_id + сообщение), стектрейс, JSON-контекст, счётчик повторений. - Дедуп: открытый тикет (
open/in_progress) с тем жеerror_hash— увеличиваетсяcountиlast_seen; послеresolved/closedповтор создаёт новый тикет (регрессия). - Новые тикеты лимитируются (~30/мин на узел); повторы по hash не ограничиваются.
- При создании нового тикета администраторам уходит WS
ticket_created. - Администраторы могут просматривать тикеты (фильтр
source), назначать ответственных, менять статус.
2.9. Платная подписка
- Пользователи могут оформить подписку (
subscription) с разными планами: monthly, quarterly, biannual, annual. - Статус подписки:
active,expired,cancelled. - Отслеживание использования пробного периода (
trial_used).
2.10. Административная панель
- Управление пользователями (просмотр, блокировка, изменение ролей).
- Просмотр статистики платформы.
- Управление жалобами и тикетами.
- Управление подписками.
- Аудит действий администраторов.
2.10.1. Ролевая модель администраторов
superadmin— полный доступ, может управлять другими администраторами.admin— управление пользователями, контентом, подписками.moderator— модерация контента (жалобы, отзывы, очередь автомодерации).support— работа с тикетами.
2.10.2. Эндпоинты для управления ролями и аудитом
GET /v1/admin/me— информация о текущем администраторе.GET /v1/admin/admins— список администраторов (только для superadmin).POST /v1/admin/admins/:id— изменение роли администратора (superadmin).GET /v1/admin/audit— просмотр аудита.
2.10.3. Статистика для дашборда с учётом ролей
GET /v1/admin/stats— базовая статистика (количество пользователей, событий, бронирований).- В будущем планируется расширенная аналитика на основе данных из таблицы
stats.
2.11. Real-time уведомления (WebSocket)
- Пользовательский WebSocket (порт 8081): подписка на обновления событий календаря.
- Административный WebSocket (порт 8446): подписка на новые жалобы и тикеты.
- Создана таблица
notificationдля хранения уведомлений (тип, заголовок, тело, флаг прочитано). Полноценная логика доставки уведомлений будет реализована позже.
2.12. Инфраструктура развертывания (Docker Compose)
- Docker Swarm с тремя репликами
eventhub-node{1..3}. - Traefik в качестве reverse-прокси и балансировщика.
- Prometheus для сбора метрик, Grafana для визуализации.
- Observer Web для мониторинга Erlang-узлов.
- Автоматическая ротация логов.
3. НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
3.1. Производительность и масштабирование
- Поддержка 100 000+ пользователей.
- Горизонтальное масштабирование добавлением новых узлов.
- Все персистентные таблицы хранятся в
disc_copiesна каждом узле (задача #13). - Сессионные таблицы:
auth_session(единая модель refresh JWT) вdisc_copiesдля user и admin API. - Legacy-таблицы
sessionиadmin_session(ram_copies) сохранены в схеме, user/admin hot path используютauth_session. - Индексы созданы для часто запрашиваемых полей (calendar_id, start_time, event_type, status и др.) (задача #13).
- Полная репликация горячих таблиц между всеми узлами кластера (задача #14).
- Автоматическое обнаружение узлов через DNS-имя
eventhub-nodeили статический список (задача #14). - Периодическая очистка «мёртвых» узлов из схемы Mnesia (каждые 30 секунд) (задача #14).
- Архивирование исторических данных (старше 30 дней) в отдельные Mnesia-узлы с
disc_only_copies(задача #15). - Пагинация всех списков.
3.2. Надёжность
- Супервизорное дерево OTP.
- Философия "let it crash" — быстрый перезапуск упавших процессов.
- Автоматическое восстановление после падения ноды (Mnesia).
- Валидация входных данных и единый формат ошибок.
- Механизм миграций схемы данных: миграции в
src/migrations/, реестр вmigration_engine; при старте после init_tables вызываетсяensure_applied/0с глобальной блокировкой в Mnesia — один узел применяет pending, join-узлы ждут синхронизации; поддерживается откат (задачи #17, #24).
3.3. Безопасность
- JWT-аутентификация с разделением ролей (user, admin, superadmin, moderator, support).
EVENTHUB_ENV:dev— допускаются documented fallbacks;stage/prod— fail-fast при слабых или отсутствующихJWT_SECRET,ADMIN_JWT_SECRETи паролях seed-админов (#25).- Проверка прав доступа к календарям и событиям.
- Пароли хэшируются с использованием Argon2.
- Защита от несанкционированного просмотра архивных данных (только владелец календаря) (задача #15).
3.4. Наблюдаемость
- Логирование в JSON-формате.
- Экспорт метрик для Prometheus (HTTP-эндпоинт
/metrics). - Встроенный Observer Web для мониторинга Erlang-системы.
- Сбор статистики использования (события, бронирования, отзывы) через триггеры Mnesia с сохранением в таблице
stats(задача #16).
3.5. CI/CD
- Контейнеризация (Docker).
- Makefile для автоматизации задач.
- Возможность развёртывания в Kubernetes (в будущем).
4. СТЕК ТЕХНОЛОГИЙ (С ВЕРСИЯМИ)
- Erlang/OTP 28
- Mnesia (встроенная БД)
- Cowboy 2.12 (HTTP-сервер)
- JWT (jose 1.11.10)
- Prometheus (prometheus 4.11.0, prometheus_cowboy 2.1.0)
- Docker Compose v3.8
- Traefik v3.1
- Grafana 11.2
- Prometheus 2.55
- Observer Web
- Logrotate
5. ИЕРАРХИЧЕСКАЯ СТРУКТУРА КОДА
src/
├── core/ — бизнес-логика (core_user, core_event, core_booking, core_review, ...)
├── handlers/ — обработчики HTTP (handler_login, handler_calendar_view, ...)
├── infra/ — инфраструктура (infra_mnesia, infra_sup, cluster_discovery,
│ archive_manager, archive_controller, stats_collector,
│ migration_engine, ...)
├── archive/ — архивирование и рендеринг (archive_controller, archive_manager,
│ calendar_html_renderer, archive_fetcher)
├── migrations/ — файлы миграций
└── eventhub_app.erl — точка входа приложения
6. ОСНОВНЫЕ API (КРАТКО)
Пользовательские (порт 8080)
POST /v1/register— регистрация.POST /v1/login— вход.POST /v1/refresh— обновление токена.GET /v1/user/me— профиль пользователя.GET /v1/user/bookings— бронирования пользователя.GET /v1/user/reviews— отзывы пользователя.GET /v1/search— поиск.GET /v1/calendars— список календарей.GET /v1/calendars/:id— календарь.GET /v1/calendars/:calendar_id/events— события календаря.GET /v1/events/:id— событие.GET /v1/events/:id/occurrences— вхождения повторяющегося события.POST /v1/events/:id/bookings— запись на событие.GET /v1/bookings/:id— статус бронирования.POST /v1/reviews— создать отзыв.GET /v1/reviews— список отзывов.PUT /v1/reviews/:id— обновить отзыв.POST /v1/reports— пожаловаться (target_type:event|calendar|review).GET /v1/tickets— тикеты пользователя.POST /v1/tickets— создать или обновить тикет (дедуп поerror_hash): body{ error_message, stacktrace?, context?, source?: frontend|manual }; ответ 201 (или 429 при rate-limit новых).GET /v1/tickets/:id— статус тикета.GET /v1/subscription— подписка пользователя.GET /v1/calendars/:calendar_id/view?month=YYYY-MM— HTML-календарь (владелец), включая архив.
Административные (порт 8445)
GET /admin/healthиGET /v1/admin/health— состояние сервера + build identity:status,service,version(изVERSION/EVENTHUB_VERSION),git_sha,built_at./admin/health— CT/Traefik;/v1/admin/health— admin SPA.GET /v1/admin/stats— агрегированная статистика дашборда.POST /v1/admin/login— вход администратора.POST /v1/admin/refresh— обновление пары access/refresh JWT администратора.GET /v1/admin/users,GET /v1/admin/users/:id— пользователи.GET /v1/admin/reports,GET /v1/admin/reports/:id— жалобы.DELETE /v1/admin/reviews/:id— удалить отзыв.GET /v1/admin/banned-words,POST /v1/admin/banned-words— запрещённые слова.GET /v1/admin/automod/settings,PUT /v1/admin/automod/settings— настройки автомодерации (чтение: admin/moderator; запись: admin/superadmin).GET /v1/admin/automod/hits,PUT /v1/admin/automod/hits/:id— очередь срабатываний (status:approved|rejected; фильтрыstatus,trigger; доступ: admin/moderator).GET /v1/admin/tickets/stats— статистика тикетов:total_tickets,open,in_progress,resolved,closed,total_errors.GET /v1/admin/tickets— список с фильтрамиstatus,source,assigned_to,q;GET /v1/admin/tickets/:id— управление тикетами.GET /v1/admin/subscriptions,POST /v1/admin/subscriptions/:id— подписки.PUT /v1/admin/:target_type/:id— модерация.GET /v1/admin/me— профиль администратора.GET /v1/admin/admins,POST /v1/admin/admins/:id— управление администраторами.GET /v1/admin/audit— аудит.
6.1. Аутентификация и авторизация
Общие принципы
- ��ользователи и администраторы используют разные эндпоинты и JWT-секреты.
- Access-токен — короткоживущий stateless JWT (HS256), TTL 86400 с (24 ч) —
eventhub_auth:generate_token/4. - Refresh-токен — 30 дней; подписанный JWT в единой session-модели (
auth_session). - Все защищённые эндпоинты требуют заголовок
Authorization: Bearer <access_token>. - Source of truth:
EventHubSpec(этот документ); Swagger в репозитории — вторичная справка, синхронизируется по факту кода.
Единая session-модель (auth_session)
Таблица Mnesia auth_session (disc_copies):
| Поле | Описание |
|---|---|
session_id |
Первичный ключ сессии устройства |
family_id |
Семейство ротаций одной сессии |
subject_id |
ID пользователя или администратора |
subject_type |
user | admin |
client_type |
admin | web | mobile |
current_jti |
Актуальный jti refresh JWT |
expires_at |
Срок жизни сессии (30 дней) |
revoked |
Флаг отзыва |
Refresh JWT (admin) — claims:
typ=refresh,aud=admin,sub=<admin_id>sid=<session_id>,fid=<family_id>,jti=<current_jti>client=admin,exp,iat
Refresh JWT (user, фаза 2) — claims:
typ=refresh,aud=user,sub=<user_id>sid,fid,jti— как у adminclient=web(фаза 3:mobile),exp,iat- Подпись:
JWT_SECRET(user JWK), отдельно от admin
Поток login admin (POST /v1/admin/login):
- Проверка email/password.
- Создание записи
auth_session. - Ответ:
{ token, refresh_token, user }.
Поток refresh admin (POST /v1/admin/refresh):
- Верификация подписи и срока refresh JWT.
- Сверка
jtiиз JWT сcurrent_jtiв Mnesia. - При совпадении — ротация: новый
jti, новая пара токенов. - При несовпадении (reuse) —
revoke_family, ответ 401.
Поток login user (POST /v1/login):
- Проверка email/password.
- Создание записи
auth_session(subject_type=user,client_type=web). - Ответ:
{ token, refresh_token, user }.
Поток refresh user (POST /v1/refresh):
- Верификация refresh JWT (
aud=user). - Сверка
jtiсcurrent_jtiв Mnesia, ротация при успехе. - Reuse —
revoke_family, 401.
Фазы внедрения:
- Фаза 1 (#23): admin API — реализовано.
- Фаза 2 (#26): user login +
/v1/refresh— реализовано. - Фаза 3: client web + mobile (явный
client_type, тот же контракт).
Legacy (не используется login/refresh user)
- Таблица
session(ram_copies) и opaque refresh вcore_session— оставлены в кодовой базе, hot path user API переведён наauth_session.
7. ВЕРСИОНИРОВАНИЕ И СТАТУС
Текущая версия: 1.5 (MVP, альфа). Включает:
- Гибридную модель повторяющихся событий
- Раздельную JWT-аутентификацию (пользователи/администраторы)
- Полноценную ролевую модель администрирования
- Версионированное админ-API (/v1/admin/...)
- Расширенную инфраструктуру (Traefik, WAF, мониторинг, failover)
- Аудит действий администраторов
- Расширенную статистику для дашборда
- Улучшенную обработку ошибок и валидацию
Новое в версии 1.5:
- Расширенная структура данных (новые поля и таблицы, задача #12)
- Дисковое хранение и индексы (задача #13)
- Репликация между узлами кластера с автоочисткой (задача #14)
- Архивирование исторических данных и серверный рендеринг календаря (задача #15)
- Сбор статистики через триггеры Mnesia (задача #16)
- Механизм миграций схемы данных (задача #17)
8. ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ
- Система не поддерживает транзакционную целостность между несколькими таблицами на уровне приложения (полагаемся на Mnesia).
- В текущей версии отсутствует полноценная система уведомлений (только таблица).
- Загрузка файлов (вложения) пока не реализована.
- Серверный рендеринг календаря работает только для владельца календаря.
- Автоматическое архивирование через
archive_controllerв локальном режиме используетslave:start, который устарел; в production планируетсяpeer.
9. ТРЕБОВАНИЯ К ОКРУЖЕНИЮ
- Erlang/OTP 28
- Docker Engine 27.3.1+
- Docker Compose v3.8
- Linux (продакшен) или WSL2 (разработка)