Corp/docs/guides/NOTIFICATIONS.md

12 KiB
Raw Blame History

Уведомления

Текущая система уведомлений — verb-based Django app с хранением в PostgreSQL и доставкой через WebSocket, email и Web Push.

Источники истины

  • models: backend/notifications/models.py
  • создание: backend/notifications/signals.py
  • channel routing: backend/notifications/channels.py
  • REST API: backend/notifications/api/
  • senders/tasks: backend/notifications/senders/ и backend/notifications/tasks/
  • realtime state events: backend/notifications/realtime.py
  • frontend context: frontend/src/contexts/NotificationsContext.tsx
  • Web Push hook: frontend/src/hooks/useWebPush.ts
  • tests: backend/tests/api/v1/notifications/ и backend/tests/realtime/

NotificationService в backend/notifications/services.py — deprecated stub и выбрасывает NotImplementedError. Новый код обязан использовать notify.send(...).

Создание уведомления

from notifications.signals import notify

notify.send(
    sender=request.user,
    recipient=recipient,
    verb="document_assigned",
    target=document,
    description="Вам назначен документ",
    action_url=f"/documents/{document.pk}",
    data={
        "title": "Новый документ",
        "document_id": document.pk,
    },
)

recipient может быть одним Employee или списком/tuple сотрудников. Обязательны recipient и verb. actor, action_object и target хранятся через GenericForeignKey.

Не создавайте уведомления пользователю о его собственном действии, если продуктовый сценарий явно этого не требует. action_url должен быть внутренним разрешённым маршрутом frontend, а data — JSON-serializable.

Для сообщений verb chat_mention создаётся только по проверенному структурированному полю Message.mentions, а не по произвольному тексту после @. Получатель упоминания исключается из обычного chat_new_message или chat_reply для этого же сообщения, поэтому одно действие не создаёт два уведомления. Старое notifications_enabled=false мигрирует в режим mentions: обращения продолжают приходить. Явный режим muted и временная пауза подавляют все события чата.

Поток доставки

  1. notify.send создаёт Notification.
  2. post_save проверяет UserChannelPreferences, disabled verb и DND.
  3. Через transaction.on_commit запускаются Celery tasks.
  4. WebSocket sender отправляет событие в user_{id}.
  5. Email sender отправляет instant email либо digest.
  6. Push sender выбирает активные устройства и маршруты приложений.

Если Celery broker недоступен, создание записи уведомления не откатывается: ошибка постановки задачи логируется. Повторная доставка требует отдельной операционной проверки.

Модели

Notification содержит recipient, actor, verb, action object, target, description, action URL, JSON data, unread/deleted/public/emailed flags и timestamps. Удаление через API мягкое (deleted=True).

UserChannelPreferences управляет:

  • web_enabled;
  • email_enabled и email_frequency: instant, daily, weekly, never;
  • push_enabled;
  • DND interval;
  • disabled_verbs.

При DND отправляется только silent WebSocket, если web channel включён. Email и push в этот момент не ставятся в очередь.

NotificationInstallation связывает Web Push subscription с приложением main или messenger и device group. NotificationDeliveryRouting выбирает приложение доставки по категориям: messages, tasks, requests, procurement, documents, guests, calendar, feed, other.

ChatUserSettings дополнительно управляет режимом конкретного чата (all, mentions, muted), muted_until, показом текста в системном уведомлении и звуком. Endpoint POST /api/v1/communications/chats/{id}/notifications/ принимает эти поля; пустой POST сохранён как совместимый быстрый переключатель all ↔ mentions.

REST API

Prefix: /api/v1/notifications/.

Method и path Назначение
GET / Список; filters page, page_size, verb, unread_only, search
GET /count/ Общее количество непрочитанных
GET /summary/ Агрегаты unread для навигации и доменных экранов
GET /verb-types/ Доступные verbs и счётчики
POST /{id}/read/ Отметить одно прочитанным
POST /{id}/unread/ Вернуть в непрочитанные
DELETE /{id}/ Soft-delete
POST /read-all/ Прочитать все или один verb
POST /category/read/ Прочитать список verbs категории
DELETE /delete-all-read/ Soft-delete всех прочитанных
GET, PUT /preferences/ Настройки каналов
GET /push/vapid-key/ Публичный VAPID key
POST /push/subscribe/ Создать/обновить push subscription
POST, DELETE /push/unsubscribe/ Удалить одну или все subscriptions
POST /push/installations/heartbeat/ Обновить installation lease
GET, PUT /push/delivery-routing/ Маршрутизация main/messenger
DELETE /push/installations/disconnect/ Отключить приложение на устройстве

