Добавлены уведомления

This commit is contained in:
mi
2026-07-27 17:36:53 +03:00
parent a072005164
commit 958fba5f3e
149 changed files with 6371 additions and 110 deletions
+29 -3
View File
@@ -32,6 +32,7 @@
- аудит, метрики, трассировку, health и readiness;
- чтение `app_settings`, `text_resources`, `popular_questions`;
- internal settings bridge для Keycloak SPI.
- Notification Center G/P: каталог, public/JWT/internal API, lifecycle, документы, realtime и фоновые задачи.
### 2.2. Сервис не отвечает за
@@ -150,6 +151,8 @@ api-backend/
safety_recovery.py
quarantine_cleanup.py
inbound_attachment.py
notification_expire.py
notification_draft_cleanup.py
settings.py
alembic/
tests/
@@ -455,6 +458,14 @@ Complete request: `{"checksum":"sha256:<64-lowercase-hex>"}`. Response `200` в
Init и complete должны быть idempotent по состоянию attachment; повтор complete с тем же checksum возвращает прежний результат, с другим — `409 resource_state_conflict`.
### 6.8. Notification Center
Контракты путей и DTO — arch-02 и `notification-requirements.md`. Реализация читает `notification_types` и реестры CTA/кнопок/цветов; ветвление по `notification_type` запрещено. Home/center/counter применяют серверные лимиты 7/15, эффективный приоритет `COALESCE(priority_override, type.priority)` и ownership.
Действие скрытия всегда ставит `visibility='hidden'`. Если `date_expired` уже задано, оно сохраняется; TTL вида/default устанавливает `date_expired=now()+N days` только при `NULL`. Первое скачивание любого связанного документа при `hide_on_document_download=true` атомарно применяет этот эффект один раз.
`install_app_prompt` возвращает только внешнее действие открытия `instruction_url` в новой вкладке. Режимы iframe/modal и allow-list для них отсутствуют.
## 7. Internal endpoints
### 7.1. Inbox Open Lines
@@ -524,6 +535,13 @@ Private network + Bearer `KEYCLOAK_SETTINGS_BRIDGE_TOKEN`.
Все вызовы: private network, `Authorization: Bearer <BITRIX_LOCAL_APP_INTERNAL_TOKEN>`, `X-Request-ID`, `traceparent`, idempotency по `message_id`.
### 7.4. Internal Notifications
- `POST /internal/notifications/v1/notifications` — Create;
- `POST /internal/notifications/v1/notifications/cancel` — Cancel.
Bearer token отдельный для каждого `source`; secret приходит из `NOTIFICATIONS_TOKEN_<SOURCE>`, в `notification_sources` хранится только hash, сравнение constant-time. `producer_test`/`NOTIFICATIONS_TOKEN_PRODUCER_TEST` служат smoke API. `(source, external_id)` уникальна бессрочно: одинаковый fingerprint → `200`, другой → `409 notification_conflict`.
## 8. JWT, JWKS и phone claims
### 8.1. Validation
@@ -972,8 +990,8 @@ Outbox worker и synchronous first attempt используют один dispatc
```json
{"type":"connected","server_time":"2026-07-09T12:00:00Z"}
{"type":"subscribe","dialog_ids":["uuid"]}
{"type":"subscribed","dialog_ids":["uuid"]}
{"type":"subscribe","dialog_ids":["uuid"],"notifications":true}
{"type":"subscribed","dialog_ids":["uuid"],"notifications":true}
```
Events:
@@ -981,9 +999,10 @@ Events:
- `message.new` с `MessageResponse`;
- `message.status`;
- `dialog.status`;
- `notification.created`, `notification.updated`, `notification.closed` через `han:rt:user:{user_id}`, включая эхо инициатору;
- `ping`; client отвечает `pong`.
Каждый subscribe проверяет ownership всех dialogs; чужие id не раскрываются. Лимиты: max connections/user, max subscriptions/connection, max frame bytes, subscribe rate. Slow consumer: bounded queue; при переполнении connection закрывается с retryable code, клиент восстанавливается polling.
`notifications` опционально и по умолчанию `false`. Каждый subscribe проверяет ownership всех dialogs; чужие id не раскрываются. Лимиты: max connections/user, max subscriptions/connection, max frame bytes, subscribe rate. Slow consumer: bounded queue; при переполнении connection закрывается с retryable code, клиент восстанавливается polling.
### 17.2. Delivery semantics
@@ -1025,6 +1044,7 @@ Runtime refresh: poll `MAX(updated_at)` каждые 30 секунд; новый
- `KEYCLOAK_SETTINGS_BRIDGE_TOKEN`;
- `SELECTEL_S3_ENDPOINT_URL`, три bucket names, write access key/secret;
- `OTEL_EXPORTER_OTLP_ENDPOINT`.
- `NOTIFICATIONS_TOKEN_PRODUCER_TEST` и последующие `NOTIFICATIONS_TOKEN_<SOURCE>`; plaintext не сохраняется в БД/логах.
`SELECTEL_S3_QUARANTINE_READ_*` принадлежит `message-safety`, не должен передаваться контейнеру API. Новые env сначала документируются в arch-04.
@@ -1039,6 +1059,8 @@ Runtime refresh: poll `MAX(updated_at)` каждые 30 секунд; новый
| create dialog/message | user + dialog + IP |
| attachment init/complete | user + dialog |
| download URL | user + resource group |
| notifications read/action/upload | user + IP / user / user |
| notifications public | IP hash |
| WS connect/subscribe | user + IP |
| internal inbox/settings | service identity + source network |
@@ -1119,6 +1141,8 @@ Graceful shutdown прекращает принимать новые requests,
- `attachment.upload_initialized/completed/promoted/rejected`;
- `attachment.download_url_issued`;
- `document.download_url_issued`;
- `notification.created/read/hidden/cta_invoked/button_pressed/closed`;
- `notification.document.download_url_issued`, `notification.documents.submitted`, `notification.expired_batch`;
- `openlines.inbox_applied`;
- повторные severe rate limit violations.
@@ -1199,6 +1223,8 @@ Startup:
8. start workers;
9. mark ready.
Notification expire запускается ежедневно в `notification.expire_job.run_at` с PostgreSQL advisory lock и set-based update; массовые WS-события не публикует. Cleanup удаляет просроченные drafts и соответствующие S3-объекты идемпотентно. Отдельные Compose-процессы используют зарегистрированные scripts `han-notification-expire-worker` и `han-notification-draft-cleanup-worker`; общий `han-cleanup-worker` сохраняет прежнюю очистку quarantine.
Nginx маршрутизирует `/api/*`, включая WS `/api/v1/realtime`. Для message POST `proxy_read_timeout >= MESSAGE_SAFETY_TASK_POLL_MAX_SEC + 30s`. Internal paths наружу не маршрутизируются.
## 25. Ключевые user flows
+8 -1
View File
@@ -23,7 +23,7 @@
| exact `/health/live`, `/health/ready` | `bitrix-local-app:8080` | по умолчанию не публикуются; только при явно выбранной ops/monitoring policy |
| `/` | static SPA либо Expo dev upstream | `try_files` fallback |
`/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files запрещены exact prefix response `404` (допустим `403`, но единообразно выбран `404`). Никакого fallback internal path в SPA или общий proxy. `message-safety` и `/internal/sms/*` не имеют публичного route.
Notification paths внутри `/api/` имеют отдельные edge-зоны: public catalog/campaigns, JWT read, actions, uploads и downloads. `/internal/`, `/_internal/`, Redis/OTLP/admin/status/config files запрещены exact prefix response `404` (допустим `403`, но единообразно выбран `404`). Никакого fallback internal path в SPA или общий proxy. `message-safety`, `/internal/sms/*` и `/internal/notifications/*` не имеют публичного route.
## 3. Upstreams
@@ -118,6 +118,10 @@ traceparent: входной валидный либо новый согласн
- `api`: общий API;
- `polling`: GET messages fallback;
- `downloads`: issuance URL;
- `notifications_read`: list/counter/detail;
- `notifications_action`: read/hide/CTA/button;
- `notification_upload`: универсальные upload drafts;
- `notifications_public`: guest notifications и каталог видов;
- `bitrix_callbacks`: мягкий burst для повторов;
- `idgtl_callbacks`: отдельный bounded burst, учитывающий повтор каждые 5 минут в течение суток;
- `ws_connect`: handshake;
@@ -158,6 +162,7 @@ CORS — exact allow-list из согласованного deploy config; appli
- connect-src `'self'` `https:` к разрешённому S3 endpoint и `wss:` текущего host;
- img-src `'self' data: blob:` и разрешённые signed HTTPS resources;
- object-src `'none'`, base-uri `'self'`, frame-ancestors `'none'`;
- frame-src `'none'`: инструкция `install_app` всегда открывается в новой вкладке, iframe/модалка не поддерживается;
- script-src без `unsafe-eval` production; nonce/hash при необходимости;
- style-src policy согласовать с Expo build, постепенно исключить unsafe-inline.
@@ -260,6 +265,8 @@ curl -i https://tohin.ru/internal/safety/v1/messages/check
- upstream down/timeout, failed reload, renewal rehearsal;
- logs не содержат secrets/query tokens.
- allowed Direct callback проходит; wrong IP/method и любой `/internal/sms/*` отклоняются; Authorization отсутствует в логах.
- `/internal/notifications/*` снаружи всегда `404`; notification read/action/upload/public routes используют свои зоны и возвращают `429`.
- CSP содержит `frame-src 'none'`; инструкция проверяется как новая вкладка без embedded content.
## 19. Definition of Done
+4
View File
@@ -25,6 +25,8 @@
Упоминания полноценного sync в arch-01/02/03 описывают будущую целевую границу, а не функциональность этого stub. Расширение требует новой версии спецификации, migrations/GRANT, OpenAPI и contract tests.
Notification Center добавляет в `han_app.sync_queue` task type `document.client_uploaded`, но stub его **не claim-ит и не подтверждает**: задача остаётся накопленной для будущей реализации. Producer — DB trigger на `client_documents`, dedup key — `client_document_id`; payload содержит `client_document_id`, `user_id`, `context_type`, `context_id`, `submission_id`, bucket/object key и безопасные metadata файла, без presigned URL.
## 2. Технологический профиль
- Python 3.12+, FastAPI, Pydantic v2, Uvicorn.
@@ -276,6 +278,7 @@ Managed init уже создаёт:
К `han_app` **не выдаются GRANT** до реализации полноценной CRM sync. Это сознательно строже общего будущего требования. Когда появится sync:
- GRANT выдаётся точечно на `sync_queue`, mapping и необходимые columns;
- для `document.client_uploaded` добавляется read только `client_documents` и обработчик с идемпотентностью по `client_document_id`;
- запрещён broad schema write;
- GUC/write-back и trigger contract проходят integration tests;
- обновляются deploy scripts и module spec.
@@ -452,6 +455,7 @@ Shutdown не пишет бизнес-данные и не требует БД.
- structured logs/metrics/traces без secrets;
- OpenAPI, Docker healthcheck и tests готовы;
- future CRM boundary документирована и не реализована скрыто.
- `document.client_uploaded` документирован как накопляемая stub-задача и не выдаётся за обработанный status.
## 18. Решения, допущения и TBD