# Design: Notification System **Date:** 2026-05-18 **Status:** Draft — awaiting review ## 1. Overview Система оповещений для craftshop: email для пользователей, email + Telegram для админа. Архитектура — event-driven с in-memory очередью и retry. Задел на подключение новых каналов (WhatsApp, Viber, push). ## 2. Architecture ### 2.1 Components ``` server/src/ lib/ email.js ← расширяется: sendNotificationEmail() notifications/ event-bus.js ← EventEmitter, центральный хаб queue.js ← in-memory очередь + воркер channels/ email-channel.js ← отправка email через nodemailer telegram-channel.js ← отправка через Telegram Bot API templates/ email-templates.js ← HTML-шаблоны писем telegram-templates.js ← форматированные сообщения TG preferences.js ← CRUD настроек оповещений routes/ api/ admin/ notifications.js ← GET/PUT настройки админа user/ notifications.js ← GET/PUT настройки пользователя ``` ### 2.2 Database (Prisma) #### NotificationPreference (настройки пользователя) | Field | Type | Description | |---|---|---| | id | String (cuid) | Primary key | | userId | String (cuid) | FK → User (unique) | | globalEnabled | Boolean | Главный переключатель | | orderCreated | Boolean | Заказ создан | | orderStatusChanged | Boolean | Статус заказа изменён | | orderMessageReceived | Boolean | Новое сообщение в чате заказа | | paymentStatusChanged | Boolean | Статус оплаты изменён | | createdAt | DateTime | | | updatedAt | DateTime | | #### AdminNotificationSettings (настройки админа) | Field | Type | Description | |---|---|---| | id | String (cuid) | Primary key | | emailEnabled | Boolean | Email вкл/выкл | | telegramEnabled | Boolean | Telegram вкл/выкл | | telegramChatId | String? | ID чата админа с ботом | | newOrder | Boolean | Новый заказ | | newOrderMessage | Boolean | Новое сообщение в заказе | | newReview | Boolean | Новый отзыв | | authCodeDuplicate | Boolean | Дублировать код входа в TG | | createdAt | DateTime | | | updatedAt | DateTime | | #### NotificationLog (лог отправки) | Field | Type | Description | |---|---|---| | id | String (cuid) | Primary key | | userId | String? | FK → User (null для админа) | | eventType | String | Тип события | | channel | String | 'email' | 'telegram' | | status | String | 'pending' | 'sent' | 'failed' | | error | String? | Текст ошибки | | payload | Json | Данные события | | attempts | Int | Количество попыток | | createdAt | DateTime | | | updatedAt | DateTime | | ### 2.3 Queue - In-memory массив задач - Воркер: `setInterval` каждые 2 секунды, максимум 5 параллельных отправок - Retry: 3 попытки с задержкой 5с, 30с, 120с - При рестарте сервера: все `pending` записи помечаются как `failed` ## 3. Events | Event | Triggered in | Payload | Recipients | |---|---|---|---| | `order:created` | user-orders.js | orderId, userId, orderData | User (orderCreated), Admin (newOrder) | | `order:statusChanged` | admin-orders.js | orderId, userId, oldStatus, newStatus | User (orderStatusChanged) | | `orderMessage:sent` | user-messages.js | orderId, authorType, messageId | Admin (newOrderMessage) | | `orderMessage:adminReply` | admin-orders.js | orderId, userId, messageId | User (orderMessageReceived) | | `payment:statusChanged` | user-payments.js | orderId, userId, paymentStatus | User (paymentStatusChanged) | | `auth:codeRequested` | auth.js | email, code, isAdmin | User (email), Admin (authCodeDuplicate if isAdmin) | ## 4. Data Flow ``` Роут → eventBus.emit(eventType, payload) → preferences.resolveRecipients(eventType, payload) → для каждого получателя: → NotificationLog.create({ status: 'pending' }) → queue.enqueue({ recipient, channel, eventType, payload }) → ответ API (без ожидания отправки) Воркер (каждые 2с, до 5 параллельно): → queue.dequeue() → channel.send(job) → NotificationLog.update({ status: 'sent' | 'failed', attempts++ }) → если failed и attempts < 3 → re-enqueue с delay ``` ## 5. Channel Interface Каждый канал реализует: ```js { name: 'email' | 'telegram', send(job: { recipient, payload, template }): Promise<{ success: boolean, error?: string }> } ``` ### 5.1 Email Channel - Использует существующий nodemailer transporter из `email.js` - `sendNotificationEmail({ to, subject, html })` - HTML-шаблоны в `email-templates.js` ### 5.2 Telegram Channel - Telegram Bot API: `POST https://api.telegram.org/bot/sendMessage` - `node-telegram-bot-api` или прямой fetch - Форматирование: HTML parse mode - Шаблоны в `telegram-templates.js` ## 6. Client-Side ### 6.1 User Notification Settings Page - Route: `/me/notifications` - MUI переключатели: - Главный toggle "Получать оповещения" (globalEnabled) - При включённом: 4 toggles для каждого типа события - При выключенном: все остальные toggles disabled - Сохранение через `apiClient` + `@tanstack/react-query` mutation + invalidate ### 6.2 Admin Notification Settings - Встраивается в существующую админку - Toggle email, toggle telegram - Если telegram включён — поле telegramChatId (заполняется автоматически при /start бота) - Toggle для каждого типа события + toggle дублирования кода входа ## 7. Error Handling | Scenario | Behavior | |---|---| | SMTP/Telegram недоступен | Retry 3 раза (5с → 30с → 120с), затем failed | | Невалидный email / chatId | Сразу failed, без retry | | Ошибка рендера шаблона | failed, лог в NotificationLog.error | | Сервер рестарт | pending → failed при старте воркера | ## 8. Security - `TELEGRAM_BOT_TOKEN` — только в `.dev_env`, не коммитится - Telegram chatId запоминается при `/start` от админа - Настройки пользователя — только через `fastify.authenticate` - Настройки админа — только через `fastify.verifyAdmin` ## 9. Extensibility ### Adding a new channel (WhatsApp, Viber, push) 1. Новый файл в `channels/` с интерфейсом `{ name, send(job) }` 2. Регистрация в `queue.js` 3. Никакие другие файлы не меняются ### Adding a new event type 1. Добавить в константы типов событий 2. Добавить поле в `NotificationPreference` / `AdminNotificationSettings` 3. Эмитить через `eventBus.emit()` в нужном роуте ### Adding new recipients (broadcasts) - `NotificationLog.userId` nullable — поддерживает системные события - Очередь поддерживает batch-задачи ## 10. Environment Variables | Variable | Description | Required | |---|---|---| | SMTP_HOST | SMTP сервер | Да (для email) | | SMTP_PORT | SMTP порт | Да | | SMTP_SECURE | SSL/TLS | Да | | SMTP_USER | SMTP логин | Да | | SMTP_PASS | SMTP пароль | Да | | MAIL_FROM | From address | Да | | TELEGRAM_BOT_TOKEN | Токен Telegram бота | Для Telegram канала | ## 11. Implementation Notes - `eventBus` декорируется на fastify instance (как `slugify`, `parseMaterialsInput`) - `bootstrap-admin.js` создаёт `AdminNotificationSettings` при создании админа - При создании пользователя — создаётся `NotificationPreference` с defaults (всё включено) - Существующий `sendLoginCodeEmail` остаётся, добавляется `sendNotificationEmail` ## 12. Telegram Bot — Setup Flow 1. Админ запускает бота командой `/start` 2. Бот проверяет, что sender — админ (сверка email через webhook или ручной ввод `TELEGRAM_ADMIN_CHAT_ID` в `.dev_env`) 3. Если совпадает — сохраняет `chatId` в `AdminNotificationSettings.telegramChatId` 4. Если `telegramChatId` уже установлен — бот просто подтверждает подписку **Fallback:** если webhook не настроен, админ вручную вписывает свой chatId в настройки админки.