Endpoint /push/subscriptions/ отсутствует. Не используйте старые примеры с /api/v2/notifications/ или /ws/notifications/.

Realtime events

Через единый /ws/ frontend получает:

  • notification — новая запись;
  • unread_count — актуальный общий счётчик;
  • notification_read — прочтение в другой вкладке/сессии;
  • notifications_read_all — массовое прочтение.
  • notifications_deleted — удаление записей из всех открытых сессий.

NotificationsContext объединяет REST initial load и realtime delta, подавляет дубли и выполняет fallback refresh. Не поднимайте отдельный notifications WebSocket.

Web Push

Backend требует VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY и VAPID_ADMIN_EMAIL. Subscription сохраняется в django-push-notifications WebPushDevice и связывается с NotificationInstallation.

Frontend flow:

  1. проверяет browser support и permission;
  2. получает VAPID public key;
  3. регистрирует service worker;
  4. отправляет installation heartbeat;
  5. только по user gesture запрашивает permission и создаёт subscription;
  6. передаёт endpoint, p256dh, auth, installation/device group и app kind backend-у.

Push работает только в secure context, кроме localhost. Permission denied нельзя повторно запросить из JavaScript — пользователь должен изменить настройку сайта в браузере.

При permanent delivery error (404, 410, expired/unregistered subscription) device деактивируется и отвязывается от installation. Transient error не удаляет устройство.

Для messenger-only route неуспешная доставка может fallback-нуться на main installation того же device group. Устаревшая messenger installation также возвращает доставку в main.

Уведомления сообщений используют стабильный tag=chat-{chat_id} и несут chat_id, message_id, notification_id. Backend отправляет невидимые control push-команды, а frontend/public/sw.js применяет их через getNotifications(): прочтение закрывает уведомление на остальных устройствах, удаление сообщения убирает относящийся к нему push, редактирование заменяет только уже показанный push. Если исходная Celery-задача задержалась, sender повторно проверяет unread/deleted и не показывает устаревшее уведомление.

Email

instant ставит отдельную Celery task при создании уведомления. daily и weekly обрабатываются Celery Beat в 09:00 по timezone проекта. never отключает email.

Проверьте EMAIL_BACKEND, SMTP settings, DEFAULT_FROM_EMAIL и SITE_URL. В DEBUG по умолчанию используется console email backend.

Настройки

REDIS_HOST=127.0.0.1
REDIS_PORT=6379
CELERY_BROKER_URL=redis://127.0.0.1:6379/0
CELERY_RESULT_BACKEND=redis://127.0.0.1:6379/1

VAPID_PUBLIC_KEY=change-me
VAPID_PRIVATE_KEY=change-me
VAPID_ADMIN_EMAIL=admin@example.com
SITE_URL=http://localhost:8000

Запуск worker и Beat:

cd backend
celery -A eusrr_backend worker -l info
celery -A eusrr_backend beat -l info

Добавление нового verb

  1. Выберите стабильное имя в snake_case с доменным prefix.
  2. Добавьте translation/category mapping на frontend, если verb виден пользователю.
  3. Передавайте title в data.title, если техническое имя verb не подходит как заголовок.
  4. Передавайте ID доменного объекта в data для unread summary/navigation.
  5. Не отправляйте секреты и большой payload.
  6. Добавьте тест создания и routing по каналам.
  7. Добавьте тест action URL и self-notification rule.

Проверка

cd backend
pytest tests/api/v1/notifications tests/realtime -q

Для ручной проверки создайте уведомление через notify.send, затем проверьте REST list, Celery log, WebSocket event и push device. Не вызывайте sender напрямую: это обходит channel preferences, DND и transaction boundary.

Диагностика

  • Запись есть, realtime нет: проверьте Celery task notifications.send_websocket_notification, Redis channel layer и подписку на user_{id}.
  • Push не создаётся: проверьте HTTPS/localhost, permission, service worker и VAPID public key.
  • Push создан, но не приходит: проверьте push_enabled, active WebPushDevice, installation route и Celery worker.
  • Уведомление дублируется: найдите повторный notify.send; UI deduplication не должна маскировать backend double-create.
  • Unread count устарел: операции read должны идти через API/model methods, которые инвалидируют cache и отправляют realtime state event.
  • Email не приходит: проверьте frequency, DND, SMTP и поле emailed.