From ce348762cea59711b72357f9028fad2faf075531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Wed, 15 Apr 2026 22:02:51 +0300 Subject: [PATCH] v1.1 --- specification.txt | 420 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 298 insertions(+), 122 deletions(-) diff --git a/specification.txt b/specification.txt index 3cf88d6..89c9ac5 100644 --- a/specification.txt +++ b/specification.txt @@ -1,5 +1,5 @@ ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB -Версия: 1.0 (расширенная) +Версия: 1.1 (расширенная с гибридной моделью повторяющихся событий) 1. ЦЕЛИ И НАЗНАЧЕНИЕ EventHub — платформа для управления событиями с поддержкой календарей, записи участников (включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, встроенного баг-трекера и платной подписки. Целевая аудитория: владельцы календарей (бизнес), участники (клиенты), администраторы. @@ -14,171 +14,347 @@ EventHub — платформа для управления событиями - Гибкое подтверждение заявок: auto (автоматически), manual (вручную), timeout (авто через N секунд) - Теги календаря, рейтинг (средняя оценка, количество голосов) -2.2. События -- Создание события (название, дата/время, длительность, тип: офлайн/онлайн) -- Для офлайн: геоточка (адрес + координаты) -- Для онлайн: ссылка (Zoom, Google Meet) -- Ограничение количества мест -- Привязка к специалисту (для коммерческих календарей) -- Теги событий, рейтинг +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, текстовый комментарий -- Отзывы на событие или на календарь -- Модерация отзывов (администраторы могут скрывать) + +Только участники событий могут оставлять отзывы + +Оценка 1-5, текстовый комментарий + +Отзывы на событие или на календарь + +Модерация отзывов (администраторы могут скрывать) 2.5. Поиск и фильтрация -- По тексту, по тегам, по гео-позиции (радиус), по дате/времени + +По тексту, по тегам, по гео-позиции (радиус), по дате/времени 2.6. Расширенные возможности -- Экспорт в Google Calendar (через OAuth2) -- Экспорт в Apple Calendar (ICS-файл) -- Геокодирование (адрес -> координаты) через внешний API (заглушка / Nominatim) + +Экспорт в Google Calendar (через OAuth2) + +Экспорт в Apple Calendar (ICS-файл) + +Геокодирование (адрес -> координаты) через внешний API (заглушка / Nominatim) 2.7. Модерация и безопасность -- Жалобы на календари, события, отзывы -- Автоматическая модерация по ключевым словам и по порогу жалоб -- Ручная заморозка / разморозка администратором -- Бан-лист слов -- Аудит действий администраторов + +Жалобы на календари, события, отзывы + +Автоматическая модерация по ключевым словам и по порогу жалоб + +Ручная заморозка / разморозка администратором + +Бан-лист слов + +Аудит действий администраторов 2.8. Баг-трекер (автоматический) -- При ошибке создаётся тикет, группировка по хэшу ошибки -- Учёт количества повторений -- Уведомление пользователя при создании и при закрытии тикета -- Доступ только у администраторов + +При ошибке создаётся тикет, группировка по хэшу ошибки + +Учёт количества повторений + +Уведомление пользователя при создании и при закрытии тикета + +Доступ только у администраторов 2.9. Платная подписка -- Личное использование бесплатно (personal) -- Коммерческое использование платно (commercial) -- Пробный период 30 дней -- Планы подписки: 1, 3, 6, 12 месяцев -- Заглушка платежного шлюза (для тестирования) -- Автоматическое истечение подписки + +Личное использование бесплатно (personal) + +Коммерческое использование платно (commercial) + +Пробный период 30 дней + +Планы подписки: 1, 3, 6, 12 месяцев + +Заглушка платежного шлюза (для тестирования) + +Автоматическое истечение подписки 2.10. Административная панель -- Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446) -- Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами -- Статистика, информация о нодах кластера -- WebSocket-уведомления администраторам + +Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446) + +Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами + +Статистика, информация о нодах кластера + +WebSocket-уведомления администраторам 2.11. Real-time уведомления (WebSocket) -- Пользователи: подписка на календарь, получение обновлений -- Администраторы: подписка на глобальные уведомления (жалобы, авто-заморозки) -3. НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ +Пользователи: подписка на календарь, получение обновлений + +Администраторы: подписка на глобальные уведомления (жалобы, авто-заморозки) + +НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ 3.1. Производительность и масштабирование -- 100 000+ пользователей -- Горизонтальное масштабирование (увеличение нод) -- Mnesia с дисковыми копиями на нескольких нодах -- Отдельные ноды для исторических данных (disc_only_copies) -- Пагинация всех списков + +100 000+ пользователей + +Горизонтальное масштабирование (увеличение нод) + +Mnesia с дисковыми копиями на нескольких нодах + +Отдельные ноды для исторических данных (disc_only_copies) + +Пагинация всех списков 3.2. Надёжность -- Супервизорное дерево OTP -- Let it crash – быстрый перезапуск процессов -- Автоматическое восстановление после падения ноды (Mnesia) + +Супервизорное дерево OTP + +Let it crash – быстрый перезапуск процессов + +Автоматическое восстановление после падения ноды (Mnesia) 3.3. Безопасность -- JWT с ролью (user / admin) -- HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod) -- Argon2 для хеширования паролей (erlang-argon2) -- OAuth2 для Google (опционально) -- Refresh token (запланирован) + +JWT с ролью (user / admin) + +HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod) + +Argon2 для хеширования паролей (erlang-argon2) + +OAuth2 для Google (опционально) + +Refresh token (запланирован) 3.4. Наблюдаемость -- Healthcheck эндпоинт -- Логирование (JSON, ротация) -- Prometheus метрики (запланированы) -- Аудит действий админов + +Healthcheck эндпоинт + +Логирование (JSON, ротация) + +Prometheus метрики (запланированы) + +Аудит действий админов 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: 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 (встроенная, распределённая) +Drone CI (или GitLab CI / GitHub Actions) -5. ИЕРАРХИЧЕСКАЯ СТРУКТУРА КОДА +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 +├── infra/ # инфраструктура (приложение, супервизоры, аутентификация, подписки) +├── core/ # слой доступа к данным (DAO) и модели +├── logic/ # бизнес-логика (независимая от HTTP/WS) +├── services/ # внешние сервисы (заглушки/интеграции) +└── handlers/ # обработчики HTTP/WebSocket Правила: -- Модули handlers/ не содержат бизнес-логики. -- Модули logic/ не знают о HTTP/WS. -- Модули core/ (DAO) работают только с Mnesia. -- Модули services/ могут быть заменены на реальные реализации без изменения остального кода. -- Заголовочный файл include/records.hrl содержит все записи таблиц Mnesia. -6. ОСНОВНЫЕ API (КРАТКО) +Модули 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 + +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 + +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=... -7. ВЕРСИОНИРОВАНИЕ И СТАТУС -Текущая версия: 0.0.1 (MVP, альфа) +ВЕРСИОНИРОВАНИЕ И СТАТУС +Текущая версия: 1.1 (MVP, альфа, включает гибридную модель повторяющихся событий) -8. ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ -- В разработке используются самоподписанные SSL-сертификаты. -- Для production требуется реальный сертификат и настройка OAuth2 для Google. -- Заглушки внешних сервисов должны быть заменены перед запуском. +ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ -9. ТРЕБОВАНИЯ К ОКРУЖЕНИЮ -- Erlang/OTP 28.2 -- rebar3 3.27.0 -- openssl -- Docker (опционально) -- Drone (опционально, для CI/CD) \ No newline at end of file +В разработке используются самоподписанные SSL-сертификаты. + +Для production требуется реальный сертификат и настройка OAuth2 для Google. + +Заглушки внешних сервисов должны быть заменены перед запуском. + +ТРЕБОВАНИЯ К ОКРУЖЕНИЮ + +Erlang/OTP 28.2 + +rebar3 3.27.0 + +openssl + +Docker (опционально) + +Drone (опционально, для CI/CD) \ No newline at end of file