Files
shop-server/docs/superpowers/specs/2026-05-18-notification-system-design.md
T
2026-05-18 11:45:51 +05:00

9.3 KiB
Raw Blame History

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'
status String 'pending'
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

Каждый канал реализует:

{
  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<TOKEN>/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 в настройки админки.