Files
EventHubSpec/specification.txt
2026-04-15 22:02:51 +03:00

360 lines
18 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB
Версия: 1.1 (расширенная с гибридной моделью повторяющихся событий)
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. Модерация и безопасность
Жалобы на календари, события, отзывы
Автоматическая модерация по ключевым словам и по порогу жалоб
Ручная заморозка / разморозка администратором
Бан-лист слов
Аудит действий администраторов
2.8. Баг-трекер (автоматический)
При ошибке создаётся тикет, группировка по хэшу ошибки
Учёт количества повторений
Уведомление пользователя при создании и при закрытии тикета
Доступ только у администраторов
2.9. Платная подписка
Личное использование бесплатно (personal)
Коммерческое использование платно (commercial)
Пробный период 30 дней
Планы подписки: 1, 3, 6, 12 месяцев
Заглушка платежного шлюза (для тестирования)
Автоматическое истечение подписки
2.10. Административная панель
Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446)
Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами
Статистика, информация о нодах кластера
WebSocket-уведомления администраторам
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)
HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod)
Argon2 для хеширования паролей (erlang-argon2)
OAuth2 для Google (опционально)
Refresh token (запланирован)
3.4. Наблюдаемость
Healthcheck эндпоинт
Логирование (JSON, ротация)
Prometheus метрики (запланированы)
Аудит действий админов
3.5. CI/CD
Drone CI (или GitLab CI / GitHub Actions)
EUnit + Common Test + Tsung
Сборка релиза
Docker-образ
Горячее обновление (rolling upgrade) кластера (3+ нод)
Миграции Mnesia
СТЕК ТЕХНОЛОГИЙ (С ВЕРСИЯМИ)
Бэкенд: Erlang/OTP 28.2
Сборка: rebar3 3.27.0
HTTP/WebSocket: Cowboy 2.10.0
JSON: jsx 3.1.0
JWT: jwerl 1.1.0
Хеширование паролей: 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 (встроенная, распределённая)
ИЕРАРХИЧЕСКАЯ СТРУКТУРА КОДА
src/
├── infra/ # инфраструктура (приложение, супервизоры, аутентификация, подписки)
├── core/ # слой доступа к данным (DAO) и модели
├── logic/ # бизнес-логика (независимая от HTTP/WS)
├── services/ # внешние сервисы (заглушки/интеграции)
└── handlers/ # обработчики HTTP/WebSocket
Правила:
Модули handlers/ не содержат бизнес-логики.
Модули logic/ не знают о HTTP/WS.
Модули core/ (DAO) работают только с Mnesia.
Модули services/ могут быть заменены на реальные реализации без изменения остального кода.
Заголовочный файл include/records.hrl содержит все записи таблиц Mnesia.
ОСНОВНЫЕ 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
GET /admin/nodes
GET /admin/calendars, PUT /admin/calendars/:id/freeze, /unfreeze
GET /admin/events, PUT /admin/events/:id/freeze
GET /admin/reviews, PUT /admin/reviews/:id/hide
GET /admin/reports, PUT /admin/reports/:id/status
GET /admin/tickets, PUT /admin/tickets/:id/status
GET /admin/banned_words, POST, DELETE
Административный WebSocket (порт 8446): wss://localhost:8446/admin/ws?token=...
ВЕРСИОНИРОВАНИЕ И СТАТУС
Текущая версия: 1.1 (MVP, альфа, включает гибридную модель повторяющихся событий)
ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ
В разработке используются самоподписанные SSL-сертификаты.
Для production требуется реальный сертификат и настройка OAuth2 для Google.
Заглушки внешних сервисов должны быть заменены перед запуском.
ТРЕБОВАНИЯ К ОКРУЖЕНИЮ
Erlang/OTP 28.2
rebar3 3.27.0
openssl
Docker (опционально)
Drone (опционально, для CI/CD)