Skip to content

Система уведомлений ​

Назначение ​

Система уведомлений разделяет три задачи:

  • in-app notification center хранит важные события для пользователя;
  • /ws/notifications доставляет новые in-app уведомления в открытый интерфейс в реальном времени;
  • notifications-worker отправляет email через outbox и ретраи.

Frontend не отправляет email и не решает, кому можно получить событие. Он подключается к WebSocket, показывает badge/toast и вызывает REST endpoint-ы для чтения и архивации.

Каналы ​

Поддерживаемые каналы v1:

  • in_app — запись в notifications, отображается в UI;
  • email — delivery-запись в notification_deliveries, отправляется отдельным worker.

Пользовательские настройки берутся из users.notification_preferences. Если in_app=false, in-app запись не создается. Если email=false, email delivery не создается.

API ​

Основные endpoint-ы:

  • GET /api/v1/notifications?unread_only=true — список уведомлений текущего пользователя;
  • GET /api/v1/notifications/unread-count — счетчик непрочитанных;
  • POST /api/v1/notifications/:id/read — отметить одно уведомление прочитанным;
  • POST /api/v1/notifications/read-all — отметить все уведомления прочитанными;
  • DELETE /api/v1/notifications/:id — soft archive.

WebSocket:

  • /ws/notifications отправляет события:
    • notification_created — новое уведомление (notification);
    • notification_read — уведомления прочитаны (notification_id — первое, notification_ids — все), в том числе при прочтении чата (POST /chats/:id/read гасит его chat.message/chat.mention);
    • notifications_read_all — прочитаны все;
    • notification_archived — уведомление notification_id удалено;
  • сервер шлёт ping-кадр каждые 30 секунд, клиент — {"type":"heartbeat"} каждые 30 секунд (иначе через 70 секунд без кадров соединение закрывается); после переподключения клиент перечитывает список и счётчик;
  • счётчик на колокольчике — GET /notifications/unread-count, а не число непрочитанных среди первых 50;
  • /ws/chat остается каналом realtime-сообщений чата.

Браузерный WebSocket() не умеет слать заголовок Authorization, поэтому подключение идёт по одноразовому тикету: access token в URL оседал бы в логах прокси и в истории.

  1. POST /api/v1/ws/ticket с обычной аутентификацией → {"ticket": "…", "expires_at": "…"}. Тикет живёт минуту и гасится при первом использовании.
  2. ws://<host>/ws/notifications?ticket=<ticket>.
  3. На каждое переподключение запрашивается новый тикет.

Небраузерные клиенты используют Authorization: Bearer <token>. Старый способ ?token=<access_token> продолжает работать, но считается устаревшим.

Каждое уведомление содержит готовое поле message для отображения. Это поле дублирует payload.message и заполняется backend-ом, чтобы frontend не собирал текст уведомления самостоятельно.

Для чатовых уведомлений payload дополнительно содержит:

json
{
  "chat_id": 10,
  "message_id": 99,
  "from_user_id": 1,
  "from_user_name": "Мария Иванова",
  "message_text": "Привет, проверь задачу",
  "preview": "Привет, проверь задачу",
  "message": "Мария Иванова: Привет, проверь задачу"
}

Delivery worker ​

Email отправляется не из HTTP request path. Backend создает delivery-запись со статусом pending, а notifications-worker забирает партии через PostgreSQL FOR UPDATE SKIP LOCKED.

Статусы delivery:

  • pending — ожидает отправки или retry;
  • processing — взято worker-ом;
  • sent — отправлено;
  • failed — исчерпаны попытки.

В Docker Compose backend запускается с APP_RUN_BACKGROUND_WORKERS=false, а notifications-worker запускает фоновые задачи и email delivery. Для локального запуска без отдельного worker значение по умолчанию остается true.

События v1 ​

Подключенные события:

  • task.assigned — назначение основному исполнителю и новым co-assignees;
  • chat.message — обычное новое сообщение всем участникам чата, кроме автора; только in_app, без email delivery;
  • chat.mention — упоминание пользователя через mention_user_ids;
  • chat.ping — ping пользователя в чате;
  • project.member_added — пользователя добавили в проектную команду; только in_app, без email delivery;
  • team_request.submitted — менеджерам отделов по позициям заявки;
  • team_request.approved / team_request.rejected — автору заявки;
  • team_request.item_assigned — автору заявки и назначенному сотруднику;
  • project_spec.submitted — менеджеру проекта;
  • project_spec.approved / project_spec.rejected — менеджеру проекта;
  • time_entries_missing_personal;
  • time_entries_missing_manager.

Обычные новые сообщения чата доставляются двумя каналами: /ws/chat обновляет открытую панель чата, а chat.message создает запись в notification center и realtime-событие /ws/notifications для badge/колокольчика. Email для chat.message не ставится в outbox. Уведомление содержит имя отправителя и текст сообщения; упоминания получают chat.mention с теми же полями payload.

Если админ отправляет сообщение в чат, где он раньше не был участником, backend добавляет его в участники перед созданием сообщения. Это нужно, чтобы последующие ответы пользователей создавали chat.message для админа так же, как для обычного участника чата.

Оповещение в браузере ​

Новое уведомление, пришедшее по /ws/notifications (action: created, ещё не прочитанное), фронт показывает сразу (useNotificationAlerts):

  • Звук — короткий двухнотный сигнал, только для chat.message и chat.mention; пачка сообщений подряд даёт один сигнал (не чаще раза в 1,5 с). Звук синтезируется Web Audio, файла нет; браузер разрешает его после первого нажатия на странице.
  • Всплывающее окошко — тост «Автор: текст» с кнопкой «Открыть» (переход по маршруту уведомления, уведомление отмечается прочитанным).
  • Рабочий стол — если вкладка свёрнута или окно не в фокусе и браузер разрешил уведомления, вместо окошка показывается системное уведомление; сообщения одного чата заменяют друг друга (tag: chat-<id>). Разрешение предлагается в колокольчике и в профиле.
  • Счётчик во вкладке — «(3) Чаты — RMS» по числу непрочитанных колокольчика.

Сообщение чата, который открыт на экране (раздел «Чаты», чат справа, чат задачи) при видимой вкладке в фокусе, не оповещается. Настройки «Звук», «Всплывающие окошки», «На рабочем столе» — профиль → «Уведомления», хранятся в браузере (localStorage, ключ rms-notification-alerts), по умолчанию всё включено.

Расширение ​

Следующие бизнес-события должны подключаться через тот же notifications.Service.Notify:

  • equipment requests: submitted/finance approved/rejected/procurement started;
  • trips: approval requested/approved/cancelled;
  • due soon/overdue/status changed для задач.

По этому же WebSocket приходят изменения сущностей (entity_changed) — см. Синхронизация между пользователями.

Загружаем документацию…