Добавлены уведомления
This commit is contained in:
@@ -33,6 +33,11 @@
|
||||
| `app_settings` | `han_app` | Бизнес-настройки |
|
||||
| `text_resources` | `han_app` | Тексты UI по мнемоникам |
|
||||
| `popular_questions` | `han_app` | Популярные вопросы главного экрана |
|
||||
| `Notification` / `GuestNotification` | `han_app` | Персональное уведомление / общая гостевая кампания |
|
||||
| `NotificationType` | `han_app` | Вид уведомления как данные: контур, приоритет, CTA, кнопки и оформление |
|
||||
| `NotificationCtaAction` / `NotificationButton` / `NotificationColorToken` | `han_app` | Реестры реализованных механик CTA, кнопок и семантической палитры |
|
||||
| `NotificationSource` | `han_app` | Продюсер Internal Notifications API; хранит hash индивидуального токена, не секрет |
|
||||
| `ClientDocument` | `han_app` | Отправленный клиентом проверенный документ; создаёт `document.client_uploaded` в `sync_queue` |
|
||||
| `dialog_sessions` | `bitrix_local` | Маппинг чата Open Lines |
|
||||
| `sms_template` | `sms` | Версионируемый согласованный SMS-шаблон; active-версия уникальна для `code`+`channel`+`locale` |
|
||||
| `sms_setting` | `sms` | Технические runtime-настройки `sms-service`, не секреты и не OTP product settings |
|
||||
@@ -54,6 +59,8 @@
|
||||
| `task_id` | ID async-проверки Message Safety |
|
||||
| `request_id` | Корреляция HTTP-запроса (заголовок `X-Request-ID`) |
|
||||
| `sms_message_id` | UUID `sms.sms_outbound_message.id`; логическая ссылка из Keycloak challenge/event, межсхемного FK нет |
|
||||
| `notification_id` | UUID v7 персонального или гостевого уведомления; генерирует приложение/seed |
|
||||
| `external_id` уведомления | Бессрочный бизнес-ключ продюсера в паре с `source` |
|
||||
| `provider_message_id` | `messageUuid` i-Digital Direct; хранится только в `sms-service` |
|
||||
| `provider_external_id` | `externalMessageId`; в v1 равен `sms_message_id` и является корреляцией, а не доказанной идемпотентностью Direct |
|
||||
|
||||
@@ -154,6 +161,15 @@ Realtime-событие `message.status` передаёт актуальные `
|
||||
| `sync` | `bitrix-sync` |
|
||||
| `settings` | internal settings bridge на `api-backend` для Keycloak SPI |
|
||||
| `sms` | `sms-service`; durable order/read API во внутренней сети |
|
||||
| `notifications` | Internal Create/Cancel уведомлений на `api-backend`; токен отдельный для каждого `source` |
|
||||
|
||||
## Жизненный цикл уведомления
|
||||
|
||||
- `lifecycle_status`: `active` / `closed`; бизнес-завершение, не soft delete.
|
||||
- `visibility`: `visible` / `hidden`; скрытое персональное уведомление отсутствует на главной, но остаётся в Центре, пока активно.
|
||||
- `close_reason`: `user_done`, `docs_submitted`, `offer_accepted`, `paid`, `expired`, `cancelled`.
|
||||
- `record_status='D'` означает только административное удаление ошибочной записи и не заменяет `lifecycle_status`.
|
||||
- `cta_action` и эффекты кнопок определяются справочниками; новый вид на существующих механиках добавляется данными.
|
||||
|
||||
## SMS-конфигурация
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ HAN Chat - приложение для мигрантов, где стартов
|
||||
- Вложения чата MVP: **только изображения и PDF** — см. [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md), «Разрешённые типы файлов чата».
|
||||
- SMS OTP вводится поэтапно: до production rollout действует явный mock (`KEYCLOAK_OTP_MOCK_ENABLED=true`); целевой real mode — Keycloak генерирует/локально проверяет OTP и создаёт durable order в `sms-service`, а worker асинхронно вызывает i-Digital Direct. Контракт и gates — [`module-11-idgtl-sms.md`](../modules/module-11-idgtl-sms.md).
|
||||
- Популярный вопрос при выборе **автоматически отправляется как сообщение**; если пользователь не авторизован — сначала согласия и OTP, затем отправка.
|
||||
- Notification Center v1 использует два контура: G — общие read-only гостевые кампании, P — персональные уведомления с состоянием в App DB. Виды, CTA, кнопки и палитра задаются каталогом данных.
|
||||
- Инструкция `install_app` всегда открывается во внешней новой вкладке; iframe/модалка для неё не используется.
|
||||
- Перечень таблиц и миграций App DB проектирует модуль `database` (и владельцы схем других сервисов); arch фиксирует только **разделение схем** PostgreSQL и контракты между сервисами.
|
||||
|
||||
## Пользовательские сценарии
|
||||
@@ -44,6 +46,7 @@ HAN Chat - приложение для мигрантов, где стартов
|
||||
- Keycloak: identity provider, OTP-only авторизация по номеру телефона.
|
||||
- SMS Service: internal durable order API, шаблоны и бессрочный журнал SMS; отдельный worker вызывает i-Digital Direct, callback обновляет только журнал.
|
||||
- api-backend: Python-приложение с REST API, realtime-доставкой сообщений и бизнес-логикой.
|
||||
- Notification producers: сервисы приватной сети, создающие/отменяющие персональные уведомления через Internal API с отдельным Bearer token на `source`; `producer_test` используется только для smoke API.
|
||||
- Nginx Reverse Proxy: единая публичная точка входа, HTTPS termination и маршрутизация на Keycloak/API/frontend web/Bitrix24.
|
||||
- Message Safety Service: отдельный сервис проверки входящих сообщений; вызов из API → `200 allow` | `403 deny` | `203 pending` + `task_id` (при `203` api-backend синхронно поллит task до финального вердикта, без очереди анализа на api-backend).
|
||||
- Bitrix24 Local App: локальное приложение, custom connector `han_mobile_app` для Bitrix24 Open Lines: чат, OAuth, webhook-события, маппинг `dialog_id` ↔ `bitrix_chat_id`.
|
||||
@@ -186,6 +189,9 @@ Frontend не должен:
|
||||
- circuit breaker + timeout budget на вызовы `message-safety` и `bitrix-local-app` (I2);
|
||||
- auth-aware rate limits для сообщений, пользовательских и сервисных операций;
|
||||
- аудит пользовательских действий;
|
||||
- публичный каталог/гостевые кампании, JWT API Notification Center и Internal Create/Cancel; дедупликацию по бессрочной паре `(source, external_id)`;
|
||||
- применение каталога уведомлений без ветвления по `notification_type`, пользовательские действия, документы и события `notification.created|updated|closed`;
|
||||
- expire job и очистку upload drafts. При скрытии TTL задаёт `date_expired` только если оно отсутствует; существующая дата не меняется;
|
||||
- единые ошибки и валидацию входных данных.
|
||||
|
||||
### Bitrix24 Local App
|
||||
@@ -458,11 +464,11 @@ api-backend не решает, sync или async нужна проверка в
|
||||
9. Frontend отображает сообщение оператора в чате.
|
||||
10. При получении от `bitrix-local-app` доменного события `dialog.closed` (Bitrix24 `ONIMCONNECTORDIALOGFINISH`) API переводит `Dialog.status` в `closed`.
|
||||
|
||||
## Документы компании (post-MVP)
|
||||
## Документы компании
|
||||
|
||||
Доставка документов из Bitrix24 в приложение **не входит в MVP** — см. [`!Backlog.md`](../../HAN_chat/!Backlog.md), п. 9.
|
||||
Notification Center v1 регистрирует переданные продюсером объекты `han-chat-documents` в реестре `documents` и связывает их с уведомлением. Это первый действующий канал наполнения будущего общего блока профиля; доставка из Bitrix24 остаётся вне scope.
|
||||
|
||||
В MVP блок профиля «Документы» и API `GET /api/v1/me/documents` зарезервированы; список может быть пустым. Контракт endpoint — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md).
|
||||
Скачивание выполняется owner-only по короткому presigned GET с audit. Для вида с `hide_on_document_download=true` первое скачивание **любого** связанного документа атомарно скрывает уведомление; последующие скачивания не меняют состояние. Если `date_expired` уже задано, оно сохраняется; TTL скрытия устанавливает дату только при её отсутствии.
|
||||
|
||||
## Профиль клиента
|
||||
|
||||
@@ -519,6 +525,7 @@ App DB — **локальный кэш** для UI. Двусторонний syn
|
||||
- Путь входит в `/api/*`; отдельный location `/realtime/*` в nginx **не** нужен.
|
||||
- Fallback: polling `GET /api/v1/dialogs/{dialog_id}/messages?after=...`.
|
||||
- События: новое сообщение, смена `delivery_status` / `safety_status`, смена `Dialog.status`.
|
||||
- Подписка расширена опциональным `notifications` (default `false`); канал пользователя передаёт `notification.created`, `notification.updated`, `notification.closed`, включая эхо инициатору. После reconnect источник истины — REST.
|
||||
|
||||
## Принципы безопасности
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
| `BITRIX_SYNC_SERVICE_TOKEN` | `bitrix-sync` | ops / мониторинг | `GET /internal/sync/v1/*` | `Authorization: Bearer` или `X-Service-Token` |
|
||||
| `KEYCLOAK_SETTINGS_BRIDGE_TOKEN` | `api-backend` | Keycloak SPI | `GET /internal/settings/v1/otp` | `Authorization: Bearer` |
|
||||
| `SMS_SERVICE_TOKEN` | `sms-service` | Keycloak SPI | `POST/GET /internal/sms/v1/*` | `Authorization: Bearer` |
|
||||
| `NOTIFICATIONS_TOKEN_<SOURCE>` | `api-backend` | соответствующий продюсер | `POST /internal/notifications/v1/*` | `Authorization: Bearer` |
|
||||
|
||||
Пары значений (должны совпадать):
|
||||
|
||||
@@ -39,6 +40,8 @@
|
||||
|
||||
Генерация: `openssl rand -hex 32`. Секреты не коммитить.
|
||||
|
||||
Для Notifications токен отдельный на каждый `source`: секрет существует только в deployment secret/env, а `notification_sources` хранит только hash. Токен разрешает identity продюсера и сравнивается constant-time; `source` в body обязан совпасть. Seed-источник `producer_test` и `NOTIFICATIONS_TOKEN_PRODUCER_TEST` предназначены для smoke Create/Cancel, не для бизнес-интеграции.
|
||||
|
||||
**Не путать с webhook-токенами** (публичные callback от Bitrix24, не internal service API):
|
||||
|
||||
| Переменная | Назначение |
|
||||
@@ -68,6 +71,15 @@
|
||||
| `POST /api/v1/dialogs/{dialog_id}/attachments/{attachment_id}/complete` | `api-backend` | Expo frontend | Подтверждение загрузки, проверка объекта в quarantine, фиксация checksum/metadata | JWT |
|
||||
| `GET /api/v1/dialogs/{dialog_id}/attachments/{attachment_id}/download-url` | `api-backend` | Expo frontend | Presigned URL вложения чата; обязателен audit | JWT |
|
||||
| `WS /api/v1/realtime` | `api-backend` | Expo frontend | Realtime-события чата, статусы доставки, unread | JWT |
|
||||
| `GET /api/v1/public/notifications` | `api-backend` | Expo frontend | Активные гостевые кампании G | public + CORS/rate limit |
|
||||
| `GET /api/v1/public/notification-types` | `api-backend` | Expo frontend | Публичный каталог видов с ETag, без серверных правил переходов | public + cache/rate limit |
|
||||
| `GET /api/v1/notifications?place=home\|center` | `api-backend` | Expo frontend | Персональная выборка P с серверными лимитами 7/15 и сортировкой | JWT |
|
||||
| `GET /api/v1/notifications/counter` | `api-backend` | Expo frontend | Счётчик непрочитанных в окне Центра | JWT |
|
||||
| `GET /api/v1/notifications/{id}` | `api-backend` | Expo frontend | Деталка активного собственного уведомления | JWT |
|
||||
| `POST /api/v1/notifications/{id}/read|hide|cta` | `api-backend` | Expo frontend | Идемпотентные действия и CTA по каталогу | JWT + rate limit |
|
||||
| `POST /api/v1/notifications/{id}/buttons/{button_code}` | `api-backend` | Expo frontend | Единое действие кнопки деталки | JWT + rate limit |
|
||||
| `GET /api/v1/notifications/{id}/documents/{document_id}/download-url` | `api-backend` | Expo frontend | Presigned GET + audit; первое скачивание любого связанного документа может скрыть уведомление | JWT |
|
||||
| `POST/GET/DELETE /api/v1/uploads/*` | `api-backend` | Expo frontend | Универсальные upload drafts клиента | JWT + rate limit |
|
||||
|
||||
Единый формат ошибки:
|
||||
|
||||
@@ -101,7 +113,10 @@
|
||||
| `403` | `forbidden` | Доступ запрещён и ресурс не скрывается | нет |
|
||||
| `404` | `not_found` | Ресурс не существует или принадлежит другому пользователю | нет |
|
||||
| `409` | `idempotency_key_reused` | Тот же `Idempotency-Key` с другим fingerprint | нет |
|
||||
| `409` | `notification_conflict` | `(source, external_id)` уже занят Create с другим fingerprint | нет |
|
||||
| `409` | `notification_closed` | Действие по уже закрытому уведомлению | нет |
|
||||
| `422` | `message_blocked` | Message Safety вернул final deny | нет |
|
||||
| `422` | `button_not_allowed` | Кнопка не привязана к виду уведомления | нет |
|
||||
| `429` | `rate_limit_exceeded` | Edge/API лимит превышен; должен быть `Retry-After`, если повтор допустим | да |
|
||||
| `503` | `dependency_unavailable` | Circuit open или недоступны safety/Bitrix/S3 | да |
|
||||
| `504` | `dependency_timeout` | Истёк timeout budget внешней зависимости | да |
|
||||
@@ -313,10 +328,10 @@ Transport: **WebSocket** over HTTPS (`wss://`), JWT в query `?access_token=` и
|
||||
3. Клиент отправляет подписку:
|
||||
|
||||
```json
|
||||
{ "type": "subscribe", "dialog_ids": ["uuid"] }
|
||||
{ "type": "subscribe", "dialog_ids": ["uuid"], "notifications": true }
|
||||
```
|
||||
|
||||
4. Сервер отвечает `{ "type": "subscribed", "dialog_ids": ["uuid"] }`.
|
||||
4. Сервер отвечает `{ "type": "subscribed", "dialog_ids": ["uuid"], "notifications": true }`. Поле `notifications` опционально, default `false`; старые chat-клиенты совместимы.
|
||||
|
||||
**События сервер → клиент:**
|
||||
|
||||
@@ -325,12 +340,17 @@ Transport: **WebSocket** over HTTPS (`wss://`), JWT в query `?access_token=` и
|
||||
| `message.new` | Новое сообщение в диалоге | `dialog_id`, `message` (DTO как в REST) |
|
||||
| `message.status` | Смена `safety_status` / `delivery_status` | `dialog_id`, `message_id`, `safety_status`, `delivery_status` |
|
||||
| `dialog.status` | Смена `Dialog.status` | `dialog_id`, `status` |
|
||||
| `notification.created` | Создано персональное уведомление | `event_id`, `occurred_at`, `notification`, `unread_count` |
|
||||
| `notification.updated` | Изменено состояние/документы уведомления | `event_id`, `occurred_at`, `notification_id`, изменённые поля, `unread_count` |
|
||||
| `notification.closed` | Уведомление закрыто | `event_id`, `occurred_at`, `notification_id`, `close_reason`, `unread_count` |
|
||||
|
||||
**Reconnect:**
|
||||
|
||||
- exponential backoff: 1s → 2s → 4s → … max 30s;
|
||||
- после reconnect — повтор `subscribe` с актуальным списком `dialog_ids`;
|
||||
- при недоступности WS > 30s — fallback на polling `GET .../messages?after=<cursor>`.
|
||||
- при недоступности WS > 30s — fallback на polling чата и, при подписке на уведомления, `GET /api/v1/notifications` + `/counter` раз в 60 секунд.
|
||||
|
||||
События уведомлений публикуются в `han:rt:user:{user_id}` на все соединения, включая инициатора. Массовый expire job не отправляет событие на каждую запись; reconnect/polling всегда выполняет REST reconcile.
|
||||
|
||||
**Ping:** сервер может слать `{ "type": "ping" }` каждые 30s; клиент отвечает `{ "type": "pong" }`.
|
||||
|
||||
@@ -350,6 +370,19 @@ Transport: **WebSocket** over HTTPS (`wss://`), JWT в query `?access_token=` и
|
||||
- internal API версионируется тем же правилом (`/internal/{mnemonic}/v2/...`);
|
||||
- OpenAPI генерируется или поддерживается вручную — на усмотрение модуля, но файл обязателен в DoD (arch-05).
|
||||
|
||||
## Producers ↔ api-backend: Notifications
|
||||
|
||||
| Контракт | Назначение | Защита |
|
||||
|---|---|---|
|
||||
| `POST /internal/notifications/v1/notifications` | Create персонального уведомления | private network + Bearer token конкретного `source` |
|
||||
| `POST /internal/notifications/v1/notifications/cancel` | Cancel по `(source, external_id)` с `cancelled` или `paid` | то же |
|
||||
|
||||
Пара `(source, external_id)` уникальна бессрочно и заменяет `Idempotency-Key`: одинаковый canonical fingerprint возвращает существующую запись с `200`, другой — `409 notification_conflict`. Cancel идемпотентен; чужой `source` не раскрывается.
|
||||
|
||||
Каталог, валидация `details`, обязательных полей CTA и эффектов кнопок применяются по данным справочников без ветвления по `notification_type`. Инструкция `install_app` всегда возвращает открытие `instruction_url` в новой вкладке, без iframe/модалки.
|
||||
|
||||
При скрытии действие всегда ставит `visibility=hidden`. TTL из вида/default применяется только если `date_expired IS NULL`; уже заданная продюсером дата сохраняется. Первое успешное получение download URL для **любого** связанного документа считается началом скачивания и, при `hide_on_document_download=true`, один раз скрывает уведомление; последующие документы состояние не меняют.
|
||||
|
||||
## Keycloak SPI ↔ api-backend settings bridge
|
||||
|
||||
Keycloak SPI получает product limits OTP из `app_settings` через internal endpoint, а не через прямой доступ к `han_app`.
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
- `/bitrix/sync/*` (public: webhook CRM sync для `bitrix-sync`) → `bitrix-sync`;
|
||||
- exact `POST /callbacks/idgtl/sms` → `sms-service`; остальные методы и SMS paths не публикуются;
|
||||
- web-сборка frontend или прокси на dev-сервер;
|
||||
- `/internal/openlines/*`, `/internal/safety/*`, `/internal/sync/*`, `/internal/sms/*` **не публикуются** наружу — доступны только из внутренней Docker-сети.
|
||||
- `/internal/openlines/*`, `/internal/safety/*`, `/internal/sync/*`, `/internal/sms/*`, `/internal/notifications/*` **не публикуются** наружу — доступны только из внутренней Docker-сети.
|
||||
- Никакой другой `nginx` (ни в контейнере сервиса, ни на хосте) не терминирует внешний HTTPS для backend-контура. Site-конфиг `tohin.ru` на хосте, если используется, должен проксировать весь трафик на корневой `nginx` контейнера, а не на порты отдельных сервисов напрямую.
|
||||
|
||||
### Структура compose через `include`
|
||||
@@ -158,6 +158,8 @@ Python FastAPI backend.
|
||||
- принимает forward нормализованных событий оператора от `bitrix-local-app`;
|
||||
- поддерживает realtime endpoint для сообщений оператора;
|
||||
- работает с Selectel S3 для файлов и документов;
|
||||
- обслуживает Notification Center G/P и Internal Create/Cancel; token каждого продюсера передаётся через env/secret, в App DB хранится только hash;
|
||||
- имеет отдельные процессы expire job (ежедневно 00:01 UTC, advisory lock) и cleanup upload drafts/S3; они используют тот же immutable image и не публикуют порты;
|
||||
- экспортирует traces/logs в `otel-collector`;
|
||||
- не хранит состояние внутри контейнера.
|
||||
|
||||
@@ -300,6 +302,8 @@ Identity provider. **Обязателен** в compose-контуре с пер
|
||||
|
||||
Корневой `backend/.env` читается всеми сервисами compose через `${VAR}` в сервисных `docker-compose.yml`. Канонический `.env.example` и `app_settings` — в [`arch-04-settings-and-content.md`](arch-04-settings-and-content.md); контракты service tokens — в [`arch-02-api-contracts.md`](arch-02-api-contracts.md).
|
||||
|
||||
Для smoke-продюсера обязателен `NOTIFICATIONS_TOKEN_PRODUCER_TEST`; это secret, а не `app_settings`. Compose передаёт его только `api-backend` и notification workers. Зарегистрированные entrypoints: `han-notification-expire-worker` и `han-notification-draft-cleanup-worker`; выдуманный command без project script в deployment запрещён.
|
||||
|
||||
## HTTPS и TLS
|
||||
|
||||
Соответствует [`arch-01-system-architecture.md`](arch-01-system-architecture.md), «Принципы безопасности» (HTTPS, TLS, HSTS). Инфраструктурная реализация:
|
||||
@@ -330,6 +334,7 @@ Identity provider. **Обязателен** в compose-контуре с пер
|
||||
- слабые шифры запрещены на уровне `nginx`;
|
||||
- `nginx` скрывает `Server`, `X-Powered-By` и аналогичные технологические заголовки;
|
||||
- security headers: `Strict-Transport-Security`, `X-Content-Type-Options`, `Referrer-Policy`, `Content-Security-Policy` для web-приложения;
|
||||
- инструкция по установке всегда открывается новой вкладкой, поэтому CSP SPA задаёт `frame-src 'none'`; allow-list iframe для инструкций отсутствует;
|
||||
- секретный ключ сертификата не коммитится в репозиторий;
|
||||
- использовать сертификаты доверенного CA; автоматизировать выпуск и продление (Let's Encrypt + reload `nginx`);
|
||||
- закрыть прямой доступ к внутренним портам контейнеров извне.
|
||||
@@ -367,7 +372,7 @@ Rate limits должны быть распределены по двум сло
|
||||
`nginx`:
|
||||
|
||||
- ограничивает частоту запросов до попадания в API;
|
||||
- держит отдельные зоны лимитов для `/auth`, `/api`, public endpoints, fallback polling и download endpoints;
|
||||
- держит отдельные зоны лимитов для `/auth`, `/api`, public endpoints, fallback polling, download endpoints, чтения/действий/загрузок Notification Center;
|
||||
- ограничивает `client_max_body_size`;
|
||||
- ограничивает загрузку файлов лимитом 5 МБ; `client_max_body_size` должен быть чуть выше бизнес-лимита для учета overhead запроса;
|
||||
- применяет `limit_req` для endpoint авторизации и fallback polling;
|
||||
@@ -435,7 +440,8 @@ WAF не заменяет обязательные лимиты, валидац
|
||||
6. `message-safety`.
|
||||
7. `bitrix-local-app`.
|
||||
8. `bitrix-sync`.
|
||||
9. `nginx`.
|
||||
9. Notification expire/cleanup workers после готовности `api-backend` и регистрации их entrypoints.
|
||||
10. `nginx`.
|
||||
|
||||
Порядок rollout SMS подробнее задаёт module-11/module-10. Зависимости запуска не образуют цикл: Keycloak стартует при недоступном `sms-service`; это блокирует только новые real-mode orders, а verify уже active challenges продолжается по snapshot.
|
||||
|
||||
|
||||
@@ -92,6 +92,8 @@ Managed PostgreSQL **поднимается до** развёртывания п
|
||||
| Consent | `consent.personal_data.*`, `consent.privacy_policy.document_url`, `consent.user_agreement.*`, `consent.marketing.*` |
|
||||
| Файлы чата | `chat.attachments.*` |
|
||||
| Rate limits (app) | `rate_limit.message_send.*`, `rate_limit.download_url.*`, `rate_limit.public_endpoints.*`, `rate_limit.login.*` |
|
||||
| Notification Center | `notification.home.max_items`, `notification.center.max_items`, `notification.carousel.*`, `notification.hidden.default_ttl_days`, `notification.documents.max_files`, `notification.expire_job.run_at`, `notification.upload_draft.ttl_days` |
|
||||
| Rate limits (notifications) | `rate_limit.notifications_read.per_user`, `rate_limit.notifications_action.per_user`, `rate_limit.notification_upload.per_user`, `rate_limit.notifications_public.per_ip` |
|
||||
| UX | `ux.session.idle_timeout_minutes` |
|
||||
| Security | `security.cors.allowed_origins`, `security.public_cache.max_age_seconds` |
|
||||
|
||||
@@ -135,6 +137,19 @@ rate_limit.message_send.per_dialog=20/minute
|
||||
rate_limit.download_url.per_user=60/hour
|
||||
rate_limit.public_endpoints.per_ip=60/minute
|
||||
rate_limit.login.per_ip=10/minute
|
||||
rate_limit.notifications_read.per_user=120/minute
|
||||
rate_limit.notifications_action.per_user=60/minute
|
||||
rate_limit.notification_upload.per_user=20/minute
|
||||
rate_limit.notifications_public.per_ip=60/minute
|
||||
|
||||
notification.home.max_items=7
|
||||
notification.center.max_items=15
|
||||
notification.carousel.autoplay_enabled=false
|
||||
notification.carousel.autoplay_interval_ms=5000
|
||||
notification.hidden.default_ttl_days=3
|
||||
notification.documents.max_files=10
|
||||
notification.expire_job.run_at=00:01
|
||||
notification.upload_draft.ttl_days=7
|
||||
|
||||
ux.session.idle_timeout_minutes=30
|
||||
|
||||
@@ -217,6 +232,10 @@ NGINX_RATE_LIMIT_AUTH=10r/m
|
||||
NGINX_RATE_LIMIT_DOWNLOADS=30r/m
|
||||
NGINX_RATE_LIMIT_PUBLIC=60r/m
|
||||
NGINX_RATE_LIMIT_POLLING=60r/m
|
||||
NGINX_RATE_LIMIT_NOTIFICATIONS_READ=120r/m
|
||||
NGINX_RATE_LIMIT_NOTIFICATIONS_ACTION=60r/m
|
||||
NGINX_RATE_LIMIT_NOTIFICATION_UPLOAD=20r/m
|
||||
NGINX_RATE_LIMIT_NOTIFICATIONS_PUBLIC=60r/m
|
||||
|
||||
# =============================================================================
|
||||
# Keycloak (mock остаётся true до controlled SMS cutover)
|
||||
@@ -254,6 +273,7 @@ BITRIX_SYNC_SERVICE_TOKEN=change-me
|
||||
KEYCLOAK_SETTINGS_BRIDGE_TOKEN=change-me
|
||||
SMS_SERVICE_TOKEN=change-me
|
||||
KEYCLOAK_SMS_SERVICE_TOKEN=change-me
|
||||
NOTIFICATIONS_TOKEN_PRODUCER_TEST=change-me
|
||||
|
||||
# =============================================================================
|
||||
# SMS provider (URL и секреты; runtime-параметры — sms.sms_setting)
|
||||
@@ -345,6 +365,8 @@ presigned URL и CORS Selectel; path-style адресация не поддер
|
||||
|
||||
**Webhook-токены** (публичные callback, не service API): `BITRIX_APPLICATION_TOKEN`, `BITRIX_SYNC_WEBHOOK_TOKEN`.
|
||||
|
||||
`NOTIFICATIONS_TOKEN_<SOURCE>` — индивидуальный секрет продюсера Internal Notifications API. Для seed/smoke используется `NOTIFICATIONS_TOKEN_PRODUCER_TEST`; secret хранится только в deployment env/secret, а `notification_sources.token_hash` — только hash. Инструкция не имеет `notification.instruction.allowed_hosts`: она всегда открывается в новой вкладке, iframe-режима нет.
|
||||
|
||||
## Namespace переменных Bitrix
|
||||
|
||||
- `bitrix-local-app`: `BITRIX_CLIENT_*`, `BITRIX_CONNECTOR_*`, `BITRIX_PUBLIC_BASE_URL`, `BITRIX_DATABASE_URL`, `BITRIX_API_FORWARD_URL`, `BITRIX_APPLICATION_TOKEN` + service tokens.
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
- Перечень таблиц, полей, индексов и миграций **определяет модуль-владелец** (`database`, `api-backend`, `bitrix-sync`, `message-safety`, `bitrix-local-app`), а не arch-*.
|
||||
- Архитектура фиксирует **разделение схем** и общие подходы к ведению баз данных, которые должны соблюдаться при проработке модулей.
|
||||
- У каждой основной **прикладной** сущности должен быть `record_status`. Базовые статусы: `A` — active, `D` — deleted.
|
||||
- `record_status` выражает только административное наличие строки. Доменное завершение (например `Notification.lifecycle_status='closed'`) хранится отдельно и не переводит запись в `D`.
|
||||
- Физическое удаление строк прикладных сущностей запрещено. Если нужно удалить сущность, сервис меняет `record_status` с `A` на `D`.
|
||||
- При смене статуса на `D` сервис обязан заполнить `status_changed_at` и `status_change_reason`.
|
||||
- Все сервисы при чтении бизнес-данных по умолчанию запрашивают только `record_status = 'A'`.
|
||||
@@ -74,6 +75,8 @@ Raw OTP запрещено хранить в открытом виде: это
|
||||
- поведение при повторной доставке webhook;
|
||||
- таймауты и retry/backoff.
|
||||
|
||||
Для Notification Center обязательны contract tests каталога без ветвления по виду, бессрочной дедупликации `(source, external_id)`, изоляции producer tokens, TTL с сохранением существующего `date_expired`, первого скачивания любого связанного документа и открытия instruction только в новой вкладке.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Модуль считается готовым, если:
|
||||
@@ -85,6 +88,7 @@ Raw OTP запрещено хранить в открытом виде: это
|
||||
- обновлены seed `app_settings` и `.env.example`, если добавлялись настройки, service tokens, лимиты или feature flags;
|
||||
- добавлены тесты;
|
||||
- сервис запускается в Docker Compose;
|
||||
- worker, указанный в Compose/runbook, имеет реально зарегистрированный entrypoint в image; deployment не может заранее выдумывать имя команды;
|
||||
- все изменяемые параметры вынесены из кода;
|
||||
- логи содержат `request_id`, `trace_id` и **`ux_session_id`** (если передан в запросе);
|
||||
- нет секретов, raw OTP и PII в логах;
|
||||
|
||||
Reference in New Issue
Block a user