From 5245cf7b0bf20466849913200b2e708b255a609e 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: Tue, 5 May 2026 13:45:45 +0300 Subject: [PATCH] v1.5 --- EventHubBackSpec.md | 456 ++++++++++++++++++++++---------------------- 1 file changed, 223 insertions(+), 233 deletions(-) diff --git a/EventHubBackSpec.md b/EventHubBackSpec.md index 6d5b709..4e57c0c 100644 --- a/EventHubBackSpec.md +++ b/EventHubBackSpec.md @@ -1,8 +1,10 @@ -ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB -Версия: 1.4 (актуальная реализация: раздельная JWT-аутентификация, версионированное админ-API, расширенная инфраструктура, аудит администраторов, расширенная статистика дашборда, улучшенная обработка ошибок) +# ТЕХНИЧЕСКОЕ ЗАДАНИЕ (ТЗ) НА ПЛАТФОРМУ EVENTHUB +Версия: 1.5 (актуальная реализация: выполнены задачи #12–#17) ## 1. ЦЕЛИ И НАЗНАЧЕНИЕ -EventHub — платформа для управления событиями с поддержкой календарей, записи участников (включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, встроенного баг-трекера и платной подписки. +EventHub — платформа для управления событиями с поддержкой календарей, записи участников +(включая специалистов), гибкого подтверждения, рейтингов, отзывов, модерации, +встроенного баг-трекера и платной подписки. Целевая аудитория: владельцы календарей (бизнес), участники (клиенты), администраторы. @@ -12,296 +14,274 @@ EventHub — платформа для управления событиями - CRUD календаря (название, описание, теги, владелец) - Расшаривание по ссылке (публичная/приватная) - Приглашение пользователей с правами "запись" или "администрирование" -- Типы календарей: personal (бесплатный, без записи), commercial (платный, запись клиентов, специалисты) -- Гибкое подтверждение заявок: auto (автоматически), manual (вручную), timeout (авто через N секунд) +- Типы календарей: 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. Типы событий и модель хранения -События делятся на два типа: -- 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. +События могут быть одиночными (`event_type = single`) или повторяющимися (`event_type = recurring`). +Для повторяющихся событий: +- Мастер-событие содержит правило повторения (`recurrence_rule`) и является шаблоном. +- При создании повторяющегося события генерируются экземпляры (instances) на определённый + период (например, на месяц вперёд), которые хранятся как отдельные записи с полем + `is_instance = true` и ссылкой на мастер (`master_id`). +- При изменении мастера можно выбрать обновление всех будущих экземпляров или только мастер-записи. +- При удалении мастера удаляются все связанные экземпляры. +- Для поддержки исключений (отмена отдельного вхождения) используется таблица + `recurrence_exception`. #### 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 элементов; для длительных серий применяется пагинация по датам. +При поиске событий на заданный диапазон дат система должна: +- Включать все одиночные события, попадающие в диапазон. +- Для повторяющихся событий генерировать виртуальные вхождения на основе `recurrence_rule`, + исключая те, что помечены как исключения. +- Возвращать как одиночные, так и сгенерированные вхождения в едином списке. #### 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), привязанное к идентификатору материализованного вхождения. -- Дальнейшие действия по подтверждению заявки выполняются уже с материализованным экземпляром. +При записи участника на конкретное вхождение повторяющегося события: +- Система материализует (создаёт) физическую запись события для этого вхождения, если оно ещё + не было материализовано (например, для хранения количества записавшихся). +- Материализованное событие имеет `is_instance = true` и ссылается на `master_id`. +- Запись участника (`booking`) всегда привязывается к конкретному экземпляру (материализованному + или одиночному событию). #### 2.2.4. Изменение и удаление серий -- При редактировании мастер-записи (изменение названия, описания, правила повторения) обновляется только сама мастер-запись. Уже существующие материализованные вхождения остаются неизменными (сохраняют старые значения атрибутов). Это поведение может быть изменено администратором через специальный флаг «применить ко всем будущим вхождениям». -- При удалении мастер-записи все связанные материализованные вхождения также помечаются удалёнными (или удаляются каскадно), а бронирования на будущие вхождения аннулируются с уведомлением участников. +- При редактировании мастера можно применить изменения ко всем будущим экземплярам или создать + новый мастер с отдельной серией. +- При удалении мастера удаляются все связанные экземпляры, если на них нет активных записей. + Если есть активные записи, мастер-событие помечается как `cancelled`, а существующие записи + остаются. #### 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() -}). +Актуальная структура записей включает дополнительные поля, добавленные в рамках задачи #12: +- `event` — добавлены `attachments :: [binary()] | undefined`, `edit_history :: [map()] | undefined` +- `booking` — добавлены `notes :: binary() | undefined`, `reminder_sent :: boolean()` +- `review` — добавлены `likes :: non_neg_integer()`, `dislikes :: non_neg_integer()`, + `edited_at :: calendar:datetime() | undefined` --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}). +- Все операции с событиями должны быть транзакционными. +- Генерация вхождений должна быть эффективной (использовать `calendar:datetime_to_gregorian_seconds` + и кэширование). +- При поиске событий для календаря учитывать права доступа пользователя. ### 2.3. Запись участников и подтверждение -- Запись через календарь -- Подтверждение согласно политике календаря -- Уведомления участника и владельца -- Привязка события к специалисту (отдельная запись в календаре специалиста) +- Пользователь может отправить заявку на участие в событии. +- В зависимости от `confirmation` календаря заявка либо подтверждается автоматически, либо + ожидает ручного подтверждения владельцем, либо подтверждается по таймауту. +- Бронирование имеет статусы: `pending`, `confirmed`, `cancelled`. +- Пользователь может отменить свою запись. +- Владелец календаря может подтвердить или отклонить заявку. +- При подтверждении фиксируется время (`confirmed_at`). +- Вместимость события (`capacity`) ограничивает количество подтверждённых записей. ### 2.4. Отзывы и рейтинги -- Только участники событий могут оставлять отзывы -- Оценка 1-5, текстовый комментарий -- Отзывы на событие или на календарь -- Модерация отзывов (администраторы могут скрывать) +- Пользователи могут оставлять отзывы (рейтинг 1–5 и комментарий) на события или календари. +- Отзыв можно редактировать (сохраняется `edited_at`). +- Отзывы могут быть скрыты модератором. +- При добавлении/изменении/удалении отзыва пересчитывается средний рейтинг события + (`rating_avg`, `rating_count`). +- Реализованы лайки/дизлайки отзывов (`likes`, `dislikes`). +- Возможность пожаловаться на отзыв (создание `report`). ### 2.5. Поиск и фильтрация -- По тексту, по тегам, по гео-позиции (радиус), по дате/времени +- Полнотекстовый поиск по названиям событий, календарей, тегам. +- Фильтрация по дате, категории, местоположению, рейтингу. +- Пагинация результатов. +- Поиск должен учитывать права доступа (не показывать скрытые/заблокированные календари). ### 2.6. Расширенные возможности -- Экспорт в Google Calendar (через OAuth2) -- Экспорт в Apple Calendar (ICS-файл) -- Геокодирование (адрес -> координаты) через внешний API (заглушка / Nominatim) +- Локация события (`location` запись с адресом, широтой, долготой). +- Онлайн-ссылка (`online_link`). +- Вложения к событию (`attachments`). +- История изменений события (`edit_history`). +- Заметки пользователя к бронированию (`notes`). +- Напоминания о событии (поле `reminder_sent` в бронировании, логика отправки не реализована). ### 2.7. Модерация и безопасность -- Жалобы на календари, события, отзывы -- Автоматическая модерация по ключевым словам и по порогу жалоб -- Ручная заморозка / разморозка администратором -- Бан-лист слов -- **Аудит действий администраторов:** Ведётся детальный журнал действий всех администраторов (кто, что, когда, IP, причина). Модель аудита (admin_audit) включает: - - admin_id — идентификатор администратора, - - email — email администратора, - - role — роль (superadmin, moderator, support), - - action — действие (например, add_banned_word, delete_banned_word, update_role), - - entity_type — тип сущности (banned_word, admin и т.д.), - - entity_id — идентификатор сущности, - - ip — IP-адрес, - - reason — причина действия (опционально), - - timestamp — время действия. +- Пользователи могут отправлять жалобы (`report`) на календари, события, отзывы. +- Модераторы могут просматривать жалобы и принимать меры (скрывать контент, блокировать). +- Список запрещённых слов (`banned_word`) для фильтрации контента. +- Аудит действий администраторов (`admin_audit`). ### 2.8. Баг-трекер (автоматический) -- При ошибке создаётся тикет, группировка по хэшу ошибки -- Учёт количества повторений -- Уведомление пользователя при создании и при закрытии тикета -- Доступ только у администраторов +- При возникновении ошибок сервер автоматически создаёт тикет (`ticket`). +- Тикет содержит хеш ошибки, сообщение, стектрейс, контекст, количество повторений. +- Администраторы могут просматривать тикеты, назначать ответственных, менять статус. ### 2.9. Платная подписка -- Личное использование бесплатно (personal), коммерческое — платно (commercial) -- Пробный период 30 дней -- Планы подписки: monthly, quarterly, biannual, annual -- Заглушка платежного шлюза (для тестирования) -- Автоматическое истечение подписки -- Автоматическое понижение календарей до personal при истечении +- Пользователи могут оформить подписку (`subscription`) с разными планами: monthly, quarterly, + biannual, annual. +- Статус подписки: `active`, `expired`, `cancelled`. +- Отслеживание использования пробного периода (`trial_used`). ### 2.10. Административная панель -- Отдельный HTTPS-сервер (порт 8445) и отдельный WSS (порт 8446) -- Управление календарями, событиями, отзывами, жалобами, тикетами, бан-словами -- Статистика, информация о нодах кластера -- WebSocket-уведомления администраторам +- Управление пользователями (просмотр, блокировка, изменение ролей). +- Просмотр статистики платформы. +- Управление жалобами и тикетами. +- Управление подписками. +- Аудит действий администраторов. -#### 2.10.1. Ролевая модель администраторов (реализована) -Реализована трехуровневая ролевая модель: superadmin, moderator, support. Проверка ролей выполняется в каждом административном обработчике через handler_auth:authenticate/1 и вспомогательную функцию is_admin/1. +#### 2.10.1. Ролевая модель администраторов +- `superadmin` — полный доступ, может управлять другими администраторами. +- `admin` — управление пользователями, контентом, подписками. +- `moderator` — модерация контента (жалобы, отзывы). +- `support` — работа с тикетами. #### 2.10.2. Эндпоинты для управления ролями и аудитом -- GET /v1/admin/me — получение текущей роли и разрешений администратора. -- GET /v1/admin/admins — список всех администраторов с ролями (только для superadmin). -- PUT /v1/admin/admins/:id — изменение роли администратора (только для superadmin). -- POST /v1/admin/admins — приглашение нового администратора с назначением роли (только для superadmin). -- GET /v1/admin/audit — журнал действий администраторов с фильтрацией по дате, пользователю, действию (только для superadmin). +- `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. Возвращает JSON, содержимое которого фильтруется в соответствии с ролью вызывающего: -- superadmin — системные метрики: все пользователи, события, жалобы, баги за период, графики регистраций/событий по дням, активность администраторов. -- moderator — собственные обработанные жалобы/события (количество, статусы, время реакции), общая статистика по модерации. -- support — количество открытых багов и жалоб, назначенных на текущего сотрудника, персональные задачи. - -**Расширенная статистика (дополнение):** -- GET /v1/admin/statistics — агрегированная статистика по администраторам: общее количество действий за период, распределение по типам действий. -- Поддержка фильтрации: admin_id, action, date_from, date_to. +- `GET /v1/admin/stats` — базовая статистика (количество пользователей, событий, бронирований). +- В будущем планируется расширенная аналитика на основе данных из таблицы `stats`. ### 2.11. Real-time уведомления (WebSocket) -- Пользователи: порт 8081, маршрут /ws, подписка на календарь, получение обновлений -- Администраторы: порт 8446, маршрут /admin/ws, подписка на глобальные уведомления (жалобы, авто-заморозки) +- Пользовательский WebSocket (порт 8081): подписка на обновления событий календаря. +- Административный WebSocket (порт 8446): подписка на новые жалобы и тикеты. +- Создана таблица `notification` для хранения уведомлений (тип, заголовок, тело, флаг прочитано). + Полноценная логика доставки уведомлений будет реализована позже. ### 2.12. Инфраструктура развертывания (Docker Compose) -- Балансировщик Traefik с поддержкой HTTPS/WSS, WAF (Coraza), Rate Limiting и Failover -- Кластер из трех нод Erlang с автоматическим обнаружением (DNS-лукап) -- Мониторинг: Prometheus (метрики), Grafana (дашборды), LogLynx (аналитика логов) -- Ротация логов (logrotate) -- Административный SPA (EventHubFrontAdmin) как отдельный сервис -- Сервис-заглушка (Fallback) для отказоустойчивости +- Docker Swarm с тремя репликами `eventhub-node{1..3}`. +- Traefik в качестве reverse-прокси и балансировщика. +- Prometheus для сбора метрик, Grafana для визуализации. +- Observer Web для мониторинга Erlang-узлов. +- Автоматическая ротация логов. ## 3. НЕФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ ### 3.1. Производительность и масштабирование -- 100 000+ пользователей -- Горизонтальное масштабирование (увеличение нод) -- Mnesia с дисковыми копиями на нескольких нодах -- Отдельные ноды для исторических данных (disc_only_copies) -- Пагинация всех списков -- Автоматическое обнаружение узлов через DNS-лукап (libcluster в перспективе) +- Поддержка 100 000+ пользователей. +- Горизонтальное масштабирование добавлением новых узлов. +- Все персистентные таблицы хранятся в `disc_copies` на каждом узле (задача #13). +- Сессионные таблицы (`session`, `admin_session`) оставлены в `ram_copies` для скорости. +- Индексы созданы для часто запрашиваемых полей (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) -- **Улучшенная обработка ошибок:** - - Валидация входных данных на всех критических эндпоинтах административного API. - - Единый формат ошибок: {"error": "код", "message": "описание"}. +- Супервизорное дерево OTP. +- Философия "let it crash" — быстрый перезапуск упавших процессов. +- Автоматическое восстановление после падения ноды (Mnesia). +- Валидация входных данных и единый формат ошибок. +- Механизм миграций схемы данных: миграции компилируются вместе с проектом и автоматически применяются при старте, поддерживается откат (задача #17). ### 3.3. Безопасность -- Раздельная JWT-аутентификация: - - Пользовательские токены: секрет JWT_SECRET, audience = <<"user">>, эндпоинт /v1/login - - Административные токены: секрет ADMIN_JWT_SECRET, audience = <<"admin">>, эндпоинт /v1/admin/login -- Проверка ролей: superadmin, moderator, support -- HTTPS / WSS (самоподписанный сертификат для dev, реальный для prod) -- Argon2 для хеширования паролей (erlang-argon2) -- Refresh token реализован (хранение в Mnesia, сессии) -- OAuth2 для Google (опционально) -- При блокировке пользователя или отклонении сущности обязательно сохранять причину (поле reason) -- **CORS:** Все ответы API включают заголовки: - - access-control-allow-origin: * - - access-control-expose-headers: Content-Range +- JWT-аутентификация с разделением ролей (user, admin, superadmin, moderator, support). +- Проверка прав доступа к календарям и событиям. +- Пароли хэшируются с использованием Argon2. +- Защита от несанкционированного просмотра архивных данных (только владелец календаря) (задача #15). ### 3.4. Наблюдаемость -- Healthcheck эндпоинт (GET /health) -- Логирование (JSON, ротация) -- Prometheus метрики (реализованы на уровне приложения через prometheus_cowboy) -- Аудит действий администраторов (запись admin_audit) +- Логирование в JSON-формате. +- Экспорт метрик для Prometheus (HTTP-эндпоинт `/metrics`). +- Встроенный Observer Web для мониторинга Erlang-системы. +- Сбор статистики использования (события, бронирования, отзывы) через триггеры Mnesia с сохранением в таблице `stats` (задача #16). ### 3.5. CI/CD -- Drone CI (или GitLab CI / GitHub Actions) -- EUnit + Common Test + Tsung -- Сборка релиза -- Docker-образ -- Горячее обновление (rolling upgrade) кластера (3+ нод) -- Миграции Mnesia +- Контейнеризация (Docker). +- Makefile для автоматизации задач. +- Возможность развёртывания в Kubernetes (в будущем). ## 4. СТЕК ТЕХНОЛОГИЙ (С ВЕРСИЯМИ) -- Бэкенд: Erlang/OTP 28.2 -- Сборка: rebar3 3.27.0 -- HTTP/WebSocket: Cowboy 2.10.0 -- JSON: jsx 3.1.0 -- JWT: jose 1.11.10 -- Хеширование паролей: erlang-argon2 1.2.0 -- Тестирование: meck 0.9.2, gun 2.0.0 -- Мониторинг: prometheus_cowboy 0.2.0 -- Нагрузочное тестирование: Tsung 1.8.0 -- CI/CD: Drone 2.0+ -- Контейнеризация: Docker 20.10+ -- База данных: Mnesia (встроенная, распределённая) +- 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/ -test/ -├── infra/ ├── unit/ # EUnit тесты -│ ├── eventhub_auth.erl # JWT └── api/ # Common Test интеграционные тесты -│ └── ... -├── core/ # слой доступа к данным (DAO) -├── logic/ # бизнес-логика -├── services/ # внешние сервисы -├── handlers/ # обработчики HTTP/WebSocket -│ ├── admin/ # административные обработчики -│ └── ... -└── middlewares/ # промежуточные слои (CORS, аутентификация) +├── 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 — точка входа приложения ``` -**Правила:** -- Модули handlers/ не содержат бизнес-логики. -- Модули logic/ не знают о HTTP/WS. -- Модули core/ (DAO) работают только с Mnesia. -- Модуль infra/eventhub_auth.erl реализует раздельную JWT-аутентификацию (пользователи и администраторы). -- Модули services/ могут быть заменены на реальные реализации без изменения остального кода. -- Заголовочный файл include/records.hrl содержит все записи таблиц Mnesia. ## 6. ОСНОВНЫЕ API (КРАТКО) ### Пользовательские (порт 8080) -- **Публичные:** GET /health, POST /v1/register, POST /v1/login, POST /v1/refresh -- **Авторизованные:** GET /v1/user/me, GET /v1/user/bookings, GET /v1/user/reviews, GET /v1/search -- **Календари:** POST /v1/calendars + CRUD, GET /v1/calendars/:id, PUT /v1/calendars/:id, DELETE /v1/calendars/:id -- **События:** POST /v1/calendars/:calendar_id/events, GET /v1/events/:id, PUT /v1/events/:id, DELETE /v1/events/:id, GET /v1/events/:id/occurrences, POST /v1/events/:id/join, POST /v1/events/:id/confirm/:user_id, GET /v1/events/:id/bookings -- **Бронирования:** GET /v1/bookings/:id, PUT /v1/bookings/:id, DELETE /v1/bookings/:id -- **Отзывы:** POST /v1/reviews, GET /v1/reviews/:id, PUT /v1/reviews/:id, DELETE /v1/reviews/:id -- **Жалобы:** POST /v1/reports -- **Тикеты:** POST /v1/tickets, GET /v1/tickets, GET /v1/tickets/:id, PUT /v1/tickets/:id, DELETE /v1/tickets/:id -- **Подписки:** GET /v1/subscription, POST /v1/subscription -- **Метрики:** GET /metrics/[:registry] - -WebSocket (порт 8081): ws://localhost:8081/ws?token= +- `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` — пожаловаться. +- `GET /v1/tickets` — тикеты пользователя. +- `POST /v1/tickets` — создать тикет. +- `GET /v1/tickets/:id` — статус тикета. +- `GET /v1/subscription` — подписка пользователя. +- `GET /v1/calendars/:calendar_id/view?month=YYYY-MM` — HTML-календарь (владелец), включая архив. ### Административные (порт 8445) -- **Версионирование:** Все административные эндпоинты имеют префикс /v1/admin/. -- **Базовые:** GET /v1/admin/health, GET /v1/admin/stats, POST /v1/admin/login -- **Пользователи:** GET /v1/admin/users, GET /v1/admin/users/:id, PUT /v1/admin/users/:id, DELETE /v1/admin/users/:id -- **Отчёты (жалобы):** GET /v1/admin/reports, GET /v1/admin/reports/:id, PUT /v1/admin/reports/:id -- **Отзывы:** GET /v1/admin/reviews/:id, PUT /v1/admin/reviews/:id (hide/show) -- **Бан-слова:** GET /v1/admin/banned-words, POST /v1/admin/banned-words, DELETE /v1/admin/banned-words/:word -- **Тикеты:** GET /v1/admin/tickets, POST /v1/admin/tickets, GET /v1/admin/tickets/:id, PUT /v1/admin/tickets/:id, DELETE /v1/admin/tickets/:id, GET /v1/admin/tickets/stats -- **Подписки:** GET /v1/admin/subscriptions, POST /v1/admin/subscriptions, GET /v1/admin/subscriptions/:id, PUT /v1/admin/subscriptions/:id, DELETE /v1/admin/subscriptions/:id -*- *Модерация:** PUT /v1/admin/:target_type/:id (target_type: calendar, event, review, user; action: freeze/unfreeze, hide/show, block/unblock) - -Административный WebSocket (порт 8446): wss://localhost:8446/admin/ws?token= +- `GET /v1/admin/health` — состояние сервера. +- `GET /v1/admin/stats` — статистика. +- `POST /v1/admin/login` — вход администратора. +- `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/tickets/stats` — статистика тикетов. +- `GET /v1/admin/tickets`, `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. Аутентификация и авторизация -Модуль infra/eventhub_auth.erl реализует раздельную JWT-аутентификацию: -- Пользовательские токены выпускаются через POST /v1/login с audience <<"user">> и секретом JWT_SECRET. Проверяются обработчиками через handler_auth:authenticate/1 → logic_auth:verify_jwt/1. -- Административные токены выпускаются через POST /v1/admin/login с audience <<"admin">> и секретом ADMIN_JWT_SECRET. Проверяются обработчиками через handler_auth:authenticate/1 с последующей проверкой роли is_admin/1. - Обработчики порта 8445 используют единый middleware handler_auth:authenticate/1, который извлекает токен из заголовка Authorization: Bearer ..., верифицирует его через logic_auth:verify_jwt/1 и возвращает {ok, UserId, Req} или {error, Code, Message, Req}. +- Пользователи и администраторы используют разные эндпоинты и JWT-токены. +- Access-токен имеет срок жизни 1 час, refresh-токен — 30 дней. +- Все защищённые эндпоинты требуют заголовок `Authorization: Bearer `. ## 7. ВЕРСИОНИРОВАНИЕ И СТАТУС -Текущая версия: 1.4 (MVP, альфа). Включает: +Текущая версия: 1.5 (MVP, альфа). Включает: - Гибридную модель повторяющихся событий - Раздельную JWT-аутентификацию (пользователи/администраторы) - Полноценную ролевую модель администрирования @@ -311,15 +291,25 @@ WebSocket (порт 8081): ws://localhost:8081/ws?token= - Расширенную статистику для дашборда - Улучшенную обработку ошибок и валидацию +**Новое в версии 1.5:** +- Расширенная структура данных (новые поля и таблицы, задача #12) +- Дисковое хранение и индексы (задача #13) +- Репликация между узлами кластера с автоочисткой (задача #14) +- Архивирование исторических данных и серверный рендеринг календаря (задача #15) +- Сбор статистики через триггеры Mnesia (задача #16) +- Механизм миграций схемы данных (задача #17) + ## 8. ОГРАНИЧЕНИЯ И ДОПУЩЕНИЯ -- В разработке используются самоподписанные SSL-сертификаты. -- Для production требуется реальный сертификат и настройка OAuth2 для Google. -- Заглушки внешних сервисов должны быть заменены перед запуском. -- Автоматическое обнаружение узлов через libcluster находится в перспективе; текущая реализация использует статический JOIN_NODES с возможностью подключения через DNS-лукап. +- Система не поддерживает транзакционную целостность между несколькими таблицами на уровне + приложения (полагаемся на Mnesia). +- В текущей версии отсутствует полноценная система уведомлений (только таблица). +- Загрузка файлов (вложения) пока не реализована. +- Серверный рендеринг календаря работает только для владельца календаря. +- Автоматическое архивирование через `archive_controller` в локальном режиме использует + `slave:start`, который устарел; в production планируется `peer`. ## 9. ТРЕБОВАНИЯ К ОКРУЖЕНИЮ -- Erlang/OTP 28.2 -- rebar3 3.27.0 -- openssl -- Docker (опционально) -- Drone (опционально, для CI/CD) \ No newline at end of file +- Erlang/OTP 28 +- Docker Engine 27.3.1+ +- Docker Compose v3.8 +- Linux (продакшен) или WSL2 (разработка) \ No newline at end of file