102 lines
5.1 KiB
Markdown
102 lines
5.1 KiB
Markdown
# HAN Chat API backend
|
||
|
||
FastAPI-сервис публичного API, internal Open Lines/settings API, WebSocket realtime,
|
||
Message Safety orchestration, S3 lifecycle и PostgreSQL workers.
|
||
|
||
## Запуск
|
||
|
||
Python 3.12+:
|
||
|
||
```bash
|
||
python -m venv .venv
|
||
.venv/Scripts/pip install -e ".[dev]"
|
||
alembic upgrade head
|
||
uvicorn app.main:app --host 0.0.0.0 --port 8000
|
||
```
|
||
|
||
Миграции выполняются отдельным deployment step. Приложение не применяет DDL при старте.
|
||
|
||
Workers запускаются независимо:
|
||
|
||
```bash
|
||
han-delivery-worker
|
||
han-safety-worker
|
||
han-cleanup-worker
|
||
han-notification-expire-worker
|
||
han-notification-draft-cleanup-worker
|
||
```
|
||
|
||
## Переменные окружения
|
||
|
||
Сервис читает только инфраструктурные параметры и секреты из корневого `backend/.env`.
|
||
Локальный `.env` не коммитится. Business settings создаются миграцией в
|
||
`han_app.app_settings`.
|
||
|
||
Обязательны:
|
||
|
||
- `APP_ENV`, `API_PORT`, `LOG_LEVEL`, `DATABASE_URL`;
|
||
- `REDIS_URL`, `REDIS_REALTIME_URL`;
|
||
- `KEYCLOAK_PUBLIC_URL`, `KEYCLOAK_INTERNAL_URL`, `KEYCLOAK_REALM`,
|
||
`KEYCLOAK_AUDIENCE`;
|
||
- `MESSAGE_SAFETY_URL`, `MESSAGE_SAFETY_SERVICE_TOKEN`,
|
||
`MESSAGE_SAFETY_CA_FILE`, `MESSAGE_SAFETY_API_PREFIX=/internal/safety/v2`,
|
||
`MESSAGE_SAFETY_POST_TIMEOUT_SEC`, `MESSAGE_SAFETY_TASK_POLL_INTERVAL_SEC`,
|
||
`MESSAGE_SAFETY_TASK_POLL_MAX_SEC`,
|
||
`MESSAGE_SAFETY_CIRCUIT_FAILURE_THRESHOLD`, `MESSAGE_SAFETY_CIRCUIT_OPEN_SEC`;
|
||
- `BITRIX_LOCAL_APP_BASE_URL`, `BITRIX_LOCAL_APP_INTERNAL_TOKEN`,
|
||
`BITRIX_API_INBOX_TOKEN`, `BITRIX_LOCAL_APP_HTTP_TIMEOUT_SEC`,
|
||
`BITRIX_LOCAL_APP_CIRCUIT_FAILURE_THRESHOLD`,
|
||
`BITRIX_LOCAL_APP_CIRCUIT_OPEN_SEC`;
|
||
- `KEYCLOAK_SETTINGS_BRIDGE_TOKEN`;
|
||
- `SELECTEL_S3_ENDPOINT_URL`, `SELECTEL_S3_BUCKET_DOCUMENTS`,
|
||
`SELECTEL_S3_BUCKET_ATTACHMENTS`, `SELECTEL_S3_BUCKET_QUARANTINE`,
|
||
`SELECTEL_S3_ACCESS_KEY`, `SELECTEL_S3_SECRET_KEY`;
|
||
- `CURSOR_HMAC_SECRET` — случайный секрет не короче 32 байт;
|
||
- `NOTIFICATIONS_TOKEN_PRODUCER_TEST` — отдельный bearer token тестового
|
||
продюсера Notification Center; в БД синхронизируется только SHA-256 hash;
|
||
- `OTEL_EXPORTER_OTLP_ENDPOINT` — опциональный endpoint collector.
|
||
|
||
Токены генерируются `openssl rand -hex 32`. `MESSAGE_SAFETY_SERVICE_TOKEN`
|
||
сохраняется как caller secret API backend; S3 credentials сервису Safety не
|
||
передаются. В production подключение PostgreSQL должно использовать TLS. Target
|
||
задаётся через `MESSAGE_SAFETY_URL=https://<private-vm2-name>:8443`
|
||
(`processing.internal` — только пример), API prefix `/internal/safety/v2`;
|
||
certificate проверяется по CA из
|
||
`MESSAGE_SAFETY_CA_HOST_PATH`, plaintext HTTP запрещён.
|
||
|
||
Smoke-сценарий `producer_test`: отправить `POST
|
||
/internal/notifications/v1/notifications` с `Authorization: Bearer
|
||
$NOTIFICATIONS_TOKEN_PRODUCER_TEST`, `source=producer_test` и уникальным
|
||
`external_id`; повтор того же тела вернёт `200`. Затем передать ту же пару
|
||
`source`/`external_id` в `POST /internal/notifications/v1/notifications/cancel`
|
||
с `close_reason=cancelled`; повторная отмена также вернёт `200`.
|
||
|
||
## Проверки
|
||
|
||
```bash
|
||
ruff check .
|
||
ruff format --check .
|
||
mypy app
|
||
pytest
|
||
```
|
||
|
||
`/health/live` проверяет процесс. `/health/ready` проверяет критические
|
||
зависимости read API. Remote Message Safety не выключает чтение/общую readiness:
|
||
send endpoint отдельно проверяет требуемую capability и fail-closed возвращает
|
||
`503`, если ВМ2 недоступна.
|
||
|
||
## Публичная конфигурация мобильных обновлений
|
||
|
||
`GET /api/v1/public/app-config` возвращает строгий объект `mobile_update` с
|
||
политиками `google_play`, `rustore` и `app_store`. Для включённого магазина
|
||
обязательны `latest_build`, `minimum_build`, `latest_version` и HTTPS `store_url`;
|
||
`minimum_build` не может превышать `latest_build`. Для отключённого магазина
|
||
эти поля возвращаются как `null`. Опциональное `release_notes` также возвращается
|
||
как `null`, если в settings задана пустая строка. Канонический URL RuStore:
|
||
`https://www.rustore.ru/catalog/app/ru.han.chat`.
|
||
|
||
Ответ содержит `ETag`, поддерживает `If-None-Match` с ответом `304` и в
|
||
production-like конфигурации кэшируется клиентом и nginx 60 секунд. Изменения
|
||
политики применяются через штатный идемпотентный `seed-settings`; некорректные
|
||
пороги, типы и URL блокируют загрузку settings.
|