Add example .env files and configuration documentation for ams-sync, auth-flow, bim, cde, comparisons, django, document-link, flows, iam, inspections services.

This commit is contained in:
emelinda 2026-07-14 19:23:02 +03:00
parent cce76e48a9
commit 28478d9c86
58 changed files with 20076 additions and 0 deletions

View File

@ -0,0 +1,85 @@
# =============================================================================
# AMS-Sync — пример конфигурации (.env)
# Версия: 1.0.0
# Скопируйте в .env и заполните значения.
#
# Сервис имеет два режима запуска:
# - `run` — демон-потребитель Kafka (CMD контейнера), использует блоки
# App / Kafka / Zitadel / Logger / Healthcheck (+ Auth);
# - CLI — команды migrate / sync / diff (запускаются вручную),
# дополнительно используют блоки Auth / User server / БД sarex.
# Значения по умолчанию проставлены там, где они заданы в коде.
# =============================================================================
# --- Приложение (без префикса) ---
# LOCAL | STAGE | PREPROD | PRODUCTION
ENVIRONMENT=LOCAL
# Топик Kafka с событиями пользователей
AMS_SYNC_TOPIC=ams-sync
# ID организации-хоста в Zitadel (обязателен)
HOST_ORGANIZATION_ID=
# При True входящие model_created обрабатываются через import_user (с верификацией)
VERIFY_USERS=False
# --- Kafka (префикс KAFKA_) ---
# JSON-массив адресов брокеров, напр. ["host:9091"]
KAFKA_BOOTSTRAP_SERVERS=
KAFKA_SASL_PLAIN_USERNAME=
KAFKA_SASL_PLAIN_PASSWORD=
# Путь к CA-сертификату для SSL-подключения к Kafka
KAFKA_SSL_CAFILE=
# PLAINTEXT | SSL | SASL_PLAINTEXT | SASL_SSL
KAFKA_SECURITY_PROTOCOL=SASL_SSL
# PLAIN | SCRAM-SHA-256 | SCRAM-SHA-512
KAFKA_SASL_MECHANISM=SCRAM-SHA-512
# --- Zitadel / AMS (префикс ZITADEL_) ---
# Хост Zitadel (обязателен для адаптера пользователей)
ZITADEL_HOST=https://idp.dev.stage.sarex.io
# Service-токен доступа к API Zitadel (обязателен)
ZITADEL_SERVICE_ACCESS_TOKEN=
# ВНИМАНИЕ: переменную читают сразу несколько классов с разным типом
# - адаптер пользователей ожидает int (минуты), напр. 10
# - адаптеры организаций/грантов ожидают строку, напр. "10m"
# Задавайте int, иначе адаптер пользователей упадёт на разборе.
ZITADEL_TIMEOUT=10
# Максимальный размер пачки при bulk-импорте
ZITADEL_MAX_BATCH=2000
# Эндпоинты (обычно не переопределяются)
ZITADEL_USERS_MANAGEMENT_ENDPOINT=management/v1/users
ZITADEL_USERS_ENDPOINT=v2/users
# --- Логирование (префикс LOG_) ---
LOG_LEVEL=INFO
# LOG_FORMAT=%(asctime)s [%(levelname)s]: %(message)s
# --- TCP Healthcheck (префикс HEALTHCHECK_) ---
HEALTHCHECK_HOST=0.0.0.0
HEALTHCHECK_PORT=8008
HEALTHCHECK_MAX_CONNECTIONS=10
HEALTHCHECK_REPLY=healthy
# --- Auth: sarex-backend (префикс AUTH_) ---
# Используется для получения JWT под админом (режим run и CLI)
AUTH_HOST=https://stage.sarex.io
AUTH_ADMIN_USERNAME=
AUTH_ADMIN_PASSWORD=
AUTH_AUTH_ENDPOINT=api/token/
# --- Sarex user server (префикс USER_SERVER_, только CLI) ---
# Источник метадаты пользователя для миграции/синхронизации
USER_SERVER_HOST=https://stage.sarex.io
USER_SERVER_USERS_ENDPOINT=api/core/admin/users
# --- База данных sarex-backend (префикс USER_DB_, только CLI) ---
# Источник пользователей/компаний для команд migrate/sync/diff
USER_DB_NAME=postgres
USER_DB_USER=postgres
USER_DB_PASSWORD=password
USER_DB_HOST=127.0.0.1
USER_DB_PORT=5432
# Имена таблиц (обычно не переопределяются)
USER_DB_USER_TABLE_NAME=base_baseuser
USER_DB_COMPANYUSER_TABLE_NAME=core_companyuser
USER_DB_COMPANY_TABLE_NAME=core_company
USER_DB_ITER_SIZE=10000

View File

@ -0,0 +1,218 @@
# Конфигурация проекта ams-sync
# Версия: 1.0.0
Документ описывает все переменные окружения и способы конфигурирования сервиса.
## Что делает сервис
`ams-sync` — утилита синхронизации пользователей sarex-backend с системой управления доступом (AMS) на базе **Zitadel**. У сервиса два режима работы:
- **`run`** — фоновый демон-потребитель Kafka (`src/internal/app/service/sync_service.py`, точка входа `python3 cmd.py run`, это же `CMD` контейнера). Слушает топик событий пользователей и создаёт/обновляет пользователей в Zitadel. Параллельно поднимает TCP-healthcheck.
- **CLI-команды** (`migrate` / `sync` / `diff`, реализованы в `src/internal/app/console.py`) — разовые задачи миграции и синхронизации, запускаются вручную. Читают пользователей и компании напрямую из БД sarex-backend.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется библиотекой [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). В отличие от монолитного `AppSettings`, настройки разбиты на несколько независимых классов, каждый со своим префиксом:
| Класс | Файл | Префикс |
| --- | --- | --- |
| `Settings` (приложение) | `src/internal/app/service/settings.py` | *(без префикса)* |
| `KafkaSettings` | `src/internal/app/service/settings.py` | `kafka_` |
| `Settings` (console) | `src/internal/app/console.py` | *(без префикса)* |
| `Settings` (logger) | `src/internal/adapter/logger/logger.py` | `log_` |
| `Settings` (healthcheck) | `src/internal/adapter/tcphealthcheck/healthcheck.py` | `healthcheck_` |
| `Settings` (auth) | `src/internal/adapter/auth/http/adapter.py` | `auth_` |
| `Settings` (user server) | `src/internal/adapter/users/http/adapter.py` | `user_server_` |
| `Settings` (zitadel users) | `src/internal/adapter/users/zitadel/adapter.py` | `zitadel_` |
| `Settings` (organizations) | `src/internal/adapter/organizations/adapter.py` | `zitadel_` |
| `Settings` (grants) | `src/internal/adapter/grants/adapter.py` | `zitadel_` |
| `Settings` (user repo) | `src/internal/repository/users/repository.py` | `user_db_` |
| `Settings` (company repo) | `src/internal/repository/companies/repository.py` | `user_db_` |
Особенности разбора (`SettingsConfigDict`):
- у каждого класса задан `env_file=".env"`, `env_file_encoding="utf-8"` и `extra="ignore"` — при наличии файла `.env` в рабочей директории он загружается автоматически, лишние переменные игнорируются;
- вложенного разделителя нет — каждая группа настроек читается отдельным классом по своему префиксу (либо без префикса для классов приложения);
- CLI-команды принимают флаг `--env-file=` (по умолчанию `.env`), который прокидывается в конструкторы настроек как `_env_file`; режим `run` всегда читает `.env`.
Отдельного конфиг-файла (yaml/toml) у приложения нет.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (CLI) | Файл `.env` (или указанный через `--env-file=`) в директории с `cmd.py` и/или переменные окружения процесса |
| Локально (контейнер) | `Dockerfile`: `CMD ["python3", "cmd.py", "run"]`; переменные пробрасываются в окружение контейнера |
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `universal-chart.envs` (обычные значения) и `universal-chart.secretEnvs` (значения из k8s-секретов). Базовый чарт — `universal-chart` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна в `workflow.rules` (`RELEASE_NAME`, `STAND`, `CHART_*`, `IMAGE_NAME`, `HELM_SET_ARGS` и т.п.) |
Команды CLI (`python3 cmd.py <command>`, см. `README.md`):
| Команда | Аргументы | Назначение |
| --- | --- | --- |
| `run` | — | Демон-потребитель Kafka + TCP-healthcheck (режим контейнера) |
| `migrate` | `--user-id`, `--with-password/--no-with-password` | Миграция пользователя(ей) из БД sarex-backend в Zitadel. Без `--user-id` мигрируются все (bulk) |
| `sync` | `--user-id`, `--company-id` | Синхронизация метадаты пользователя(ей) из БД sarex-backend в Zitadel |
| `diff` | — | Сравнение множества пользователей БД и Zitadel, отчёт по отсутствующим |
## Переменные приложения
Дефолт `—` означает, что значение обязательно (иначе ошибка старта).
### App (без префикса)
Класс `Settings` в `src/internal/app/service/settings.py` (режим `run`) и `Settings` в `console.py` (CLI).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENVIRONMENT` | enum | `LOCAL` | Окружение: `LOCAL`/`STAGE`/`PREPROD`/`PRODUCTION` |
| `AMS_SYNC_TOPIC` | string | `ams-sync` | Имя топика Kafka с событиями пользователей |
| `HOST_ORGANIZATION_ID` | string | — | ID организации-хоста в Zitadel (обязателен и в `run`, и в CLI) |
| `VERIFY_USERS` | bool | `False` | При `True` события `model_created` обрабатываются через `import_user` (с верификацией e-mail), иначе через обычный `create` |
### Kafka (`KAFKA_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_BOOTSTRAP_SERVERS` | list[str] (JSON) | — | Адреса брокеров, напр. `["host:9091"]` |
| `KAFKA_SASL_PLAIN_USERNAME` | string | — | Логин SASL |
| `KAFKA_SASL_PLAIN_PASSWORD` | string | — | Пароль SASL |
| `KAFKA_SSL_CAFILE` | string | — | Путь к CA-сертификату для SSL |
| `KAFKA_SECURITY_PROTOCOL` | string | `SASL_SSL` | Протокол безопасности |
| `KAFKA_SASL_MECHANISM` | string | `SCRAM-SHA-512` | Механизм SASL |
### Zitadel / AMS (`ZITADEL_*`)
Префикс используют три класса (адаптеры пользователей, организаций и грантов). Общие поля:
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ZITADEL_SERVICE_ACCESS_TOKEN` | string | — | Service-токен доступа к API Zitadel (Bearer) |
| `ZITADEL_HOST` | string | — (у адаптера пользователей); `https://idp.dev.stage.sarex.io` (у адаптеров организаций/грантов) | Базовый URL Zitadel |
| `ZITADEL_TIMEOUT` | int / string | `10` (адаптер пользователей, минуты) / `10m` (организации, гранты) | Таймаут. См. замечание о конфликте типов ниже |
| `ZITADEL_MAX_BATCH` | int | `2000` | Максимум пользователей в одной пачке bulk-импорта |
| `ZITADEL_USERS_MANAGEMENT_ENDPOINT` | string | `management/v1/users` | Базовый путь management-API |
| `ZITADEL_USERS_ENDPOINT` | string | `v2/users` | Базовый путь users-API v2 |
### Логирование (`LOG_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `LOG_LEVEL` | string | `INFO` | Уровень логирования |
| `LOG_FORMAT` | string | `%(asctime)s [%(levelname)s]: %(message)s` | Формат строки лога |
Логи пишутся в stdout и в файл `latest.log` в рабочей директории.
### TCP Healthcheck (`HEALTHCHECK_*`)
Только режим `run`. Поднимает сырой TCP-сокет, отвечающий строкой-ответом на каждое подключение.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `HEALTHCHECK_HOST` | string | `0.0.0.0` | Адрес прослушивания |
| `HEALTHCHECK_PORT` | int | `8008` | Порт |
| `HEALTHCHECK_MAX_CONNECTIONS` | int | `10` | Размер очереди подключений (`listen`) |
| `HEALTHCHECK_REPLY` | string | `healthy` | Строка-ответ на подключение |
### Auth: sarex-backend (`AUTH_*`)
Получение JWT под сервисным админом (используется в обоих режимах — адаптер создаётся и в `run`, и в CLI).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `AUTH_HOST` | string | — | Базовый URL sarex-backend |
| `AUTH_ADMIN_USERNAME` | string | — | Логин админа |
| `AUTH_ADMIN_PASSWORD` | string | — | Пароль админа |
| `AUTH_AUTH_ENDPOINT` | string | `api/token/` | Путь получения токена |
### Sarex user server (`USER_SERVER_*`) — только CLI
Источник метадаты пользователя при миграции/синхронизации.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USER_SERVER_HOST` | string | — | Базовый URL сервиса пользователей sarex-backend |
| `USER_SERVER_USERS_ENDPOINT` | string | `api/core/admin/users` | Базовый путь эндпоинта пользователей |
### База данных sarex-backend (`USER_DB_*`) — только CLI
Один префикс на два репозитория (пользователи и компании). Подключение через `psycopg2`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USER_DB_NAME` | string | — | Имя базы данных |
| `USER_DB_USER` | string | — | Пользователь БД |
| `USER_DB_PASSWORD` | string | — | Пароль |
| `USER_DB_HOST` | string | — | Хост PostgreSQL |
| `USER_DB_PORT` | int | — | Порт PostgreSQL |
| `USER_DB_USER_TABLE_NAME` | string | `base_baseuser` | Таблица пользователей |
| `USER_DB_COMPANYUSER_TABLE_NAME` | string | `core_companyuser` | Таблица связи пользователь↔компания |
| `USER_DB_COMPANY_TABLE_NAME` | string | `core_company` | Таблица компаний |
| `USER_DB_ITER_SIZE` | int | `10000` | Размер серверного курсора при итерации |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Базовый чарт — `universal-chart`. Обычные значения (`universal-chart.envs`) задаются на окружение (`_default`/`stage`/`preprod`/`production`):
| Переменная | Значения по окружениям |
| --- | --- |
| `AMS_SYNC_TOPIC` | `ams-sync` |
| `ENVIRONMENT` | `STAGE` / `PREPROD` / `PRODUCTION` |
| `HOST_ORGANIZATION_ID` | stage `339439562105337368`, preprod `337394329179947547`, production `337555824748561429` |
| `USER_SERVER_HOST` | stage `https://stage.sarex.io`, preprod `https://preprod.sarex.io`, production `https://.lk.sarex.io` |
| `AUTH_HOST` | stage `https://stage.sarex.io`, preprod `https://preprod.sarex.io`, production `https://lk.sarex.io` |
| `KAFKA_SECURITY_PROTOCOL` | `SASL_SSL` |
| `KAFKA_SASL_MECHANISM` | `SCRAM-SHA-512` |
Значения из секретов (`universal-chart.secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `ZITADEL_HOST` | `zitadel-token-secret` | `zitadel-host` |
| `ZITADEL_SERVICE_ACCESS_TOKEN` | `zitadel-token-secret` | `zitadel-access-token` |
| `KAFKA_BOOTSTRAP_SERVERS` | `ams-kafka-secret` | `kafka-bootstrap-servers` |
| `KAFKA_SASL_PLAIN_USERNAME` | `ams-kafka-secret` | `kafka-username` |
| `KAFKA_SASL_PLAIN_PASSWORD` | `ams-kafka-secret` | `kafka-password` |
| `KAFKA_SSL_CAFILE` | `ams-kafka-secret` | `kafka-ca-file` |
| `AUTH_ADMIN_USERNAME` | `auth-secret` | `admin-username` |
| `AUTH_ADMIN_PASSWORD` | `auth-secret` | `admin-password` |
| `USER_DB_NAME` | `user-db-secret` | `user-db-name` |
| `USER_DB_USER` | `user-db-secret` | `user-db-user` |
| `USER_DB_PASSWORD` | `user-db-secret` | `user-db-password` |
| `USER_DB_HOST` | `user-db-secret` | `user-db-host` |
| `USER_DB_PORT` | `user-db-secret` | `user-db-port` |
Помимо env, чарт монтирует CA-сертификат Kafka из секрета `ya-ca-secret` в `/etc/ca-certificates/Yandex` (`volumes`/`volumeMounts`) и раздаёт `YandexInternalRootCA.crt` через configMap `ya-ca-cert`. Прочие значения чарта (не переменные приложения): `deployment.*` (имя, реплики, ресурсы, порт `8008`), `service.*` (ClusterIP, порт `8008`), `probes.*` (TCP-пробы на `8008`, по умолчанию выключены), `serviceAccount.*`, `imagePullSecrets.*`.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace |
| --- | --- | --- |
| ветка `stage` | `stage` | `platform` (`STAGE_NAMESPACE`) |
| ветка `master` | `preprod` | `ams-sync-preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `ams-sync-prod` |
Ключевые переменные пайплайна: `RELEASE_NAME=ams-sync`, `CHART_NAME=ams-sync`, `CHART_VERSION` (`0.0.1-<stand>`), `IMAGE_NAME`, `IMAGE_PATH=universal-chart.image.name`, `HELM_SET_ARGS`, `DOCKERFILE_PATH=Dockerfile`, флаги `ENABLE_LINTER`/`ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_STATE_UPDATE`/`ENABLE_DEPLOY`.
## Замечания и потенциальные проблемы
- **Конфликт `ZITADEL_TIMEOUT`.** Переменную читают три класса с общим префиксом `zitadel_`, но разного типа: адаптер пользователей ожидает `int` (минуты, дефолт `10`), адаптеры организаций и грантов — `str` (дефолт `10m`). Если задать `ZITADEL_TIMEOUT=10m`, инициализация адаптера пользователей упадёт на разборе `int`. Рекомендуется задавать целое число.
- **`ZITADEL_HOST` без дефолта у адаптера пользователей.** В `users/zitadel/adapter.py` поле `host` обязательно (дефолта нет), тогда как в адаптерах организаций/грантов есть дефолт `https://idp.dev.stage.sarex.io`. Для режима `run` переменную нужно задать явно (в Helm приходит из секрета `zitadel-token-secret`).
- **Режим `run` не использует БД и user-server.** Демон-потребитель (`sync_service.py`) поднимает только logger, healthcheck, auth-провайдер, адаптер Zitadel и Kafka. Переменные `USER_DB_*` и `USER_SERVER_*` требуются только CLI-командам (`migrate`/`sync`/`diff`), хотя в Helm они проброшены для запуска этих команд через `kubectl exec`.
- **Приложение загружает `.env` автоматически** (у всех классов задан `env_file`), в отличие от некоторых других сервисов. CLI дополнительно поддерживает `--env-file=` для альтернативного файла.
- **TLS-проверка отключена.** Все исходящие HTTP-запросы (Zitadel, sarex-backend) выполняются с `verify=False`. Отдельного флага в конфиге нет.
- **Zitadel org-ID захардкожены в коде.** Помимо `HOST_ORGANIZATION_ID`, в use-case зашиты `glorax_org_id` и `dogma_org_id`, в которые распределяются пользователи по доменам e-mail (см. `usecase/migration/usecase.py`, `usecase/user/usecase.py`).
## Минимальный набор для режима `run` (демон)
- `ENVIRONMENT`, `HOST_ORGANIZATION_ID`, `AMS_SYNC_TOPIC`
- `KAFKA_BOOTSTRAP_SERVERS`, `KAFKA_SASL_PLAIN_USERNAME`, `KAFKA_SASL_PLAIN_PASSWORD`, `KAFKA_SSL_CAFILE` (+ при необходимости `KAFKA_SECURITY_PROTOCOL`, `KAFKA_SASL_MECHANISM`)
- `ZITADEL_HOST`, `ZITADEL_SERVICE_ACCESS_TOKEN`
- `AUTH_HOST`, `AUTH_ADMIN_USERNAME`, `AUTH_ADMIN_PASSWORD`
## Дополнительно для CLI (`migrate` / `sync` / `diff`)
- `USER_SERVER_HOST`
- `USER_DB_NAME`, `USER_DB_USER`, `USER_DB_PASSWORD`, `USER_DB_HOST`, `USER_DB_PORT`

View File

@ -0,0 +1,99 @@
# Интерфейсы сервиса ams-sync
# Версия: 1.0.0
Документ описывает интерфейсную поверхность сервиса: потребляемый топик Kafka, входящий TCP-healthcheck, исходящие HTTP-запросы к внешним сервисам (Zitadel, sarex-backend) и обращения к БД.
> `ams-sync` — не backend-сервис с REST API, а утилита синхронизации: демон-потребитель Kafka (`run`) плюс набор CLI-команд (`migrate`/`sync`/`diff`). Публичного HTTP API у сервиса нет — единственный входящий сокет отвечает на healthcheck сырой строкой (не HTTP). Поэтому файла `openapi.yaml` для сервиса нет.
## Как устроено взаимодействие
Приложение построено по слоям (adapter → repository → usecase → controller). Исходящие вызовы к внешним сервисам выполняются через `requests` (`requests.Session` с ретраями на `5xx`), к БД — через `psycopg2`. Все HTTP-запросы идут с `verify=False` (без проверки TLS). Базовые хосты берутся из переменных окружения (см. `CONFIGURATION.md`): `ZITADEL_HOST`, `AUTH_HOST`, `USER_SERVER_HOST`.
Аутентификация исходящих запросов:
- **Zitadel** — заголовок `Authorization: Bearer <ZITADEL_SERVICE_ACCESS_TOKEN>` (service-токен из конфига). Для части операций дополнительно проставляется `x-zitadel-orgid`.
- **sarex-backend** — JWT, получаемый под админом (`AUTH_ADMIN_USERNAME`/`AUTH_ADMIN_PASSWORD`) через `POST api/token/`; кешируется и обновляется за 30 секунд до `exp`.
## Входящие интерфейсы
### TCP Healthcheck
Только режим `run`. Сырой TCP-сокет (не HTTP): на каждое подключение отправляет строку `HEALTHCHECK_REPLY` (по умолчанию `healthy`) и закрывает соединение. Слушает `HEALTHCHECK_HOST:HEALTHCHECK_PORT` (по умолчанию `0.0.0.0:8008`). В Helm k8s-пробы настроены на TCP-порт `8008` (по умолчанию отключены).
### Kafka-потребитель
Топик — переменная `AMS_SYNC_TOPIC` (по умолчанию `ams-sync`). Параметры консьюмера (`src/internal/controller/sync_service/controller.py`):
| Параметр | Значение |
| --- | --- |
| `group_id` | `ams_sync_group` |
| `auto_offset_reset` | `earliest` |
| `enable_auto_commit` | `True` |
| десериализация | JSON (`utf-8`) |
Формат сообщения — объект с полями `type` и `body`. Диспетчеризация по `type`:
| `type` сообщения | Тело (`body`) | Обработчик | Действие |
| --- | --- | --- | --- |
| `model_created` | `SarexUserWithPermissions` | при `VERIFY_USERS=True``import_user` UC, иначе `user` UC `create` | Создание пользователя в Zitadel + запись метадаты |
| `model_updated` | `Metadata` | `user` UC `update_metadata` | Обновление метадаты существующего пользователя в Zitadel |
Сообщения без `type` или `body` пропускаются с предупреждением. Ошибки обработки логируются (traceback), сообщение не переотправляется (auto-commit включён).
## Исходящие HTTP-запросы
### Zitadel (`ZITADEL_HOST`)
Адаптеры `users/zitadel/adapter.py` и `organizations/adapter.py`. Итоговый URL = `<ZITADEL_HOST>` + путь.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/v2/users/new` | Создать пользователя (human) |
| POST | `/management/v1/users/human/_import` | Импортировать пользователя (заголовок `x-zitadel-orgid`) |
| POST | `/admin/v1/import` | Массовый импорт пользователей (bulk, с таймаутом) |
| POST | `/v2/users` | Поиск пользователей (по username / email / org) |
| GET | `/management/v1/users/{id}` | Получить пользователя по id |
| GET | `/management/v1/users/{id}/metadata/{key}` | Получить метадату пользователя по ключу |
| POST | `/v2/users/{id}/metadata` | Массово задать метадату пользователя (значения в base64) |
| POST | `/v2/users/{id}/deactivate` | Деактивировать пользователя |
| DELETE | `/v2/users/{id}` | Удалить пользователя |
| POST | `/v2/organizations/_search` | Найти организацию по имени |
> Пути `/v2/...` частично захардкожены в адаптере, часть базовых путей управляется `ZITADEL_USERS_MANAGEMENT_ENDPOINT` (`management/v1/users`) и `ZITADEL_USERS_ENDPOINT` (`v2/users`). Методы грантов (`grants/adapter.py`) в текущей версии закомментированы.
### sarex-backend — авторизация (`AUTH_HOST`)
Адаптер `auth/http/adapter.py`.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{AUTH_AUTH_ENDPOINT}` (по умолчанию `api/token/`) | Получить JWT по логину/паролю админа. Возвращает поле `access` |
### sarex-backend — сервис пользователей (`USER_SERVER_HOST`), только CLI
Адаптер `users/http/adapter.py`. Сессия с ретраями на `500/502/503/504` (до 5 попыток).
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `{USER_SERVER_USERS_ENDPOINT}/{user_id}/metadata` (по умолчанию `api/core/admin/users/{user_id}/metadata`) | Получить метадату пользователя sarex для синхронизации в Zitadel |
## Обращения к БД sarex-backend (`USER_DB_*`), только CLI
Репозитории `repository/users` и `repository/companies` читают данные напрямую из PostgreSQL sarex-backend (только чтение, серверный курсор `itersize`).
| Операция | Таблицы | Назначение |
| --- | --- | --- |
| Пользователь по id | `base_baseuser` | `migrate --user-id` / `sync --user-id` |
| Все пользователи (итерация) | `base_baseuser` | `migrate` / `sync` / `diff` (bulk) |
| Пользователи компании | `core_companyuser``base_baseuser` | `sync --company-id` (только с непустым email) |
| Компания по id / имени | `core_company` | Вспомогательные запросы |
## Внешние зависимости (инфраструктура)
| Зависимость | Режим | Назначение |
| --- | --- | --- |
| Kafka | `run` | Источник событий пользователей (топик `AMS_SYNC_TOPIC`) |
| Zitadel (AMS) | `run` + CLI | Целевая система управления доступом (создание/обновление/удаление пользователей и метадаты) |
| sarex-backend (auth) | `run` + CLI | Получение JWT под админом |
| sarex-backend (user server) | CLI | Источник метадаты пользователя |
| PostgreSQL sarex-backend | CLI | Источник пользователей и компаний для миграции/синхронизации |

View File

@ -0,0 +1,15 @@
# auth-flow-frontend — переменные СБОРКИ (build-time), НЕ рантайм-.env.
# Приложение не читает .env: значения подставляются в код на этапе сборки
# через webpack DefinePlugin (см. webpack.config.ts) или Dockerfile --build-arg / CI BUILD_ARGS.
# Окружение сборки: local | stage | prod | preprod | contour
# local -> mode=development, isDev=true (доступны dev-роуты /login, /logout, /)
# остальные -> mode=production
ENDPOINT=local
# Токен приватного npm-реестра @sarex-team (nexus.infra.sarex.io), нужен для `npm i` (.npmrc)
NPM_NEXUS_TOKEN=
# NOTE: IS_DEV вычисляется автоматически (ENDPOINT === 'local'), задавать вручную не нужно.
# NOTE: authority и client_id OIDC (Zitadel) НЕ задаются здесь — хостовое приложение
# кладёт их в localStorage (STORAGE.AUTHORITY / STORAGE.CLIENT_ID) во время работы.

View File

@ -0,0 +1,162 @@
# Конфигурация проекта auth-flow-frontend
Документ описывает способы конфигурирования микрофронтенда аутентификации `auth-flow-frontend` (React + webpack), а также все переменные сборки, деплоя и рантайма.
## Способы конфигурирования
В отличие от бэкенд-сервисов, приложение **не читает `.env` и не использует dotenv**. Это статический фронтенд, собираемый webpack, поэтому конфигурация распределена по трём уровням:
1. **Сборка (build-time).** Переменная `ENDPOINT` определяет окружение. Её значение подставляется в код на этапе сборки через `webpack.DefinePlugin` (`webpack.config.ts`) — заменяет обращения `process.env.ENDPOINT` и `process.env.IS_DEV` на строковые литералы. Выбор режима (`development`/`production`) и флага `isDev` выполняется в `webpack/build.config.ts`.
2. **Установка зависимостей.** Пакеты `@sarex-team/*` тянутся из приватного npm-реестра (`.npmrc` → `nexus.infra.sarex.io`). Для доступа нужен токен `NPM_NEXUS_TOKEN`.
3. **Рантайм (runtime).** Параметры OIDC-провайдера — `authority` и `client_id` — читаются из `localStorage` (`src/config/zitadel.ts`) по ключам `STORAGE.AUTHORITY` и `STORAGE.CLIENT_ID` из `@sarex-team/sdk-js`. Их задаёт хостовое приложение, а не сборка.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (dev) | `npm run dev``ENDPOINT=local webpack serve`. Dev-сервер на `https://localhost:9000` |
| Локально (build) | `npm run build``webpack --config webpack.config.ts` (по умолчанию `ENDPOINT=prod`, см. `build.config.ts`) |
| Docker | `Dockerfile`: build-args `ENDPOINT` и `NPM_NEXUS_TOKEN`; сборка dist → отдача через nginx |
| Kubernetes (Helm) | `.helm/values.yaml` (чарт `universal-chart`): блок `services.frontend`, `global.env` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: `workflow.rules` (ветка/тег → `STAND`, `ENDPOINT`, `CHART_VERSION`), `BUILD_ARGS`, `HELM_SET_ARGS` |
## Переменные сборки (build-time)
Подставляются в код на этапе сборки; в рантайме это уже константы.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENDPOINT` | enum | `prod``build.config.ts`, если не задана) | Окружение сборки: `local`/`stage`/`prod`/`preprod`/`contour`. Определяет `mode` (`development` для `local`, иначе `production`) и `isDev` |
| `IS_DEV` | bool | — (вычисляется) | `true`, если `ENDPOINT === 'local'`. Включает dev-роуты (`/login`, `/logout`, `/`). Задаётся автоматически через `DefinePlugin`, вручную указывать не нужно |
| `NPM_NEXUS_TOKEN` | string | — | Токен авторизации в приватном npm-реестре `@sarex-team` (`.npmrc`). Обязателен для `npm i` |
> `mode` по окружениям (`webpack/build.config.ts`): `local``development`, `stage`/`prod`/`preprod`/`contour` → `production`. Неизвестное значение `ENDPOINT` приводит к ошибке сборки.
## Переменные рантайма (localStorage)
Читаются в браузере во время работы приложения; задаются хостовым приложением, не сборкой.
| Ключ | Источник | Назначение |
| --- | --- | --- |
| `STORAGE.AUTHORITY` | `@sarex-team/sdk-js` | URL issuer'а Zitadel (`authority` OIDC). Обязателен — при отсутствии `src/config/zitadel.ts` бросает `Error("Authority or client ID is not set")` |
| `STORAGE.CLIENT_ID` | `@sarex-team/sdk-js` | `client_id` OIDC-клиента. Обязателен (та же проверка) |
| `sarex_logout` (`LOGOUT_STORAGE_KEY`) | `src/config/logout.ts` | Состояние логаута между вкладками (`{ logoutAt, owner, reason }`). Снимается только валидным токеном в `setTokensInfo` |
| access / refresh / id токены | `storage` из `@sarex-team/sdk-js` | Устанавливаются в `setTokensInfo` (`storage.setAccessToken`/`setRefreshToken`/`setIdentityToken`) после успешного колбэка |
## Конфигурация OIDC (Zitadel)
Задаётся в `src/config/zitadel.ts` (`configZitadel: ZitadelConfig`), клиент создаётся через `createZitadelAuth`.
| Параметр | Значение | Назначение |
| --- | --- | --- |
| `authority` | из `localStorage` (`STORAGE.AUTHORITY`) | Issuer Zitadel |
| `client_id` | из `localStorage` (`STORAGE.CLIENT_ID`) | Идентификатор клиента |
| `response_type` | `code` | Authorization Code Flow |
| `scope` | `openid profile email offline_access urn:zitadel:iam:user:metadata` | Запрашиваемые области |
| `redirect_uri` | `${window.location.origin}/auth/callback` | URL возврата после логина |
| `post_logout_redirect_uri` | `${window.location.origin}/login` | URL после логаута |
| `redirectMethod` | `replace` | Замена записи в истории браузера |
| `revokeTokensOnSignout` | `false` | Не отзывать токены при выходе |
| `automaticSilentRenew` | `false` | Фоновое обновление токенов выключено |
| `validateSubOnSilentRenew` | `false` | — |
| `silentRequestTimeoutInSeconds` | `100` | Таймаут silent-запроса |
## Роуты приложения
Определены в `src/config/urls.ts`, диспетчеризация в `src/App.tsx`.
| Роут | Константа | Доступность | Назначение |
| --- | --- | --- | --- |
| `/auth/callback` | `CALLBACK` | всегда | Обработка OIDC-колбэка (по умолчанию для неизвестных путей) |
| `/auth/error` | `ERROR` | всегда | Страница ошибки аутентификации |
| `/login` | `LOGIN` | только dev | Dev-страница входа |
| `/logout` | `LOGOUT` | только dev | Dev-страница выхода |
| `/` | `HOME` | только dev | Dev-навигация |
> В production разрешены только `/auth/callback` и `/auth/error` (`ALLOWED_PATHS`); в dev дополнительно `/login`, `/logout`, `/` (`ALLOWED_PATHS_DEV`). Неразрешённый путь заменяется на `/auth/callback` (`history.replaceState`).
## Сборка (webpack)
`webpack.config.ts` + `webpack/build.config.ts`.
| Параметр | Значение | Примечание |
| --- | --- | --- |
| `entry` | `src/index.tsx` | Точка входа |
| `output.path` | `dist/` | |
| `output.filename` | `index.js` | |
| `output.publicPath` | `/` (dev) или `/auth/callback/` (prod) | Зависит от `isDev` |
| loader | `esbuild-loader`, target `es2015` | Для `.tsx?/.jsx?` |
| `devServer` | `https`, `port: 9000`, `hot`, `historyApiFallback`, `open` | Только dev |
| plugins | `HtmlWebpackPlugin` (`public/index.html`), `DefinePlugin` (`ENDPOINT`, `IS_DEV`) | |
Node-версия для разработки: `v20.0.0` (`.nvmrc`; в `package.json` заявлено `v20.0.0`, реальная база образа — `node:20`).
## Docker
`Dockerfile` — двухстадийная сборка.
| Стадия | База | Действия |
| --- | --- | --- |
| build | `cr.yandex/crp3ccidau046kdj8g9q/node:20` | `npm i` (с `NPM_NEXUS_TOKEN`), `npm run build` (с `ENDPOINT`) |
| runtime | `cr.yandex/crp3ccidau046kdj8g9q/nginx:latest` | Копирует `/app/dist/``/dist/`, `nginx/nginx.conf``/etc/nginx/nginx.conf` |
Build-args: `ENDPOINT`, `NPM_NEXUS_TOKEN`.
## Nginx
`nginx/nginx.conf` — отдача статики и healthcheck.
| Локация | Поведение |
| --- | --- |
| `= /auth/callback/index.js` | `alias /dist/index.js` |
| `^~ /auth/callback` | `try_files $uri $uri/ /index.html`; заголовки `Cache-Control: no-store,...`, кеш отключён |
| `= /ping` | `200 {"result": "ok"}` (healthcheck) |
`listen 80`, `root /dist`, логи в stdout/stderr, `gzip on`.
## Helm-чарт (`.helm/values.yaml`)
Деплой через зонтичный чарт `universal-chart` (`oci://cr.yandex/crp3ccidau046kdj8g9q/charts`, версия `0.1.7`; `Chart.yaml` приложения — `auth-flow-frontend` `1.0.0`). Значения задаются с ключами по окружениям (`_default`/`stage`/`preprod`/`production`).
| Параметр | Значение | Назначение |
| --- | --- | --- |
| `global.env` | `stage` (`_default` в файле) | Активное окружение чарта |
| `services.frontend.enabled` | `true` | Включение сервиса |
| `deployment.name._default` | `auth-flow-frontend` | Имя деплоймента |
| `deployment.replicaCount` | `_default: 1`, `production: 2` | Число реплик |
| `deployment.port._default` | `80` | Порт контейнера |
| `deployment.revisionHistoryLimit._default` | `5` | Хранимые ревизии |
| `deployment.resources.requests` | `memory: 128Mi`, `cpu: 100m` | Реквесты ресурсов |
| `deployment.probes.liveness/readiness` | `httpGet` `/` : `80` | Пробы |
| `image.name._default` | `cr.yandex/crp3ccidau046kdj8g9q/auth-flow-frontend` | Образ |
| `image.pullPolicy._default` | `IfNotPresent` | |
| `imagePullSecrets.name._default` | `dockerhub` (enabled `false`) | Секрет реестра |
| `service` | `ClusterIP`, port/targetPort `80`, portName `http`, имя `auth-flow-frontend-service` | Service |
| `envs` | `[]` | Переменные окружения контейнера (пусто) |
| `volumes._default` | `[]` | Тома |
| `commitSha`/`gitlabUri`/`gitlabJobUrl`/`owner` | заполняются из CI (`owner` по умолчанию `team-abc`) | Метаданные |
## CI (`.gitlab-ci.yml`)
Подключает общие шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml` ref `apps-business`).
Базовые переменные: `SERVICE_NAME=auth-flow-frontend`, `DOCKERFILE_PATH=./Dockerfile`, `CI_TRIGGER_SOURCE=app`.
Переключение окружения по `workflow.rules`:
| Условие | STAND | ENDPOINT | CHART_VERSION | K8S_HUSTLER_BRANCH |
| --- | --- | --- | --- | --- |
| `merge_request_event` | — | — | — (`ENABLE_BUILD_IMAGE=false`) | — |
| ветка `stage` | `stage` | `stage` | `0.0.1-stage` | `universal-chart-stage` |
| тег (`CI_COMMIT_TAG`) | `production` | `prod` | `0.0.1-prod` | `universal-chart-production` |
> Правило для ветки `master``preprod` присутствует, но **закомментировано**. `NAMESPACE` для всех окружений — `platform`, `RELEASE_NAME`/`CHART_NAME` — `${SERVICE_NAME}`.
`BUILD_ARGS` прокидывают `--build-arg ENDPOINT=<env>` и `--build-arg NPM_NEXUS_TOKEN=${NPM_NEXUS_TOKEN}`. `HELM_SET_ARGS` устанавливают `universal-chart.services.frontend.image.name.<env>`, `global.env`, а также `commitSha`, `gitlabUri`, `gitlabJobUrl`, `owner`.
## Замечания и потенциальные проблемы
- **Обязательные runtime-параметры.** Если в `localStorage` нет `AUTHORITY` или `CLIENT_ID`, `src/config/zitadel.ts` бросает ошибку при инициализации — приложение не стартует. Эти значения должен положить хостовый сервис до загрузки микрофронтенда.
- **Нет `.env`.** Приложение не читает dotenv; `process.env.ENDPOINT`/`IS_DEV` существуют только на этапе сборки (замена через `DefinePlugin`). Значение `ENDPOINT` задаётся только через CLI/`--build-arg`/CI.
- **publicPath в prod**`/auth/callback/`: приложение обслуживается nginx под префиксом `/auth/callback`, что согласовано с `redirect_uri` OIDC.
- **`silentRenew` отключён** намеренно (`automaticSilentRenew: false`) — обновление токенов не выполняется в фоне.

View File

@ -0,0 +1,80 @@
# Эндпоинты, с которыми взаимодействует auth-flow-frontend
Документ описывает внешние эндпоинты, внутренние роуты и каналы межвкладочной коммуникации микрофронтенда аутентификации `auth-flow-frontend`.
## Как устроено взаимодействие
Приложение отвечает только за аутентификацию по протоколу **OIDC (Authorization Code Flow + PKCE)**. Прямых REST-запросов к бэкенд-сервисам Sarex у него **нет** — всё сетевое взаимодействие идёт с провайдером идентификации **Zitadel** через обёртку `@sarex-team/sdk-js/zitadel` (поверх `oidc-client`).
Клиент создаётся в `src/config/zitadel.ts` (`createZitadelAuth(configZitadel)`). Базовый адрес провайдера (`authority`, issuer) и `client_id` берутся из `localStorage` во время работы, а не задаются сборкой. OIDC-клиент сам находит конкретные эндпоинты issuer'а через discovery-документ `/.well-known/openid-configuration`.
## Провайдер идентификации (Zitadel) по окружениям
Фактический `authority` подставляется хостовым приложением через `localStorage` (`STORAGE.AUTHORITY`) — в самом `auth-flow-frontend` хосты не захардкожены. Ниже — известные для платформы Sarex значения (справочно):
| Окружение | `authority` (issuer) |
| --- | --- |
| `stage` | `https://idp.dev.stage.sarex.io` |
| `prod` | `https://login.sarex.io` |
## OIDC-эндпоинты провайдера
Обнаруживаются через discovery и вызываются `oidc-client` относительно `authority`. Итоговые пути определяются метаданными issuer'а.
| Эндпоинт (метаданные) | Метод | Где инициируется | Назначение |
| --- | --- | --- | --- |
| `/.well-known/openid-configuration` | GET | инициализация `UserManager` | Discovery метаданных провайдера |
| `authorization_endpoint` | GET (redirect) | `LoginPage``zitadel.authorize()` | Старт авторизации, редирект на форму входа |
| `token_endpoint` | POST | `AuthCallbackPage``zitadel.userManager.signinCallback()` | Обмен `code` → access/refresh/id токены |
| `jwks_uri` | GET | `oidc-client` | Ключи для проверки подписи токенов |
| `userinfo_endpoint` | GET | `oidc-client` (при необходимости) | Профиль пользователя |
| `end_session_endpoint` | GET (redirect) | `LogoutPage``zitadel.signout()` | Завершение сессии (логаут) |
Параметры OIDC-запросов (из `configZitadel`):
- `response_type`: `code`
- `scope`: `openid profile email offline_access urn:zitadel:iam:user:metadata`
- `redirect_uri`: `${window.location.origin}/auth/callback`
- `post_logout_redirect_uri`: `${window.location.origin}/login`
- `revokeTokensOnSignout`: `false`, `automaticSilentRenew`: `false`
## Внутренние роуты приложения
Диспетчеризация — в `src/App.tsx` по `window.location.pathname` (роуты из `src/config/urls.ts`).
| Роут | Страница | Метод | Назначение |
| --- | --- | --- | --- |
| `/auth/callback` | `AuthCallbackPage` | — | Обработка OIDC-колбэка: `signinCallback()`, установка токенов, рассылка события в `login_channel`. Дефолт для неизвестных путей |
| `/auth/error` | `AuthErrorPage` | — | Отображение ошибки аутентификации (параметры `error`, `error_description`, `state`, `message` из query) |
| `/login` | `LoginPage` (dev) | — | Кнопка входа (`zitadel.authorize()`) |
| `/logout` | `LogoutPage` (dev) | — | Кнопка выхода (`zitadel.signout()`) |
| `/` | `MainPage` (dev) | — | Dev-навигация (login/logout) |
| `/ping` | nginx | GET | Healthcheck, отдаёт `{"result": "ok"}` |
## Поток аутентификации (кратко)
1. `LoginPage` вызывает `zitadel.authorize()` → редирект на `authorization_endpoint` Zitadel.
2. Провайдер возвращает пользователя на `redirect_uri` = `/auth/callback` с `code`.
3. `AuthCallbackPage` вызывает `signinCallback()` → обмен `code` на токены через `token_endpoint`.
4. Токены сохраняются (`setTokensInfo` → `storage.setAccessToken/setRefreshToken/setIdentityToken`), снимается флаг логаута.
5. Результат рассылается остальным вкладкам, происходит закрытие overlay-окна (`window.opener`) или переход на `/` (`HOME`).
6. При ошибке — редирект на `/auth/error` с деталями.
## Межвкладочная и оконная коммуникация
| Канал | Ключ / имя | Назначение |
| --- | --- | --- |
| `BroadcastChannel` | `login_channel` | Сообщения `{ type: "login_success" }` и `{ type: "login_error", reason }` между вкладками |
| `localStorage` | `sarex_logout` (`LOGOUT_STORAGE_KEY`) | Состояние логаута между вкладками (`logoutAt`, `owner`, `reason`) |
| window events | `setTokensInfoIntoWindowEvents` (`@sarex-team/sdk-js`) | Оповещение хостового приложения об обновлении токенов (`reason: "auth_callback"`) |
| `window.opener` | — | Режим overlay: после успешного входа окно закрывается (`window.close()`) |
## Обработка ошибок
Логика — в `src/utils/authError.ts`; отображение — `AuthErrorPage`.
- Параметры ошибки читаются из query-строки колбэка: `error`, `error_description`, `state`, `message` (`parseAuthErrorFromSearch`).
- Причина ошибки: `error_description``message``error``"Unknown error"` (`getAuthErrorReason`).
- URL страницы ошибки собирается `buildAuthErrorUrl` (query из непустых параметров, иначе просто `/auth/error`).
- Если контекста ошибки нет — редирект на `/` (`HOME`).
- Пользователю доступны кнопки «Скопировать детали ошибки» (`formatAuthErrorForCopy`) и «Вернуться на форму входа» (`/login`), а также ссылка в поддержку `mailto:support@sarex.io`.

64
apps/bim/.env.example Normal file
View File

@ -0,0 +1,64 @@
# App
# APP_NAME/APP_VERSION/LOG_LEVEL читаются кодом, но в деплой-envs их обычно не задают
APP_NAME='BIM Backend v2'
APP_VERSION=local
LOG_LEVEL=info
# Адрес прослушивания HTTP API (host:port)
API_ADDRESS=0.0.0.0:5555
# Master-кластер PostgreSQL (BIM id <= LAST_MASTER_BIM)
POSTGRES_ADDRESS=localhost
POSTGRES_PORT=5432
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=bimbackendv2
POSTGRES_POOL_SIZE=10
DB_CERT_PATH=./.docker/yandex_pg.pem
# Slave-1 кластер PostgreSQL (шардирование по BIM id)
POSTGRES_ADDRESS_2=localhost
POSTGRES_PORT_2=5433
POSTGRES_USER_2=user
POSTGRES_PASSWORD_2=password
POSTGRES_DB_2=bimbackendv2
POSTGRES_POOL_SIZE_2=10
DB_CERT_PATH_2=./.docker/yandex_pg.pem
# Slave-2 кластер PostgreSQL (шардирование по BIM id)
POSTGRES_ADDRESS_3=localhost
POSTGRES_PORT_3=5434
POSTGRES_USER_3=user
POSTGRES_PASSWORD_3=password
POSTGRES_DB_3=bimbackendv2
POSTGRES_POOL_SIZE_3=10
DB_CERT_PATH_3=./.docker/yandex_pg.pem
# Границы шардирования BIM id между кластерами
LAST_MASTER_BIM=10
LAST_MASTER_BIM_V3=0
LAST_SLAVE_1_BIM=0
LAST_SLAVE_1_BIM_V3=0
# TLS до PostgreSQL (0 — локально, 1 — в облаке)
ENABLE_SSL=0
# Логировать SQL-запросы (1 — включено)
ENABLE_SQL_QUERY=1
# Внешний Django-бэкенд (проверка прав администратора)
DJANGO_HOST=https://stage.sarex.io
# Режим интеграционных тестов (в этом режиме CheckUserIsAdmin всегда true)
INTEGRATION_TESTS=0
# --- Переменные окружения, не читаемые config.go (инфраструктура/тесты/сборка) ---
# Проброс портов и локальная БД в docker-compose
# API_PORT=5555
# POSTGRES_EXTERNAL_PORT=5432
# POSTGRES_EXTERNAL_PORT_2=5433
# Значения для контейнеров тестов
# TEST_ENV=1
# TEST_BIM=1
# JWT_TEST=<jwt>
# Объявлены в .env, но config.go их не использует
# GRPC_ADDRESS=0.0.0.0
# GRPC_PORT=50051

189
apps/bim/CONFIGURATION.md Normal file
View File

@ -0,0 +1,189 @@
# Конфигурация проекта bim-backend-v2
Документ описывает все переменные окружения и способы конфигурирования сервиса.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` (функция `NewConfig`) через библиотеку [`kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (структура `Config`).
Особенности разбора:
- **Префикса нет**`envconfig.Process("", &cfg)` вызывается с пустым префиксом, поэтому имена переменных задаются как есть (напр. `POSTGRES_ADDRESS`, `API_ADDRESS`). Имя переменной определяется тегом `envconfig:"..."` у каждого поля.
- Типы приводятся автоматически по типу поля Go (`string`, `int`, `bool`, `uint64`). Для `bool` подходят `1`/`0`/`true`/`false`.
- Ошибка разбора приводит к `panic` при старте (в `NewConfig` `envconfig.Process` завёрнут в `sync.Once`, ошибка не возвращается, а паникует).
- Отсутствующая переменная не является ошибкой — поле получает нулевое значение соответствующего типа (пустая строка, `0`, `false`). Обязательность полей не проверяется.
В отличие от python-сервисов, приложение **загружает `.env` автоматически**: в `cmd/httpserver/main.go` вызывается `godotenv.Load(".env")` (если файла нет — печатается предупреждение и используются переменные окружения процесса).
Отдельного конфиг-файла (yaml/toml) у приложения нет.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Файл `.env` в рабочем каталоге (`godotenv.Load`) либо переменные окружения процесса |
| Локально (контейнеры) | `.docker/docker-compose.yml`: `env_file``.docker/.env` + `.docker/.docker.env` |
| Kubernetes (Helm, репозиторий приложения) | `.helm/values-<env>.yaml`: блоки `envs` (обычные значения) и `secrets` (из k8s-секретов); шаблон `.helm/templates/api.yaml` |
| Kubernetes (IaC, этот репозиторий) | `iac/apps/bim/base/backend-deployment.yaml`: блок `env` и секреты из Vault (аннотации `vault.hashicorp.com/*`, шаблон `bim-postgresql`) |
| CI/CD (GitLab) | `.gitlab-ci.yml`: подключение общих пайплайнов `generic/common-ci` и переменные `workflow.rules` по ветке/тегу |
Способы запуска процессов:
| Команда | Точка входа | Назначение |
| --- | --- | --- |
| `httpserver` (`make api` → `go install ./cmd/httpserver`) | `cmd/httpserver/main.go` | HTTP API. При старте применяет миграции (`appmigrations.RunOnStartup`), поднимает `net/http/pprof` на `:8081`, затем запускает API на `API_ADDRESS` |
| `migrations` (`go install ./cmd/migrations`) | `cmd/migrations/main.go` | Отдельный запуск миграций БД |
Порядок запуска в контейнере (`.docker/entrypoint.sh`): запускается `/go/bin/httpserver` (строка запуска миграций закомментирована — миграции выполняются самим приложением на старте). Финальный образ (`.docker/api.dockerfile`) — `scratch` со статически слинкованным бинарником `httpserver` и вшитым CA-сертификатом Postgres (`/root/yandex_pg.pem`).
## Переменные приложения
Ниже перечислены все переменные, читаемые кодом (`config/config.go`). Префикса нет. Дефолт — это нулевое значение типа Go, если переменная не задана.
### App
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `APP_NAME` | string | `""` | Имя приложения (в коде помечено TODO — в deploy-envs не задаётся) |
| `APP_VERSION` | string | `""` | Версия приложения (TODO — в deploy-envs не задаётся) |
| `LOG_LEVEL` | string | `""` | Уровень логирования, передаётся в `logging.NewLogger` (TODO — в deploy-envs не задаётся) |
| `API_ADDRESS` | string | `""` | Адрес прослушивания HTTP API в формате `host:port` (напр. `0.0.0.0:8080`) |
| `TEST_ENV` | string | `""` | Служебное поле для тестов |
### PostgreSQL
Сервис работает с **тремя** кластерами PostgreSQL (master, slave-1, slave-2). Выбор кластера для конкретного BIM выполняется по его id в `internal/app/http/httpserver.go` (сравнение с `LAST_MASTER_BIM*`/`LAST_SLAVE_1_BIM*`). Строка подключения собирается в `Config.GetPostgresConnectionURL` (`postgres://user:password@addr:port/db`, при `ENABLE_SSL=false` добавляется `?sslmode=disable`).
Master-кластер:
| Переменная | Тип | Назначение |
| --- | --- | --- |
| `POSTGRES_ADDRESS` | string | Хост PostgreSQL (master) |
| `POSTGRES_PORT` | string | Порт PostgreSQL (master) |
| `POSTGRES_USER` | string | Пользователь БД |
| `POSTGRES_PASSWORD` | string | Пароль пользователя БД |
| `POSTGRES_DB` | string | Имя базы данных |
| `POSTGRES_POOL_SIZE` | int | Размер пула соединений |
| `DB_CERT_PATH` | string | Путь к CA-сертификату PostgreSQL (используется при `ENABLE_SSL=1`) |
Slave-1 кластер — те же поля с суффиксом `_2`:
| Переменная | Тип | Назначение |
| --- | --- | --- |
| `POSTGRES_ADDRESS_2` | string | Хост PostgreSQL (slave-1) |
| `POSTGRES_PORT_2` | string | Порт |
| `POSTGRES_USER_2` | string | Пользователь |
| `POSTGRES_PASSWORD_2` | string | Пароль |
| `POSTGRES_DB_2` | string | База данных |
| `POSTGRES_POOL_SIZE_2` | int | Размер пула |
| `DB_CERT_PATH_2` | string | Путь к CA-сертификату |
Slave-2 кластер — те же поля с суффиксом `_3`:
| Переменная | Тип | Назначение |
| --- | --- | --- |
| `POSTGRES_ADDRESS_3` | string | Хост PostgreSQL (slave-2) |
| `POSTGRES_PORT_3` | string | Порт |
| `POSTGRES_USER_3` | string | Пользователь |
| `POSTGRES_PASSWORD_3` | string | Пароль |
| `POSTGRES_DB_3` | string | База данных |
| `POSTGRES_POOL_SIZE_3` | int | Размер пула |
| `DB_CERT_PATH_3` | string | Путь к CA-сертификату |
### Шардирование BIM по кластерам
| Переменная | Тип | Назначение |
| --- | --- | --- |
| `LAST_MASTER_BIM` | uint64 | Верхняя граница id BIM для master-кластера (API v1) |
| `LAST_MASTER_BIM_V3` | uint64 | Верхняя граница id BIM для master-кластера (API v2 / BIM v3) |
| `LAST_SLAVE_1_BIM` | uint64 | Верхняя граница id BIM для slave-1 (API v1) |
| `LAST_SLAVE_1_BIM_V3` | uint64 | Верхняя граница id BIM для slave-1 (API v2 / BIM v3) |
### Прочее
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_SSL` | bool | `false` | Подключение к PostgreSQL по TLS. При `false` в DSN добавляется `sslmode=disable` |
| `ENABLE_SQL_QUERY` | bool | `false` | Логировать SQL-запросы |
| `DJANGO_HOST` | string | `""` | Базовый URL Django-бэкенда для проверки прав администратора (см. `ENDPOINTS.md`) |
| `INTEGRATION_TESTS` | bool | `false` | Режим интеграционных тестов. При `true` `CheckUserIsAdmin` всегда возвращает `true` |
## Переменные, не читаемые приложением (инфраструктура/сборка/тесты)
Присутствуют в `.docker/.env`, `.docker/.docker.env`, `docker-compose.yml` или Helm, но `config/config.go` их не разбирает.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `API_PORT` | `.docker/.env` | Порт API для локального compose |
| `POSTGRES_EXTERNAL_PORT`, `POSTGRES_EXTERNAL_PORT_2` | `docker-compose.yml` | Проброс портов контейнеров Postgres |
| `POSTGRES_ADDRESS2` | `.docker/.docker.env` | Опечатка/легаси (нет суффикса `_`), кодом не читается |
| `GRPC_ADDRESS`, `GRPC_PORT` | `.docker/.env` | Объявлены, но кодом не используются |
| `TEST_BIM`, `JWT_TEST` | `.docker/.env`, `.docker/.docker.env` | Данные для интеграционных тестов |
| `POSTGRES_*_4`, `DB_CERT_PATH_4` | `.helm/values-*.yaml`, IaC `backend-deployment.yaml` | Четвёртый кластер задаётся в деплое, но `config.go` доходит только до суффикса `_3` |
| `LAST_SLAVE_2_BIM(_V3)`, `LAST_SLAVE_3_BIM(_V3)`, `LAST_SLAVE_4_BIM(_V3)` | `.helm/values-*.yaml` | Заданы в values, но кодом не читаются (актуальны только `LAST_MASTER_*` и `LAST_SLAVE_1_*`) |
| `CI_COMMIT_SHORT_SHA` | `.gitlab-ci.yml` (build-arg) | Тег/версия сборки образа |
## Переменные из Helm-чарта приложения (`.helm/values-<env>.yaml`)
Обычные значения задаются в блоке `envs` для каждого окружения (`stage`/`preprod`/`production`) и содержат те же переменные `POSTGRES_*`, `API_ADDRESS`, `DJANGO_HOST`, `ENABLE_SSL`, `ENABLE_SQL_QUERY`, `LAST_*`, что описаны выше (различаются адресами БД, размерами пула, границами шардирования и доменом Django).
Значения из секретов (блок `secrets`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
| --- | --- | --- |
| `POSTGRES_USER` | `bim-v2-database-secret` | `username` |
| `POSTGRES_PASSWORD` | `bim-v2-database-secret` | `password` |
| `POSTGRES_USER_2` | `bim-v2-database-secret-2` | `username` |
| `POSTGRES_PASSWORD_2` | `bim-v2-database-secret-2` | `password` |
| `POSTGRES_USER_3` | `bim-v2-database-secret-3` | `username` |
| `POSTGRES_PASSWORD_3` | `bim-v2-database-secret-3` | `password` |
| `POSTGRES_USER_4` | `bim-v2-database-secret-3` | `username` |
| `POSTGRES_PASSWORD_4` | `bim-v2-database-secret-3` | `password` |
Ключевые не-env значения чарта: `api.name`, `api.image`, `api.version`, `api.port` (`8080`), `api.replicas`, `api.service_name`/`api.service_port` (`80`), `api.service_account`, `api.requests.memory`/`cpu`, `api.api_host`, `api.api_host_prefix` (`/bimv2/api/`), `api.api_path` (`/api/`), `api.internal_path` (`/internal/`), `api.permitted_ns` (namespace'ы, которым разрешён доступ к `/internal/*`), `imagePullSecrets`.
Сетевой слой (`.helm/templates/mesh-config.yaml`, Istio):
- `VirtualService` (при `api.virtual_service.enabled`) маршрутизирует `api_host_prefix``api_path`, задаёт CORS (`allowOrigins` — `*.sarex.io` и `localhost`, `allowHeaders` включают `Authorization`, `Content-Type`, `Identity`).
- `AuthorizationPolicy` `routes-v2`: доступ к `/api/*` разрешён только от istio-ingressgateway при наличии claim `token_type=access`; доступ к `/internal/*` — только из namespace'ов `api.permitted_ns`.
## Переменные из IaC-репозитория (`iac/apps/bim/`)
Деплой в этом репозитории (kustomize) устроен иначе, чем чарт приложения:
- `base/backend-deployment.yaml` — Deployment `backend` в namespace `bim`, образ `bim-api`, `containerPort: 8000`, `API_ADDRESS=0.0.0.0:8000`, health-проба `GET /ping`.
- Секреты PostgreSQL инъектируются **из Vault** (аннотации `vault.hashicorp.com/*`, роль `bim`, путь `secrets/data/postgresql/apps/bim`) в файл `/vault/secrets/bim-postgresql`, который экспортируется в окружение перед запуском (`set -a; . /vault/secrets/bim-postgresql; set +a; exec ./httpserver`). Шаблон Vault задаёт `POSTGRES_ADDRESS[_2../_4]`, `POSTGRES_PORT*`, `POSTGRES_DB*` (все указывают на `postgresql.bim.svc.cluster.local:5432`, БД `bim_db`) и `POSTGRES_USER*`/`POSTGRES_PASSWORD*` из Vault.
- Прочие env заданы прямо в манифесте: `LAST_MASTER_BIM`, `LAST_MASTER_BIM_V3`, `LAST_SLAVE_1_BIM`, `POSTGRES_POOL_SIZE`, `DB_CERT_PATH_2/3/4`, `DJANGO_HOST` (`http://backend.django.svc.cluster.local:8000`), `ENABLE_SQL_QUERY=0`, `ENABLE_SSL=0`.
- Оверлеи `brusnika-prod`, `brusnika-stage`, `yc-k8s-test` патчат base (реплики, образ, postgresql).
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны `generic/common-ci` (`universal-pipeline-<env>.yaml`, `common-security-scan.yaml`, `common-build.yaml`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace |
| --- | --- | --- |
| ветка `master` | `preprod` | `bim-api-preprod` |
| ветка `stage` | `stage` | `bim-api-stage` |
| тег (`CI_COMMIT_TAG`) | `prod` | `bim-api-prod` |
Общие переменные job'ов: `RELEASE_NAME=bim-backend-v2`, `CHART_NAME=bim-backend-v2`, `CHART_VERSION=0.0.1-<stand>`, `IMAGE_PATH=api.image`, `DOCKERFILE_PATH=.docker/api.dockerfile`, `HELM_SET_ARGS=--set api.image=${IMAGE_NAME}`, `BUILD_ARGS=--build-arg CI_COMMIT_SHORT_SHA=...`, флаги `ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_STATE_UPDATE`/`ENABLE_DEPLOY=true`, `ENABLE_LINTER=false`. Стадии: `linter → test → unittest → prebuild-secscan → build → state-update → deploy`.
## Замечания и потенциальные проблемы
- Переменные разбираются **без префикса** (`envconfig.Process("", ...)`) — имена совпадают с тегами `envconfig` полей структуры `Config`.
- Обязательность полей не валидируется: отсутствующая переменная молча получает нулевое значение. Например пустой `API_ADDRESS` приведёт к попытке слушать на `":0"`.
- В коде объявлены только три кластера (`POSTGRES_*`, `_2`, `_3`), тогда как в деплое присутствует и четвёртый (`_4`). Переменные `_4` и дополнительные `LAST_SLAVE_2/3/4_*` в окружении задаются, но приложением не используются.
- Приложение загружает `.env` автоматически (`godotenv.Load(".env")`); при отсутствии файла ошибки нет — берутся переменные процесса.
- В `NewConfig` ошибка `envconfig.Process` не возвращается, а вызывает `panic` (обёрнута в `sync.Once`).
## Минимальный набор для локального запуска
Postgres поднимается через `docker-compose` (`.docker/docker-compose.yml`), приложение — через `make api` и запуск `httpserver`. Минимально необходимо задать (готовые примеры — в `.env.example`):
- `API_ADDRESS`
- Master-кластер: `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_POOL_SIZE`
- Slave-кластеры `_2` и `_3` (те же поля) — сервис создаёт соединения ко всем трём при старте
- `ENABLE_SSL=0`, `DB_CERT_PATH*` (при `ENABLE_SSL=1`)
- `DJANGO_HOST`
- `LAST_MASTER_BIM`, `LAST_MASTER_BIM_V3`, `LAST_SLAVE_1_BIM`, `LAST_SLAVE_1_BIM_V3`
- `INTEGRATION_TESTS=0`, `ENABLE_SQL_QUERY` по необходимости

55
apps/bim/ENDPOINTS.md Normal file
View File

@ -0,0 +1,55 @@
# Эндпоинты, с которыми взаимодействует bim-backend-v2
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается сам сервис `bim-backend-v2` (исходящие вызовы). Эндпоинты, которые сервис **предоставляет**, описаны в `openapi.yaml`.
## Как устроено взаимодействие
Исходящие вызовы выполняются HTTP-клиентом на базе [`go-resty/resty`](https://github.com/go-resty/resty) в пакете `pkg/django_client` (`DjangoClient`). Клиент создаётся в `NewRestClient`:
- базовый хост — `SetHostURL(cfg.DjangoHost)` (переменная `DJANGO_HOST`);
- таймаут запроса — `SetTimeout(10 * time.Second)`;
- число повторов — `SetRetryCount(5)`.
Аутентификация проксируется: JWT пользователя извлекается из контекста запроса (`auth.JWTFromContext`), при необходимости отбрасывается схема `Bearer `, и токен передаётся во внешний сервис заголовком `Authorization: Bearer <jwt>` (`SetAuthToken` + `SetAuthScheme("Bearer")`). Заголовок `Content-Type: application/json`.
В режиме интеграционных тестов (`INTEGRATION_TESTS=1`) внешний вызов не выполняется — `CheckUserIsAdmin` возвращает `true`.
## Базовые хосты по сервисам и окружениям
Значение берётся из переменной `DJANGO_HOST` (см. `CONFIGURATION.md`). Итоговый URL = `<DJANGO_HOST>` + `path` эндпоинта.
| Сервис | Назначение | Значение `DJANGO_HOST` |
| --- | --- | --- |
| `django` (Sarex backend) | Проверка прав пользователя (админ/не админ) | локально/`stage`: `https://stage.sarex.io`; `preprod`: `https://lk.preprod.sarex.io`; `prod`: `https://lk.sarex.io`; контур (IaC): `http://backend.django.svc.cluster.local:8000` |
## Эндпоинты по сервисам
### `django` — Sarex backend
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `api/client/settings/` | Получить настройки текущего пользователя. Используется поле `is_admin` (проверка прав администратора в `CheckUserIsAdmin`) |
Детали вызова `GET api/client/settings/`:
| Параметр | Значение |
| --- | --- |
| Заголовки | `Authorization: Bearer <jwt пользователя>`, `Content-Type: application/json` |
| Тело запроса | нет |
| Ожидаемый ответ | `{ "is_admin": bool }` (структура `userSettingsFromDjango`) |
| Успешные статусы | `200`, `201` |
| Поведение при ошибке | Любая ошибка транспорта, `resp == nil` или статус вне `200/201` логируется, метод трактует пользователя как **не администратора** (`false`) |
## Где используется
Проверка `CheckUserIsAdmin` вызывается в обработчиках, изменяющих модели статусов (требуют прав администратора). При отсутствии прав такие эндпоинты возвращают `403 No admin rights`:
- `POST /api/v1/bims/{bim_id}/status_model` — создание модели статусов BIM;
- `DELETE /api/v1/bims/{bim_id}/delete_status_model` — удаление модели статусов BIM;
- `POST /api/v1/companies/{company_id}/status_model` — создание модели статусов компании;
- `DELETE /api/v1/companies/{company_id}/status_model` — удаление модели статусов компании.
## Замечания
- Единственная внешняя HTTP-зависимость сервиса — Django-бэкенд (`DJANGO_HOST`). Прочие интеграции (PostgreSQL, pprof) не являются HTTP-вызовами к внешним REST-сервисам.
- Подпись JWT самим сервисом не проверяется — проброшенный токен просто пересылается в Django, который и выполняет авторизацию (см. также раздел «Аутентификация» в `openapi.yaml`).

1158
apps/bim/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

80
apps/cde/.env.example Normal file
View File

@ -0,0 +1,80 @@
# =============================================================================
# cde-orchestration-demo (Оркестратор) — пример переменных окружения
#
# Разбор переменных: github.com/sethvargo/go-envconfig.
# Файл .env подгружается через godotenv (флаг -env-file, по умолчанию `.env`).
# Глобального префикса у переменных НЕТ (в отличие от других сервисов Sarex).
# Вложенные секции задаются префиксом в env-тегах (напр. DATABASE_URL).
#
# Значения ниже — примеры и значения по умолчанию из кода. Секреты (ключи,
# пароли, токены) оставлены пустыми — заполните собственными.
# Один и тот же .env используется всеми бинарниками (см. docker-compose.yml),
# но каждый бинарник читает только нужное ему подмножество (см. CONFIGURATION.md).
# =============================================================================
# --- Общие ---
ENVIRONMENT=production
LOG_LEVEL=info # cmd/http: default=info; воркеры: default=debug
IS_CONTOUR=false
# --- HTTP-сервер (cmd/http) ---
ADDRESS=:8080
PROCESS_CACHE_TTL=60
PROCESS_CACHE_CLEAR_INTERVAL=60
PUBLIC_KEY= # PEM RSA public key для проверки JWT (обязателен для http)
OPERATE_URL= # Базовый URL Camunda Operate/REST
SAREX_BACKEND_BASE_URL=https://stage.sarex.io
# --- Camunda ---
CAMUNDA_KEYCLOAK_URL=
CAMUNDA_CLIENT_ID=operate # только cmd/http
CAMUNDA_CLIENT_SECRET=identity-secret-for-components # только cmd/http
CAMUNDA_PROCESS_DEFINITION_ID=actionsOnApproval # только cmd/http
# --- Zeebe ---
ZEEBE_GATEWAY=
ZEEBE_CLIENT_ID=zeebe
ZEEBE_CLIENT_SECRET=identity-secret-for-components
ZEEBE_WORKER_JOB_TYPE=markDocuments # у каждого воркера своё значение по умолчанию (см. CONFIGURATION.md)
# --- Database (PostgreSQL) ---
DATABASE_URL=
DATABASE_POOL_SIZE=10
# --- S3 ---
S3_ENDPOINT_URL=https://storage.yandexcloud.net
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_PARTITION_ID=yc
S3_SIGNING_REGION=ru-central1
# --- Auth (без префикса) ---
AUTH_HOST=
USERNAME=
PASSWORD=
# --- Flows (сервис рабочих процессов) ---
FLOWS_URL=
FLOWS_INTERNAL_URL=
# --- Workspaces ---
WORKSPACES_URL=
# --- Workflows (используется воркером split_pdf) ---
WORKFLOWS_HOST=
WORKFLOWS_IMAGE_TAG=latest
# --- System log ---
SYSTEM_LOG_URL=
# --- Telegram (алертинг воркеров) ---
TELEGRAM_TOKEN=
TELEGRAM_ALERT_GROUP_ID=
TELEGRAM_DEBUG=false
# --- AMQP / RabbitMQ (markings v2, copy v2) ---
AMQP_HOST=
AMQP_PORT=
AMQP_USER=
AMQP_PASSWORD=
AMQP_PATH_API=

215
apps/cde/CONFIGURATION.md Normal file
View File

@ -0,0 +1,215 @@
# Конфигурация проекта cde-orchestration-demo (Оркестратор)
Документ описывает все переменные окружения и способы конфигурирования сервиса.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется библиотекой [`github.com/sethvargo/go-envconfig`](https://github.com/sethvargo/go-envconfig): в каждом бинарнике вызывается `envconfig.Process(ctx, config)` со своей структурой `Config` (см. `internal/app/http/config.go` и `internal/app/worker/*/config.go`).
Особенности разбора:
- **Глобального префикса нет** — в отличие от других сервисов Sarex, переменные не имеют общего префикса (напр. просто `LOG_LEVEL`, `DATABASE_URL`).
- Вложенные секции задаются тегом `env:", prefix=XXX_"` на поле-структуре. Например поле `Database DatabaseConfig` с `prefix=DATABASE_` и полем `Url` с тегом `env:"URL"` даёт переменную `DATABASE_URL`.
- Структура `Auth` **не имеет** тега `prefix`, поэтому её поля читаются без префикса: `AUTH_HOST`, `USERNAME`, `PASSWORD`.
- Значения по умолчанию задаются в теге через `default=...`. Отсутствие поля без дефолта не приводит к ошибке `envconfig` (пустое значение), но может привести к падению при инициализации зависимого клиента (напр. пустой `PUBLIC_KEY` вызовет панику при старте http).
Файл `.env` подгружается через [`github.com/lpernett/godotenv`](https://github.com/lpernett/godotenv): в `main` вызывается `godotenv.Load(*envFileFlag)`, путь задаётся флагом `-env-file` (по умолчанию `.env`). Если файла нет — загрузка пропускается, переменные берутся из окружения процесса.
Отдельного конфиг-файла (yaml/toml) у приложения нет.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Флаг `-env-file``.env` (godotenv) и/или переменные окружения процесса |
| Локально (docker-compose) | `docker-compose.yml`: у каждого сервиса `env_file: .env` |
| Kubernetes (Helm) | `.helm/values.yaml`: блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секрета `cde-secret`) чарта `universal-chart` |
| Kubernetes (kustomize, этот репозиторий) | Vault Agent инжектит секрет `secrets/data/vault/apps/cde` в файл `/vault/secrets/cde-env`, который экспортируется в окружение перед запуском бинарника (`source /vault/secrets/cde-env`) |
## Бинарники (точки входа)
| Бинарник | Точка входа | Config | Назначение |
| --- | --- | --- | --- |
| `http` | `cmd/http/main.go` | `internal/app/http` | HTTP API оркестрации (процессы, подпись, загрузка BPMN) |
| `copy` | `cmd/worker/copy/main.go` | `.../worker/copy` | Воркер копирования документов (`copyDocuments`) |
| `copyv2` | `cmd/worker/copyv2/main.go` | `.../worker/copyv2` | Копирование документов v2 (`copyDocumentsv2`) |
| `create_versions` | `cmd/worker/create_versions/main.go` | `.../worker/create_versions` | Создание версий (`createVersions`) |
| `create_versionsv2` | `cmd/worker/create_versionsv2/main.go` | `.../worker/create_versionsv2` | Создание версий v2 (`createVersionsv2`) |
| `flows_callback` | `cmd/worker/flows_callback/main.go` | `.../worker/flows_callback` | Обратный вызов в сервис flows (`flowsCallback`) |
| `markings` | `cmd/worker/markings/main.go` | `.../worker/markings` | Маркировка документов (`markDocuments`) |
| `markingsv2` | `cmd/worker/markingsv2/main.go` | `.../worker/markingsv2` | Маркировка v2 (`markDocumentsv2`) |
| `sign` | `cmd/worker/sign/main.go` | `.../worker/sign` | Подпись документов (`signDocuments`) |
| `signv2` | `cmd/worker/signv2/main.go` | `.../worker/signv2` | Подпись v2 (`signDocumentsv2`) |
| `split_pdf` | `cmd/worker/split_pdf/main.go` | `.../worker/split_pdf` | Разбиение/обработка PDF (`splitPDF`) |
| `update_bundles` | `cmd/worker/update_bundles/main.go` | `.../worker/update_bundles` | Обновление бандлов (`updateBundles`) |
Воркеры — это Zeebe job-workers: они подключаются к Zeebe-gateway и обрабатывают Service Task соответствующего типа (`ZEEBE_WORKER_JOB_TYPE`). HTTP-сервер, помимо приёма запросов, обращается к Camunda Operate/Zeebe (см. `ENDPOINTS.md`).
## Переменные приложения
Дефолт `—` означает, что значения по умолчанию нет.
### Общие (для всех бинарников)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENVIRONMENT` | string | `production` (у воркеров) | Окружение развёртывания. У `http` не читается |
| `LOG_LEVEL` | string | `info` (http) / `debug` (воркеры) | Уровень логирования |
| `IS_CONTOUR` | bool | `false` | Режим изолированного контура (влияет на инициализацию S3) |
### HTTP-сервер (`cmd/http`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ADDRESS` | string | `:8080` | Адрес прослушивания Fiber |
| `PROCESS_CACHE_TTL` | int (сек) | `60` | TTL кеша процессов |
| `PROCESS_CACHE_CLEAR_INTERVAL` | int (сек) | `60` | Интервал очистки кеша процессов |
| `PUBLIC_KEY` | string (PEM) | — | RSA public key для проверки JWT. Обязателен: при пустом/некорректном значении сервис падает при старте |
| `OPERATE_URL` | string | — | Базовый URL Camunda Operate/REST |
| `SAREX_BACKEND_BASE_URL` | string | — | Базовый URL sarex-backend (проверка MRPA при подписи) |
### Camunda (`CAMUNDA_*`)
| Переменная | Тип | Значение по умолчанию | Назначение | Где используется |
| --- | --- | --- | --- | --- |
| `CAMUNDA_KEYCLOAK_URL` | string | — | URL Keycloak для OAuth (Zeebe/Operate) | http + воркеры |
| `CAMUNDA_CLIENT_ID` | string | `operate` | Client ID для Operate | только http |
| `CAMUNDA_CLIENT_SECRET` | string | `identity-secret-for-components` | Client secret для Operate | только http |
| `CAMUNDA_PROCESS_DEFINITION_ID` | string | `actionsOnApproval` | ID определения процесса | только http |
### Zeebe (`ZEEBE_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ZEEBE_GATEWAY` | string | — | Адрес Zeebe gateway |
| `ZEEBE_CLIENT_ID` | string | `zeebe` | Client ID |
| `ZEEBE_CLIENT_SECRET` | string | `identity-secret-for-components` | Client secret |
| `ZEEBE_WORKER_JOB_TYPE` | string | зависит от воркера | Тип Service Task, который слушает воркер |
Значения `ZEEBE_WORKER_JOB_TYPE` по умолчанию: `markDocuments` (http/markings), `markDocumentsv2` (markingsv2), `copyDocuments` (copy), `copyDocumentsv2` (copyv2), `createVersions` (create_versions), `createVersionsv2` (create_versionsv2), `flowsCallback` (flows_callback), `signDocuments` (sign), `signDocumentsv2` (signv2), `splitPDF` (split_pdf), `updateBundles` (update_bundles).
### Database (`DATABASE_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DATABASE_URL` | string | — | DSN подключения к PostgreSQL (pgx) |
| `DATABASE_POOL_SIZE` | int32 | `10` | Размер пула соединений |
### S3 (`S3_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `S3_ENDPOINT_URL` | string | `https://storage.yandexcloud.net` | Эндпоинт S3 |
| `S3_ACCESS_KEY_ID` | string | — | Access key |
| `S3_SECRET_ACCESS_KEY` | string | — | Secret key |
| `S3_PARTITION_ID` | string | `yc` | Partition ID (aws-sdk-go-v2) |
| `S3_SIGNING_REGION` | string | `ru-central1` | Регион для подписи запросов |
### Auth (без префикса)
Поле-структура `Auth` не имеет префикса, поэтому переменные читаются напрямую.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `AUTH_HOST` | string | — | Хост сервиса аутентификации (получение токенов пользователя/админа) |
| `USERNAME` | string | — | Логин админ-учётки |
| `PASSWORD` | string | — | Пароль админ-учётки |
### Flows (`FLOWS_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `FLOWS_URL` | string | — | Базовый URL сервиса flows |
| `FLOWS_INTERNAL_URL` | string | — | Внутренний URL flows (обновление документов review). Есть только в конфигах `copy`/`copyv2` |
### Workspaces (`WORKSPACES_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WORKSPACES_URL` | string | — | URL сервиса рабочих областей (используется воркерами copy/copyv2) |
### Workflows (`WORKFLOWS_*`)
Используется воркером `split_pdf`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WORKFLOWS_HOST` | string | — | Хост сервиса workflows |
| `WORKFLOWS_IMAGE_TAG` | string | `latest` | Тег docker-образа задач обработки PDF |
> Container registry (`cr.yandex/crp3ccidau046kdj8g9q`) и флаг `UploadResultsToS3=true` заданы в коде воркера `split_pdf` (`worker.go`), а не через окружение.
### System log (`SYSTEM_LOG_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SYSTEM_LOG_URL` | string | — | URL сервиса системных логов (copy, copyv2, create_versions, create_versionsv2) |
### Telegram (`TELEGRAM_*`)
Клиент алертинга для воркеров.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `TELEGRAM_TOKEN` | string | — | Токен бота |
| `TELEGRAM_ALERT_GROUP_ID` | int64 | — | ID группы для алертов |
| `TELEGRAM_DEBUG` | bool | `false` | Debug-режим бота |
### AMQP / RabbitMQ (`AMQP_*`)
Используется воркерами `markingsv2` и `copyv2` (маркировка бандлов через RabbitMQ).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `AMQP_HOST` | string | — | Хост RabbitMQ |
| `AMQP_PORT` | string | — | Порт RabbitMQ |
| `AMQP_USER` | string | — | Пользователь |
| `AMQP_PASSWORD` | string | — | Пароль |
| `AMQP_PATH_API` | string | — | Vhost / путь API в URL подключения |
## Матрица «переменная → бинарник»
| Секция | http | copy | copyv2 | create_versions | create_versionsv2 | flows_callback | markings | markingsv2 | sign | signv2 | split_pdf | update_bundles |
| --- | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: |
| Общие | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `ADDRESS`/`PROCESS_CACHE_*` | ✓ | | | | | | | | | | | |
| `PUBLIC_KEY`,`OPERATE_URL`,`SAREX_BACKEND_BASE_URL`,`CAMUNDA_CLIENT_*`,`CAMUNDA_PROCESS_DEFINITION_ID` | ✓ | | | | | | | | | | | |
| `CAMUNDA_KEYCLOAK_URL`,`ZEEBE_*` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `DATABASE_*` | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | |
| `S3_*` | ✓ | ✓ | ✓ | ✓ | ✓ | | | ✓ | ✓ | ✓ | | |
| `AUTH_*`/`USERNAME`/`PASSWORD` | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `FLOWS_URL` | | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ |
| `FLOWS_INTERNAL_URL` | | ✓ | ✓ | | | | | | | | | |
| `WORKSPACES_URL` | | ✓ | ✓ | | | | | | | | | |
| `WORKFLOWS_*` | | | | | | | | | | | ✓ | |
| `SYSTEM_LOG_URL` | | ✓ | ✓ | ✓ | ✓ | | | | | | | |
| `AMQP_*` | | | ✓ | | | | | ✓ | | | | |
| `TELEGRAM_*` | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
> Матрица построена по структурам `Config` соответствующих бинарников. Наличие поля в структуре не всегда означает, что клиент инициализируется — см. замечания ниже.
## Переменные в Helm-чарте (`.helm/values.yaml`)
Обычные значения (блок `envs`):
| Переменная | Значения по окружениям |
| --- | --- |
| `SAREX_BACKEND_BASE_URL` | stage: `https://stage.sarex.io`, preprod: `https://preprod.sarex.io`, production: `https://lk.sarex.io` |
Значения из секрета (блок `secretEnvs`, общий для всех сервисов через якорь `*cde_secret_envs`), берутся из k8s-секрета `cde-secret` одноимёнными ключами: `ENVIRONMENT`, `LOG_LEVEL`, `ZEEBE_GATEWAY`, `DATABASE_URL`, `S3_ENDPOINT_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `PUBLIC_KEY`, `PDM_URL`, `FLOWS_URL`, `FLOWS_INTERNAL_URL`, `USERNAME`, `PASSWORD`, `CAMUNDA_PROCESS_DEFINITION_ID`, `OPERATE_URL`, `CAMUNDA_KEYCLOAK_URL`, `CAMUNDA_CLIENT_ID`, `CAMUNDA_CLIENT_SECRET`, `WORKFLOWS_HOST`, `WORKSPACES_URL`, `AUTH_HOST`, `TELEGRAM_ALERT_GROUP_ID`, `TELEGRAM_TOKEN`, `IS_CONTOUR`, `AMQP_HOST`, `AMQP_PORT`, `AMQP_USER`, `AMQP_PASSWORD`, `AMQP_PATH_API`, `SYSTEM_LOG_URL`.
## Развёртывание через kustomize (этот репозиторий)
В `iac/apps/cde` секреты доставляются не через `secretEnvs` чарта, а через **Vault Agent Injector**: аннотации подов монтируют секрет `secrets/data/vault/apps/cde` в файл `/vault/secrets/cde-env`, который экспортируется перед запуском (`source /vault/secrets/cde-env`, затем `exec /http` или `/worker`). Дополнительно контейнерам задаётся `S3_IS_CONTOUR=true` (примечание: в коде используется переменная `IS_CONTOUR`).
Оверлеи: `base` (общие манифесты), `brusnika-stage`, `brusnika-prod`, `yc-k8s-test`.
## Замечания и потенциальные проблемы
- **Нет глобального префикса.** Имена переменных короткие (`USERNAME`, `PASSWORD`, `AUTH_HOST`) — легко пересечься с системными; следите за окружением процесса.
- **`PUBLIC_KEY` обязателен для `http`** — при пустом/некорректном PEM сервис паникует на старте (`server.go`).
- **`PDM_URL`** присутствует в `secretEnvs` Helm, но соответствующий клиент (`internal/adapters/http/pdm`) в текущей сборке нигде не инициализируется — переменная фактически не используется кодом.
- **`S3_IS_CONTOUR`** задаётся в kustomize-манифестах, тогда как код читает `IS_CONTOUR` (без префикса `S3_`). Проверьте, что для влияния на поведение выставлен именно `IS_CONTOUR`.
- **`FLOWS_INTERNAL_URL`** объявлен только в конфигах `copy`/`copyv2`; в остальных воркерах поля нет, хотя ключ есть в общем секрете.
- **Значения по умолчанию для секретов Camunda/Zeebe** (`identity-secret-for-components`) подходят для локального стенда, но должны переопределяться в prod.
- Приложение читает `.env` только если файл существует; иначе используются переменные окружения. `make`-целей для генерации `.env` в репозитории нет — используйте этот `.env.example` как шаблон.

130
apps/cde/ENDPOINTS.md Normal file
View File

@ -0,0 +1,130 @@
# Эндпоинты внешних сервисов, с которыми взаимодействует cde-orchestration-demo
Документ описывает все HTTP/AMQP/gRPC-эндпоинты внешних сервисов, к которым обращается оркестратор (сервер `cmd/http` и воркеры). Это исходящие вызовы; описание API, который оркестратор **предоставляет**, — в `openapi.yaml`.
## Как устроено взаимодействие
Клиенты внешних сервисов лежат в `internal/adapters/http/*` и `internal/adapters/*`. Базовые URL берутся из переменных окружения (см. `CONFIGURATION.md`). Для HTTP используются два клиента: Fiber `client` (camunda, flows, pdm, workspaces, system_log) и `go-resty` (workflows, sarexbackend). Аутентификация — по-разному в зависимости от сервиса (OAuth client_credentials, Bearer-токен пользователя/админа, Basic).
## Базовые адреса по сервисам
| Сервис | Переменная базового адреса | Клиент | Назначение |
| --- | --- | --- | --- |
| Camunda Operate / REST | `OPERATE_URL` | Fiber | Управление инстансами процессов, переменными, сообщениями |
| Camunda Keycloak | `CAMUNDA_KEYCLOAK_URL` | Fiber | OAuth-токен для Operate |
| Zeebe Gateway | `ZEEBE_GATEWAY` | gRPC (SDK) | Деплой BPMN, обработка job'ов воркерами |
| Auth (токены) | `AUTH_HOST` | Fiber/resty | Токены пользователя/админа для flows и pdm |
| Flows | `FLOWS_URL`, `FLOWS_INTERNAL_URL` | Fiber | Ревью, действия пользователя, обновление бандлов/документов |
| PDM | `PDM_URL` | Fiber | Маркировка бандлов *(клиент не подключён — см. примечание)* |
| Workflows | `WORKFLOWS_HOST` | resty | Создание workflow обработки PDF |
| Workspaces | `WORKSPACES_URL` | Fiber | Создание рабочих областей |
| System log | `SYSTEM_LOG_URL` | Fiber | Отправка системных логов |
| Sarex backend | `SAREX_BACKEND_BASE_URL` | resty | Получение MRPA по id |
| Telegram Bot API | `TELEGRAM_TOKEN` | tgbotapi | Алертинг воркеров |
| RabbitMQ (AMQP) | `AMQP_*` | amqp091 | Маркировка бандлов (RPC), вычисление хеш-сумм |
## Эндпоинты по сервисам
### Camunda Operate / REST (`OPERATE_URL`, `CAMUNDA_KEYCLOAK_URL`)
`internal/adapters/http/camunda/client.go`. Все запросы (кроме получения токена) идут с заголовком `Authorization: Bearer <access_token>`.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{CAMUNDA_KEYCLOAK_URL}/auth/realms/camunda-platform/protocol/openid-connect/token` | OAuth-токен (`grant_type=client_credentials`) |
| POST | `{OPERATE_URL}/v2/process-instances` | Создать инстанс процесса |
| GET | `{OPERATE_URL}/api/process-instances/{key}` | Получить инстанс процесса |
| POST | `{OPERATE_URL}/v1/variables/search` | Поиск переменных процесса по имени/значению |
| GET | `{OPERATE_URL}/api/process-instances/{key}/variables/{varId}` | Значение конкретной переменной |
| POST | `{OPERATE_URL}/api/process-instances/{key}/variables` | Список переменных инстанса (`scopeId`) |
| POST | `{OPERATE_URL}/v2/messages/publication` | Публикация сообщения процессу (напр. `signRequest`) |
### Zeebe Gateway (`ZEEBE_GATEWAY`)
`internal/adapters/zeebe`. gRPC через официальный SDK `camunda/zeebe/clients/go/v8`. Используется для деплоя определений процессов (`NewProcessDefinition`) и для job-воркеров, которые слушают Service Task типа `ZEEBE_WORKER_JOB_TYPE` и по завершении/ошибке возвращают результат в инстанс процесса.
### Auth — токены (`AUTH_HOST`)
Используется адаптерами flows и pdm для получения Bearer-токенов.
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `{AUTH_HOST}/token/user/{userID}/` | Токен от имени пользователя |
| POST | `{AUTH_HOST}/token/` | Токен админа (`username`/`password`) |
### Flows (`FLOWS_URL`, `FLOWS_INTERNAL_URL`)
`internal/adapters/http/flows/adapter.go`. Запросы (кроме `update-documents`) идут с `Authorization: Bearer <token>`; часть операций — с ретраями (экспоненциальный backoff).
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{FLOWS_URL}/user-actions/` | Добавить действие в историю |
| PATCH | `{FLOWS_URL}/reviews/{reviewID}/approve/` | Утвердить review (со статусом/комментарием) |
| PATCH | `{FLOWS_URL}/reviews/{reviewID}/update-bundles/` | Обновить бандлы review |
| PATCH | `{FLOWS_INTERNAL_URL}/reviews/{reviewID}/update-documents/` | Обновить документы review (внутренний URL, без авторизации) |
| GET | `{FLOWS_URL}/reviews/{reviewID}/documents/` | История бандлов документов review |
### PDM (`PDM_URL`, `AUTH_HOST`) — не подключён
`internal/adapters/http/pdm/client.go`. Клиент реализован, но в текущей сборке нигде не инициализируется (см. примечание в конце). Для полноты — какие вызовы он делает:
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `{AUTH_HOST}/token/user/{userID}/` | Токен пользователя (Basic auth логин/пароль) |
| PUT | `{PDM_URL}/bundles/{bundleID}/marks` | Проставить маркировки бандлу |
### Workflows (`WORKFLOWS_HOST`, `AUTH_HOST`)
`internal/adapters/http/workflows/adapter.go` (resty). Используется воркером `split_pdf`.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{WORKFLOWS_HOST}/internal/v1/companies/{companyID}/workflows` | Создать workflow обработки/оптимизации PDF |
> Базовый URL resty-клиента установлен в `AUTH_HOST`, а адрес workflows подставляется полным (`WORKFLOWS_HOST`). В параметрах задач передаётся `django_host = AUTH_HOST`.
### Workspaces (`WORKSPACES_URL`)
`internal/adapters/http/workspaces/client.go`. Используется воркерами `copy`/`copyv2`.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{WORKSPACES_URL}/internal/v2/workspaces` | Создать рабочую область |
### System log (`SYSTEM_LOG_URL`)
`internal/adapters/http/system_log/client.go`. Используется воркерами `copy`, `copyv2`, `create_versions`, `create_versionsv2`.
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `{SYSTEM_LOG_URL}/api/v0/system_log` | Отправить пакет системных логов |
### Sarex backend (`SAREX_BACKEND_BASE_URL`)
`internal/adapters/sarexbackend/client.go` (resty). Используется HTTP-сервером при подписи (проверка MRPA). Токены проксируются из входящего запроса (`Authorization`, опц. `Identity`).
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `{SAREX_BACKEND_BASE_URL}/api/core/mrpa/{id}/` | Получить MRPA по id |
### Telegram Bot API (`TELEGRAM_TOKEN`, `TELEGRAM_ALERT_GROUP_ID`)
`internal/adapters/http/telegram/client.go` через `go-telegram-bot-api`. Отправка алертов в заданную группу при ошибках/паниках в задачах воркеров.
### RabbitMQ / AMQP (`AMQP_*`)
Подключение вида `amqp://{USER}:{PASSWORD}@{HOST}:{PORT}/{PATH_API}`.
| Адаптер | Назначение |
| --- | --- |
| `internal/adapters/amqp/markings` | Маркировка бандла и получение его хеш-суммы (RPC поверх временной очереди, `correlation_id`). Используется воркерами `markingsv2`/`copyv2` |
| `internal/adapters/amqp/rpc` | Общий RPC-клиент RabbitMQ (переподключение, вычисление хеш-сумм объектов) |
## Обработка ошибок
Каждый адаптер проверяет `StatusCode()` ответа и оборачивает не-`200 OK` в ошибку с телом ответа (`internal/errors`). Отдельно у sarexbackend маппинг: `404 → ErrNotFound`, `403 → ErrForbidden`, прочие → generic. Адаптеры flows и pdm выполняют ретраи с экспоненциальным backoff (до 5 попыток).
## Примечания
- **PDM-клиент не подключён.** `internal/adapters/http/pdm` реализован, а переменная `PDM_URL` присутствует в Helm-секрете, но `pdm.New(...)` не вызывается ни в одном бинарнике. Раздел оставлен для полноты; при фактическом использовании актуализируйте документ.
- Пути даны относительно базовых URL из окружения; итоговый URL = `<базовый адрес>` + `путь`.

298
apps/cde/openapi.yaml Normal file
View File

@ -0,0 +1,298 @@
openapi: 3.0.3
info:
title: CDE Orchestration API
version: "0.0.0"
description: |
HTTP API оркестратора **cde-orchestration-demo**
(`gitlab.com/sarex-team/cde-orchestration-demo`) — управление процессами
согласования/подписи документов в Camunda (Zeebe/Operate).
Сервис написан на Go (**Fiber v3**), точка входа — `cmd/http/main.go`,
сборка приложения — `internal/app/http/server.go`. Все ручки объявлены в
`internal/controller/http/v0` и смонтированы под префиксом `/api`.
### Аутентификация
Все эндпоинты проходят через middleware `pkg/http/middleware/auth.go`.
Токен передаётся заголовком `Authorization: Bearer <jwt>`. Поддерживаются
два режима:
1. **Sarex** (по умолчанию) — подпись JWT проверяется RSA public key из
переменной `PUBLIC_KEY`.
2. **Zitadel** — если передан дополнительный заголовок
`Identity: Bearer <jwt>`, полезная нагрузка берётся из метаданных
этого токена (`urn:zitadel:iam:user:metadata`); подпись основным
сервисом не проверяется.
При отсутствии/некорректности заголовков middleware возвращает `401`.
### Замечания
- Ручка `GET /api/process/{instance_key}` может вернуть `425 Too Early`,
если процесс есть в кеше, но ещё не создан в Camunda (идёт обработка).
- Тело ответов на запись (`process`, `sign`, `operate`) обычно пустое —
значим только HTTP-статус.
contact:
name: cde-orchestration-demo
url: https://gitlab.com/sarex-team/cde-orchestration-demo
servers:
- url: http://localhost:8080/api
description: Локальный запуск (Fiber, ADDRESS по умолчанию :8080)
- url: http://cde-svc.cde.svc.cluster.local/api
description: Внутрикластерный адрес (ClusterIP)
tags:
- name: infra
description: Служебные эндпоинты
- name: process
description: Процессы согласования/подписи
- name: sign
description: Отправка подписей в процесс
- name: operate
description: Управление определениями процессов (BPMN)
security:
- bearerAuth: []
paths:
/:
get:
tags: [infra]
summary: Проверка доступности
description: Возвращает 200 OK. Требует валидной авторизации (middleware).
operationId: root
responses:
"200":
description: OK
"401":
description: Не авторизован
/process/:
post:
tags: [process]
summary: Запустить процесс
description: |
Создаёт инстанс процесса согласования/подписи в Camunda по документам
из `payload`. Если для `flow_id` уже есть активный инстанс — вернётся
`400`.
operationId: createProcess
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ProcessCreateRequest"
responses:
"202":
description: Процесс принят к обработке
"400":
description: Ошибка валидации или активный инстанс уже существует
"401":
description: Не авторизован
"500":
description: Внутренняя ошибка (ошибка создания инстанса в Camunda)
/process/{instance_key}:
get:
tags: [process]
summary: Получить состояние процесса
description: |
Возвращает текущее состояние процесса по `flow_id` (в пути — числовой
ключ). Логика: если запись есть в кеше, но нет активного инстанса в
Camunda — процесс ещё обрабатывается (`425`).
operationId: getProcess
parameters:
- name: instance_key
in: path
required: true
description: Числовой идентификатор (`flow_id`)
schema:
type: integer
format: uint64
responses:
"200":
description: Состояние процесса
content:
application/json:
schema:
$ref: "#/components/schemas/GetProcessInstanceResponse"
"401":
description: Не авторизован
"404":
description: Процесс не найден
"425":
description: Too Early — процесс ещё обрабатывается
"500":
description: Внутренняя ошибка
/sign/:
post:
tags: [sign]
summary: Отправить подписи в процесс
description: |
Публикует сообщение `signRequest` в процесс Camunda с подписями из
`payload`. Для элементов с `mrpa_id` предварительно проверяется доступ
через sarex-backend.
operationId: sign
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SignRequest"
responses:
"200":
description: Подписи приняты, сообщение отправлено в процесс
"401":
description: Отсутствует токен авторизации
"403":
description: Нет прав на MRPA
"422":
description: MRPA не найдена
"500":
description: Внутренняя ошибка
/operate/processes:
post:
tags: [operate]
summary: Загрузить определение процесса (BPMN)
description: |
Принимает BPMN-файл (multipart, поле `definition`) и деплоит его в
Zeebe.
operationId: deployProcessDefinition
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [definition]
properties:
definition:
type: string
format: binary
description: BPMN-файл определения процесса
responses:
"200":
description: Определение загружено
"400":
description: Файл не передан/некорректен
"401":
description: Не авторизован
"500":
description: Внутренняя ошибка деплоя
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT Sarex (проверяется по `PUBLIC_KEY`). Для режима Zitadel
дополнительно передаётся заголовок `Identity: Bearer <jwt>`.
schemas:
ProcessCreateRequest:
type: object
required: [flow_id, author_id]
description: Запрос на старт процесса (`internal/dto/http.go`).
properties:
flow_id:
type: integer
format: uint64
description: Внешний ID процесса (обязателен, != 0)
author_id:
type: integer
format: uint64
description: ID автора запроса (обязателен, != 0)
company_id:
type: integer
format: uint64
step_id:
type: integer
format: uint64
metadata:
type: object
additionalProperties: true
payload:
type: array
description: Документы для обработки (произвольные объекты)
items:
type: object
additionalProperties: true
overwrite_marks:
type: boolean
mode:
type: string
description: Режим работы ("original", "copy", "both")
use_signature:
type: boolean
create_copy_on_finish:
type: boolean
comment:
type: string
is_last_signer:
type: boolean
GetProcessInstanceResponse:
type: object
description: Состояние инстанса процесса (`internal/dto/http.go`).
properties:
status:
type: string
use_signature:
type: boolean
is_finished:
type: boolean
is_ready_for_sign:
type: boolean
is_last_signer:
type: boolean
create_copy_on_finish:
type: boolean
comment:
type: string
payload:
description: Полезная нагрузка процесса (структура зависит от процесса)
nullable: true
instance_key:
type: integer
format: uint64
SignRequest:
type: object
required: [flow_id, payload]
description: Запрос на подпись документов в процессе (`internal/dto/http.go`).
properties:
flow_id:
type: integer
format: uint64
metadata:
type: object
additionalProperties: true
payload:
type: array
items:
$ref: "#/components/schemas/SignRequestPayloadElem"
SignRequestPayloadElem:
type: object
properties:
bundle_id:
type: string
format: uuid
author_id:
type: integer
format: uint64
signature:
type: string
description: Сгенерированная подпись (помещается в p7s)
algorithm:
type: string
mrpa_id:
type: string
format: uuid
nullable: true
description: Если задан — проверяется доступ через sarex-backend

View File

@ -0,0 +1,54 @@
# Пример переменных окружения для comparisons-backend.
# Значения разбираются пакетом kelseyhightower/envconfig (config/config.go, FromEnv).
# Префикса нет — имена переменных используются как есть.
# Приложение НЕ загружает .env автоматически: переменные нужно экспортировать
# в окружение (для локального запуска см. .docker/.env и docker-compose).
# API
API_ADDRESS=0.0.0.0:8080
# Database (PostgreSQL)
POSTGRES_ADDRESS=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_DB=comparisons
POSTGRES_POOL_SIZE=10
# TLS-подключение к БД. При ENABLE_SSL=1 используется YC-PG-CERTIFICATE как CA.
ENABLE_SSL=0
DB_CERT_PATH=/home/user/.postgresql/root.crt
YC-PG-CERTIFICATE=
# Внешние сервисы (внутрикластерные адреса)
DOCUMENTATION_URL=http://documentations-service.documentations-stage/
EXTERNAL_DOCUMENTATION_URL=https://stage-api.sarex.io/documentations
# Хранилище файлов PDM (multistorage, config/storage.go). Без него сервис
# стартует, но с предупреждением — PDM-хранилище будет недоступно.
DOCUMENTATION_FILESTREAM_URL=http://documentations-filestream-service.documentations-stage/
WORKFLOW_URL=http://workflows-service.processing-stage/
WORKSPACE_URL=http://workspaces-service.workspaces-stage/
EXTERNAL_WORKSPACE_URL=
COMPARISON_URL=http://comparisons-backend-service.comparisons-stage/
WORKFLOW_IMAGES_VERSION=develop
BIM_V2_INTERNAL_URL=http://bim-backend-v2-service.bim-api-stage/
# Используется клиентом workflow (pdf2pdf) как django_host в параметрах задачи.
DJANGO_HOST=https://stage.sarex.io
# Comparisons
# Ограничение параллелизма ABAP-сравнения (0 — без ограничения).
ABAP_FIXED_CONC=0
# Sentry / окружение
ENVIRONMENT=stage
SENTRY_DSN=
SENTRY_DEBUG=0
# Отладка
# Логировать SQL-запросы (go-pg query hook).
ENABLE_SQL_QUERY=0
# --- Дополнительно (используются только при локальном запуске / внешними
# --- библиотеками, кодом config.Config напрямую не читаются) ---
# S3_SERVICE_ACCOUNT=/etc/sarex/yc_s3_doc_account.json
# DJANGO_ORIGINATOR=docs_local
# NAMESPACE=local

View File

@ -0,0 +1,148 @@
# Конфигурация проекта comparisons-backend
Документ описывает все переменные окружения и способы конфигурирования сервиса `comparisons-backend` (Go).
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` функцией `FromEnv()` через библиотеку [`kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (структура `Config`).
Особенности разбора:
- **префикса нет**`envconfig.Process("", &cfg)` вызывается с пустым префиксом, поэтому имена переменных совпадают с тегами `envconfig:"..."` (напр. `POSTGRES_ADDRESS`, `API_ADDRESS`);
- **вложенности нет** — все переменные плоские (без разделителя секций);
- отдельные значения читаются напрямую через `os.Getenv` в обход структуры `Config`: `DOCUMENTATION_FILESTREAM_URL` (`config/storage.go`) и `DJANGO_HOST` (`clients/workflow_cli/pdf2pdf.go`).
Отдельного конфиг-файла (yaml/toml) у приложения нет. Приложение **не загружает `.env` автоматически** — переменные нужно экспортировать в окружение самому либо задавать через `--env`/`env_file` (docker-compose).
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса. Собирается через `make api` (`go install ./cmd/api`, `./cmd/migrations`) |
| Локально (контейнеры) | `.docker/.env` + `.docker/docker-compose.yml` (`make docker`). Postgres поднимается из этого же compose |
| Kubernetes (Helm, репозиторий бэкенда) | `.helm/values-<env>.yaml`: блок `envs` (обычные значения) и `secrets` (значения из k8s-секретов); шаблон `.helm/templates/api.yaml` |
| Kubernetes (Kustomize, этот репозиторий infra) | `apps/comparisons/base` + оверлеи; env задаются в `base/backend-deployment.yaml` и патчах оверлеев. **Схема переменных здесь отличается — см. раздел ниже** |
| CI/CD (GitLab) | `.gitlab-ci.yml`: подключает шаблоны `generic/common-ci` (stage/preprod/prod), job `unit-tests` (образ `golang:1.21`) |
Способы запуска процессов (`cmd/*`):
| Команда | Точка входа | Назначение |
| --- | --- | --- |
| `api` (`make api`) | `cmd/api/main.go` | HTTP API (gorilla/mux). Перед стартом настраивает Sentry и подключение к Postgres |
| `migrations` | `cmd/migrations/main.go` | Миграции БД (`robinjoseph08/go-pg-migrations`). Создание: `go run ./cmd/migrations/main.go create <name>` |
## Переменные приложения
Дефолт `—` означает, что явного значения по умолчанию нет (пустая строка / нулевое значение типа Go). Обязательность отдельных переменных проверяется в рантайме при создании клиентов.
### API
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `API_ADDRESS` | string | — | Адрес и порт прослушивания HTTP-сервера (напр. `0.0.0.0:8080`) |
### Database (PostgreSQL)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
| `POSTGRES_USER` | string | — | Пользователь БД |
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
| `POSTGRES_DB` | string | — | Имя базы данных |
| `POSTGRES_POOL_SIZE` | int | — | Размер пула соединений (в коде `main.go` пул жёстко равен `30`; переменная читается, но фактически используется значение из кода) |
| `ENABLE_SSL` | bool | `false` | Подключение к БД по TLS. При `true` используется `YC-PG-CERTIFICATE` как корневой сертификат, `ServerName` = `POSTGRES_ADDRESS` |
| `DB_CERT_PATH` | string | — | Путь к CA-сертификату PostgreSQL (используется инфраструктурой; в Helm — смонтированный файл) |
| `YC-PG-CERTIFICATE` | string | — | Содержимое CA-сертификата для TLS-подключения к БД. Имя с дефисами не соответствует остальным (`envconfig` допускает произвольный тег) |
### Внешние сервисы
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DOCUMENTATION_URL` | string | — | Внутренний URL сервиса документаций (обязателен для `documentation_cli`) |
| `EXTERNAL_DOCUMENTATION_URL` | string | — | Внешний URL сервиса документаций (обязателен для `documentation_cli`) |
| `DOCUMENTATION_FILESTREAM_URL` | string | — | URL PDM-хранилища файлов (`config/storage.go`). Если не задан — сервис стартует, но PDM-хранилище недоступно (лог-warning) |
| `WORKFLOW_URL` | string | — | Внутренний URL сервиса workflow (обязателен для `workflow_cli`) |
| `WORKSPACE_URL` | string | — | Внутренний URL сервиса workspace (обязателен для `workspace_cli`) |
| `EXTERNAL_WORKSPACE_URL` | string | — | Внешний URL сервиса workspace |
| `COMPARISON_URL` | string | — | URL самого сервиса сравнений (обязателен для `workflow_cli`) |
| `WORKFLOW_IMAGES_VERSION` | string | — | Версия/тег образов задач workflow (обязателен для `workflow_cli`) |
| `BIM_V2_INTERNAL_URL` | string | — | Внутренний URL BIM API v2 |
| `DJANGO_HOST` | string | — | Хост Django (LK). Передаётся как `django_host` в параметры задачи pdf2pdf (`clients/workflow_cli/pdf2pdf.go`) |
### Comparisons
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ABAP_FIXED_CONC` | uint64 | `0` | Ограничение параллелизма ABAP-сравнения (`0` — без ограничения) |
### Sentry и окружение
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENVIRONMENT` | string | — | Окружение развёртывания (`stage`/`preprod`/`prod`); передаётся в Sentry |
| `SENTRY_DSN` | string | — | DSN Sentry |
| `SENTRY_DEBUG` | bool | `false` | Debug-режим Sentry |
### Отладка
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_SQL_QUERY` | bool | `false` | Логировать SQL-запросы (query hook go-pg) |
### Дополнительные переменные (не читаются `config.Config`)
Присутствуют в `.docker/.env` для локального запуска или используются внешними библиотеками (`gotools`), но напрямую структурой `Config` не разбираются:
| Переменная | Где встречается | Назначение |
| --- | --- | --- |
| `S3_SERVICE_ACCOUNT` | `.docker/.env` | Путь к JSON сервисного аккаунта S3 |
| `DJANGO_ORIGINATOR` | `.docker/.env` | Ориджинатор для интеграции с Django |
| `NAMESPACE` | `.docker/.env` | Логическое пространство имён для локального запуска |
| `API_ADDRESS_FILE` | `.helm/values-*.yaml` | Адрес file-варианта API (задан в Helm, кодом не используется) |
## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)
Обычные значения задаются в блоке `envs` для каждого окружения (`stage`/`preprod`/`production`) и содержат те же переменные приложения, что описаны выше (различаются адресами БД/сервисов, `WORKFLOW_IMAGES_VERSION`, доменом Django и т.п.).
Значения из секретов (блок `secrets`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
| --- | --- | --- |
| `POSTGRES_USER` | `ya-pg-secret` | `username` |
| `POSTGRES_PASSWORD` | `ya-pg-secret` | `password` |
| `YC-PG-CERTIFICATE` | `yc-pg-certificate` | `certificate` |
Прочие значения чарта (не переменные приложения): `api.*` (имя, образ, порт, реплики, ресурсы, ingress `api_host`/`api_host_prefix`/`api_path`/`internal_path`, `permitted_ns`), `version`, `imagePullSecrets`.
## Развёртывание через Kustomize (этот репозиторий, `apps/comparisons`)
Структура: `base` (общие манифесты) и оверлеи `brusnika-stage`, `brusnika-prod`, `yc-k8s-test`. Секреты БД и публичный JWT-ключ подтягиваются из **HashiCorp Vault** (аннотации `vault.hashicorp.com/*` в `base/backend-deployment.yaml`), а не из k8s-секретов.
> **Важно: расхождение схем переменных.** Манифест `base/backend-deployment.yaml` использует другой (более новый) набор имён переменных, чем Go-код из `comparisons-backend` (`config/config.go`): напр. `HTTP_PORT`, `LOGGER_LOG_LEVEL`, `DATABASE_NAME`, `DOCUMENTATIONS_INTERNAL_HOST`, `DOCUMENTATIONS_EXTERNAL_HOST`, `WORKFLOWS_HOST`, `WORKFLOWS_DJANGO_HOST`, `WORKFLOWS_BIMV2_INTERNAL_HOST`, `WORKSPACES_HOST`, `EAV_HOST`, `APP_NAME`, `AUTH_PUBLIC_KEY`, `WORKFLOWS_CONFIG_FILEPATH` и др., а также `/ping` в health-проверках и образ `comparisons_backend_prod`. Такой схемы нет в Go-репозитории. Перед использованием этих файлов стоит убедиться, какой именно образ бэкенда деплоится оверлеем: если это Go-сервис из `comparisons-backend`, набор env нужно привести к именам из таблиц выше.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны `generic/common-ci` и переключает окружение по ветке/тегу:
| Условие | Шаблон | Окружение |
| --- | --- | --- |
| ветка `stage` | `gitlab-ci/comparisons-backend/.gitlab-ci-stage.yml` | stage |
| ветка `master` | `gitlab-ci/comparisons-backend/.gitlab-ci-preprod.yml` | preprod |
| тег (`CI_COMMIT_TAG`) | `gitlab-ci/comparisons-backend/.gitlab-ci-prod.yml` | prod |
Стадии: `prebuild-secscan`, `dependencies-build`, `unittest`, `build`, `state-update`, `deploy`. Job `unit-tests` (образ `golang:1.21`) выполняет `make unit-tests`.
## Минимальный набор для локального запуска
Postgres поднимается через `.docker/docker-compose.yml` (`make docker`). Минимально необходимо задать:
- `API_ADDRESS`
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_POOL_SIZE`, `ENABLE_SSL` (`0` локально)
- `DOCUMENTATION_URL`, `EXTERNAL_DOCUMENTATION_URL`, `DOCUMENTATION_FILESTREAM_URL`
- `WORKFLOW_URL`, `WORKSPACE_URL`, `COMPARISON_URL`, `WORKFLOW_IMAGES_VERSION`
- `BIM_V2_INTERNAL_URL`, `DJANGO_HOST`
- `ENVIRONMENT`; при использовании Sentry — `SENTRY_DSN`
- по желанию: `ENABLE_SQL_QUERY` (`1` для отладки SQL), `ABAP_FIXED_CONC`
Готовые значения-примеры приведены в `.env.example`.

View File

@ -0,0 +1,77 @@
# Эндпоинты, с которыми взаимодействует comparisons-frontend
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `comparisons-frontend`).
## Как устроено взаимодействие
Все запросы описаны декларативно в реестре `module/networking/endpoints.ts` (объект `endpoints`). Каждый эндпоинт задаётся структурой `Endpoint`:
- `service` — логическое имя сервиса (`EServices`, см. таблицу хостов ниже);
- `method` — HTTP-метод (`GET`/`POST`/`PUT`/`PATCH`/`DELETE`);
- `path(args)` — функция, возвращающая путь запроса (с подстановкой параметров/query);
- `body(args)` — опционально, формирование тела запроса.
Запрос выполняется единой функцией `fetch(endpoint, params)`, которая через `httpService` (`module/httpService/httpService.ts`, поверх `@sarex-team/sdk-js` + `axios`) отправляет запрос на базовый хост сервиса. Базовый хост подставляется `resolveHost(service)` из `module/httpService/hosts.ts` в зависимости от `buildEnv` (`__BUILD_ENV__`, задаётся сборкой; по умолчанию `prod`). Результат возвращается как `{ resp }` либо `{ errMessage }`.
## Базовые хосты по сервисам и окружениям
Значения из `module/httpService/hosts.ts` (`allHosts`). Итоговый URL = `<базовый хост сервиса>` + `path` эндпоинта. Хосты берутся из `@sarex-team/sdk-js` (`resolveHost`).
| Сервис (`EServices`) | Назначение | `stage` | `prod` |
| --- | --- | --- | --- |
| `comparisons` | Сервис сравнений (comparisons-backend) | `https://stage-api.sarex.io/comparisons` | `https://api.sarex.io/comparisons` |
| `documentations` | Сервис документации (диски, документы) | `https://stage-api.sarex.io/documentations` | `https://api.sarex.io/documentations` |
| `sarexApi` | Gateway/API Sarex (`/gateway`) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
| `sarex` | Локальный сервис данных (`/api/core`) | `""` (относительные пути) | `""` |
| `workflows` | Сервис обработки документов | `https://stage-api.sarex.io/workflows` | `https://api.sarex.io/workflows` |
| `bimv2` | BIM API v2 | `https://stage-api.sarex.io/bimv2` | `https://api.sarex.io/bimv2` |
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
> Также определены окружения `local`, `preprod` и `contour`. В `local` сервисы проксируются на `https://localhost:9000/sarex-backend` и `https://localhost:9000/sarex-api-backend/*`. В `contour` используются относительные пути (`/comparisons`, `/documentations`, `/bimv2`, `/workflows`) для изолированного контура. В `preprod``https://api.preprod.sarex.io/*`.
## Эндпоинты по сервисам
### `comparisons` — Сервис сравнений
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getTypes` | GET | `/api/v1/types` | Справочник типов сравнений и их параметров |
| `createComparison` | POST | `/api/v1/comparisons` | Создать сравнение (тело — параметры сравнения) |
| `getComparisons` | GET | `/api/v1/comparisons?workspace_id={workspaceId}` | Список сравнений рабочей области |
| `deleteComparison` | DELETE | `/api/v1/comparisons/{id}` | Удалить сравнение |
### `documentations` — Сервис документации
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getDisks` | GET | `/api/v1/disks` | Список дисков |
| `getDocuments` | GET | `/api/v1/disks/{id}/documents` | Документы диска |
| `getNearestNameTemplate` | GET | `/api/v1/documents/{documentId}/name_template` | Ближайший шаблон имени документа |
### `sarexApi` — Gateway/API Sarex
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getDocumentsV2` | GET | `/gateway/api/v1/disks/{id}/documents?parent_id=&child_id=&search=&{attributeValue}` | Документы диска с фильтрами (родитель/ребёнок/поиск/атрибуты) |
### `sarex` — Локальный сервис данных
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getUsersByIDs` | GET | `/api/core/users/?id={ids}` | Пользователи по списку id |
### `workflows` — Сервис обработки документов
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getWorkflow` | GET | `/api/v1/workflows/{workflowId}` | Workflow по id |
### `bimv2` — BIM API v2
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getBim` | GET | `/api/v1/bims/{bimId}` | BIM-модель по id |
## Обработка ошибок
`fetch` перехватывает исключения запроса и возвращает `{ errMessage }` (строка ошибки) вместо данных; успешный ответ приходит как `{ resp: <data> }`. Явного маппинга кодов ответа в реестре нет — обработка и отображение ошибок выполняются на уровне репозиториев/вью-моделей, использующих `fetch`.

View File

@ -0,0 +1,793 @@
openapi: 3.0.3
info:
title: Comparisons Service API
version: "1.0.0"
description: |
REST API сервиса **comparisons-backend** (`pdm/comparisons-backend`) — создание и
просмотр сравнений (облако-BIM отклонение/статусы, облако-облако, облако-поверхность,
pdf-pdf), их элементов, изменений и фильтров.
Сервис написан на Go (роутер **gorilla/mux**). Сервер собирается функцией
`bootstrapAPI` в `cmd/api/bootstrap.go`. Роутинг состоит из двух групп:
- публичный API — префикс `/api/v1` (`cmd/api/routes_api.go`);
- внутренний API — префикс `/internal/v1` (`cmd/api/routes_internal.go`),
предназначен для вызовов внутри кластера (через ingress не публикуется).
### Аутентификация
Публичные эндпоинты (`/api/v1/*`) требуют JWT: middleware `auth.JWTToCtx` +
`auth.JWTUserExtractorFromCtx` (`gitlab.com/sarex-team/gotools/auth`). Токен
передаётся заголовком `Authorization: Bearer <jwt>`; middleware
`auth.DeleteJWTFromQueryMiddleware` дополнительно позволяет передать токен
query-параметром `jwt` (он удаляется из запроса после разбора).
Внутренние эндпоинты (`/internal/v1/*`) аутентификации на уровне приложения
не требуют — ограничение доступа обеспечивается сетевым слоем.
### Обработка ошибок
Ошибки возвращаются функцией `httperror.Write` (`gotools/httperror`). Основные
статусы: `400` — некорректный запрос/параметры, `404` — сравнение не найдено,
`500` — внутренняя ошибка. Идентификатор запроса пробрасывается middleware
`reqid`.
### Замечания (расхождения кода)
- `GET /api/v1/comparisons` возвращает данные разной формы в зависимости от
query-параметра: `workspace_id` → `{ comparisons: [] }`, `document_id` →
`{ results: [] }`, `bundle_id` → одиночный объект сравнения.
- `GET /api/v1/filter_fields` и `GET /api/v1/elements` работают только со
сравнениями типа `deviation`; для других типов вернётся `400`.
- Схема сравнения полиморфна по полю `type` (`deviation`/`c2c`/`c2s`/`abap`/`pdf2pdf`).
contact:
name: comparisons-backend
url: https://gitlab.com/sarex-team/comparisons-backend
servers:
- url: https://api.sarex.io/comparisons
description: Production (ingress)
- url: https://stage-api.sarex.io/comparisons
description: Stage (ingress)
- url: https://api.preprod.sarex.io/comparisons
description: Preprod (ingress)
- url: http://comparisons-backend-service.comparisons-stage
description: Внутрикластерный адрес (ClusterIP). Единственный способ достучаться до /internal/v1
- url: http://localhost:8080
description: Локальный запуск (API_ADDRESS по умолчанию 0.0.0.0:8080)
tags:
- name: types
description: Справочник типов сравнений
- name: comparisons
description: Сравнения — создание, просмотр, удаление
- name: elements
description: Элементы сравнения и их изменения
- name: internal
description: Внутренние эндпоинты (только внутри кластера)
security:
- bearerAuth: []
paths:
# ==========================================================================
# Types
# ==========================================================================
/api/v1/types:
get:
tags: [types]
summary: Справочник типов сравнений
description: Возвращает доступные типы сравнений и их параметры (для построения форм).
operationId: getTypes
responses:
'200':
description: Список типов сравнений
content:
application/json:
schema:
type: object
properties:
types:
type: array
items:
$ref: '#/components/schemas/CompareType'
# ==========================================================================
# Comparisons
# ==========================================================================
/api/v1/comparisons:
post:
tags: [comparisons]
summary: Создать сравнение
description: |
Создаёт сравнение указанного типа. Автор берётся из JWT. Если `parent_doc_id`
и `parent_disk_id` не заданы — вычисляются по рабочей области.
operationId: createComparison
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateRequest'
responses:
'200':
description: Созданное сравнение
content:
application/json:
schema:
$ref: '#/components/schemas/Comparison'
'400':
description: Некорректный запрос / неизвестный тип сравнения
'500':
description: Внутренняя ошибка
get:
tags: [comparisons]
summary: Список сравнений
description: |
Возвращает сравнения по одному из query-параметров. Форма ответа зависит
от параметра (см. описание в `info`). Должен быть задан ровно один параметр.
operationId: getComparisons
parameters:
- name: workspace_id
in: query
required: false
schema:
type: string
format: uuid
description: "Сравнения рабочей области → ответ `{ comparisons: [] }`"
- name: bundle_id
in: query
required: false
schema:
type: string
format: uuid
description: Сравнение по бандлу → ответ — одиночный объект
- name: document_id
in: query
required: false
schema:
type: integer
format: int64
description: "Сравнения документа → ответ `{ results: [] }`"
responses:
'200':
description: Результат (форма зависит от параметра запроса)
content:
application/json:
schema:
oneOf:
- type: object
properties:
comparisons:
type: array
items:
$ref: '#/components/schemas/Comparison'
- type: object
properties:
results:
type: array
items:
$ref: '#/components/schemas/Comparison'
- $ref: '#/components/schemas/Comparison'
'400':
description: Не задан workspace_id / bundle_id / document_id
'500':
description: Внутренняя ошибка
/api/v1/comparisons/{comparison_id}:
parameters:
- name: comparison_id
in: path
required: true
schema:
type: integer
format: int64
get:
tags: [comparisons]
summary: Сравнение по id
operationId: getComparisonById
responses:
'200':
description: Сравнение
content:
application/json:
schema:
$ref: '#/components/schemas/Comparison'
'404':
description: Сравнение не найдено
'500':
description: Внутренняя ошибка
delete:
tags: [comparisons]
summary: Удалить сравнение
description: Мягкое удаление сравнения. Автор берётся из JWT.
operationId: deleteComparisonById
responses:
'200':
description: Успешно удалено
'400':
description: Некорректный id
'500':
description: Внутренняя ошибка
/api/v1/filter_fields:
get:
tags: [comparisons]
summary: Поля фильтрации сравнения
description: |
Возвращает набор полей фильтрации для сравнения типа `deviation` по документу.
Для полей отклонения (`deviation`, `deviation_x/y/z`) заполняются `min`/`max`.
Если сравнение по документу не найдено — вернётся пустой `results`.
operationId: getFilterFields
parameters:
- name: doc_id
in: query
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Поля фильтрации
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
$ref: '#/components/schemas/FilterFields'
'400':
description: Не задан doc_id / сравнение не типа deviation
'500':
description: Внутренняя ошибка
/api/v1/tolerance:
get:
tags: [comparisons]
summary: Допуск (tolerance) сравнения
description: Возвращает значение допустимого отклонения для сравнения типа `deviation` по бандлу.
operationId: getTolerance
parameters:
- name: bundle_id
in: query
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Допуск
content:
application/json:
schema:
type: object
properties:
tolerance:
type: number
format: double
'400':
description: Не задан / некорректный bundle_id
'404':
description: Сравнение не найдено
'500':
description: Внутренняя ошибка
# ==========================================================================
# Elements
# ==========================================================================
/api/v1/elements:
get:
tags: [elements]
summary: Список элементов сравнения
description: |
Постранично возвращает элементы сравнения типа `deviation` по документу.
Параметры отклонения задаются строкой вида `min:max`. Если сравнение не
найдено — вернётся пустой `results` с типами по умолчанию.
operationId: getListElements
parameters:
- name: doc_id
in: query
required: true
schema:
type: integer
format: int64
- name: limit
in: query
required: false
schema:
type: integer
default: 10
minimum: 0
- name: offset
in: query
required: false
schema:
type: integer
default: 0
minimum: 0
- name: order_by
in: query
required: false
schema:
type: string
enum: [deviation, deviation_x, deviation_y, deviation_z, sarex_id, name]
default: sarex_id
- name: order
in: query
required: false
schema:
type: string
enum: [asc, desc]
default: asc
- name: deviation
in: query
required: false
schema:
type: string
description: Диапазон общего отклонения в формате `min:max`
- name: deviation_x
in: query
required: false
schema:
type: string
description: Диапазон отклонения по X в формате `min:max`
- name: deviation_y
in: query
required: false
schema:
type: string
description: Диапазон отклонения по Y в формате `min:max`
- name: deviation_z
in: query
required: false
schema:
type: string
description: Диапазон отклонения по Z в формате `min:max`
- name: deviation_status
in: query
required: false
schema:
type: string
enum: [in_tolerance, out_of_tolerance]
- name: view_status
in: query
required: false
schema:
type: string
enum: [viewed, not_viewed]
- name: only_with_comment
in: query
required: false
schema:
type: boolean
responses:
'200':
description: Страница элементов
content:
application/json:
schema:
$ref: '#/components/schemas/ListElementsResponse'
'400':
description: Некорректные параметры запроса
'500':
description: Внутренняя ошибка
/api/v1/elements/{element_id}:
parameters:
- name: element_id
in: path
required: true
schema:
type: integer
format: int64
patch:
tags: [elements]
summary: Обновить элемент
description: |
Обновляет один из атрибутов элемента: комментарий, статус отклонения или
статус просмотра. Должно быть задано ровно одно из полей. Изменение
фиксируется записью в истории изменений.
operationId: updateElement
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateElementRequest'
responses:
'200':
description: Успешно обновлено (тело не возвращается)
'400':
description: Некорректный запрос
'500':
description: Внутренняя ошибка
/api/v1/elements/{element_id}/changes:
get:
tags: [elements]
summary: История изменений элемента
operationId: getElementChanges
parameters:
- name: element_id
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Список изменений
content:
application/json:
schema:
type: object
properties:
changes:
type: array
items:
$ref: '#/components/schemas/Change'
'400':
description: Некорректный element_id
'500':
description: Внутренняя ошибка
# ==========================================================================
# Internal
# ==========================================================================
/internal/v1/comparisons/{comparison_id}/webhook:
post:
tags: [internal]
summary: Webhook результата сравнения (внутренний)
description: |
Вызывается после завершения обработки сравнения типа `deviation`. Скачивает
`deviation_json` из PDM-хранилища по бандлу, создаёт элементы сравнения и
возвращает содержимое `deviation_json`. Аутентификация на уровне приложения
не требуется.
operationId: comparisonWebhook
security: []
parameters:
- name: comparison_id
in: path
required: true
schema:
type: integer
format: int64
responses:
'200':
description: Содержимое deviation_json
content:
application/json:
schema:
$ref: '#/components/schemas/DeviationJSON'
'404':
description: Сравнение не найдено
'500':
description: Внутренняя ошибка
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT в заголовке `Authorization: Bearer <jwt>`. Допускается передача
query-параметром `jwt` (удаляется middleware после разбора).
schemas:
ComparisonType:
type: string
enum: [c2c, c2s, deviation, abap, pdf2pdf]
CompareType:
type: object
description: Тип сравнения и набор его параметров (для формы создания).
properties:
name:
$ref: '#/components/schemas/ComparisonType'
verbose_name:
type: string
params:
type: array
items:
$ref: '#/components/schemas/CompareFormParam'
CompareFormParam:
type: object
properties:
name:
type: string
verbose_name:
type: string
hint:
type: string
source:
type: string
enum: [form, viewer]
type:
type: string
description: Тип поля (напр. cloud, bimv2, number, multichoice, choice, array, pdf, surface)
options:
type: array
items:
type: object
additionalProperties: true
default: {}
min:
type: number
format: double
max:
type: number
format: double
CreateRequest:
type: object
required: [name, workspace_id, type, company_id, params]
properties:
name:
type: string
workspace_id:
type: string
format: uuid
parent_doc_id:
type: integer
format: int64
nullable: true
parent_disk_id:
type: string
format: uuid
nullable: true
type:
$ref: '#/components/schemas/ComparisonType'
company_id:
type: integer
format: int64
params:
type: object
description: Параметры сравнения, зависят от type (см. /api/v1/types).
additionalProperties: true
Comparison:
type: object
description: |
Полиморфное сравнение. Поле `type` определяет набор `params`. Ниже приведён
пример для типа `deviation`; для других типов набор params отличается.
properties:
id:
type: integer
format: int64
type:
$ref: '#/components/schemas/ComparisonType'
created_by:
type: integer
format: int64
name:
type: string
workspace_id:
type: string
format: uuid
workflow_id:
type: string
format: uuid
nullable: true
document_id:
type: integer
format: int64
nullable: true
bundle_id:
type: string
format: uuid
nullable: true
params:
$ref: '#/components/schemas/DeviationParams'
DeviationParams:
type: object
properties:
cloud_bundle_id:
type: string
format: uuid
model_bundle_id:
type: string
format: uuid
down_sample_size:
type: number
format: double
resolution:
type: number
format: double
linear_margin:
type: number
format: double
tolerance:
type: number
format: double
sarex_ids:
type: array
items:
type: integer
format: int64
transformation:
type: array
items:
type: number
format: double
applied_statuses:
type: array
items:
type: string
crop_margin:
type: number
format: double
nullable: true
Element:
type: object
properties:
id:
type: integer
format: int64
sarex_id:
type: integer
format: int64
name:
type: string
deviation_status:
type: string
enum: [in_tolerance, out_of_tolerance]
view_status:
type: string
enum: [viewed, not_viewed]
deviation:
type: number
format: double
deviation_x:
type: number
format: double
deviation_y:
type: number
format: double
deviation_z:
type: number
format: double
axis_and_angle:
type: object
description: Ось и угол поворота (utils.AxisAndAngle)
additionalProperties: true
comment:
type: string
nullable: true
bbox_matrix:
type: array
items:
type: number
format: double
reg_matrix:
type: array
items:
type: number
format: double
ListElementsResponse:
type: object
properties:
count:
type: integer
next:
type: string
nullable: true
previous:
type: string
nullable: true
types:
type: object
additionalProperties:
type: array
items:
$ref: '#/components/schemas/Option'
crop_margin:
type: number
format: double
nullable: true
results:
type: array
items:
$ref: '#/components/schemas/Element'
UpdateElementRequest:
type: object
description: |
Должно быть задано ровно одно из полей (`comment`, `deviation_status`,
`view_status`) — валидатор `required_without_all`.
properties:
comment:
type: string
deviation_status:
type: string
enum: [in_tolerance, out_of_tolerance]
view_status:
type: string
enum: [viewed, not_viewed]
Change:
type: object
properties:
id:
type: integer
format: int64
element_id:
type: integer
format: int64
created_at:
type: string
format: date-time
author:
type: integer
format: int64
changed_field:
type: string
enum: [deviation_status, view_status, comment]
old_value:
type: string
new_value:
type: string
FilterFields:
type: object
properties:
name:
type: string
enum: [deviation, deviation_x, deviation_y, deviation_z, deviation_status, view_status, only_with_comment]
verbose_name:
type: string
type:
type: string
enum: [checkbox, selector, slider]
options:
type: array
items:
$ref: '#/components/schemas/Option'
min:
type: number
format: double
nullable: true
max:
type: number
format: double
nullable: true
Option:
type: object
properties:
name:
type: string
verbose_name:
type: string
DeviationJSON:
type: object
properties:
crop_margin:
type: number
format: double
nodes:
type: object
additionalProperties:
$ref: '#/components/schemas/Node'
Node:
type: object
properties:
bbox_matrix:
type: array
items:
type: number
format: double
reg_matrix:
type: array
items:
type: number
format: double
node_name:
type: string

259
apps/django/.env.example Normal file
View File

@ -0,0 +1,259 @@
# =============================================================================
# Пример конфигурации sarex-backend (Django) + sarex-frontend
# Значения читаются кодом через django-environ (env(...)) и pydantic-settings
# (классы *Settings с env_prefix) в config/settings/*.py.
# В кластере переменные приходят из Vault (файлы /vault/secrets/*) и из блока
# env Deployment-манифестов (см. base/backend-deployment.yaml, celery-deployment.yaml).
# =============================================================================
# -----------------------------------------------------------------------------
# Django core
# -----------------------------------------------------------------------------
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_DEBUG=False
DJANGO_ISOLATED=False
ALLOWED_HOSTS='*'
APPEND_SLASH=True
FZ152_COMPLIANCE=False
PDM_SYNC=1
OBJECT_STORAGE_SYNC=True
# Секретный ключ Django. В production.py по умолчанию задан хардкодом,
# при необходимости переопределяется переменной SECRET_KEY.
SECRET_KEY=
# STATIC_ROOT / MEDIA_ROOT нужны только если раскомментированы в production.py
# STATIC_ROOT=/opt/sarex/static
# MEDIA_ROOT=/opt/sarex/media
DISK_USAGE_ROOT=/
USE_SSL_FOR_URL_SERIALIZATION=True
WEB_APP_AUTH_MODE=jwt-session-based
# -----------------------------------------------------------------------------
# База данных (PostgreSQL) — читается в config/settings/production.py
# В кластере приходит из Vault-секрета secrets/data/postgresql/apps/django
# -----------------------------------------------------------------------------
DJANGO_POSTGRES_HOST=postgresql.django.svc.cluster.local
DJANGO_POSTGRES_PORTS=5432
DJANGO_POSTGRES_DATABASE=sarex_db
DJANGO_POSTGRES_USER=sarex
DJANGO_POSTGRES_PASSWORD=password
# -----------------------------------------------------------------------------
# JWT (RS512). Ключи в кластере приходят из Vault (rsa_keys), \n экранируются.
# -----------------------------------------------------------------------------
JWT_PRIVATE_KEY=
JWT_PUBLIC_KEY=
JWT_KID=1
DJANGO_JWT_SECRET='Froom too much love of living'
SIMPLE_JWT_ISSUER=django
# -----------------------------------------------------------------------------
# Celery — брокер (RabbitMQ) и backend результатов (Redis или Postgres)
# CELERY_RABBITMQ_* приходит из Vault-секрета secrets/data/rabbitmq/apps/django
# -----------------------------------------------------------------------------
CELERY_USE_POSTGRES=False
CELERY_RABBITMQ_HOST=rabbitmq.rabbitmq.svc.cluster.local
CELERY_RABBITMQ_PORT=5672
CELERY_RABBITMQ_USER=rabbit
CELERY_RABBITMQ_PASSWORD=rabbit
CELERY_RABBITMQ_VHOST=api
CELERY_REDIS_HOST=redis
CELERY_REDIS_PORT=6379
CELERY_REDIS_DATABASE=0
# Backend результатов на Postgres (используется при CELERY_USE_POSTGRES=True)
CELERY_POSTGRES_DATABASE=celery_db
CELERY_POSTGRES_USER=sarex
CELERY_POSTGRES_PASSWORD=sarex
CELERY_POSTGRES_HOST=localhost
CELERY_POSTGRES_PORT=5432
# Дублирующий набор RabbitMQ (django), прокидывается Vault-шаблоном
DJANGO_RABBIT_HOSTNAME=rabbitmq.rabbitmq.svc.cluster.local
DJANGO_RABBIT_USER=rabbit
DJANGO_RABBIT_PASS=rabbit
DJANGO_RABBIT_VHOST=api
# Redis для Django-кеша/сервисов
DJANGO_REDIS_HOST=redis
DJANGO_REDIS_PORT=6379
# -----------------------------------------------------------------------------
# Redis-кеш (CacheSettings)
# -----------------------------------------------------------------------------
CACHE_HOST=localhost
CACHE_PORT=6379
# CACHE_PASSWORD=
CACHE_SSL=False
# CACHE_SSL_CA_CERTS=
# -----------------------------------------------------------------------------
# S3 / объектное хранилище (S3Settings, env_prefix S3_)
# S3_* приходит из Vault-секрета secrets/data/minio/apps/django
# -----------------------------------------------------------------------------
S3_HOST=https://storage.yandexcloud.net
AWS_S3_ENDPOINT_URL=https://storage.yandexcloud.net
S3_LOGIN=
S3_PASSWORD=
S3_BUCKET=sarex-media-storage
S3_REGION=ru-central1
# AWS_DEFAULT_REGION=ru-central1
# Нативная библиотека загрузки в S3 (см. Dockerfile)
S3TOOLS_LIB_PATH=/opt/sarex/lib/s3tools.so
S3TOOLS_WORKERS=10
# -----------------------------------------------------------------------------
# Kafka (KafkaSettings, env_prefix KAFKA_)
# Аутентификация приходит из Vault-секрета secrets/data/kafka/apps/django
# -----------------------------------------------------------------------------
KAFKA_BOOTSTRAP_SERVERS='["localhost:9092"]'
KAFKA_SECURITY_PROTOCOL=
KAFKA_SASL_MECHANISM=
KAFKA_SASL_PLAIN_USERNAME=user
KAFKA_SASL_PLAIN_PASSWORD=password
KAFKA_SSL_CAFILE=/usr/local/share/ca-certificates/kafka.crt
KAFKA_TOPICS='{"planning": "message-hub-stage", "ams-sync": "ams-sync"}'
# -----------------------------------------------------------------------------
# Sentry (SentrySettings, env_prefix SENTRY_) — читается из .env.base/.env
# -----------------------------------------------------------------------------
SENTRY_USE=0
SENTRY_HOST="https://sentry.sarex.io/"
SENTRY_ENVIRONMENT="production"
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_PROFILES_SAMPLE_RATE=0.1
# -----------------------------------------------------------------------------
# Server-флаги приложения (ServerSettings, env_prefix SERVER_)
# Ниже — переменные, реально задаваемые в манифестах кластера.
# -----------------------------------------------------------------------------
SERVER_HOST=https://lk.sarex.io
SERVER_API_HOST=https://api.sarex.io
SERVER_ZITADEL_ENABLED=True
SERVER_KAFKA_ENABLED=False
SERVER_USE_METASHAPE=0
SERVER_USE_CLICKHOUSE=0
SERVER_USE_CHANGELOG=0
SERVER_CHANGELOG_MODE=0
SERVER_CHANGELOG_MODE_SYSTEM_LOG=1
SERVER_SAVE_DIFF_DEM=1
SERVER_S3_STREAM_IMPORT=1
SERVER_USE_DJANGO_STORAGE=1
SERVER_DJANGO_URLS=1
SERVER_CHECK_IMPORT_HASH=1
SERVER_USE_WRORKFLOW_STATUS=1
SERVER_HIDE_USER_SCROLL_PERMISSIONS=0
SERVER_EXTERNAL_FIND_BY_USERNAME_ENABLED=True
SERVER_EXTERNAL_FIND_BY_EMAIL_ENABLED=True
SERVER_CHUNKED_PATH=/tmp/chunked_uploads/%Y/%m/%d
CHECK_IMPORT_HASH=1
# -----------------------------------------------------------------------------
# Workflows / processing (WorkFlowsSettings, env_prefix WORKFLOWS_)
# -----------------------------------------------------------------------------
WORKFLOWS_USE=1
WORKFLOWS_HOST=https://api.sarex.io
WORKFLOWS_BASE_HOST=https://lk.sarex.io
WORKFLOWS_PREFIX=/internal/v1
# WORKFLOWS_TIMEOUT=120
# WORKFLOWS_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
# WORKFLOWS_TAG=stable
# -----------------------------------------------------------------------------
# Внешние API-сервисы (наследуют BaseApiServiceMixin:
# host / api_prefix / internal_host / internal_prefix / timeout / enable)
# -----------------------------------------------------------------------------
# Gateway (env_prefix GATEWAY_)
# GATEWAY_HOST=https://api.sarex.io
# Gatekeeper (env_prefix GK_)
# GK_ENCRYPTION_KEY=
# BIM v2 (env_prefix BIMV2_)
BIMV2_INTERNAL_HOST=http://bim-backend-v2-service.bim-api
BIMV2_TIMEOUT=60
# EAV (env_prefix EAV_)
EAV_ENABLE=1
# Documentation (env_prefix DOCUMENTATION_)
# DOCUMENTATION_HOST=https://api.sarex.io
# Analytics (env_prefix ANALYTICS_)
# ANALYTICS_HOST=https://lk.sarex.io
# Users (env_prefix USERS_)
# USERS_HOST=https://lk.sarex.io
# System log (env_prefix SYSTEM_LOG_)
# SYSTEM_LOG_INTERNAL_HOST=
# Resources (env_prefix RESOURCES_)
# RESOURCES_INTERNAL_HOST=http://localhost:8001
# -----------------------------------------------------------------------------
# Measurements (MeasurementSettings, env_prefix MEASUREMENTS_)
# -----------------------------------------------------------------------------
MEASUREMENTS_HOST=https://api.sarex.io/measurements/
# MEASUREMENTS_TIMEOUT=180
# MEASUREMENTS_WINDOW_SIZE=1000
# -----------------------------------------------------------------------------
# ClickHouse (ClickHouseSettings, env_prefix CLICKHOUSE_)
# -----------------------------------------------------------------------------
# CLICKHOUSE_HOST=
# CLICKHOUSE_PORT=9000
# CLICKHOUSE_USER=
# CLICKHOUSE_PASSWORD=
# CLICKHOUSE_DATABASE=values_db
# CLICKHOUSE_TABLE=values
# CLICKHOUSE_SECURE=False
# CLICKHOUSE_VERIFY=False
# CLICKHOUSE_CERT=
# -----------------------------------------------------------------------------
# Zitadel (ZitadelSettings, env_prefix ZITADEL_)
# ZITADEL_ACCESS_TOKEN приходит из Vault-секрета secrets/data/vault/common/django_auth
# -----------------------------------------------------------------------------
ZITADEL_HOST=https://zitadel.contour.infra.sarex.tech
ZITADEL_ACCESS_TOKEN=
# ZITADEL_USERS_ENDPOINT=/v2/users
# -----------------------------------------------------------------------------
# Keycloak (KeyCloakSettings env_prefix KC_, KeyCloakSyncSettings env_prefix KC_SYNC)
# -----------------------------------------------------------------------------
KC_SYNC_ENABLE=0
KC_USE_REDIRECT_LOGOUT=False
# KC_CLIENT_ID=
# KC_CLIENT_SECRET=
# KC_DISCOVERY_URL=
# KC_REALM=sarex
# -----------------------------------------------------------------------------
# Трейсинг (TracingConfig, env_prefix TRACING_) + OpenTelemetry
# -----------------------------------------------------------------------------
# TRACING_SERVICE_NAME=backend.sarex-stage
# TRACING_ENDPOINT=localhost:4317
# TRACING_INSECURE=False
# TRACING_ENVIRONMENT=prod
# -----------------------------------------------------------------------------
# Comparator / прочее
# -----------------------------------------------------------------------------
COMPARATOR_URL=https://wb.sarex.io/comparator
COMPARATOR_SECTION=sarex-production-storage
# COMPARATOR_JWT=
# COMPARATOR_BASIC_TOKEN=
# WORKFLOWSSETTINGS_HOST=https://api.sarex.io # используется в configmap production.py
# WORKFLOWSSETTINGS_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
# -----------------------------------------------------------------------------
# Легаси/интеграции (значения по умолчанию есть в base.py)
# -----------------------------------------------------------------------------
# PG_NODE_HOST=127.0.0.1:5000
# PG_API_KEY=
# PG_MONGO_HOST=localhost
# PG_MONGO_PORT=27017
# PG_IMPORT_PATH=/home/sarex/pg/import
# WIKIMAPIA_API_KEY=
# ANALYTICS_IMPORT_METRICS_SHEET_NAME=SRX
# =============================================================================
# Frontend (sarex-frontend) — сборочные переменные (webpack DefinePlugin)
# Базовые хосты сервисов берутся из endpoints.js по ключу ENDPOINT.
# =============================================================================
# ENDPOINT=prod # stage | prod | preprod | contour | local
# SAREX_BACKEND=https://stage.sarex.io/ # прокси-таргет backend при npm start (env.js)
# TYPE_OF_HTTP_SERVICE=original
# CLIENT_ID=
# AMPLITUDE_ENABLED=false

View File

@ -0,0 +1,357 @@
# Конфигурация проекта sarex-backend (Django)
Документ описывает способы конфигурирования backend-сервиса `sarex` (Django) и
основные переменные окружения. Фронтенд-приложение `sarex-frontend` (шелл на
Module Federation) конфигурируется отдельно на этапе сборки — см. раздел в конце
и `ENDPOINTS.md`.
## Способы конфигурирования
Сервис — это Django-приложение (проект `config`, бизнес-логика в пакете `sarex`).
Конфигурация складывается из двух механизмов:
1. **Модуль настроек Django** выбирается переменной `DJANGO_SETTINGS_MODULE`.
Модули лежат в `config/settings/` и наследуются друг от друга через
`from .base import *`.
2. **Переменные окружения** читаются двумя способами:
- `django-environ` — объект `env = environ.Env()` в `config/settings/base.py`,
вызовы `env('NAME', default=...)`, `env.bool(...)`, `env.list(...)`,
`env.str(...)`;
- `pydantic-settings` — классы-наследники `BaseSettings` с `env_prefix`
(напр. `ServerSettings` → префикс `SERVER_`), инстанцируются как синглтоны
(`SERVERSETTINGS = ServerSettings()` и т.п.).
Приложение **не загружает `.env` автоматически** в основном конфиге
(`DJANGO_READ_DOT_ENV_FILE` закомментирован). Исключение — pydantic-классы
`SentrySettings` (читает `.env.base`, `.env`), `ZitadelSettings`, `KafkaSettings`
(читают `.env`). В остальном переменные нужно экспортировать в окружение процесса.
### Модули настроек (`config/settings/*.py`)
| Модуль | Назначение |
| --- | --- |
| `base.py` | Базовые настройки, все классы `*Settings`, INSTALLED_APPS, DRF, Celery-очереди |
| `production.py` | Продакшн: `DEBUG=False`, БД из `DJANGO_POSTGRES_*`, SimpleJWT (RS512), логирование |
| `docker.py` | Наследует `test.py`, `ALLOWED_HOSTS=["*"]`, БД на хосте `postgres` |
| `test.py` / `test_ksg.py` | Прогон тестов |
| `example.local.py` / `example.ldap.local.py` | Шаблоны для локального `local.py` (копируются вручную) |
По умолчанию `manage.py` и `config/celery.py` используют `config.settings.local`.
В кластере задаётся `DJANGO_SETTINGS_MODULE=config.settings.production`, при этом
файл `production.py` **подменяется** ConfigMap-ом `django-configmap` (монтируется в
`/opt/sarex/config/settings/production.py`) — см. раздел про деплой.
### Способы запуска процессов
| Процесс | Команда | Назначение |
| --- | --- | --- |
| Web/API (uWSGI) | `uwsgi --plugin python3 --ini uwsgi.ini` | HTTP API на `0.0.0.0:8000`, модуль `config.wsgi:application` |
| Web/API (dev) | `python manage.py runserver` | Локальный запуск |
| Celery worker+beat | `celery -A config worker -B -l info -E -Q default -n default_worker.%h` | Фоновые задачи и периодические таски |
| Миграции | `python manage.py migrate` | Выполняются в `entrypoint.sh` перед стартом uWSGI |
Порядок запуска контейнера backend (`entrypoint.sh`): сначала
`opentelemetry-instrument python manage.py migrate`, затем
`opentelemetry-instrument uwsgi --plugin python3 --ini uwsgi.ini`. В кластере
перед `entrypoint.sh` секреты из Vault экспортируются в окружение (`set -a; . /vault/secrets/...`).
## Переменные приложения
Ниже перечислены основные переменные. Дефолт `—` означает, что значение
обязательно (в `production.py` без него будет ошибка старта).
### Django core
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_SETTINGS_MODULE` | string | `config.settings.local` | Модуль настроек Django |
| `DJANGO_DEBUG` | bool | `False` | Режим отладки |
| `DJANGO_ISOLATED` | bool | `False` | Изолированный режим (в configmap отключает Sentry) |
| `ALLOWED_HOSTS` | list/str | — (в prod из env) | Разрешённые хосты; в кластере `*` |
| `APPEND_SLASH` | bool | `True` | Автодобавление слеша в URL |
| `FZ152_COMPLIANCE` | bool | `False` | Режим соответствия 152-ФЗ |
| `PDM_SYNC` | bool | `False` | Синхронизация с PDM |
| `OBJECT_STORAGE_SYNC` | bool | `True` | Синхронизация с объектным хранилищем |
| `SECRET_KEY` | string | хардкод в `production.py` | Секретный ключ Django |
| `SIMPLE_JWT_ISSUER` | string | `django` | Issuer для JWT |
| `DISK_USAGE_ROOT` | string | `/` | Корень для расчёта занятого места |
| `USE_SSL_FOR_URL_SERIALIZATION` | bool | `True` | Использовать https при сериализации URL |
| `WEB_APP_AUTH_MODE` | string | `JWTDefault` | Режим авторизации веб-приложения |
### База данных (PostgreSQL)
Читаются в `config/settings/production.py`. В кластере приходят из Vault-секрета
`secrets/data/postgresql/apps/django` (файл `/vault/secrets/django-postgresql`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_POSTGRES_HOST` | string | — | Хост PostgreSQL |
| `DJANGO_POSTGRES_PORTS` | string | `5432` | Порт PostgreSQL |
| `DJANGO_POSTGRES_DATABASE` | string | — | Имя базы |
| `DJANGO_POSTGRES_USER` | string | — | Пользователь |
| `DJANGO_POSTGRES_PASSWORD` | string | — | Пароль |
Движок БД — `django_prometheus.db.backends.postgresql`.
### JWT (SimpleJWT, RS512)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `JWT_PRIVATE_KEY` | string | — | Приватный RSA-ключ подписи (`\n` заменяются на переводы строк). В кластере — из Vault `rsa_keys` |
| `JWT_PUBLIC_KEY` | string | — | Публичный RSA-ключ проверки |
| `JWT_KID` | string | `None` | `kid` в заголовке токена (используется для межсервисных вызовов) |
| `DJANGO_JWT_SECRET` | string | `Froom too much love of living` | Легаси-секрет |
### Celery (`CELERY_*`)
Брокер — RabbitMQ; backend результатов — Redis (по умолчанию) или Postgres
(`CELERY_USE_POSTGRES=True`). `CELERY_RABBITMQ_*` в кластере из Vault-секрета
`secrets/data/rabbitmq/apps/django`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `CELERY_USE_POSTGRES` | bool | `False` | Использовать Postgres как result backend |
| `CELERY_RABBITMQ_HOST` | string | `localhost` | Хост RabbitMQ |
| `CELERY_RABBITMQ_PORT` | int | `5672` | Порт |
| `CELERY_RABBITMQ_USER` | string | `rabbit` | Пользователь |
| `CELERY_RABBITMQ_PASSWORD` | string | `rabbit` | Пароль |
| `CELERY_RABBITMQ_VHOST` | string | `api` | Виртуальный хост |
| `CELERY_REDIS_HOST` | string | `localhost` | Хост Redis (result backend) |
| `CELERY_REDIS_PORT` | int | `6379` | Порт Redis |
| `CELERY_REDIS_DATABASE` | int | `0` | Номер БД Redis |
| `CELERY_POSTGRES_DATABASE` | string | `celery_db` | БД для result backend на Postgres |
| `CELERY_POSTGRES_USER` | string | `sarex` | Пользователь |
| `CELERY_POSTGRES_PASSWORD` | string | `sarex` | Пароль |
| `CELERY_POSTGRES_HOST` | string | `localhost` | Хост |
| `CELERY_POSTGRES_PORT` | string | `5432` | Порт |
Дополнительно из Vault-шаблона прокидываются дублирующие `DJANGO_RABBIT_HOSTNAME`,
`DJANGO_RABBIT_USER`, `DJANGO_RABBIT_PASS`, `DJANGO_RABBIT_VHOST`, а также
`DJANGO_REDIS_HOST` / `DJANGO_REDIS_PORT`.
### Кеш Redis (`CACHE_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `CACHE_HOST` | string | `localhost` | Хост Redis |
| `CACHE_PORT` | int | `6379` | Порт |
| `CACHE_PASSWORD` | string \| null | `None` | Пароль |
| `CACHE_SSL` | bool | `False` | TLS |
| `CACHE_SSL_CA_CERTS` | string \| null | `None` | CA-сертификат |
### S3 / объектное хранилище (`S3_*`)
`S3_*` в кластере из Vault-секрета `secrets/data/minio/apps/django`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `S3_HOST` | string | `https://storage.yandexcloud.net` | Эндпоинт S3 |
| `S3_LOGIN` | string | `""` | Access key |
| `S3_PASSWORD` | string | `""` | Secret key |
| `S3_BUCKET` | string | `sarex-media-storage` | Бакет по умолчанию |
| `S3_REGION` | string | `""` | Регион (fallback: `AWS_DEFAULT_REGION`) |
| `AWS_S3_ENDPOINT_URL` | string | `https://storage.yandexcloud.net` | Эндпоинт (легаси-переменная) |
| `S3TOOLS_LIB_PATH` | string | `/opt/sarex/lib/s3tools.so` | Путь к нативной библиотеке загрузки (Dockerfile) |
| `S3TOOLS_WORKERS` | int | `10` | Число воркеров загрузки |
### Kafka (`KAFKA_*`)
Аутентификация в кластере из Vault-секрета `secrets/data/kafka/apps/django`.
Продюсер создаётся только при `SERVER_KAFKA_ENABLED=True`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_BOOTSTRAP_SERVERS` | list[str] (JSON) | `["localhost:9092"]` | Список брокеров |
| `KAFKA_SECURITY_PROTOCOL` | string | `""` | Протокол безопасности |
| `KAFKA_SASL_MECHANISM` | string | `""` | SASL-механизм |
| `KAFKA_SASL_PLAIN_USERNAME` | string | `user` | Логин |
| `KAFKA_SASL_PLAIN_PASSWORD` | string | `password` | Пароль |
| `KAFKA_SSL_CAFILE` | string | `""` | Путь к CA-сертификату |
| `KAFKA_TOPICS` | dict (JSON) | `{}` | Маппинг логических имён на топики |
### Sentry (`SENTRY_*`)
Читается классом `SentrySettings` из `.env.base` / `.env`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SENTRY_USE` | bool | `True` | Включить Sentry |
| `SENTRY_HOST` | string | `""` | DSN/хост Sentry |
| `SENTRY_ENVIRONMENT` | string | `""` | Окружение |
| `SENTRY_TRACES_SAMPLE_RATE` | float | `1.0` | Доля трейсов |
| `SENTRY_PROFILES_SAMPLE_RATE` | float | `0.1` | Доля профилей |
### Флаги приложения (`SERVER_*`, класс `ServerSettings`)
Класс содержит десятки булевых флагов и параметров. Наиболее значимые (реально
задаются в манифестах):
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SERVER_HOST` | string | `https://lk.sarex.io` | Внешний хост ЛК |
| `SERVER_API_HOST` | string | `https://api.sarex.io` | Внешний хост API |
| `SERVER_ZITADEL_ENABLED` | bool | `True` | Включить Zitadel-аутентификацию |
| `SERVER_KAFKA_ENABLED` | bool | `False` | Включить Kafka-продюсер |
| `SERVER_USE_METASHAPE` | bool | `True` | Использовать Metashape |
| `SERVER_USE_CLICKHOUSE` | bool | `False` | Использовать ClickHouse |
| `SERVER_CACHE_ENABLED` | bool | `False` | Включить кеш (в configmap выставляется `True`) |
| `SERVER_USE_NOTIFICATIONS` | bool | `True` | Уведомления |
| `SERVER_TIMEOUT` | int | `60` | Таймаут по умолчанию |
| `SERVER_CHUNKED_PATH` | string | — | Путь для чанкованных загрузок |
Полный список полей — в `ServerSettings` (`config/settings/base.py`). Любое поле
переопределяется переменной `SERVER_<FIELD>` в верхнем регистре.
### Workflows / processing (`WORKFLOWS_*`, класс `WorkFlowsSettings`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WORKFLOWS_USE` | bool | `False` | Включить интеграцию с processing |
| `WORKFLOWS_HOST` | string | `https://api.sarex.io` | Хост сервиса processing |
| `WORKFLOWS_BASE_HOST` | string | `https://lk.sarex.io` | Базовый хост |
| `WORKFLOWS_PREFIX` | string | `/internal/v1` | Префикс внутреннего API |
| `WORKFLOWS_TIMEOUT` | int | `120` | Таймаут |
| `WORKFLOWS_REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр образов задач |
| `WORKFLOWS_TAG` | string | `stable` | Тег образов |
### Внешние API-сервисы (`BaseApiServiceMixin`)
Классы `GateWaySetttings` (`GATEWAY_`), `BimV2ApiSettings` (`BIMV2_`),
`EAVSettings` (`EAV_`), `AnalyticsSettings` (`ANALYTICS_`),
`DocumentationSettings` (`DOCUMENTATION_`), `UsersSettings` (`USERS_`),
`SystemLogSettings` (`SYSTEM_LOG_`), `ResourceSettings` (`RESOURCES_`) наследуют
общий набор полей:
| Поле (переменная `<PREFIX>_<FIELD>`) | Тип | Назначение |
| --- | --- | --- |
| `HOST` | string | Внешний хост сервиса |
| `API_PREFIX` | string | Префикс публичного API |
| `INTERNAL_HOST` | string | Внутренний хост (внутрикластерный) |
| `INTERNAL_PREFIX` | string | Префикс внутреннего API |
| `TIMEOUT` | int | Таймаут запроса |
| `ENABLE` | bool | Включён ли сервис |
Реально задаваемые в манифестах: `BIMV2_INTERNAL_HOST`, `BIMV2_TIMEOUT`,
`EAV_ENABLE`. Отдельно — `GK_ENCRYPTION_KEY` (класс `GatekeeperSettings`).
### Measurements (`MEASUREMENTS_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `MEASUREMENTS_HOST` | string | `https://api.sarex.io/measurements/` | Хост сервиса измерений |
| `MEASUREMENTS_TIMEOUT` | int | `180` | Таймаут |
| `MEASUREMENTS_WINDOW_SIZE` | int | `1000` | Размер окна |
### ClickHouse (`CLICKHOUSE_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `CLICKHOUSE_HOST` | string | `rc1d-...yandexcloud.net` | Хост |
| `CLICKHOUSE_PORT` | int | `9000` | Порт |
| `CLICKHOUSE_USER` / `CLICKHOUSE_PASSWORD` | string | `""` | Учётные данные |
| `CLICKHOUSE_DATABASE` | string | `values_db` | База |
| `CLICKHOUSE_TABLE` | string | `values` | Таблица |
| `CLICKHOUSE_SECURE` / `CLICKHOUSE_VERIFY` | bool | `False` | TLS и проверка сертификата |
| `CLICKHOUSE_CERT` | string | `""` | CA-сертификат |
### Zitadel (`ZITADEL_*`) и Keycloak (`KC_*`, `KC_SYNC*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ZITADEL_HOST` | string | `""` | Хост Zitadel (IdP) |
| `ZITADEL_ACCESS_TOKEN` | string | `""` | Сервисный токен (из Vault `django_auth`) |
| `ZITADEL_USERS_ENDPOINT` | string | `/v2/users` | Эндпоинт пользователей |
| `KC_SYNC_ENABLE` | bool | `False` | Включить синхронизацию с Keycloak |
| `KC_USE_REDIRECT_LOGOUT` | bool | `False` | Redirect при logout |
| `KC_CLIENT_ID` / `KC_CLIENT_SECRET` / `KC_DISCOVERY_URL` / `KC_REALM` | string | см. `KeyCloakSettings` | Параметры клиента Keycloak |
### Трейсинг (`TRACING_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `TRACING_SERVICE_NAME` | string | `backend.sarex-stage` | Имя сервиса в трейсах |
| `TRACING_ENDPOINT` | string | `localhost:4317` | OTLP-коллектор |
| `TRACING_INSECURE` | bool | `False` | Без TLS |
| `TRACING_ENVIRONMENT` | string | `prod` | Окружение |
### Comparator и прочее
| Переменная | Значение по умолчанию | Назначение |
| --- | --- | --- |
| `COMPARATOR_URL` | `https://wb.sarex.io/comparator` | URL сервиса сравнения |
| `COMPARATOR_SECTION` | `sarex-production-storage` | Секция хранилища |
| `COMPARATOR_JWT` | `default_jwt` | Токен сравнения |
| `WORKFLOWSSETTINGS_HOST` / `WORKFLOWSSETTINGS_REGISTRY` | — | Используются напрямую в configmap `production.py` |
| `PG_NODE_HOST`, `PG_API_KEY`, `PG_MONGO_HOST`, `PG_MONGO_PORT`, `PG_IMPORT_PATH` | см. `base.py` | Легаси-интеграции PG |
## Конфигурация в кластере (Kubernetes)
Манифесты приложения — в этом же каталоге (`base/`, оверлеи `brusnika-stage`,
`brusnika-prod`, `yc-k8s-test`). Секреты монтируются через **Vault Agent Injector**
(аннотации `vault.hashicorp.com/*` на Deployment `backend` и `celery`). Файлы
секретов в контейнере и их содержимое:
| Файл `/vault/secrets/...` | Секрет Vault | Переменные |
| --- | --- | --- |
| `django-postgresql` | `secrets/data/postgresql/apps/django` | `DJANGO_POSTGRES_HOST/PORTS/DATABASE/USER/PASSWORD` |
| `django-rabbitmq` | `secrets/data/rabbitmq/apps/django` | `CELERY_RABBITMQ_*`, `DJANGO_RABBIT_*` |
| `django-s3` | `secrets/data/minio/apps/django` | `AWS_S3_ENDPOINT_URL`, `S3_HOST/BUCKET/LOGIN/PASSWORD` |
| `django-kafka` | `secrets/data/kafka/apps/django` | `KAFKA_BOOTSTRAP_SERVERS/SECURITY_PROTOCOL/SASL_*` |
| `django-jwt-private` / `django-jwt-public` | `secrets/data/vault/common/rsa_keys` | `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` |
| `django-common` | `secrets/data/vault/common/django_auth` | `ZITADEL_ACCESS_TOKEN` |
Контейнер экспортирует эти файлы в окружение до запуска (`set -a; . /vault/secrets/...`).
Кроме того, `production.py` из ConfigMap содержит функцию `_load_env_file`, которая
подхватывает те же файлы при запуске `manage.py` через `kubectl exec` вне entrypoint.
Остальные (несекретные) переменные задаются в блоке `env` контейнеров
`backend`/`celery` (`SERVER_*`, `WORKFLOWS_*`, `BIMV2_*`, `MEASUREMENTS_*`,
`ZITADEL_HOST`, `KAFKA_TOPICS`, `EAV_ENABLE`, `PDM_SYNC`, `JWT_KID` и др.).
ConfigMap `django-configmap` подменяет `config/settings/production.py`
(смонтирован в `/opt/sarex/config/settings/production.py`) и переопределяет
`ALLOWED_HOSTS`, CORS, `DATABASES`, `SIMPLE_JWT`, `REST_FRAMEWORK`, `MIDDLEWARE`,
`KeyCloakSettings`, `SAREX_MODULES`, а также включает Sentry (если не `ISOLATED`).
ConfigMap `zitadel-configmap` содержит `config.json` с `client_id`/`host` Zitadel.
`uwsgi-configmap` монтирует `uwsgi.ini`.
## CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci`
(`common-security-scan.yaml`, `universal-pipeline.yaml`) и переключает окружение
по ветке/тегу через `workflow.rules`:
| Условие | STAND | NAMESPACE |
| --- | --- | --- |
| ветка `stage` | `stage` | `aero` |
| ветка `master` | `preprod` | (см. правила) |
| тег | `prod` | (см. правила) |
Ключевые переменные: `SERVICE_NAME=backend`, `DOCKERFILE_PATH=./Dockerfile`,
`RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `HELM_SET_ARGS`
(`universal-chart.services.backend.image.name…`, `…celery.image.name…`).
## Замечания и потенциальные проблемы
- В `production.py` `SECRET_KEY` задан хардкодом (закомментированный `env('SECRET_KEY')`).
Для реального прод-развёртывания ключ желательно вынести в секрет.
- Основной конфиг не читает `.env` автоматически; переменные нужно экспортировать
в окружение (в кластере это делает Vault + `set -a`). Только `SENTRY_*`,
`ZITADEL_*`, `KAFKA_*` читаются из файлов `.env.base`/`.env` их pydantic-классами.
- `production.py` в репозитории backend и `production.py` из ConfigMap `django-configmap`
**разные** файлы. В кластере используется версия из ConfigMap (в ней, в частности,
выставлено `DEBUG=True` в конце и включён `corsheaders`).
- `DATABASES['default']['ENGINE']``django_prometheus.db.backends.postgresql`
(обёртка для метрик Prometheus).
- Значения `SERVER_*`-флагов у `backend` и `celery` местами различаются
(напр. `SERVER_ZITADEL_ENABLED`, `SERVER_API_HOST`) — это ожидаемо.
## Минимальный набор для локального запуска
Согласно `README.md` backend: поднять Postgres/Redis/RabbitMQ (docker-compose),
скопировать шаблон настроек `cp config/settings/example.local.py config/settings/local.py`,
применить миграции (`python manage.py migrate`) и создать суперпользователя.
Минимально требуются переменные БД (`DJANGO_POSTGRES_*` или значения в `local.py`),
брокера Celery (`CELERY_RABBITMQ_*`) и, при использовании соответствующих функций,
`S3_*`, `JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`. Примеры значений — в `.env.example`
рядом с этим файлом.

127
apps/django/ENDPOINTS.md Normal file
View File

@ -0,0 +1,127 @@
# Эндпоинты и хосты сервисов для sarex-frontend
Документ описывает базовые хосты сервисов, к которым обращается фронтенд-шелл
`sarex-frontend`, и удалённые модули (Module Federation), которые он подгружает.
Backend для этого приложения — `sarex-backend` (Django), его конфигурация и
переменные описаны в `CONFIGURATION.md`, а серверное REST API — в `openapi.yaml`.
## Как устроено взаимодействие
`sarex-frontend` — это host-приложение на **Webpack Module Federation**. Реестр
хостов задаётся декларативно в `endpoints.js` (корень репозитория): объект вида
`<сервис>.<тип>.<окружение>` → URL. Возможные типы:
- `api` / `apiV1` / `apiV2` / … — базовый URL REST API сервиса;
- `module` — URL `remoteEntry.js` удалённого микрофронтенда;
- `gateway` — базовый URL gateway.
Окружение выбирается сборочной переменной `ENDPOINT` (`process.env.ENDPOINT`,
по умолчанию `prod`) в `webpack.common.js`. Значения `endpoints.js[сервис][тип][ENDPOINT]`
пробрасываются в код как константы `process.env.<CONST>` через `DefinePlugin`
(объект `processEnvByEndpoint`), напр. `endpoints.bim.api[ENDPOINT]``BIM_API`.
Для локального запуска backend-таргет задаётся отдельно в `env.js`
(`SAREX_BACKEND`, по умолчанию `https://stage.sarex.io/`) и используется
dev-сервером (`webpack.dev.js`) как прокси; собственный Django-backend в окружении
`contour` доступен по относительным путям (пустой хост).
## Окружения (`ENDPOINT`)
| Значение | Назначение |
| --- | --- |
| `prod` | Продакшн (`https://api.sarex.io`, `https://modules.sarex.io`) |
| `stage` | Stage (`https://stage-api.sarex.io`, `https://stage-modules.sarex.io`) |
| `preprod` | Preprod (`https://api.preprod.sarex.io`, `https://modules.preprod.sarex.io`) |
| `contour` | Изолированный контур — относительные пути (пустой хост) |
| `local` | Локальная разработка (задан не у всех сервисов) |
> У части сервисов `gateway` определены также специальные ключи `contour_local`
> (`https://stage.sarex.io`) и `contour_prod` (`https://lk.sarex.io`).
## Базовые хосты API-сервисов
Итоговый URL = `<базовый хост сервиса>` + путь запроса.
| Сервис (`endpoints.*`) | Константа | `stage` | `prod` | `contour` |
| --- | --- | --- | --- | --- |
| `api.api` | `SAREX_API` | `https://stage-api.sarex.io` | `https://api.sarex.io` | `""` |
| `bim.api` | `BIM_API` | `https://stage-api.sarex.io/bim/api/v1` | `https://api.sarex.io/bim/api/v1` | `""` |
| `bim.apiV2` | `BIM_API_V2` | `…/bim/api/v2` | `…/bim/api/v2` | `""` |
| `bim.files` | `BIM_FILES` | `…/bim` | `…/bim` | `""` |
| `workspaces.api` / `workspacesV2.api` | `WORKSPACESV2_API` | `…/workspaces/` | `…/workspaces/` | `/workspaces/` |
| `workflows.api` | `WORKFLOWS_API` | `…/workflows` | `…/workflows` | `/workflows` |
| `remarks.api` | `REMARKS_API` | `…/remarks/api/v1` | `…/remarks/api/v1` | `/remarks/api/v1` |
| `issues.api` | `ISSUES_API` | `…/issues/api/v1` | `…/issues/api/v1` | `/issues/api/v1` |
| `issuesBase.api` | `ISSUES_BASE_API` | `…/issues/api` | `…/issues/api` | `/issues/api` |
| `flows.api` | — | `…/issues/api/v1` | `…/issues/api/v1` | `/issues/api/v1` |
| `inspections.api` | — | `…/inspections/api/v1` | `…/inspections/api/v1` | `/inspections/api` |
| `documentations.api` | `DOCUMENTATIONS_API` | `…/documentations/api/v1` | `…/documentations/api/v1` | `/documentations/api/v1` |
| `pm.api` | — | `…/documentations/api/v1` | `…/documentations/api/v1` | `/documentations/api/v1` |
| `analyticsV2.api` | — | `…/analytics-v2/api/v1` | `…/analytics-v2/api/v1` | `/analytics-v2/api/v1` |
| `analytics.api` | `ANALYTICS_API` | `…/analytics` | `…/analytics` | `/analytics` |
| `processes.api` | `PROCESSES_API` | `…/flows/api/v1` | `…/flows/api/v1` | `/flows/api/v1` |
| `gateway.gateway` | `GATEWAY` | `…/gateway` | `…/gateway` | (local: `http://localhost:9000/gateway`) |
| `gateway.api` | `GATEWAY_API` | `…/gateway/api/v1` | `…/gateway/api/v1` | `/gateway/api/v1` |
| `gateway.apiV2` | `GATEWAY_API_V2` | `…/gateway/api/v2` | `…/gateway/api/v2` | `/gateway/api/v2` |
| `eav.api` | `EAV_API` | `…/eav/api/v0` | `…/eav/api/v0` | `/eav/api/v0` |
| `eav.apiV1…apiV4` | `EAV_API_V1…V4` | `…/eav/api/v1…v4` | `…/eav/api/v1…v4` | `/eav/api/v1…v4` |
| `notifications.api` | `NOTIFICATIONS_API` | `…/lambdas/notification/` | `…/lambdas/notification/` | `""` |
| `lambdas.api` | `LAMBDAS_API` | `…/lambdas` | `…/lambdas` | `/lambdas` |
| `automations.api` | `AUTOMATIONS_API` | `…/automation/api/v1` | `…/automation/api/v1` | `/automation/api/v1` |
| `orchestrator.api` | `ORCHESTRATOR_API` | `…/orchestrator` | `…/orchestrator/api` | `/orchestrator/api` |
> `…` = `https://stage-api.sarex.io` (stage) или `https://api.sarex.io` (prod).
> Собственный Django-backend (`sarex-backend`) в контуре обслуживается по
> относительным путям `/api/...` — см. `openapi.yaml`.
## Удалённые модули (Module Federation, `remoteEntry.js`)
Хост подгружает микрофронтенды по URL из `endpoints.<сервис>.module[ENDPOINT]`.
Базовый хост модулей: `https://stage-modules.sarex.io` (stage) /
`https://modules.sarex.io` (prod); в контуре — относительные пути.
| Сервис | Константа | Путь `module` (относительный, контур) |
| --- | --- | --- |
| `workspaces` | `WORKSPACES_MODULE` | `/workspaces/module/remoteEntry.js` |
| `workspacesV2` | `WORKSPACESV2_MODULE` | `/workspaces-v2/module/remoteEntry.js` |
| `workflows` | `WORKFLOWS_MODULE` | `/workflows/module/remoteEntry.js` |
| `remarks` | `REMARKS_MODULE` | `/remarks/static/module/remoteEntry.js` |
| `issues` | `ISSUES_MODULE` | `/issues/static/module/remoteEntry.js` |
| `flows` | `FLOWS_MODULE` | `/flows/static/module/remoteEntry.js` |
| `inspections` | `INSPECTIONS_MODULE` | `/inspections/static/module/remoteEntry.js` |
| `documentations` | `DOCUMENTATIONS_MODULE` | `/documentations/static/module/remoteEntry.js` |
| `pm` | `PM_MODULE` | `/pm/module/remoteEntry.js` |
| `projects` | `PROJECTS_MODULE` | `/projects/static/module/remoteEntry.js` |
| `analyticsV2` | `ANALYTICS_MODULE` | `/analytics-v2/static/module/remoteEntry.js` |
| `reviews` | `REVIEWS_MODULE` | `/reviews/static/module/remoteEntry.js` |
| `administration` | `ADMINISTRATION_MODULE` | `/control-interface/modules/admin/remoteEntry.js` |
| `adminProc` | `ADMIN_PROC_MODULE` | `/admin-frontend/static/module/remoteEntry.js` |
| `assets` | `ASSETS_MODULE` | `/control-interface/modules/assets/remoteEntry.js` |
| `premises` | `PREMISES_MODULE` | `/premises/static/module/remoteEntry.js` |
| `contracts` | `CONTRACTS_MODULE` | `/cotracts/static/module/remoteEntry.js` |
| `transmittal` | `TRANSMITTAL_MODULE` | `/transmittal/static/module/remoteEntry.js` |
| `prescriptions` | `PRESCRIPTIONS_MODULE` | `/prescriptions/static/module/remoteEntry.js` |
| `rfi` | `RFI_MODULE` | `/rfi/static/module/remoteEntry.js` |
| `assistant` | — | `/assistant/static/module/remoteEntry.js` |
Список подключаемых в ЛК модулей (пункты меню) дублируется на стороне backend в
`SAREX_MODULES` (ConfigMap `django-configmap`): `remarks`, `issues`,
`documentations`, `reviews`, `processes`, `rfi`, `transmittal`.
## Backend, обслуживающий шелл
Основной API самого шелла (аутентификация, пользователи, настройки приложения,
проекты/цели/миссии, аналитика) — это `sarex-backend` под префиксом `/api/...`
`/internal/...` для внутрикластерных вызовов). Полное описание серверных
эндпоинтов приведено в `openapi.yaml`. Ключевые группы:
| Префикс | Назначение |
| --- | --- |
| `/api/token…`, `/api/auth/…`, `/api/login`, `/api/logout` | Аутентификация и JWT |
| `/api/app-settings/` | Настройки приложения |
| `/api/core/…` | Пользователи, компании, цели, миссии, ортофото, облака точек и т.д. |
| `/api/client/…` | Клиентский дашборд, загрузки, self-сервис |
| `/api/analytics/…` | Аналитические дашборды, метрики, виджеты |
| `/api/map/…` | Кадастр и заметки на карте |
| `/api/pg/…` | Облака точек, экспорт, измерения |
| `/internal/client/…` | Внутренние вызовы (настройки, токены) |

View File

@ -0,0 +1,417 @@
# Полный перечень запросов sarex-frontend
Извлечено из исходников `src/` и `modules/`: обёртки `httpService.{get,post,put,patch,delete}Request`, вызовы `fetch`, эндпоинты RTK Query (`builder.query/mutation`). Базовый хост подставляется по ключу `service` (для `httpService`, см. `src/Model/api/hosts.ts`) либо по `env.*` из `endpoints.js`; выбор окружения — переменной сборки `ENDPOINT`. В путях `{param}` — подстановки, `?a=&b=` — query-параметры, заданные в коде. Хосты приведены для `prod`.
**Всего вызовов: 392** — httpService: 296, fetch: 76, RTK Query: 20.
## `sarex` — Django backend (`sarex-backend`), host `/` (в контуре относительные пути)
Уникальных запросов: 206
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `(динамический URL — передаётся переменной)` | src/Model/BaseApi.ts (+2) |
| POST | `(динамический URL — передаётся переменной)` | src/Model/BaseApi.ts (+1) |
| PUT | `(динамический URL — передаётся переменной)` | src/Model/BaseApi.ts (+3) |
| PATCH | `(динамический URL — передаётся переменной)` | src/Model/BaseApi.ts |
| DELETE | `(динамический URL — передаётся переменной)` | src/Model/BaseApi.ts |
| POST | `/api/analytics/attachments/` | src/Components/ImageDropZone/ImageDropZone.jsx |
| GET | `/api/analytics/attachments/?dashboard={dashboardId}` | src/Components/ImageDropZone/hook.js |
| DELETE | `/api/analytics/attachments/{id}/` | src/Components/ImageDropZone/ImageDropZone.jsx |
| DELETE | `/api/analytics/attributes-options/{id}/` | src/Model/api.js |
| DELETE | `/api/analytics/attributes/{id}/` | src/Model/api.js |
| PUT | `/api/analytics/dashboards/{id}/` | src/Model/api.js |
| DELETE | `/api/analytics/dashboards/{id}/` | src/Model/api.js |
| GET | `/api/analytics/expressions/` | modules/analytics/entities/ComputationValue/ComputationValueEntity.ts |
| POST | `/api/analytics/expressions/` | modules/analytics/entities/ComputationValue/ComputationValueEntity.ts |
| DELETE | `/api/analytics/expressions/{id}/` | modules/analytics/entities/ComputationValue/ComputationValueEntity.ts |
| PUT | `/api/analytics/expressions/{newComputationValue.id}` | modules/analytics/entities/ComputationValue/ComputationValueEntity.ts |
| PATCH | `/api/analytics/folders/{body.id}/` | modules/analytics/store/api/api.ts |
| DELETE | `/api/analytics/folders/{id}/` | modules/analytics/store/api/api.ts (+1) |
| PATCH | `/api/analytics/groups/{body.id}/` | src/Model/api.js |
| DELETE | `/api/analytics/groups/{id}/` | src/Model/api.js |
| DELETE | `/api/analytics/metric2widget/{id}/` | src/Model/api.js |
| GET | `/api/analytics/metrics/` | modules/analytics/entities/Metric/MetricEntity.ts |
| DELETE | `/api/analytics/metrics/{id}/` | modules/analytics/store/api/api.ts |
| PUT | `/api/analytics/metrics/{metric.id}/` | modules/analytics/store/api/api.ts |
| GET | `/api/analytics/metrics/{params \|\|` | modules/analytics/store/api/api.ts |
| GET | `/api/analytics/texts/?dashboard={id}` | modules/widget/store/widgets.ts |
| PATCH | `/api/analytics/texts/{body.id}/` | src/Model/api.js |
| DELETE | `/api/analytics/texts/{id}/` | src/Model/api.js |
| POST | `/api/analytics/values/import_xlsx/` | modules/analytics/store/api/api.ts |
| PATCH | `/api/analytics/values/{id}/` | src/Model/api.js |
| DELETE | `/api/analytics/values/{valueId}/` | src/Model/api.js |
| PATCH | `/api/analytics/widgets/{id}/` | src/Model/api.js |
| DELETE | `/api/analytics/widgets/{id}/` | src/Model/api.js |
| GET | `/api/client/dashboard/feed/` | src/Model/actions.js |
| GET | `/api/client/dashboard/targets/{orderId}/feed/` | src/Model/actions.js |
| POST | `/api/client/dashboard/targets/{orderId}/feed/` | src/Model/actions.js |
| POST | `/api/client/dashboard/targets/{targetId}/request_survey/` | src/Model/actions.js |
| GET | `/api/client/dashboard/webcams/` | src/Model/api.js |
| GET | `/api/client/folders/?target={targetId}&limit=10000` | src/Model/api.js |
| PATCH | `/api/client/folders/{id}/` | src/Model/api.js |
| DELETE | `/api/client/folders/{id}/` | src/Model/api.js |
| GET | `/api/client/settings/` | src/Model/actions.js |
| PUT | `/api/client/settings/` | src/Model/actions.js |
| POST | `/api/client/uploads/` | src/Model/actions.js |
| GET | `/api/client/uploads/?target={targetId}&limit=10000` | src/Model/actions.js |
| GET | `/api/client/uploads/{doc}/layers/` | src/Model/actions.js |
| DELETE | `/api/client/uploads/{fileId}/` | src/Model/actions.js |
| POST | `/api/client/uploads/{fileId}/translate_to_svf/` | src/Model/actions.js |
| PATCH | `/api/client/uploads/{id}/` | src/Model/actions.js |
| GET | `/api/commons/cs/` | src/Model/actions.js (+1) |
| GET | `/api/core/admin/companies/` | src/shared/api/fetch/company.api.ts |
| POST | `/api/core/c2s-comparisons/` | src/Components/Viewer/Panels/ComparisonsPanel/store/api/api.ts |
| GET | `/api/core/companies/` | modules/targetsTree/repositories/CompanyRepository/RESTCompanyRepository.ts (+1) |
| POST | `/api/core/contour/export/` | src/Model/api.js |
| GET | `/api/core/contour/export/?pointcloud={pointcloudId}` | src/Model/api.js |
| GET | `/api/core/materials/` | modules/measurements/components/volume/store/materials.ts |
| POST | `/api/core/media-folders/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| DELETE | `/api/core/media-folders/{id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| PATCH | `/api/core/media-folders/{params.id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| POST | `/api/core/mesh/` | src/Model/api.js |
| DELETE | `/api/core/mesh/{id}/` | src/Model/api.js |
| POST | `/api/core/missions/import/create/` | src/Model/actions.js |
| GET | `/api/core/missions/{id}/` | src/Model/api.js |
| POST | `/api/core/mrpa/` | src/shared/api/fetch/mrpa.api.ts |
| POST | `/api/core/mrpa/list/` | src/shared/api/fetch/mrpa.api.ts |
| DELETE | `/api/core/mrpa/{id}/` | src/shared/api/fetch/mrpa.api.ts |
| GET | `/api/core/orthophotos/{id}/` | src/Model/api.js |
| GET | `/api/core/orthophotos/{orthoId}/altitude_and_temperature/?points={lng},{lat}` | src/Model/api.js |
| GET | `/api/core/panoramas/{id}/` | src/Model/api.js |
| DELETE | `/api/core/panoramas/{id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| PATCH | `/api/core/panoramas/{params.id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| DELETE | `/api/core/photos/{id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| PATCH | `/api/core/photos/{params.id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| GET | `/api/core/pointclouds/?mission={mission}&comparison_type={type}` | src/Components/Viewer/Panels/ComparisonsPanel/store/api/api.ts |
| POST | `/api/core/polygons/` | src/Model/actions.js |
| GET | `/api/core/polygons/?with_links_folder{targetIdQuery}` | src/Model/api.js |
| POST | `/api/core/state/` | src/Components/Viewer/Services/States/Api/ViewerStatesApi.ts |
| GET | `/api/core/state/?pointcloud_id={pointCloudId}` | src/Components/Viewer/Services/States/Api/ViewerStatesApi.ts |
| PATCH | `/api/core/state/{id}/` | src/Components/Viewer/Services/States/Api/ViewerStatesApi.ts |
| DELETE | `/api/core/state/{id}/` | src/Components/Viewer/Services/States/Api/ViewerStatesApi.ts |
| GET | `/api/core/targets/{id}/` | src/Model/api.js |
| GET | `/api/core/users/` | modules/dashboards/models/entities/Users/UsersEntity.ts |
| DELETE | `/api/core/videos/{id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| PATCH | `/api/core/videos/{params.id}/` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| GET | `/api/map/cadastre/point/?point={point}` | src/Model/actions.js |
| GET | `/api/map/notes/folders/?orthophoto={targetId}` | src/Model/api.js |
| PATCH | `/api/map/notes/folders/{id}/` | src/Model/api.js |
| DELETE | `/api/map/notes/folders/{id}/` | src/Model/api.js |
| PATCH | `/api/mar/tasks/{taskId}/` | src/Model/api.js |
| DELETE | `/api/mar/tasks_attachments/{id}/` | src/Model/api.js |
| DELETE | `/api/mar/tasks_groups_attachments/{id}/` | src/Model/api.js |
| GET | `/api/notifications` | modules/targetsTree/repositories/TargetRepository/RESTTragetRepository.ts (+1) |
| POST | `/api/pg/attachments/` | src/Model/actions.js (+1) |
| DELETE | `/api/pg/attachments/{id}/` | src/Model/api.js |
| POST | `/api/pg/compare/c2c/` | src/Model/api.js |
| GET | `/api/pg/compare/c2c/?pointcloud={pointcloud}` | src/Model/api.js |
| POST | `/api/pg/compare/c2s/` | src/Model/actions.js |
| POST | `/api/pg/compare/t2t/` | src/Model/api.js |
| GET | `/api/pg/compare/t2t/?pointcloud={pointcloud}` | src/Model/api.js |
| POST | `/api/pg/export/pointcloud/` | src/Model/api.js |
| GET | `/api/pg/export/pointcloud/?pointcloud={pointcloudId}` | src/Model/api.js |
| POST | `/api/pg/measurements/` | src/Model/Store/voxelVolumeStore.js (+1) |
| GET | `/api/pg/measurements/?target={targetId}` | src/Model/actions.js |
| PUT | `/api/pg/measurements/{idOnServer}/` | src/Model/Store/voxelVolumeStore.js |
| DELETE | `/api/pg/measurements/{idOnServer}/` | src/Model/Store/voxelVolumeStore.js |
| PUT | `/api/pg/measurements/{measurement.idOnServer}/` | src/Model/actions.js |
| DELETE | `/api/pg/measurements/{measurementId}/` | src/Model/actions.js |
| GET | `/api/pg/orthomosaicexport/?mission_id={missionId}` | src/Model/api.js |
| POST | `/api/pg/pdf-overlays/` | src/Model/api.js |
| DELETE | `/api/pg/pdf-overlays/{id}/` | src/Model/api.js |
| GET | `/api/pg/pointclouds/{id}/comparisons/` | src/Model/api.js |
| GET | `/api/pg/pointclouds/{pointCloudId}/` | src/Model/actions.js |
| GET | `/api/pg/pointclouds/{pointCloudId}/altitude/?points={formattedPoints}` | src/Model/actions.js |
| POST | `/api/pg/pointclouds/{pointCloudId}/baseplane_volume/` | src/Model/actions.js |
| POST | `/api/pg/pointclouds/{pointCloudId}/measurements/` | src/Model/actions.js |
| GET | `/api/pg/pointclouds/{pointCloudId}/measurements/delete_measurements/` | src/Model/actions.js |
| DELETE | `/api/pg/pointclouds/{pointCloudId}/measurements/{idOnServer}/` | src/Model/actions.js |
| PATCH | `/api/pg/pointclouds/{pointCloudId}/measurements/{measurementId}/` | src/Model/actions.js |
| POST | `/api/pg/pointclouds/{pointCloudId}/project_volume/` | src/Model/actions.js |
| POST | `/api/pg/pointclouds/{pointCloudId}/volume/` | src/Model/actions.js |
| GET | `/api/pg/pointclouds/{pointcloud}/comparisons/` | src/Model/api.js |
| POST | `/api/pg/pointclouds/{targetId}/measurements/{measurementId}/calculate/` | src/Model/api.js |
| GET | `/api/pg/projects/` | src/Model/actions.js |
| POST | `/api/pg/projects/` | src/Model/actions.js |
| POST | `/api/pg/projects/{id}/images/` | src/Model/actions.js |
| PATCH | `/api/pg/projects/{id}/images/{photo.id}/` | src/Model/actions.js |
| GET | `/api/pg/projects/{id}/status/` | src/Model/actions.js |
| POST | `/api/pg/projects/{projectId}/build/` | src/Model/actions.js |
| GET | `/api/pg/projects/{projectId}/images/{GCPName ? `?marker={GCPName}` :` | src/Model/actions.js |
| POST | `/api/pg/projects/{projectId}/init/` | src/Model/actions.js |
| GET | `/api/pg/projects/{projectId}/markers/` | src/Model/actions.js |
| POST | `/api/pg/projects/{projectId}/optimize_cameras/` | src/Model/actions.js |
| POST | `/api/pg/projects/{projectId}/update_marker/` | src/Model/actions.js |
| GET | `/api/pm/dms/?target={targetId}` | src/Model/actions.js |
| GET | `/api/pm/msp/projects/?bundle_id={bundleID}&schema=tiny` | modules/pm/gate/repositories/SelectedKSGProjectRepository/WorkspaceSelectedKSGProjectRepository.ts |
| DELETE | `/api/pm/msp/projects/{id}/` | src/Model/api.js |
| GET | `/api/pm/msp/projects/{id}/profiles/` | modules/pm/gate/repositories/TaskResourceConnectionBaseRepository/TaskResourceConnectionBaseRepository.ts |
| GET | `/api/pm/msp/resources-tasks/?projects={project}` | modules/pm/gate/repositories/TaskResourcesConnectionsRepository/TaskResourcesConnectionsRepository.ts |
| GET | `/api/pm/msp/tasks/?project={project}{query ? `&{query}` :` | modules/pm/gate/repositories/TasksRepository/TasksRepository.ts |
| GET | `/api/pm/msp/tasks/{id}/` | modules/pm/gate/repositories/TasksRepository/TasksRepository.ts |
| GET | `/api/workflows/?target={id}` | modules/targetsV2_OLD_DEPRECATED/services/TargetTreeService/TargetTreeService.ts |
| GET | `/api/workflows/?target={this.targetId}` | modules/missions/controllers/MissionsCardList/MissionsCardListController.ts |
| POST | `Analytic.baseUrl` | src/Model/api.js |
| POST | `ClientFolders.baseUrl` | src/Model/api.js |
| GET | `DashboardsAPI.baseUrl` | src/Model/api.js |
| POST | `DashboardsAPI.baseUrl` | src/Model/api.js |
| POST | `GroupsApi.baseUrl` | src/Model/api.js |
| POST | `MetricsApi.baseUrl` | modules/analytics/store/api/api.ts |
| GET | `MetricsFoldersAPI.baseUrl` | modules/analytics/store/api/api.ts |
| POST | `MetricsFoldersAPI.baseUrl` | modules/analytics/store/api/api.ts |
| POST | `NotesFolders.baseUrl` | src/Model/api.js |
| POST | `ProjectsAPI.taskUrl` | src/Model/api.js |
| PUT | `StreamFile.baseUrl` | src/Model/api.js |
| GET | `host` | modules/control/entities/Storage/StorageRESTEntity.ts |
| POST | `host` | modules/control/entities/Storage/StorageRESTEntity.ts |
| GET | `hostGetInfo` | modules/control/entities/Storage/StorageRESTEntity.ts |
| GET | `hostInfo` | modules/account/entities/Storage/StorageRESTEntity.ts |
| POST | `hostInfo` | modules/account/entities/Storage/StorageRESTEntity.ts |
| POST | `hostUpdatePassword` | modules/account/entities/Storage/StorageRESTEntity.ts |
| GET | `hosts` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| POST | `hosts` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| POST | `hosts.expression2widget` | modules/widget/entities/Expressions/ExpressionEntity.ts |
| GET | `hosts.expressions` | modules/widget/entities/Expressions/ExpressionEntity.ts |
| GET | `link` | src/Model/actions.js |
| POST | `mediaEndpoint` | src/Model/Media/api.ts |
| GET | `nextUrl` | src/Model/MediaSidebar/Api/MediaModelApi.ts |
| POST | `panoramasEndpoint` | src/Model/Media/api.ts |
| POST | `photosEndpoint` | src/Model/Media/api.ts |
| GET | `this.path` | modules/analytics/entities/Folder/FolderEntity.ts |
| GET | `this.pathname` | modules/missions/entity/Mission/MissionRESTEntity.ts |
| GET | `this.props.url` | modules/auth/TokenIssuer.ts |
| POST | `this.uploadUrl` | src/Model/streamFile.ts |
| GET | `url` | src/Model/api.js |
| GET | `url.toString()` | modules/targetsTree/repositories/TargetRepository/RESTTragetRepository.ts (+2) |
| POST | `videosEndpoint` | src/Model/Media/api.ts |
| POST | `{Export.baseUrlHorizontal}` | src/Model/api.js |
| POST | `{Export.urlTablePoints}` | src/Model/api.js |
| POST | `{Export.urlTablePoints}pointcloud/` | src/Model/api.js |
| PATCH | `{ProjectsAPI.taskUrl}{body.id}/` | src/Model/api.js |
| DELETE | `{ProjectsAPI.taskUrl}{id}/` | src/Model/api.js |
| GET | `{TrackingMetaData.trackerUrl}events/` | src/Model/api.js |
| GET | `{hosts.expression2widget}?widget={id}` | modules/widget/entities/Expressions/ExpressionEntity.ts |
| PUT | `{hosts.expression2widget}{body.id}/` | modules/widget/entities/Expressions/ExpressionEntity.ts |
| DELETE | `{hosts.expression2widget}{id}/` | modules/widget/entities/Expressions/ExpressionEntity.ts |
| GET | `{hosts}aggregates/` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| POST | `{hosts}{body.dashboboardId}/copy/` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| PUT | `{hosts}{body.id}/` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| DELETE | `{hosts}{id}/` | modules/dashboards/models/entities/Dashboards/DashboardsEntity.ts |
| GET | `{host}api/tracking/meta/contractor/` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}api/tracking/meta/vehicle/` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}api/tracking/meta/vehicletracker/` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}api/tracking/meta/zones/` | modules/vehicle-tracking/methods/inner/api.ts |
| POST | `{host}api/tracking/meta/zones/` | modules/vehicle-tracking/methods/inner/api.ts |
| PUT | `{host}api/tracking/meta/zones/{id}` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}api/tracking/stats-zoned/?{queryParams.toString()}` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}buffer/` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{host}entries-log/?{queryParams.toString()}` | modules/vehicle-tracking/methods/inner/api.ts |
| GET | `{panoramasEndpoint}?mission={mission}` | src/Model/Media/api.ts |
| PATCH | `{panoramasEndpoint}{panoramaId}` | src/Model/Media/api.ts |
| GET | `{photosEndpoint}?mission={mission}` | src/Model/Media/api.ts |
| PATCH | `{photosEndpoint}{photoId}/` | src/Model/Media/api.ts |
| GET | `{this.pathname}?{searchParams.toString()}` | modules/targets/entities/Target/TargetRESTEntity.ts |
| PATCH | `{this.pathname}{params.id}/` | modules/missions/entity/Mission/MissionRESTEntity.ts |
| DELETE | `{this.pathname}{params.id}/` | modules/missions/entity/Mission/MissionRESTEntity.ts |
| GET | `{videosEndpoint}?mission={mission}` | src/Model/Media/api.ts |
| PATCH | `{videosEndpoint}{videoId}` | src/Model/Media/api.ts |
## `sarexApi``https://api.sarex.io` (stage `https://stage-api.sarex.io`)
Уникальных запросов: 23
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `(динамический URL — передаётся переменной)` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+3) |
| GET | `/flows/api/v1/{entity}/tasks-count/` | src/Model/actions.js |
| GET | `/transmittals/api/v1/transmittals/count` | src/Model/actions.js |
| GET | `pulse/api/core/check_admin/` | src/Model/Pulse/api.ts |
| GET | `pulse/api/core/user/` | src/Model/Pulse/api.ts |
| GET | `{baseUrl}/` | modules/sarexPulse/SarexPulse/banners/api/bannersPublicApi.ts (+1) |
| POST | `{baseUrl}/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+2) |
| DELETE | `{baseUrl}/bulk_delete/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+1) |
| GET | `{baseUrl}/check_new/` | modules/sarexPulse/SarexPulse/changelog/api/changelogPublicApi.ts |
| GET | `{baseUrl}/last-viewed` | modules/sarexPulse/SarexPulse/changelog/api/notificationApi.ts |
| POST | `{baseUrl}/mark-all-viewed` | modules/sarexPulse/SarexPulse/changelog/api/notificationApi.ts |
| POST | `{baseUrl}/mark-viewed` | modules/sarexPulse/SarexPulse/changelog/api/notificationApi.ts |
| GET | `{baseUrl}/unread-count` | modules/sarexPulse/SarexPulse/changelog/api/notificationApi.ts |
| POST | `{baseUrl}/upload` | modules/sarexPulse/SarexPulse/shared/api/mediaApi.ts |
| POST | `{baseUrl}/viewed/` | modules/sarexPulse/SarexPulse/banners/api/bannersPublicApi.ts (+1) |
| GET | `{baseUrl}/{id}/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+1) |
| PUT | `{baseUrl}/{id}/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+1) |
| DELETE | `{baseUrl}/{id}/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+2) |
| POST | `{baseUrl}/{id}/close/` | modules/sarexPulse/SarexPulse/banners/api/bannersPublicApi.ts |
| POST | `{baseUrl}/{id}/dislike/` | modules/sarexPulse/SarexPulse/changelog/api/changelogPublicApi.ts |
| POST | `{baseUrl}/{id}/like/` | modules/sarexPulse/SarexPulse/changelog/api/changelogPublicApi.ts |
| POST | `{baseUrl}/{id}/publish/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+1) |
| POST | `{baseUrl}/{id}/unpublish/` | modules/sarexPulse/SarexPulse/banners/api/bannersAdminApi.ts (+1) |
## `bim``https://api.sarex.io/bim`
Уникальных запросов: 17
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| PATCH | `/api/v1/bims/{bimId}` | src/Model/BIM/api.js |
| POST | `/api/v1/bims/{bimId}/archive` | src/Model/BIM/api.js |
| GET | `/api/v1/bims/{bimId}/changes` | src/Model/BIM/api.js |
| POST | `/api/v1/bims/{bimId}/changes` | src/Model/BIM/api.js |
| GET | `/api/v1/bims/{bimId}/deviations` | src/Model/BIM/api.js |
| GET | `/api/v1/bims/{bimId}/elements` | src/Model/BIM/api.js |
| GET | `/api/v1/bims/{bimId}/sarexid/{sarexIds}` | src/Model/BIM/api.js |
| POST | `/api/v1/bims/{bimId}/unarchive` | src/Model/BIM/api.js |
| POST | `/api/v1/bims/{srcBIMId}/merge/{dstBIMId}` | src/Model/BIM/api.js |
| POST | `/api/v1/changes` | src/Model/BIM/api.js |
| POST | `/api/v1/comparisons` | src/Model/BIM/api.js |
| GET | `/api/v1/elements/{elementId}/properties` | src/Model/BIM/api.js |
| PUT | `/api/v1/elements/{elementId}/properties` | src/Model/BIM/api.js |
| GET | `/api/v1/elements/{elementId}/stats` | src/Model/BIM/api.js |
| POST | `/api/v1/targets/{targetId}/bims` | src/Model/BIM/api.js |
| GET | `/api/v1/targets/{targetId}/bims?archived=1` | src/Model/BIM/api.js |
| POST | `/api/v2/deviations` | src/Model/BIM/api.js |
## `workflows``https://api.sarex.io/workflows`
Уникальных запросов: 2
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `/api/v1/workflows/{comparison.c2c_workflow},{comparison.t2t_workflow}/state` | src/Components/Viewer/Panels/ComparisonsPanel/components/MissionComparison/MissionComparison.tsx |
| GET | `/api/v1/workflows/{workflowId}` | src/Components/Viewer/Panels/FilesPanel/hooks/useGetWorkflowStatus.ts |
## `workspaces``https://api.sarex.io/workspaces`
Уникальных запросов: 1
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `/api/v1/workspaces/{workspaceID}` | modules/dashboards/view-model/WorkspacePreviewSelectorViewModel/WorkspacePreviewSelectorViewModel.ts |
## `gateway``https://api.sarex.io/gateway`
Уникальных запросов: 1
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `/api/v1/resources` | src/Model/actions.js |
## `comparisons``https://api.sarex.io/comparisons`
Уникальных запросов: 1
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `/{id}/` | src/Components/Viewer/Panels/LayersPanel/store/comparisonsLayers.ts |
## `notifications``https://api.sarex.io/lambdas/notification`
Уникальных запросов: 1
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| POST | `(динамический URL — передаётся переменной)` | modules/notifications/email-notification/email-notifier.ts |
## `processes` (fetch) — `https://api.sarex.io/flows/api/v1`
Уникальных запросов: 32
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| POST | `/documents/` | modules/reviews/api/index.ts |
| GET | `/documents/?full=true{document_ids ? `&document_ids={document_ids}` :` | modules/reviews/api/index.ts |
| PATCH | `/documents/set-status/?document_ids={ids.join(",")}` | modules/reviews/api/index.ts |
| PUT | `/documents/{id}/` | modules/reviews/api/index.ts |
| POST | `/flows/` | modules/processes/store/api/index.ts |
| GET | `/flows/count_flows_by_resource/?{params}` | modules/processes/store/api/index.ts |
| PUT | `/flows/{body.id}/?full=true` | modules/processes/store/api/index.ts |
| POST | `/flows/{id}/copy/?full=true` | modules/processes/store/api/index.ts |
| POST | `/reviewers/` | modules/processes/store/api/index.ts |
| PUT | `/reviewers/{body.id}/` | modules/processes/store/api/index.ts |
| DELETE | `/reviewers/{id}/` | modules/processes/store/api/index.ts |
| POST | `/reviews/` | modules/reviews/api/index.ts |
| GET | `/reviews/count_by_resource_id/?{params}` | modules/reviews/api/index.ts |
| GET | `/reviews/count_by_reviewer_id/?current_reviewers={query}` | modules/reviews/api/index.ts |
| PUT | `/reviews/{body.id}/` | modules/reviews/api/index.ts |
| DELETE | `/reviews/{id}/` | modules/reviews/api/index.ts |
| PATCH | `/reviews/{id}/approve/` | modules/reviews/api/index.ts |
| GET | `/reviews/{id}/documents/` | modules/reviews/api/index.ts |
| PATCH | `/reviews/{id}/update-bundles/` | modules/reviews/api/index.ts |
| PATCH | `/reviews/{reviewId}/change_reviewers/` | modules/reviews/api/index.ts |
| PUT | `/reviews/{reviewId}/documents/` | modules/reviews/api/index.ts |
| POST | `/statuses/` | modules/processes/store/api/index.ts |
| PUT | `/statuses/{body.id}/` | modules/processes/store/api/index.ts |
| DELETE | `/statuses/{id}/` | modules/processes/store/api/index.ts |
| POST | `/steps/` | modules/processes/store/api/index.ts |
| PUT | `/steps/{body.id}/?full=true` | modules/processes/store/api/index.ts |
| GET | `/steps/{stepId}/active_reviews/` | modules/processes/store/api/index.ts |
| GET | `/steps/{stepId}/get_reviewers/?review_id={reviewId}` | modules/reviews/api/index.ts |
| PATCH | `/steps/{stepId}/update_reviewers/` | modules/processes/store/api/index.ts |
| GET | `/tasks/reviewers-max-end-dates/?{query}` | modules/reviews/api/index.ts |
| PATCH | `/tasks/{id}/change-duration/` | modules/reviews/api/index.ts |
| PATCH | `/tasks/{id}/change-priority/` | modules/reviews/api/index.ts |
## `orchestrator` (fetch) — `https://api.sarex.io/orchestrator/api`
Уникальных запросов: 3
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| POST | `/process` | modules/reviews/api/marks.ts |
| GET | `/process/{id}` | modules/reviews/api/marks.ts |
| POST | `/sign` | modules/reviews/api/marks.ts |
## `automation` (fetch) — `https://api.sarex.io/automation/api/v1`
Уникальных запросов: 1
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| POST | `/automations` | modules/automation/store/automation.ts |
## `analytics-v2` (RTK Query) — `https://api.sarex.io/analytics-v2/api/v1`
Уникальных запросов: 19
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `/api/commons/wiki.get` | src/Components/Wiki/apiWiki/api.ts |
| POST | `/api/commons/wiki.media.save` | src/Components/Wiki/apiWiki/api.ts |
| POST | `/api/commons/wiki.update` | src/Components/Wiki/apiWiki/api.ts |
| GET | `/api/core/media-notes-attachments/` | src/Model/MediaNotes/api.ts |
| POST | `/api/core/media-notes-attachments/` | src/Model/MediaNotes/api.ts |
| DELETE | `/api/core/media-notes-attachments/{attachmentId}` | src/Model/MediaNotes/api.ts |
| GET | `/api/core/media-notes-comments/` | src/Model/MediaNotes/api.ts |
| POST | `/api/core/media-notes-comments/` | src/Model/MediaNotes/api.ts |
| DELETE | `/api/core/media-notes-comments/{body.commentId}/` | src/Model/MediaNotes/api.ts |
| GET | `/api/core/media-notes/` | src/Model/MediaNotes/api.ts |
| POST | `/api/core/media-notes/` | src/Model/MediaNotes/api.ts |
| PATCH | `/api/core/media-notes/{body.id}/` | src/Model/MediaNotes/api.ts |
| DELETE | `/api/core/media-notes/{params.noteId}/` | src/Model/MediaNotes/api.ts |
| POST | `/api/core/targets/v2/{target_id}/calculate_panoramas_relative_orientation/` | src/Components/MediaViewer/MediaPanorama/panoramaApi.ts |
| GET | `/api/core/targets/{target}/missions/` | src/Components/Viewer/Panels/Media/MediaApi/missionsApi.ts |
| GET | `/api/notifications/?page={page}` | src/Model/AeroNoty/api.ts |
| POST | `/api/notifications/update/` | src/Model/AeroNoty/api.ts |
| GET | `/workflows/get_items_by_generic_key/?model=target&object_id={target_id}` | src/Components/MediaViewer/MediaPanorama/panoramaApi.ts |
| GET | `wfs` | src/Model/GIS/api.ts |
## Прочее (static-конфиг, внешние сервисы, wrappers с динамическим URL)
Уникальных запросов: 13
| Метод | Путь | Файл (источник) |
| --- | --- | --- |
| GET | `(динамический URL — передаётся переменной)` | src/Components/Viewer/Panels/Media/MediaRedux.tsx |
| GET | `/static/config.json` | src/shared/config/config.ts |
| GET | `<processes>/tasks/?` + params.toString()` | modules/reviews/api/index.ts |
| GET | `file.attachment` | src/Components/Viewer/Panels/MeasurementDetails/MediaFiles_old.jsx |
| GET | `http://localhost:8000{path}` | src/Model/fakeServer/fakeServer.js |
| GET | `https://blooming-cove-51473.herokuapp.com/track/list` | src/Model/Store/trackingStore.js |
| GET | `https://blooming-cove-51473.herokuapp.com/track/notifications` | src/Model/Store/trackingStore.js |
| GET | `image` | src/Components/Viewer/Services/States/Stores/ViewerStates.ts |
| GET | `link` | src/Model/actions.js |
| GET | `url` | modules/account/ui/StorageCard/lib/file-utils.ts (+2) |
| POST | `url` | modules/reviews/api/index.ts |
| PUT | `url` | modules/reviews/api/index.ts |
| PATCH | `url` | modules/reviews/api/index.ts |

589
apps/django/openapi.yaml Normal file
View File

@ -0,0 +1,589 @@
openapi: 3.0.3
info:
title: Sarex Backend API
version: "1.1.12"
description: |
REST API сервиса **sarex-backend** — монолитное Django-приложение (проект
`config`, бизнес-логика в пакете `sarex`) на Django REST Framework. Отдаётся
через uWSGI (`config.wsgi:application`) на порту `8000`.
Документ описывает основную поверхность публичного API под префиксом `/api/`
и внутреннего API под префиксом `/internal/`. Маршрутизация собирается в
`config/urls.py` и включаемых `sarex/*/api/urls.py`. Многие ресурсы
зарегистрированы через DRF-роутеры (`SimpleRouter`/`DefaultRouter`), поэтому
поддерживают стандартный набор действий (list/create/retrieve/update/
partial_update/destroy). Конкретные схемы запросов/ответов в коде не
объявлены декларативно (используется `rest_framework.schemas.coreapi.AutoSchema`),
поэтому тела здесь описаны обобщённо.
### Аутентификация
Большинство эндпоинтов требуют аутентификации (DRF
`DEFAULT_PERMISSION_CLASSES = [IsAuthenticated]`). Поддерживаются несколько
механизмов (`DEFAULT_AUTHENTICATION_CLASSES`): Zitadel JWT, SimpleJWT
(`Authorization: Bearer <jwt>`, алгоритм `RS512`), Basic, Session,
RemoteUser. Токены выпускаются эндпоинтами `/api/token…`.
### Пагинация
По умолчанию используется `LimitOffsetPagination` (`PAGE_SIZE = 1000`).
Списочные ответы содержат `count`, `next`, `previous`, `results`.
### Замечание о полноте
Перечислены основные маршруты. Часть включаемых подмодулей
(`sarex/pg/api/*`, `sarex/mar/*`, `sarex/base/api/commons`) представлена
группами; детальные под-пути см. в соответствующих `urls.py`.
servers:
- url: https://api.sarex.io
description: prod
- url: https://stage-api.sarex.io
description: stage
- url: /
description: contour (относительные пути)
security:
- bearerAuth: []
tags:
- name: auth
description: Аутентификация и JWT-токены
- name: base
description: Уведомления, workflows, модули, health
- name: core
description: Пользователи, компании, цели, миссии, медиа
- name: client
description: Клиентский дашборд и self-сервис
- name: analytics
description: Дашборды, метрики, виджеты
- name: map
description: Кадастр и заметки на карте
- name: pg
description: Облака точек, экспорт, измерения
- name: internal
description: Внутрикластерные вызовы
- name: system
description: Метрики и служебные эндпоинты
paths:
# ---------------------------------------------------------------------------
# Auth / tokens
# ---------------------------------------------------------------------------
/api/login/:
post:
tags: [auth]
summary: Вход пользователя (сессия)
security: []
responses:
"200": { $ref: "#/components/responses/Ok" }
"401": { $ref: "#/components/responses/Unauthorized" }
/api/logout/:
post:
tags: [auth]
summary: Выход пользователя
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/app-settings/:
get:
tags: [auth]
summary: Настройки приложения
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/token/:
post:
tags: [auth]
summary: Получить пару access/refresh токенов
security: []
requestBody: { $ref: "#/components/requestBodies/Generic" }
responses:
"200": { $ref: "#/components/responses/TokenPair" }
"401": { $ref: "#/components/responses/Unauthorized" }
/api/token/me:
post:
tags: [auth]
summary: Токен для текущего пользователя
responses:
"200": { $ref: "#/components/responses/TokenPair" }
/api/token/user/{pk}/:
post:
tags: [auth]
summary: Токен для пользователя по id (из админки)
parameters: [ { $ref: "#/components/parameters/PkPath" } ]
responses:
"200": { $ref: "#/components/responses/TokenPair" }
/api/token/jwks:
get:
tags: [auth]
summary: JWKS (набор публичных ключей)
security: []
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/token/refresh/:
post:
tags: [auth]
summary: Обновить access-токен (ротация)
security: []
requestBody: { $ref: "#/components/requestBodies/Generic" }
responses:
"200": { $ref: "#/components/responses/TokenPair" }
/api/token/public/:
get:
tags: [auth]
summary: Публичный ключ проверки JWT
security: []
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/auth/obtain/:
post:
tags: [auth]
summary: Получить refresh-токен
security: []
requestBody: { $ref: "#/components/requestBodies/Generic" }
responses:
"200": { $ref: "#/components/responses/TokenPair" }
/api/auth/refresh/:
post:
tags: [auth]
summary: Обновить access-токен
security: []
requestBody: { $ref: "#/components/requestBodies/Generic" }
responses:
"200": { $ref: "#/components/responses/TokenPair" }
# ---------------------------------------------------------------------------
# System
# ---------------------------------------------------------------------------
/metrics:
get:
tags: [system]
summary: Метрики Prometheus
security: []
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/health/:
get:
tags: [base]
summary: Health check
security: []
responses:
"200": { $ref: "#/components/responses/Ok" }
# ---------------------------------------------------------------------------
# Base
# ---------------------------------------------------------------------------
/api/modules/:
get:
tags: [base]
summary: Список доступных модулей
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/update/notifications/:
post:
tags: [base]
summary: Массовое обновление уведомлений
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/workflows/:
get:
tags: [base]
summary: Список workflow
responses:
"200": { $ref: "#/components/responses/List" }
/api/workflows/{id}/:
parameters: [ { $ref: "#/components/parameters/IdPath" } ]
get:
tags: [base]
summary: Workflow по id
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/notifications/:
get:
tags: [base]
summary: Список уведомлений
responses:
"200": { $ref: "#/components/responses/List" }
/api/notifications/{id}/:
parameters: [ { $ref: "#/components/parameters/IdPath" } ]
get:
tags: [base]
summary: Уведомление по id
responses:
"200": { $ref: "#/components/responses/Ok" }
/api/usernotifications/:
get:
tags: [base]
summary: Пользовательские уведомления
responses:
"200": { $ref: "#/components/responses/List" }
/api/commons/cs/:
get:
tags: [base]
summary: Справочник систем координат
responses:
"200": { $ref: "#/components/responses/List" }
# ---------------------------------------------------------------------------
# Core — ресурсы DRF-роутера (CRUD)
# ---------------------------------------------------------------------------
/api/core/users/:
get:
tags: [core]
summary: Список пользователей
responses: { "200": { $ref: "#/components/responses/List" } }
post:
tags: [core]
summary: Создать пользователя
requestBody: { $ref: "#/components/requestBodies/Generic" }
responses: { "201": { $ref: "#/components/responses/Ok" } }
/api/core/users/{id}/:
parameters: [ { $ref: "#/components/parameters/IdPath" } ]
get: { tags: [core], summary: Пользователь по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
put: { tags: [core], summary: Обновить пользователя, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "200": { $ref: "#/components/responses/Ok" } } }
patch: { tags: [core], summary: Частично обновить пользователя, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "200": { $ref: "#/components/responses/Ok" } } }
delete: { tags: [core], summary: Удалить пользователя, responses: { "204": { description: No Content } } }
/api/core/users/introspect:
get:
tags: [core]
summary: Интроспекция текущего пользователя
responses: { "200": { $ref: "#/components/responses/Ok" } }
/api/core/users/{pk}/introspect:
parameters: [ { $ref: "#/components/parameters/PkPath" } ]
get:
tags: [core]
summary: Интроспекция пользователя (админ)
responses: { "200": { $ref: "#/components/responses/Ok" } }
/api/core/users/bulk/notifications/:
post:
tags: [core]
summary: Массовое обновление уведомлений пользователей
responses: { "200": { $ref: "#/components/responses/Ok" } }
/api/core/users_by_sa/:
get:
tags: [core]
summary: Пользователи по сервисному аккаунту
responses: { "200": { $ref: "#/components/responses/List" } }
/api/core/v2/users/:
get:
tags: [core]
summary: Упрощённый список пользователей (v2)
responses: { "200": { $ref: "#/components/responses/List" } }
/api/core/v3/users/:
get:
tags: [core]
summary: Оптимизированный список пользователей (v3)
responses: { "200": { $ref: "#/components/responses/List" } }
/api/core/companies/:
get: { tags: [core], summary: Список компаний, responses: { "200": { $ref: "#/components/responses/List" } } }
post: { tags: [core], summary: Создать компанию, requestBody: { $ref: "#/components/requestBodies/Generic" }, responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/companies/{id}/:
parameters: [ { $ref: "#/components/parameters/IdPath" } ]
get: { tags: [core], summary: Компания по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/surfaces/:
get: { tags: [core], summary: Список поверхностей, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/mesh/:
get: { tags: [core], summary: Список mesh, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/multiple-surface/:
get: { tags: [core], summary: Множественные поверхности, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/state/:
get: { tags: [core], summary: Состояние приложения, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/polygons/:
get: { tags: [core], summary: Полигоны, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/media-notes/:
get: { tags: [core], summary: Медиа-заметки, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/media-notes-comments/:
get: { tags: [core], summary: Комментарии медиа-заметок, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/media-notes-attachments/:
get: { tags: [core], summary: Вложения медиа-заметок, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/media-folders/:
get: { tags: [core], summary: Папки медиа, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/target-links/:
get: { tags: [core], summary: Ссылки на цели, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/materials/:
get: { tags: [core], summary: Материалы, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/pointclouds/:
get: { tags: [core], summary: Облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/orthophotos/:
get: { tags: [core], summary: Ортофотопланы, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/videos/:
get: { tags: [core], summary: Видео, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/photos/:
get: { tags: [core], summary: Фото, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/panoramas/:
get: { tags: [core], summary: Панорамы, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/color-presets/:
get: { tags: [core], summary: Цветовые пресеты компании, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/missions/:
get: { tags: [core], summary: Список миссий, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/missions/import/:
get: { tags: [core], summary: Задачи импорта миссий, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/missions/import/create/:
post: { tags: [core], summary: Создать задачу импорта миссии, responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/targets/:
get: { tags: [core], summary: Список целей, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/targets/v2/:
get: { tags: [core], summary: Список целей (v2), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/targets/v3/:
get: { tags: [core], summary: Список целей (v3), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/targets/tree/:
get: { tags: [core], summary: Дерево целей, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/targets/pdm/:
get: { tags: [core], summary: Цели PDM, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/targets/{id}/missions/:
parameters: [ { $ref: "#/components/parameters/IdPath" } ]
get: { tags: [core], summary: Миссии цели, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/export/:
get: { tags: [core], summary: Экспорт данных, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/export/pointcloud/:
post: { tags: [core], summary: Экспорт региона из облака точек, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/contour/export/:
post: { tags: [core], summary: Экспорт контура, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/contour/exports/:
get: { tags: [core], summary: Список экспортов контура, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/uploads/streaming/:
post: { tags: [core], summary: Потоковая загрузка (создание), responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/uploads/streaming/{pk}/:
parameters: [ { $ref: "#/components/parameters/PkStrPath" } ]
put: { tags: [core], summary: Догрузка чанка, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/storage-cleanup/:
post: { tags: [core], summary: Очистка хранилища, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/celery/stats:
get: { tags: [core], summary: Статистика Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/celery/active:
get: { tags: [core], summary: Активные задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/celery/scheduled:
get: { tags: [core], summary: Запланированные задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/celery/{uuid}/details/:
parameters: [ { name: uuid, in: path, required: true, schema: { type: string } } ]
get: { tags: [core], summary: Детали задачи Celery, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/admin/users/:
get: { tags: [core], summary: Админ — пользователи, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/groups/:
get: { tags: [core], summary: Админ — группы, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/companies/:
get: { tags: [core], summary: Админ — компании, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/positions/:
get: { tags: [core], summary: Админ — должности, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/departments/:
get: { tags: [core], summary: Админ — подразделения, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/contractors/:
get: { tags: [core], summary: Админ — подрядчики, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/admin/permissions/:
get: { tags: [core], summary: Админ — права, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/service_accounts/:
get: { tags: [core], summary: Сервисные аккаунты, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/service-accounts/personalized/:
get: { tags: [core], summary: Персонализированный сервисный аккаунт, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/service-accounts/user/{user_id}/:
parameters: [ { name: user_id, in: path, required: true, schema: { type: integer } } ]
get: { tags: [core], summary: Сервисный аккаунт пользователя (v2), responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/core/c2s-comparisons/:
post: { tags: [core], summary: Создать c2s-сравнение, responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/permissions/:
get: { tags: [core], summary: Права (только чтение), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/units/:
get: { tags: [core], summary: Единицы измерения, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/upload/media-file/:
post: { tags: [core], summary: Загрузка медиа-файла, responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/mrpa/list/:
get: { tags: [core], summary: Список MRPA, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/core/mrpa/:
post: { tags: [core], summary: Создать MRPA, responses: { "201": { $ref: "#/components/responses/Ok" } } }
/api/core/mrpa/{pk}/:
parameters: [ { name: pk, in: path, required: true, schema: { type: string, format: uuid } } ]
get: { tags: [core], summary: MRPA по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
# ---------------------------------------------------------------------------
# Client
# ---------------------------------------------------------------------------
/api/client/dashboard/targets/:
get: { tags: [client], summary: Дашборд — цели, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/client/dashboard/missions/:
get: { tags: [client], summary: Дашборд — миссии, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/client/dashboard/webcams/:
get: { tags: [client], summary: Дашборд — веб-камеры, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/client/settings/:
get: { tags: [client], summary: Настройки пользователя, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/client/settings/user/{user_id}/:
parameters: [ { name: user_id, in: path, required: true, schema: { type: integer } } ]
get: { tags: [client], summary: Настройки пользователя по id, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/client/self/:
get: { tags: [client], summary: Информация о себе, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/client/self/set_password/:
post: { tags: [client], summary: Смена пароля, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/client/uploads/:
get: { tags: [client], summary: Загрузки клиента, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/client/folders/:
get: { tags: [client], summary: Папки загрузок клиента, responses: { "200": { $ref: "#/components/responses/List" } } }
# ---------------------------------------------------------------------------
# Analytics
# ---------------------------------------------------------------------------
/api/analytics/dashboards/:
get: { tags: [analytics], summary: Дашборды, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/widgets/:
get: { tags: [analytics], summary: Виджеты, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/metrics/:
get: { tags: [analytics], summary: Метрики, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/expressions/:
get: { tags: [analytics], summary: Выражения, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/folders/:
get: { tags: [analytics], summary: Папки метрик, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/filters/:
get: { tags: [analytics], summary: Фильтры, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/groups/:
get: { tags: [analytics], summary: Группы, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/values/:
get: { tags: [analytics], summary: Значения метрик, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/attributes/:
get: { tags: [analytics], summary: Атрибуты, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/attachments/:
get: { tags: [analytics], summary: Вложения дашбордов, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/analytics/reviews-service-feed/:
post: { tags: [analytics], summary: Приём событий из сервиса reviews, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/analytics/remarks-service-feed/:
post: { tags: [analytics], summary: Приём событий из сервиса remarks, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/analytics/tracking-service-feed/:
post: { tags: [analytics], summary: Приём событий трекинга, responses: { "200": { $ref: "#/components/responses/Ok" } } }
# ---------------------------------------------------------------------------
# Map
# ---------------------------------------------------------------------------
/api/map/cadastre/point/:
get: { tags: [map], summary: Данные кадастра по точке, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/map/cadastre/export/:
post: { tags: [map], summary: Экспорт кадастровых данных, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/map/cadastre/wikimapia/redirect/:
get: { tags: [map], summary: Редирект Wikimapia, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/map/notes/:
get: { tags: [map], summary: Заметки на ортофото, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/map/notes/folders/:
get: { tags: [map], summary: Папки заметок на ортофото, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/ds/telecom/cadastre/:
get: { tags: [map], summary: Изображение кадастра (telecom), responses: { "200": { $ref: "#/components/responses/Ok" } } }
# ---------------------------------------------------------------------------
# PG (облака точек / экспорт / измерения)
# ---------------------------------------------------------------------------
/api/pg/pointclouds/:
get: { tags: [pg], summary: Облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/pointclouds/{pointcloud}/measurements/:
parameters: [ { name: pointcloud, in: path, required: true, schema: { type: string } } ]
get: { tags: [pg], summary: Измерения облака точек, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/volume-dynamic/:
get: { tags: [pg], summary: Динамика объёмов, responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/pg/orthomosaicexport/:
get: { tags: [pg], summary: Экспорт ортомозаики (кастомный), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/exports/pointcloud/:
get: { tags: [pg], summary: Экспорты облаков точек, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/exports/orthomosaic/:
get: { tags: [pg], summary: Экспорты ортомозаики, responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/measurements/:
get: { tags: [pg], summary: Измерения (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/attachments/:
get: { tags: [pg], summary: Вложения (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/compare/:
get: { tags: [pg], summary: Сравнение (подмодуль), responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/pg/export/:
get: { tags: [pg], summary: Экспорт (подмодуль), responses: { "200": { $ref: "#/components/responses/Ok" } } }
/api/pg/pdf-overlays/:
get: { tags: [pg], summary: PDF-оверлеи (подмодуль), responses: { "200": { $ref: "#/components/responses/List" } } }
/api/pg/projects/:
get: { tags: [pg], summary: Проекты Metashape (при SERVER_USE_METASHAPE), responses: { "200": { $ref: "#/components/responses/List" } } }
# ---------------------------------------------------------------------------
# Internal
# ---------------------------------------------------------------------------
/internal/client/settings/{pk}/:
parameters: [ { $ref: "#/components/parameters/PkPath" } ]
get:
tags: [internal]
summary: Внутренние настройки клиента
responses: { "200": { $ref: "#/components/responses/Ok" } }
/internal/client/token/{pk}/:
parameters: [ { $ref: "#/components/parameters/PkPath" } ]
get:
tags: [internal]
summary: Внутренний токен клиента
responses: { "200": { $ref: "#/components/responses/TokenPair" } }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
parameters:
IdPath:
name: id
in: path
required: true
schema: { type: integer }
PkPath:
name: pk
in: path
required: true
schema: { type: integer }
PkStrPath:
name: pk
in: path
required: true
schema: { type: string }
requestBodies:
Generic:
required: true
content:
application/json:
schema:
type: object
additionalProperties: true
responses:
Ok:
description: Успешный ответ
content:
application/json:
schema:
type: object
additionalProperties: true
List:
description: Списочный ответ с пагинацией (LimitOffset)
content:
application/json:
schema:
$ref: "#/components/schemas/PaginatedList"
TokenPair:
description: Пара токенов
content:
application/json:
schema:
$ref: "#/components/schemas/TokenPair"
Unauthorized:
description: Не аутентифицирован
content:
application/json:
schema:
$ref: "#/components/schemas/Error"
schemas:
PaginatedList:
type: object
properties:
count: { type: integer }
next: { type: string, nullable: true, format: uri }
previous: { type: string, nullable: true, format: uri }
results:
type: array
items:
type: object
additionalProperties: true
TokenPair:
type: object
properties:
access: { type: string }
refresh: { type: string }
Error:
type: object
properties:
detail: { type: string }

View File

@ -0,0 +1,16 @@
# document-link-frontend (Next.js)
#
# Внимание: текущий код фронтенда НЕ читает переменные окружения напрямую
# (в src/ нет обращений к process.env / NEXT_PUBLIC_*). Базовый URL API
# выбирается по window.location.hostname, а JWT зашит в коде (см. CONFIGURATION.md).
# Перечисленные ниже переменные — это конфигурационная поверхность, объявленная
# в Helm (.helm/values.yaml), и их следует использовать вместо хардкода.
# Публичный базовый хост API (сервис documentations), без схемы.
# Итоговый запрос: https://<NEXT_PUBLIC_API_BASE_URL>/documentations/api/v1/public/documents/public_link/<uuid>
# stage: stage-api.sarex.io, production: api.sarex.io
NEXT_PUBLIC_API_BASE_URL=stage-api.sarex.io
# JWT сервисного аккаунта для запроса публичной ссылки (Authorization: Bearer <jwt>).
# В инфраструктуре берётся из секрета documentations-publiclink-jwt-secret (ключ jwt).
NEXT_PUBLIC_API_TOKEN=

View File

@ -0,0 +1,95 @@
# Конфигурация проекта document-link (document-link-frontend)
Документ описывает способы конфигурирования и все переменные окружения сервиса публичных ссылок на документы.
## Что это за сервис
`document-link-frontend` — микрофронтенд на **Next.js 13** (App Router, `src/app`), отдающий публичную страницу-карточку документа по ссылке вида `https://document-link.<env>.sarex.io/<uuid>`. По `uuid` фронтенд запрашивает метаданные документа у сервиса `documentations` и показывает название, автора, версию, размер, срок действия ссылки и кнопку скачивания (см. `ENDPOINTS.md`). Собственного бэкенда у сервиса нет — это чистый фронтенд, поэтому файлы вида `openapi.yaml` для него неприменимы.
## Способы конфигурирования
В отличие от бэкенд-сервисов, у фронтенда **нет разбора переменных окружения в коде**. На текущий момент:
- Базовый хост API выбирается в рантайме по `window.location.hostname` в `src/app/[uuid]/components/modal.tsx` (жёстко заданный `switch`), а не из переменной окружения;
- JWT для авторизации запроса **зашит в коде** (константа `fixedToken` в том же файле) — временное решение;
- Обращений к `process.env` / `NEXT_PUBLIC_*` в `src/` нет.
При этом конфигурационная поверхность **объявлена в инфраструктуре** (Helm-чарт репозитория и CI) в виде переменных `NEXT_PUBLIC_*` — их предполагается использовать вместо хардкода. Ниже описаны и код, и инфраструктурные объявления.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся значения |
| --- | --- |
| Локально (`npm run dev`) | Значения зашиты в коде; `apiBaseUrl` для `localhost``stage-api.sarex.io` |
| Локально (Docker / `docker-compose`) | `Dockerfile` (`node:18-alpine`, `next build`, `ENTRYPOINT npm start`, порт `3000`); `docker-compose.yaml` пробрасывает `8000:3000` |
| Kubernetes — репозиторный чарт | `.helm/values.yaml`: блок `services.frontend.envs` и `secretEnvs` (см. ниже) |
| Kubernetes — infra (этот репозиторий) | `apps/document-link/base` (kustomize) и `apps/document-link/{brusnika-stage,brusnika-prod}` (FluxCD `HelmRelease` поверх `universal-chart`) |
| CI/CD (GitLab) | `.gitlab-ci.yml`: `generic/common-ci` (`universal-pipeline`, ref `apps-business`), `SERVICE_NAME=document-link` |
## Переменные приложения
Объявлены в `.helm/values.yaml` исходного репозитория. **Важно:** текущий код фронтенда их не читает (см. раздел «Замечания»).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `NEXT_PUBLIC_API_BASE_URL` | string | `stage-api.sarex.io` (stage), `api.sarex.io` (production) | Базовый хост API (сервис `documentations`), без схемы |
| `NEXT_PUBLIC_API_TOKEN` | string (secret) | — | JWT сервисного аккаунта для `Authorization: Bearer <jwt>`. Берётся из секрета `documentations-publiclink-jwt-secret` (ключ `jwt`) |
## Базовые хосты API по окружениям
Логика `src/app/[uuid]/components/modal.tsx` (`switch` по `window.location.hostname`):
| Hostname фронтенда | `apiBaseUrl` |
| --- | --- |
| `localhost` | `stage-api.sarex.io` |
| `document-link.stage.sarex.io` | `stage-api.sarex.io` |
| `document-link.sarex.io` | `api.sarex.io` |
| прочее | не определён (ошибка в консоль, fallback `stage-api.sarex.io`) |
## Сборка и контейнер
| Параметр | Значение | Где задано |
| --- | --- | --- |
| Базовый образ | `node:18-alpine` | `Dockerfile` |
| Команда сборки | `npm i``npm run build` (`next build`) | `Dockerfile` |
| Entrypoint | `npm start` (`next start`) | `Dockerfile` |
| Порт приложения | `3000` | `Dockerfile` (`EXPOSE 3000`), `.helm/values.yaml` (`port._default: 3000`) |
| Образ (registry) | `cr.yandex/crp3ccidau046kdj8g9q/document-link-frontend` | `.helm/values.yaml`, infra `HelmRelease` |
## Деплой (infra: `apps/document-link`)
| Оверлей | Механизм | Особенности |
| --- | --- | --- |
| `base` | kustomize (`Deployment` + `Service` + `Namespace`) | namespace `document-link` c `istio-injection: enabled`; `Deployment/frontend` |
| `brusnika-stage` | FluxCD `HelmRelease``universal-chart` `0.1.7` | `replicaCount`: stage `1`; probes выключены |
| `brusnika-prod` | FluxCD `HelmRelease``universal-chart` `0.1.7` | `replicaCount`: preprod/production `3`; probes выключены |
| `yc-k8s-test` | kustomize (`../base`) | тестовый контур |
Порты сервиса: в `universal-chart``service.port 8080``targetPort 3000` (`portName: http`); в `base/service.yaml``port 80``targetPort 80`.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`). Ключевые переменные: `SERVICE_NAME=document-link`, `DOCKERFILE_PATH=./Dockerfile`, `CI_TRIGGER_SOURCE=app`. Окружение переключается по ветке/тегу:
| Условие | STAND | Namespace | Chart |
| --- | --- | --- | --- |
| `merge_request_event` | — | — | `ENABLE_BUILD_IMAGE=false` (только проверки) |
| ветка `stage` | `stage` | `documentations` | `document-link` `0.1.7`, `--build-arg ENV=stage` |
| ветка `master` | `preprod` | `document-link-preprod` | `document-link` `0.1.7`, `--build-arg ENV=preprod` |
| тег (`CI_COMMIT_TAG`) | `production` | `document-link-prod` | `document-link` `0.1.7`, `--build-arg ENV=prod` |
`HELM_SET_ARGS` пробрасывает в `universal-chart` образ (`services.frontend.image.name.<env>`), `global.env`, а также `commitSha`/`gitlabUri`/`gitlabJobUrl`/`owner`.
## Замечания и потенциальные проблемы
- **Env-переменные не используются кодом.** `NEXT_PUBLIC_API_BASE_URL` и `NEXT_PUBLIC_API_TOKEN` объявлены в `.helm/values.yaml`, но `src/` их не читает: базовый хост берётся из `switch` по `hostname`, а токен зашит константой `fixedToken`. Для корректной работы на разных стендах логику стоит перевести на `process.env.NEXT_PUBLIC_*` (и тогда `.env.example` станет рабочим шаблоном).
- **Зашитый JWT.** Константа `fixedToken` в `modal.tsx` — секрет в исходниках и с ограниченным сроком действия (`exp`). Должен приходить из секрета `documentations-publiclink-jwt-secret` через `NEXT_PUBLIC_API_TOKEN`.
- **Неизвестный hostname.** При домене, не входящем в `switch`, `apiBaseUrl` не задаётся явно (используется дефолт `stage-api.sarex.io`) — для новых стендов список нужно расширять.
- **Расхождение портов.** Приложение слушает `3000` (Dockerfile/helm `targetPort`), но `apps/document-link/base/deployment.yaml` объявляет `containerPort: 80`, а `base/service.yaml``port/targetPort 80`. В `universal-chart` (`HelmRelease`) — корректный `targetPort 3000`. Kustomize-`base` стоит выровнять на `3000`.
- **Многоступенчатый Dockerfile закомментирован.** Финальный `runner`-stage отключён — образ запускается из `builder` с полным `node_modules`; для прод-образа стоит включить slim-runner.
## Минимальный набор для запуска
- Локально: `npm i && npm run dev`, открыть `http://localhost:3000/<uuid>` (API — `stage-api.sarex.io`).
- Docker: `docker compose up` (порт `8000` → контейнер `3000`).
- В кластере фактически требуется рабочий JWT для сервиса `documentations` (сейчас — `fixedToken`; целевое — секрет `documentations-publiclink-jwt-secret`).

View File

@ -0,0 +1,66 @@
# Эндпоинты, с которыми взаимодействует document-link-frontend
Документ описывает HTTP-эндпоинты внешних сервисов, к которым обращается фронтенд публичных ссылок (`document-link-frontend`).
## Как устроено взаимодействие
Фронтенд загружает карточку документа по `uuid` из URL (`/<uuid>`). Запрос выполняется хуком `useSWR` в `src/app/[uuid]/components/modal.tsx` через нативный `fetch`. Базовый хост API выбирается в рантайме по `window.location.hostname`, итоговый URL = `https://<apiBaseUrl>` + путь эндпоинта. Скачивание файлов выполняется переходом браузера по ссылкам, которые возвращает сам API (`download_link`, `download_mrpas_link`).
Авторизация: заголовок `Authorization: Bearer <jwt>` (сейчас — зашитая константа `fixedToken`; целевое — `NEXT_PUBLIC_API_TOKEN` из секрета `documentations-publiclink-jwt-secret`). Запрос идёт с `credentials: "include"`.
## Базовые хосты по окружениям
Значения из `switch` по `window.location.hostname` в `modal.tsx`:
| Hostname фронтенда | `apiBaseUrl` (базовый хост API) |
| --- | --- |
| `localhost` | `https://stage-api.sarex.io` |
| `document-link.stage.sarex.io` | `https://stage-api.sarex.io` |
| `document-link.sarex.io` | `https://api.sarex.io` |
| прочее | не определён (fallback `https://stage-api.sarex.io`) |
## Эндпоинты по сервисам
### `documentations` — Сервис документации
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| Публичная ссылка | GET | `/documentations/api/v1/public/documents/public_link/{uuid}` | Метаданные документа по публичной ссылке (`uuid`) |
Пример итогового URL: `https://stage-api.sarex.io/documentations/api/v1/public/documents/public_link/e602b98d-58a1-4ba3-8a89-84e5617b5aac`.
Ожидаемые поля ответа (используются во фронтенде):
| Поле ответа | Тип | Использование |
| --- | --- | --- |
| `name` | string | Название документа |
| `document_type` | string | Тип (иконка): `bim`/`bimv2`/`cloud`/`surface`/`workspace`/`pdf`/`deviation`/`c2s`/`c2c`/`abap`/`ksg`/`docx`/`xlsx`/`dxf`/`dwg`/… |
| `author` | string | Автор |
| `version` | string | Версия документа (скрывается для `workspace`/`folder`/`project`) |
| `size` | number | Размер в байтах (форматируется библиотекой `bytes`) |
| `download_token` | string | Токен скачивания |
| `download_link` | string (URL) | Прямая ссылка на скачивание файла |
| `download_mrpas_link` | string (URL) \| null | Ссылка на скачивание МЧД (опционально) |
| `expires_at` | string (datetime) \| null | Срок действия ссылки; `null` → «Неограничено» |
| `is_connector` | bool | Признак «файл > 5 Гб, требуется Sarex-коннектор» |
### Скачивание файлов (динамические ссылки)
Не отдельные эндпоинты реестра, а переход браузера по URL из ответа:
| Действие | Источник URL |
| --- | --- |
| Скачать файл/папку | `download_link` из ответа `public_link` |
| Скачать МЧД | `download_mrpas_link` из ответа `public_link` (если не `null`) |
При `is_connector = true` вместо прямого скачивания показывается предупреждение со ссылкой на [Sarex-коннектор](https://support.sarex.io/knowledge_base/item/347946).
## Обработка ошибок
Статус ответа маппится в человекочитаемое сообщение (`modal.tsx`):
| Статус | Сообщение |
| --- | --- |
| `400`, `404` | «Ссылка не найдена» |
| `410` | «Время действия вашей ссылки истекло» |
| `500`, `503` | «Что-то пошло не так» |

View File

@ -0,0 +1,229 @@
# Конфигурация проекта documentations-api-v2
Документ описывает все переменные окружения и способы конфигурирования сервиса **documentation-api-v2** (`pdm/documentation-api-v2`) — Go-сервис домена «documentations» (v2), отвечающий за диски, документы, бандлы, data source, страницы, публичные ссылки, workflow-обработку и подписи.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`github.com/kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (функция `config.New()``envconfig.Process("", cfg)`).
Особенности разбора:
- **Префикса нет** — переменные читаются под своими именами (напр. `API_ADDRESS`, `POSTGRES_ADDRESS`), имя задаётся тегом `envconfig:"..."`.
- **Вложенность не используется** — конфиг плоский. Параметры БД вынесены в встроенную структуру `gopg.Config` (`pkg/postgres/gopg/postgres.go`), но остаются на верхнем уровне переменных.
- **Дефолты** заданы тегом `default:"..."` только у части полей (см. таблицы). Поле без дефолта, которое не передали, получает нулевое значение Go (`""`, `0`, `false`) — жёсткой валидации «обязательности» у envconfig в этом коде нет, сервис стартует и с пустыми значениями.
- Переменная БД-сертификата имеет имя с дефисами `YC-PG-CERTIFICATE` (тег `envconfig:"YC-PG-CERTIFICATE"`).
Отдельного конфиг-файла (yaml/toml) у приложения нет. Единственный внешний файл — JSON-описание workflow-задач (`WORKFLOWS_CONFIG_FILEPATH`), разбираемый отдельно в `config/workflows.go` (`NewTasksExecutionConfigFromFilepath`); при ошибке чтения/парсинга сервис **паникует**.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса. `.env.template` — только шаблон; приложение **не загружает `.env` автоматически** (в коде нет dotenv) |
| Локально (docker-compose) | `make docker-compose``.docker/docker-compose.yml` с `env_file: .docker/.docker.env` |
| Kubernetes (Helm) | `.helm/values.yaml`, чарт-зависимость `universal-chart`: блоки `envs` (обычные значения) и `secretEnvs` (из k8s-секретов) сервиса `api` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) — переключение окружения/namespace, `HELM_SET_ARGS` |
Способы запуска процессов (`cmd/`):
| Бинарник | Точка входа | Назначение |
| --- | --- | --- |
| `api_server` | `cmd/api_server/main.go` | Основной HTTP API (Fiber). Точка входа контейнера (`CMD ["./api_server"]`) |
| `migrate` (`migrations`) | `cmd/migrate/main.go` | Миграции БД (`go-pg-migrations`): `migrate` / `rollback`. Собирается как `./migrations` |
| `filestream_server` | `cmd/filestream_server/main.go` | Отдельный сервер потоковой отдачи файлов (в основном Dockerfile **не собирается**) |
Сборка образа (`.docker/api.Dockerfile`, тег `-tags migrate`) кладёт `api_server`, `migrations` и `.example.tasks_execution_config.json`. Миграции применяются автоматически при старте приложения (см. README), либо вручную через бинарник `migrations`.
## Переменные приложения
Дефолт `—` означает, что значение в коде по умолчанию не задано (используется нулевое значение Go, если переменную не передать).
### App / окружение
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `APP_NAME` | string | `documentations-backend` | Имя приложения (Sentry `ServerName`) |
| `APP_VERSION` | string | `v1` | Версия (Sentry `Release`) |
| `ENVIRONMENT` | string | — | Окружение: `stage`/`preprod`/`production` (Sentry `Environment`) |
| `NAMESPACE` | string | — | k8s namespace, используется в логике дисков |
### HTTP-сервер
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `API_ADDRESS` | string | — | Адрес прослушивания Fiber. Читается **обоими** бинарниками (`api_server` и `filestream_server`) |
> `api_server` устанавливает большой `BodyLimit` (5 ТБ) и `ReadBufferSize` 96×4096; `filestream_server``BodyLimit` 64 МБ. Оба отдают `GET /ping` для проб k8s; `api_server` дополнительно отдаёт `GET /swagger/*`.
### Database (`gopg.Config`, `pkg/postgres/gopg/postgres.go`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
| `POSTGRES_USER` | string | — | Пользователь БД |
| `POSTGRES_DB` | string | — | Имя базы данных |
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
| `POSTGRES_POOL_SIZE` | int | — | Размер пула соединений |
| `ENABLE_SSL` | bool | — | Подключение к БД по TLS (сертификат из `YC-PG-CERTIFICATE`) |
| `ENABLE_SQL_QUERY` | bool | — | Логирование SQL-запросов |
| `YC-PG-CERTIFICATE` | string (PEM) | — | CA-сертификат PostgreSQL. Обязателен при `ENABLE_SSL=1` |
### Аутентификация и публичные ссылки
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `PUBLIC_KEY` | string (PEM) | — | Публичный RSA-ключ (PKIX) для проверки JWT sarex-backend |
| `DOCUMENT_PUBLIC_LINK_JWT_SECRET` | string | — | HMAC-секрет JWT для публичных/временных ссылок на документы |
| `DOCUMENT_PUBLIC_LINK_JWT_EXPIRATION_MINUTES` | uint8 | — | TTL публичной ссылки, минуты |
| `PUBLIC_LINK_HOST` | string | — | Базовый хост генерируемых публичных ссылок |
### Django / IAM (`pkg/django`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_HOST` | string | — | Базовый URL Django/монолита (пользователи, компании, сервис-аккаунты) |
| `DJANGO_BASIC_AUTH` | string (base64) | — | Basic-auth `base64(login:password)` для Django и клиента flows |
| `DJANGO_ORIGINATOR` | string | — | Идентификатор источника (`docs_stage`/`docs_preprod`/`docs_prod`) |
### Внешние сервисы (базовые URL клиентов)
Каждый клиент (`pkg/clients/*`) создаётся с `SetBaseURL(<URL>)` и ретраями. Подробнее по путям — см. `api-v2.ENDPOINTS.md`.
| Переменная | Тип | Назначение |
| --- | --- | --- |
| `DOCUMENTATION_URL` | string | Self-URL сервиса, подставляется в workflow-задачи |
| `WORKFLOW_URL` | string | Сервис workflows (запуск обработки) |
| `WORKSPACE_URL` | string | Сервис workspaces |
| `WORKSPACE_V2_EXTERNAL_URL` | string | Внешний URL workspaces v2 |
| `WORKSPACE_BUNDLE_VERSION` | string | Версия бандла для интеграции с workspaces |
| `MARKS_PROCESSING_URL` | string | Сервис PDF-маркировок (marks) |
| `BIM_API_URL` | string | BIM API v1 |
| `BIM_API_V2_URL` | string | BIM API v2 (bim-core) |
| `BIM_API_URL_EXTERNAL` | string | Внешний URL BIM API |
| `SYSTEM_LOG_URL` | string | Сервис журналирования (system-log) |
| `FLOWS_URL` | string | Сервис flows |
| `FILE_URL_EXTERNAL` | string | Внешний URL для отдачи файлов |
### S3 / MinIO (`pkg/s3/minio`)
Клиент инициализируется только при `ENABLE_S3=1` (иначе `api_server` работает без S3).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_S3` | bool | — | Включить инициализацию S3-клиента |
| `S3_SERVICE_ACCOUNT` | string | — | Путь к JSON сервис-аккаунта Yandex S3 |
| `S3_SERVICE_ACCOUNT_STR` | string | — | Альтернатива: JSON сервис-аккаунта строкой |
### BIM-логика
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_BIMV1_FOR_BIMV2` | bool | — | Использовать BIM v1 API вместо v2 в пайплайне документов |
| `LAST_MASTER_BIM` | int | — | Граничный id для маршрутизации BIM master |
| `LAST_SLAVE_1_BIM` | int | — | Граничный id для маршрутизации BIM slave 1 |
### Кеш и файловый стример
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `READ_WRITE_TIMEOUT_FILE_STREAM` | duration | — | Таймаут чтения/записи файлового стримера (напр. `6h`) |
| `CACHE_DEFAULT_EXPIRATION` | duration | — | TTL кеша (напр. `60s`) |
| `CACHE_CLEANUP_INTERVAL` | duration | — | Интервал очистки кеша |
| `USE_CACHE_IN_FILE_STREAMER` | bool | — | Включить кеш в файловом стримере |
### Workflow-задачи
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WORKFLOWS_CONFIG_FILEPATH` | string | `.example.tasks_execution_config.json` | Путь к JSON-описанию задач (`config/workflows.go`); при ошибке — паника |
| `WORKFLOWS_IMAGES_VERSION` | string | — | Тег образов задач (`develop`/`master`) |
| `CONTAINER_REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр образов задач |
| `IS_CONVERTED_PDF_UPLOADING_TO_S3` | bool | `true` | Загружать сконвертированный PDF в S3 |
### Наблюдаемость: Sentry и OpenTelemetry
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SENTRY_DSN` | string | — | DSN Sentry |
| `SENTRY_DEBUG` | bool | — | Debug-режим Sentry |
| `ENABLE_OBSERVABILITY` | bool | — | Включить slog-хендлер observability и трассировку запросов БД |
| `OBSERVABILITY_COLLECTOR_ENDPOINT` | string | — | Endpoint OTLP-коллектора логов |
| `TRACER_USE` | bool | `false` | Включить OTEL-трейсинг Fiber |
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора трейсов |
| `TRACER_USE_INSECURE` | bool | `true` | Подключение к коллектору без TLS |
| `SERVICE_NAME` | string | `documentations-api-v2` | Имя сервиса в трейсах |
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя OTEL-логгера |
## Переменные инфраструктуры и сборки
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `API_FILESTREAM_ADDRESS` | `.env.template`, `.docker.env` | Присутствует в шаблонах, но **кодом не читается** (см. Замечания) |
| `POSTGRES_EXTERNAL_PORT` | `.docker/docker-compose.yml` | Внешний порт проброса контейнера Postgres |
| `API_VERSION` | `.docker/docker-compose.yml` | Тег образа `api` (по умолчанию `local`) |
| `GITLAB_CREDENTIALS` | `.docker/api.Dockerfile` (build-arg) | Доступ к приватному GitLab при `go build` |
| `CI_COMMIT_SHORT_SHA` | `.docker/api.Dockerfile` (build-arg), CI | Идентификатор сборки |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт — обёртка над зависимостью `universal-chart` (`oci://.../charts`, версия `0.1.7`). Сервис `api`: `deployment` (реплики, ресурсы, пробы `GET /ping:8080`), `service` (ClusterIP `80 → 8080`), `image`, `volumes`.
Смонтированные тома:
- Секрет `documentations-yc-s3``/etc/sarex/yc-s3-storage` (на него указывает `S3_SERVICE_ACCOUNT`);
- ConfigMap `tasks-execution-config-documentation-v2``/etc/app/tasks_execution_config.json` (на него указывает `WORKFLOWS_CONFIG_FILEPATH` в k8s).
Обычные значения (`envs`) задают те же переменные `APP_NAME`, `API_ADDRESS` (`0.0.0.0:8080`), `ENVIRONMENT`, `NAMESPACE`, URL внешних сервисов, `ENABLE_SSL=1`, `ENABLE_S3=1`, флаги трассировки и т.п. — с разбивкой по окружениям (`_default`/`stage`/`preprod`/`production`).
Значения из секретов (`secretEnvs`, монтируются через `secretKeyRef`):
| Переменная | Секрет (`_default`) | Секрет (`preprod`) | Ключ |
| --- | --- | --- | --- |
| `POSTGRES_USER` | `documentations-postgresql-secret` | `ya-pg-secret` | `username` |
| `POSTGRES_ADDRESS` | `documentations-postgresql-secret` | `ya-pg-secret` | `host` |
| `POSTGRES_PORT` | `documentations-postgresql-secret` | `ya-pg-secret` | `port` |
| `POSTGRES_DB` | `documentations-postgresql-secret` | `ya-pg-secret` | `database` |
| `POSTGRES_PASSWORD` | `documentations-postgresql-secret` | `ya-pg-secret` | `password` |
| `YC-PG-CERTIFICATE` | `documentations-postgresql-secret` (`ca.crt`) | `yc-pg-certificate` (`certificate`) | см. столбцы |
| `DJANGO_BASIC_AUTH` | `django-auth` | — | `key` |
| `DOCUMENT_PUBLIC_LINK_JWT_SECRET` | `yc-jwt-secret` | — | `secret` |
| `PUBLIC_KEY` | `public-key` | — | `key` |
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | NAMESPACE | Chart version |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `documentations` | `0.0.1-stage` |
| ветка `master` | `preprod` | `documentations-preprod` | `0.0.1-preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `documentations-prod` | `0.0.1-prod` |
| `merge_request_event` | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
Ключевые переменные: `SERVICE_NAME=documentations-v2`, `RELEASE_NAME`/`CHART_NAME=documentations-v2`, `DOCKERFILE_PATH=.docker/api.Dockerfile`, `HELM_SET_ARGS` (`--set universal-chart.global.env=…`, `--set universal-chart.services.api.image.name.<env>=${IMAGE_NAME}`), флаги `ENABLE_LINTER`/`ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_DEPLOY`.
## Замечания и потенциальные проблемы
- **Нет обязательности полей.** `envconfig` в этом коде не помечает поля как required — при отсутствии переменной берётся нулевое значение Go. Пустые критичные значения (адрес БД, `PUBLIC_KEY`) приведут к ошибке уже в рантайме (падение при `db.Ping`, отказ проверки JWT), а не на этапе разбора конфига.
- **`API_FILESTREAM_ADDRESS` не читается.** Оба сервера слушают `API_ADDRESS`; отдельной переменной для порта файлового стримера в коде нет.
- **Опечатка `POSTGRES_POLL_SIZE`.** В `.env.template`/`.docker.env` встречается `POSTGRES_POLL_SIZE`; код читает `POSTGRES_POOL_SIZE` (в helm имя корректное). Значение с опечаткой не подхватывается.
- **`YC-PG-CERTIFICATE`** — имя с дефисами, читается через явный тег `envconfig`. В `.helm` для preprod монтируется из отдельного секрета `yc-pg-certificate` (ключ `certificate`).
- **Файл workflow-задач обязателен по существу.** Если файл по пути `WORKFLOWS_CONFIG_FILEPATH` отсутствует или не парсится — `config.NewTasksExecutionConfigFromFilepath` вызывает `panic`. Локально нужен `.example.tasks_execution_config.json`, в k8s — том ConfigMap.
- **S3 опционален.** При `ENABLE_S3=0` S3-клиент не создаётся; хендлеры, работающие с хранилищем, будут получать `nil`-хранилище.
- **Автомиграции при старте.** Приложение накатывает миграции автоматически (см. README); ручной прогон — бинарником `migrations migrate`/`migrations rollback`.
## Минимальный набор для локального запуска
Postgres поднимается через `make docker-compose` (образ `timescale/timescaledb-ha:pg13`, инициализация расширений uuid/ltree из `.docker/install-uuid-ltree.sql`). Приложение — бинарник `./api_server`. Минимально задать:
- `API_ADDRESS` (напр. `localhost:8000`);
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_DB`, `POSTGRES_PASSWORD`, `POSTGRES_POOL_SIZE`, `ENABLE_SSL=0`, `ENABLE_SQL_QUERY`;
- `PUBLIC_KEY` (для проверки JWT sarex-backend), `DOCUMENT_PUBLIC_LINK_JWT_SECRET`;
- `WORKFLOWS_CONFIG_FILEPATH` с существующим JSON (по умолчанию `.example.tasks_execution_config.json`);
- `ENABLE_S3=0`, `TRACER_USE=false`, `ENABLE_OBSERVABILITY=0` — чтобы не поднимать S3/OTEL локально;
- URL внешних сервисов (`DJANGO_HOST`, `WORKFLOW_URL`, `WORKSPACE_URL`, `BIM_API_URL`, `BIM_API_V2_URL`, `MARKS_PROCESSING_URL`, `SYSTEM_LOG_URL`, `FLOWS_URL`) — по мере необходимости для соответствующих сценариев.
Готовые значения-примеры приведены в `api-v2.env.example` (с учётом замечаний выше).

View File

@ -0,0 +1,88 @@
# Эндпоинты, с которыми взаимодействует documentations-api-v2
Документ описывает HTTP-эндпоинты внешних сервисов, к которым обращается Go-сервис **documentation-api-v2** (`pdm/documentation-api-v2`).
## Как устроено взаимодействие
Внешние вызовы выполняются типизированными клиентами в `pkg/clients/*` и `pkg/django`. Каждый клиент строится на [`github.com/go-resty/resty/v2`](https://github.com/go-resty/resty) с `SetBaseURL(<URL>)`, ретраями и (опционально) OTEL-трассировкой. Базовый URL берётся из переменной окружения (см. `api-v2.CONFIGURATION.md`); итоговый URL = `<базовый URL>` + путь, указанный в коде метода клиента.
Клиенты создаются в `internal/api/httpserver/server.go` (`App.Run`) и передаются в репозитории/юзкейсы. Аутентификация к внешним сервисам:
- **Django** и **flows** используют Basic-auth из `DJANGO_BASIC_AUTH`;
- **workspace** (`Archive`) пробрасывает пользовательский Bearer-токен (`SetAuthToken`);
- прочие внутренние сервисы вызываются по кластерным адресам без явной авторизации на уровне клиента.
## Базовые адреса по сервисам
| Клиент (`pkg/...`) | Переменная базового URL | Назначение |
| --- | --- | --- |
| `django` | `DJANGO_HOST` | Монолит/IAM: пользователи, компании, сервис-аккаунты, настройки |
| `clients/workspace` | `WORKSPACE_URL` | Сервис рабочих областей |
| `clients/workflows` | `WORKFLOW_URL` | Запуск workflow-обработки |
| `clients/bimv1` | `BIM_API_URL` | BIM API v1 |
| `clients/bimv2` | `BIM_API_V2_URL` | BIM API v2 (bim-core) |
| `clients/flows` | `FLOWS_URL` | Сервис flows (процессы) |
| `clients/marks` | `MARKS_PROCESSING_URL` | Сервис PDF-маркировок |
| `clients/system_log` | `SYSTEM_LOG_URL` | Сервис журналирования |
## Эндпоинты по сервисам
### `django` — монолит/IAM (`pkg/django`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `api/client/settings/` | Клиентские настройки |
| GET | `/api/core/companies/` | Список компаний |
| GET | `/api/core/users/` | Список пользователей |
| GET | `api/core/users/{user_id}/introspect` | Интроспекция пользователя |
| GET | `/api/core/service_accounts/` | Сервис-аккаунты |
| GET | `/api/core/service-accounts/personalized/` | Персонализированные сервис-аккаунты |
### `workspace` — рабочие области (`pkg/clients/workspace`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `internal/v2/workspaces` | Создать рабочую область |
| DELETE | `internal/v2/documents/{document_ids}` | Удалить документы из рабочих областей; возвращает id опустевших областей |
| POST | `api/v1/workspaces/{workspace_id}/archive` | Архивировать рабочую область (с пользовательским Bearer-токеном) |
### `workflows` — workflow-обработка (`pkg/clients/workflows`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `internal/v1/companies/{company_id}/workflows` | Создать/запустить workflow для компании |
### `bimv1` — BIM API v1 (`pkg/clients/bimv1`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/internal/v1/targets/{target_id}/bims-pdm` | Зарегистрировать новый BIM (v1) |
| POST | `/internal/v1/targets/{target_id}/bims-v2-pdm` | Зарегистрировать новый BIM (v2) |
### `bimv2` — BIM API v2 (`pkg/clients/bimv2`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/internal/v1/projects/{project_id}/bims` | Создать BIM в проекте |
### `flows` — процессы (`pkg/clients/flows`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `api/v1/documents/?full=true&document_ids={id}` | Документы процессов по id |
### `marks` — PDF-маркировки (`pkg/clients/marks`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/api/v1/marks/{bundle_id}` | Массовое создание маркировок для бандла |
### `system_log` — журналирование (`pkg/clients/system_log`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/api/v0/system_log` | Отправка пакета записей журнала |
## Обработка ошибок
Клиенты проверяют HTTP-статус ответа и при коде, отличном от ожидаемого (обычно `200`), оборачивают ошибку через `github.com/rotisserie/eris` с указанием имени метода клиента (напр. `bimapi.addNewBim: invalid response code %d expected 200`). Транспортные ошибки resty также оборачиваются `eris.Wrap`. Настроены ретраи (напр. клиент workspace — 5 попыток с паузой 1 c, таймаут 10 c).

View File

@ -0,0 +1,87 @@
# =============================================================================
# documentations-api-v2 (documentation-api-v2) — пример переменных окружения
# Разбор: github.com/kelseyhightower/envconfig, config/config.go + pkg/postgres/gopg/postgres.go
# Префикса у переменных нет; вложенность не используется. Дефолт указан там, где он задан в коде.
# =============================================================================
# --- App ---------------------------------------------------------------------
APP_NAME=documentations-backend # default: documentations-backend
APP_VERSION=v1 # default: v1; используется как Sentry Release
ENVIRONMENT=stage # stage/preprod/production; идёт в Sentry Environment
NAMESPACE=documentations # k8s namespace, используется в бизнес-логике дисков
# --- HTTP-сервер -------------------------------------------------------------
API_ADDRESS=localhost:8000 # адрес прослушивания Fiber (и api_server, и filestream_server)
# API_FILESTREAM_ADDRESS=localhost:8050 # присутствует в шаблоне, но КОДОМ НЕ ЧИТАЕТСЯ (см. Замечания)
# --- PostgreSQL (pkg/postgres/gopg) ------------------------------------------
POSTGRES_ADDRESS=localhost
POSTGRES_PORT=5432
POSTGRES_DB=postgres
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_POOL_SIZE=1 # в helm: POSTGRES_POOL_SIZE (в шаблоне встречается опечатка POSTGRES_POLL_SIZE — не читается)
ENABLE_SSL=0 # 1 → TLS к БД по сертификату из YC-PG-CERTIFICATE
ENABLE_SQL_QUERY=1 # логирование SQL-запросов
YC-PG-CERTIFICATE="" # PEM CA-сертификат PostgreSQL (имя с дефисами, читается через envconfig-тег)
# POSTGRES_EXTERNAL_PORT=6432 # только для docker-compose, приложением не читается
# --- Django / IAM (pkg/django) -----------------------------------------------
DJANGO_HOST=https://stage.sarex.io
DJANGO_BASIC_AUTH= # base64(login:password) для Basic-auth в Django и flows
DJANGO_ORIGINATOR=docs_stage # идентификатор источника для system-log/Django
# --- Внешние сервисы (base URL клиентов) -------------------------------------
DOCUMENTATION_URL=http://api-v2-service.documentations/ # self-URL, подставляется в workflow-задачи
WORKFLOW_URL=http://workflows-api-service.platform:8000/
WORKSPACE_URL=http://workspaces-backend-service.platform:8000/
WORKSPACE_V2_EXTERNAL_URL=https://stage.sarex.io/workspaces-v2/
WORKSPACE_BUNDLE_VERSION=v1
MARKS_PROCESSING_URL=http://marks-service.documentations:8000
BIM_API_URL=http://bim-api-service.bim-api-stage/
BIM_API_V2_URL=http://bim-core-api.platform.svc.cluster.local:8000/
BIM_API_URL_EXTERNAL=https://stage-api.sarex.io/bim
SYSTEM_LOG_URL=http://system-log-api-service.platform:80
FLOWS_URL=
FILE_URL_EXTERNAL= # внешний URL для отдачи файлов
# --- S3 / MinIO (pkg/s3/minio) -----------------------------------------------
ENABLE_S3=0 # 1 → инициализировать S3-клиент
S3_SERVICE_ACCOUNT=/etc/sarex/yc-s3-storage/yc-s3-service-account.json # путь к JSON сервис-аккаунта
S3_SERVICE_ACCOUNT_STR= # альтернатива: JSON сервис-аккаунта строкой
# --- Публичные ссылки на документы -------------------------------------------
PUBLIC_LINK_HOST= # хост генерируемых публичных ссылок
DOCUMENT_PUBLIC_LINK_JWT_SECRET="" # HMAC-секрет JWT публичной ссылки
DOCUMENT_PUBLIC_LINK_JWT_EXPIRATION_MINUTES=5 # TTL публичной ссылки (uint8)
# --- Аутентификация ----------------------------------------------------------
PUBLIC_KEY="" # PKIX RSA public key (PEM) для проверки JWT sarex-backend
# --- BIM-логика --------------------------------------------------------------
USE_BIMV1_FOR_BIMV2=0 # использовать BIM v1 API вместо v2
LAST_MASTER_BIM=0 # граничные id для маршрутизации BIM-запросов
LAST_SLAVE_1_BIM=0
# --- Кеш / файловый стример --------------------------------------------------
READ_WRITE_TIMEOUT_FILE_STREAM=6h # Go duration
CACHE_DEFAULT_EXPIRATION=60s # Go duration
CACHE_CLEANUP_INTERVAL=60s # Go duration
USE_CACHE_IN_FILE_STREAMER=1 # bool
# --- Workflow-задачи ---------------------------------------------------------
WORKFLOWS_IMAGES_VERSION=develop # тег образов задач (develop/master)
WORKFLOWS_CONFIG_FILEPATH=.example.tasks_execution_config.json # default; в k8s — /etc/app/tasks_execution_config.json
CONTAINER_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q # default; реестр образов задач
IS_CONVERTED_PDF_UPLOADING_TO_S3=true # default: true
# --- Наблюдаемость: Sentry + OpenTelemetry -----------------------------------
SENTRY_DSN=""
SENTRY_DEBUG=false
ENABLE_OBSERVABILITY=0 # bool; включает slog-хендлер и трассировку в БД
OBSERVABILITY_COLLECTOR_ENDPOINT=0 # endpoint OTLP-коллектора логов
TRACER_USE=false # default: false; включить OTEL-трейсинг Fiber
TRACER_HOST=localhost:4317 # default: localhost:4317
TRACER_USE_INSECURE=true # default: true
SERVICE_NAME=documentations-api-v2 # default: documentations-api-v2
TRACER_LOGGER_NAME=tracer_logger # default: tracer_logger

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,345 @@
# Конфигурация проекта documentations-api
Документ описывает все переменные окружения и способы конфигурирования сервиса документаций (`documentation-api`). Репозиторий собирает **два бинарника/образа**, разворачиваемых в неймспейсе `documentations`:
| Бинарник | Точка входа | Образ | Deployment | Назначение |
| --- | --- | --- | --- | --- |
| **API** | `cmd/api` | `documentations` (`cr.yandex/.../documentations-api`) | `documentations-api` | Основной REST-API: диски, документы, бандлы, права, воркспейсы, штампы, публичные ссылки и т. д. |
| **Filestream** | `cmd/filestreamer` | `documentations-api-files` (`.../documentations-filestream`) | `documentations-filestream` | Потоковая отдача/приём файлов из S3 (скачивание документов и бандлов, догрузка частей, gzip/range). |
Оба бинарника используют **одну и ту же структуру конфигурации** (`config.Config`) — различия только в том, какие поля реально задействуются (см. раздел «Различия api и filestream»).
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`github.com/kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (функция `config.FromEnv()``envconfig.Process("", &cfg)`).
Особенности разбора:
- **Префикса нет**`envconfig.Process` передаётся пустая строка) — имена переменных плоские, задаются тегом `envconfig:"..."` у каждого поля структуры `Config`;
- **Вложенности нет** — двойных подчёркиваний/секций, как в pydantic, здесь не используется;
- **Значения по умолчанию** задаются тегом `default:"..."` прямо в структуре (например `USE_BIM_INSERTER default:"true"`). Поля без `default` и без значения в окружении получают нулевое значение типа (`""`, `0`, `false`, `nil`) — то есть формально **обязательных полей с ошибкой старта у envconfig нет**; отсутствующая переменная просто становится «пустой», а несостоятельность конфигурации всплывает позже в рантайме (например, невозможность подключиться к БД/S3);
- **Отдельного конфиг-файла (yaml/toml) у приложения нет.** Приложение **не загружает `.env` автоматически** — переменные должны быть в окружении процесса (в контейнере их проставляет Helm, локально — вручную или через `docker-compose --env-file`);
- Единственный внешний файл конфигурации — `WORKFLOWS_CONFIG_FILEPATH` (JSON с параметрами запуска задач обработки, см. `.example.tasks_execution_config.json`), читается `workflow.NewTasksExecutionConfigFromFilepath` при старте api.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса. Готовый шаблон — `.docker/.env` |
| Локально (контейнеры) | `.docker/docker-compose.yml` + `make docker`: значения из `.docker/.env` и `.docker/.docker.env` |
| Kubernetes (Helm) | `.helm/values.yaml` (universal-chart): блоки `services.api.envs`/`secretEnvs` и `services.filestream.envs`/`secretEnvs` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: `workflow.rules` (окружение по ветке/тегу) и `HELM_SET_ARGS` |
Способы запуска процессов:
| Команда | Точка входа | Назначение |
| --- | --- | --- |
| `api` (`make api` / `make run-api-dev`) | `cmd/api` | Основной REST-API |
| `filestreamer` (`make file_api` / `make run-filestreamer-dev`) | `cmd/filestreamer` | Файловый стример |
| `migrations migrate` | `cmd/migrations` | Прогон миграций БД |
| `delete_expired_public_links` | `cmd/scripts/delete_expired_public_links` | CronJob удаления просроченных публичных ссылок |
| `refresh_latest_bundle_filters_view` | `cmd/scripts/refresh_latest_bundle_filters_view` | CronJob обновления материализованного представления |
| `cleanup_failed_s32d_sessions` | `cmd/scripts/cleanup_failed_s32d_sessions` | CronJob очистки зависших s3d→ifc сессий |
Порядок запуска в контейнере: сначала миграции, затем сервер.
- `entrypoint.sh` (api): `migrations migrate``api`;
- `file_entrypoint.sh` (filestream): `migrations migrate``filestreamer`.
Для локальной разработки предусмотрен hot-reload через `air`: `.air.toml` (api, `./tmp/api`) и `.air.filestreamer.toml` (filestream, `./tmp/filestreamer`). Окружение сборки описано в `flake.nix` (Go 1.22 + `air`), образы собираются с Go 1.24 (`.docker/api.dockerfile`, `.docker/api-filestream.dockerfile`).
## HTTP-фреймворк, порты, health
- Роутер — `gorilla/mux`, обёрнутый в `rest.NewCustomRouter` (`gitlab.sarex.io/platform/gotools/rest`). Ответы оборачиваются в JSON (`rest.JSONResponse`), включена gzip-компрессия (`gorilla/handlers.CompressHandler`).
- Пробы `liveness`/`readiness` в Helm ходят на `GET /ping` (эндпоинт предоставляется кастомным роутером `rest`, в коде маршрутов репозитория не объявлен).
- Порт api — из `API_ADDRESS`, порт filestream — из `API_ADDRESS_FILE` (в k8s оба слушают `0.0.0.0:8080`).
- Таймауты: у api `Read/WriteTimeout = 30m` (жёстко в коде), у filestream `Read/WriteTimeout = READ_WRITE_TIMEOUT_FILE_STREAM` (по умолчанию окружения — `6h`).
- Оба сервиса регистрируют `net/http/pprof` (`/debug/pprof/...`).
## Аутентификация и авторизация
Разбор описан в `cmd/api/bootstrap.go`, `cmd/filestreamer/main.go`, `pkg/midleware/auth.go`, `pkg/midleware/signature.go`.
Цепочка middleware для `/api/v1/*`: `sentry``JSONResponse``reqid``logging` → (**только filestream**: `SignatureMiddleware`) → `auth.JWTToCtx``JWTUserExtractorFromCtx``DjangoToCtx``NewAuthMiddleware`/`NewAuthMiddlewareWithZitadel` → `DeleteJWTFromQueryMiddleware``sentry.AddUser`.
- **JWT**: токен из заголовка `Authorization: Bearer ...` проверяется по RSA-публичному ключу (`PUBLIC_KEY`, формат PEM/PKIX). Из claims извлекаются `company_ids` и `service_accounts`.
- **Zitadel** (опционально, `USE_ZITADEL=1`): дополнительная проверка токена через Zitadel и разбор метаданных пользователя (`urn:zitadel:iam:user:metadata`, base64-поля `company_ids`/`service_accounts`).
- **Identity-заголовок**: при наличии `Identity` метаданные берутся из него.
- **Публичные ссылки/временные загрузки**: пути `/api/v1/public/...`, `/api/v1/public_link_mrpas/...` и запросы с `download_type=temporary` проверяются по HMAC-секрету `DOCUMENT_PUBLIC_LINK_JWT_SECRET`.
- **Подписанные ссылки (только filestream)**: при наличии query-параметра `signature` `SignatureMiddleware` убирает `Authorization` и проверяет подпись (`SIGNATURE_SECRET_KEY`) с `expires_at`; включается флагом `ENABLE_SIGNATURE_IN_URL` (при `true` обязателен рабочий Valkey — иначе api/filestream завершается с кодом 2).
- Маршруты `/internal/v1/*` используют облегчённую цепочку (`auth.JWTToCtxIfPossible`) без обязательной проверки.
## Переменные приложения
В столбце «Переменная» — точное имя (тег `envconfig`). Дефолт `—` означает, что тег `default` не задан (поле получает нулевое значение типа, если переменная не задана в окружении).
### PostgreSQL
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
| `POSTGRES_USER` | string | — | Пользователь БД |
| `POSTGRES_PASSWORD` | string | — | Пароль БД |
| `POSTGRES_DB` | string | — | Имя базы данных |
| `POSTGRES_POOL_SIZE` | int | — | Размер пула соединений (**только api**; filestreamer использует фиксированное значение 50) |
| `ENABLE_SSL` | bool | — | TLS-подключение к БД; при `true` используется `YC-PG-CERTIFICATE` |
| `YC-PG-CERTIFICATE` | string | — | PEM CA-сертификат PostgreSQL (имя с дефисами; в проде — из секрета `yc-pg-certificate`) |
### S3
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_S3` | bool | — | Включить S3. В api при `false` сервис стартует без S3-клиента; в filestream S3 нужен всегда |
| `S3_SERVICE_ACCOUNT` | string | — | Путь к JSON сервис-аккаунта S3 (монтируется как файл) |
| `S3_SERVICE_ACCOUNT_STR` | string | — | Альтернатива: JSON сервис-аккаунта строкой |
### API-адреса
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `API_ADDRESS` | string | — | Адрес прослушивания основного API (`cmd/api`) |
| `API_ADDRESS_FILE` | string | — | Адрес прослушивания файлового стримера (`cmd/filestreamer`) |
### Sarex backend (Django) и Zitadel
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_HOST` | string | — | Базовый URL Sarex backend (Django). Используется клиентами `django`, `users`, `sarex_backend`, `accounts` |
| `DJANGO_BASIC_AUTH` | string | — | Basic-auth для системных вызовов Django |
| `DJANGO_BASIC_AUTH_FOR_GET_USER` | string | — | Отдельный basic-auth для запросов пользователей |
| `DJANGO_ORIGINATOR` | string | — | Идентификатор источника запросов |
| `USE_ZITADEL` | bool | — | Включить проверку токенов через Zitadel |
| `ZITADEL_DOMAIN` | string | — | Домен Zitadel (IdP) |
| `ZITADEL_ACCOUNT` | string | — | Путь к JSON сервис-аккаунта Zitadel |
### Внешние сервисы (базовые URL)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `FILE_URL_EXTERNAL` | string | — | Внешний URL файлового сервиса |
| `DOCUMENTATION_URL` | string | — | URL самого сервиса документаций (для внутренних ссылок) |
| `WORKFLOW_URL` | string | — | URL сервиса workflows (создание/чтение процессов обработки) |
| `WORKSPACE_URL` | string | — | URL сервиса воркспейсов |
| `WORKSPACE_V2_EXTERNAL_URL` | string | — | Внешний URL воркспейсов v2 |
| `WORKSPACE_BUNDLE_VERSION` | string | — | Версия бандла воркспейса (`v1`) |
| `MARKS_PROCESSING_URL` | string | — | URL сервиса штампов/маркировок (при HTTP-режиме) |
| `BIM_API_URL` | string | — | URL BIM-API v1 |
| `BIM_API_V2_URL` | string | — | URL BIM-API v2 (bim-core-api) |
| `BIM_API_URL_EXTERNAL` | string | — | Внешний URL BIM-API |
| `SYSTEM_LOG_URL` | string | — | URL сервиса системного лога |
| `FLOWS_URL` | string | — | URL сервиса flows |
| `AUTOMATION_URL` | string | — | URL сервиса автоматизаций |
| `TRANSMITTALS_BASE_URL` | string (nullable) | `nil` | URL сервиса трансмитталов; клиент создаётся только если переменная задана |
### Публичные ссылки и JWT
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `PUBLIC_LINK_HOST` | string | — | Хост публичных ссылок на документы |
| `PUBLIC_KEY` | string | — | RSA-публичный ключ (PEM/PKIX) для проверки JWT |
| `DOCUMENT_PUBLIC_LINK_JWT_SECRET` | string | — | HMAC-секрет для JWT публичных ссылок и временных загрузок |
| `DOCUMENT_PUBLIC_LINK_JWT_EXPIRATION_MINUTES` | uint8 | — | Время жизни JWT публичной ссылки (мин.) |
| `PUBLIC_LINK_FOLDER_CONNECTOR_ENABLED` | bool | `true` | Коннектор публичных ссылок для папок |
### Подпись ссылок (signature-in-URL)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_SIGNATURE_IN_URL` | bool | `false` | Проверять подпись в URL. При `true` требуется рабочий Valkey (иначе сервис завершается с кодом 2) |
| `SIGNATURE_SECRET_KEY` | string | `""` | Секрет для подписи ссылок скачивания |
| `SIGNATURE_IN_URL_EXPIRATION_SECONDS` | uint64 | `600` | Срок жизни подписи (сек.) |
| `ENABLE_AUTH_JWT_IN_URL` | bool | `true` | Добавлять `auth_jwt` в URL |
### Valkey (Redis-совместимый) — кэши
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `VALKEY_ADDR` | string | `localhost:6380` | Адрес `host:port`. Пустая строка полностью отключает клиент Valkey |
| `VALKEY_LOGIN` | string | `""` | Логин |
| `VALKEY_HOST` | string | `""` | Хост (доп. поле) |
| `VALKEY_PASSWORD` | string | `""` | Пароль |
| `VALKEY_DB` | int | `0` | Номер БД Redis/Valkey |
| `VALKEY_CACHE_TTL` | duration | `1h` | TTL кэша |
| `VALKEY_SSL` | bool | `false` | TLS-подключение |
| `VALKEY_SSL_CA_CERTS` | string | `""` | CA-сертификат для TLS |
### Файловый стример и in-memory кэш
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `READ_WRITE_TIMEOUT_FILE_STREAM` | duration | — | Read/Write-таймаут HTTP-сервера filestream (в окружении — `6h`) |
| `USE_CACHE_IN_FILE_STREAMER` | bool | — | Включить in-memory кэш в filestream-хранилище |
| `CACHE_DEFAULT_EXPIRATION` | duration | — | TTL записей кэша |
| `CACHE_CLEANUP_INTERVAL` | duration | — | Интервал очистки кэша |
### BIM / обработка файлов
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_BIMV1_FOR_BIMV2` | bool | — | Использовать BIM v1 для v2 |
| `USE_BIM_INSERTER` | bool | `true` | Включить BIM-inserter |
| `USE_LEGACY_BIM_FLOW` | bool | `false` | Старый flow BIM |
| `LAST_MASTER_BIM` | uint64 | — | Граница master-BIM |
| `LAST_SLAVE_1_BIM` | uint64 | — | Граница slave-1-BIM |
| `LAST_SLAVE_2_BIM` | uint64 | — | Граница slave-2-BIM |
| `CONVERT_DWG_TO_GEOJSON` | bool | `true` | Конвертация DWG→GeoJSON |
| `CONVERT_DXF_TO_GEOJSON` | bool | `true` | Конвертация DXF→GeoJSON |
| `IS_CONVERTED_PDF_UPLOADING_TO_S3` | bool | `true` | Загружать сконвертированный PDF в S3 |
| `DELETE_S3D_AFTER_MESHOPT` | bool | `false` | Удалять s3d после mesh-оптимизации |
| `WORKFLOW_IMAGES_VERSION` | string | — | Тег образов workflow |
| `WORKFLOWS_IMAGES_VERSION` | string | — | Тег образов задач обработки (используется клиентом `workflow`) |
| `CONTAINER_REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр контейнеров для образов задач |
| `WORKFLOWS_CONFIG_FILEPATH` | string | `.example.tasks_execution_config.json` | Путь к JSON с ресурсами задач обработки |
### Штампы/маркировки (HTTP или RabbitMQ)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_MARKS_RABBITMQ` | bool | `0` | `0` — ходить в `MARKS_PROCESSING_URL` по HTTP; `1` — через RabbitMQ |
| `MARKS_RABBITMQ_HOST` | string | `""` | Хост RabbitMQ |
| `MARKS_RABBITMQ_PORT` | string | `""` | Порт RabbitMQ |
| `MARKS_RABBITMQ_USER` | string | `""` | Пользователь (из секрета) |
| `MARKS_RABBITMQ_PASSWORD` | string | `""` | Пароль (из секрета) |
| `MARKS_RABBITMQ_API` | string | `""` | Vhost/имя очереди |
### Rate limit эндпоинта метаданных
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `METADATA_RATE_LIMIT_ENABLED` | bool | `true` | Включить rate-limit для `/documents/metadata` |
| `METADATA_RATE_LIMIT_MAX_REQUESTS` | int | `10` | Макс. число запросов |
### Почта
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_MAILGUN` | bool | `true` | Флаг использования Mailgun |
| `ENABLE_SMTP` | bool | `false` | Флаг использования SMTP |
### Наблюдаемость
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENVIRONMENT` | string | — | Окружение (для Sentry/трейсинга) |
| `SENTRY_DSN` | string | — | DSN Sentry |
| `SENTRY_DEBUG` | bool | — | Отладка Sentry |
| `NAMESPACE` | string | — | Неймспейс (для контекста запусков задач) |
| `ENABLE_SQL_QUERY` | bool | — | Логировать SQL-запросы |
| `TRACER_USE` | bool | `false` | Включить OpenTelemetry-трейсинг |
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора |
| `TRACER_USE_INSECURE` | bool | `true` | Подключение к коллектору без TLS |
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя логгера трейсинга |
| `SERVICE_NAME` | string | `documentations-api` | Имя сервиса в трейсах (используется api) |
| `SERVICE_NAME_FILESTREAM` | string | `filestream-api` | Имя сервиса в трейсах (используется filestream) |
## Различия api и filestream
Оба процесса читают одну и ту же структуру `config.Config`, но:
| Аспект | api (`cmd/api`) | filestream (`cmd/filestreamer`) |
| --- | --- | --- |
| Слушает адрес из | `API_ADDRESS` | `API_ADDRESS_FILE` |
| Пул соединений к БД | `POSTGRES_POOL_SIZE` | фиксировано `50` (+ `PoolTimeout=1m`), `POSTGRES_POOL_SIZE` игнорируется |
| Read/Write-таймаут сервера | жёстко `30m` | `READ_WRITE_TIMEOUT_FILE_STREAM` |
| S3 | опционален (`ENABLE_S3`) | обязателен (при отсутствии кредов процесс завершается) |
| In-memory кэш хранилища | выключен | управляется `USE_CACHE_IN_FILE_STREAMER` / `CACHE_*` |
| Имя сервиса в трейсах | `SERVICE_NAME` | `SERVICE_NAME_FILESTREAM` |
| Signature-middleware на `/api/v1` | нет | есть (`SignatureMiddleware`) |
| Набор маршрутов | полный REST CRUD (`cmd/api/routes_api.go`, `routes_internal.go`) | только потоковые скачивания/загрузки файлов (`cmd/filestreamer/routes_api.go`, `routes_internal.go`) |
**Что делает filestream-бинарник.** Это отдельный HTTP-сервис для тяжёлой потоковой работы с файлами, вынесенный из основного API, чтобы не блокировать его долгими соединениями (отсюда таймаут в часы и увеличенный пул БД). Публичные маршруты (`/api/v1`):
- `GET /documents/folders` — скачивание нескольких папок архивом (gzip);
- `GET|HEAD|POST /bundles/...` — скачивание файлов бандла; при `?format=gz` отдаётся без повторного сжатия, иначе — `CompressHandler`; поддержаны HEAD (range) и внешний матчер `DownloadMatcherExternal`;
- `GET|HEAD|POST /pages/...` — скачивание страниц (постранично);
- `GET|HEAD /documents/...` — скачивание по документам; `POST /documents/...` — скачивание по списку bundle-id;
- `POST /bundles_mrpas/...` и `GET /public_link_mrpas/...` — выгрузка MRPA (в т. ч. по публичной ссылке).
Внутренние маршруты (`/internal/v1`): скачивание/загрузка бандлов между сервисами и `POST /upload_finish/bundles/...`.
## Переменные сборки и запуска (не читаются кодом приложения)
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `APP_VERSION` | `Makefile`, dockerfiles | Версия, зашиваемая в бинарь (`-ldflags -X main.version`) |
| `CI_COMMIT_SHORT_SHA` | dockerfiles | Тег версии образа files |
| `GITLAB_CREDENTIALS` | dockerfiles (build-arg) | Доступ к приватным Go-модулям `gitlab.sarex.io` |
| `API_VERSION` | `.docker/docker-compose.yml` | Тег локально запускаемого образа |
| `POSTGRES_EXTERNAL_PORT` | `.docker/docker-compose.yml` | Внешний порт локального Postgres |
| `KEY_JWT` / `JWT_KEY` | `.docker/.env`, docker-compose | JWT-ключ **только для интеграционных тестов**, кодом приложения не читается |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт основан на `universal-chart` (зависимость из `Chart.yaml`) и описывает два сервиса — `services.api` и `services.filestream`. У каждого свои блоки `envs` (обычные значения, с разбивкой по окружениям `_default`/`stage`/`preprod`/`production`) и `secretEnvs` (значения из k8s-секретов). Наборы переменных у обоих сервисов практически идентичны.
Значения из секретов (`secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `SIGNATURE_SECRET_KEY` | `documentations-download-secret` | `secret` |
| `VALKEY_ADDR` | `valkey-secret` | `url` |
| `VALKEY_LOGIN` | `valkey-secret` | `login` |
| `VALKEY_PASSWORD` | `valkey-secret` | `password` |
| `VALKEY_HOST` | `valkey-secret` | `host` |
| `VALKEY_PORT` | `valkey-secret` | `port` |
| `VALKEY_CA_CERTS` | `valkey-secret` | `cert` |
| `POSTGRES_USER` | `documentations-postgresql-secret` | `user` |
| `POSTGRES_PORT` | `documentations-postgresql-secret` | `port` |
| `POSTGRES_ADDRESS` | `documentations-postgresql-secret` | `host` |
| `POSTGRES_DB` | `documentations-postgresql-secret` | `database` |
| `POSTGRES_PASSWORD` | `documentations-postgresql-secret` | `password` |
| `DJANGO_BASIC_AUTH` | `django-auth` | `key` |
| `DJANGO_BASIC_AUTH_FOR_GET_USER` | `django-auth-get-user` | `key` |
| `DOCUMENT_PUBLIC_LINK_JWT_SECRET` | `yc-jwt-secret` | `secret` |
| `PUBLIC_KEY` | `public-key` | `key` |
| `MARKS_RABBITMQ_USER` | `cde-rabbitmq-secret` (в api; prod — `marks-rabbit-secret`) / `marks-rabbit-secret` (в filestream) | `user` |
| `MARKS_RABBITMQ_PASSWORD` | то же | `password` |
| `YC-PG-CERTIFICATE` | `yc-pg-certificate` | `certificate` |
Тома (`volumes`) монтируют секреты как файлы: `documentations-yc-s3``/etc/sarex/yc-s3-storage` (на него указывает `S3_SERVICE_ACCOUNT`), `zitadel-account``/etc/sarex/zitadel` (на него указывает `ZITADEL_ACCOUNT`). Файл `WORKFLOWS_CONFIG_FILEPATH` в проде — `/etc/app/tasks_execution_config.json`.
Ingress включён только у `filestream` (`ingress.enabled: false` по умолчанию, path `/files/api/` → rewrite `/api/`); у api ingress-блока в values нет — сервис доступен через `documentations-api-svc`.
Прочие значения чарта (не переменные приложения): `deployment.*` (реплики stage/preprod/prod = 1/3/6, ресурсы, revisionHistoryLimit), `probes` (`/ping`), `service.*`, `serviceAccount`, `imagePullSecrets: dockerhub`, `affinity` (podAntiAffinity у filestream), а также блок `cronjobs` (`delete_expired_public_links`, `refresh_latest_bundle_filters_view`, `refresh_string_path_materialized_view`).
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`). Окружение переключается по ветке/тегу через `workflow.rules`:
| Условие | STAND | NAMESPACE | universal-chart env |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `documentations` | `stage` |
| ветка `master` | `preprod` | `documentations-preprod` | `preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `documentations-prod` | `production` |
| merge request | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
Общие для всех окружений: `RELEASE_NAME=documentations`, `CHART_NAME=documentations`, `SERVICE_NAME: documentations`, `DOCKERFILE_PATH: .docker/api.dockerfile`, `IMAGE_PATH: api.deployment.image`. Через `HELM_SET_ARGS` проставляются образы: `services.api.image` (основной), `services.filestream.image` (`IMAGE_NAME_API_FILES`, dockerfile `.docker/api-filestream.dockerfile`), а также образы кронджоб `cronjobs.delete_expired_public_links`, `cronjobs.refresh_latest_bundle_filters_view`, `cronjobs.cleanup_failed_s32d_sessions`. Дополнительные образы собираются отдельными job-ами `build_files`, `build_public_link_autodeletion`, `build_refresh_latest_bundle_filters_view`, `build_cleanup_failed_s32d_sessions` (только на `stage`/`master`/тег).
## Замечания и потенциальные проблемы
- **У envconfig нет «обязательных» полей.** Отсутствующая переменная без `default` становится нулевым значением, ошибка старта не выбрасывается. Некорректная конфигурация проявляется в рантайме (не удаётся подключиться к БД/S3, невалидный `PUBLIC_KEY` при первой проверке JWT и т. п.).
- **`.env` не подхватывается автоматически** — приложение читает только окружение процесса. Локально удобнее запускать через `docker-compose --env-file` (`make docker`) или экспортировать `.docker/.env` вручную.
- **`POSTGRES_POOL_SIZE` игнорируется в filestream** — там пул жёстко задан как `50`. В api берётся из переменной.
- **`YC-PG-CERTIFICATE`** — имя с дефисами (не в стиле `SNAKE_CASE`), но envconfig читает его по точному тегу. Значение приходит из секрета и используется как содержимое PEM (`AppendCertsFromPEM`), а не как путь к файлу.
- **Рассинхрон имён Valkey.** В Helm задаются `VALKEY_PORT` и `VALKEY_CA_CERTS`, но код читает `VALKEY_ADDR` (host:port одной строкой) и `VALKEY_SSL_CA_CERTS`. Переменные `VALKEY_PORT`/`VALKEY_CA_CERTS` приложением напрямую не читаются (адрес и CA берутся из `VALKEY_ADDR`/`VALKEY_SSL_CA_CERTS`).
- **`HOST` и `DOCUMENTATION_EXTERNAL_URL` из Helm кодом не читаются** — в `config.Config` таких полей нет (для внутренних ссылок используется `DOCUMENTATION_URL`, `FILE_URL_EXTERNAL`, `PUBLIC_LINK_HOST`).
- **`ENABLE_SIGNATURE_IN_URL=true` требует Valkey.** Если клиент Valkey не инициализировался, а флаг включён, оба процесса завершаются с кодом `2`.
- **Флаги `ENABLE_MAILGUN`/`ENABLE_SMTP`** присутствуют в конфиге и Helm, но собственной отправкой почты сервис не занимается (в отличие от transmittal-api); это флаги для внешних интеграций.
- **`TRANSMITTALS_BASE_URL` — nullable.** Клиент трансмитталов создаётся только если переменная задана; иначе связанные вызовы пропускаются.
- **Штампы: HTTP vs RabbitMQ.** При `USE_MARKS_RABBITMQ=1` используется RPC-клиент через RabbitMQ (`MARKS_RABBITMQ_*`), при `0` — HTTP-клиент на `MARKS_PROCESSING_URL`. В values для prod-секретов имя `marks-rabbit-secret` отличается от stage/preprod (`cde-rabbitmq-secret`).
## Минимальный набор для локального запуска
Ориентир — `.docker/.env` (+ `make docker` для запуска в контейнерах вместе с Postgres). Минимально нужно задать:
- `API_ADDRESS`, `API_ADDRESS_FILE`;
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_POOL_SIZE`, `ENABLE_SSL=0`;
- `ENABLE_S3` (`0` для api без S3; для filestream — `1` и `S3_SERVICE_ACCOUNT`/`S3_SERVICE_ACCOUNT_STR`);
- `DJANGO_HOST`, `DJANGO_ORIGINATOR`, `NAMESPACE`;
- сервисные URL по необходимости: `WORKFLOW_URL`, `WORKSPACE_URL`, `SYSTEM_LOG_URL`, `FLOWS_URL`, `MARKS_PROCESSING_URL`;
- для filestream: `READ_WRITE_TIMEOUT_FILE_STREAM`, `USE_CACHE_IN_FILE_STREAMER`, `CACHE_DEFAULT_EXPIRATION`, `CACHE_CLEANUP_INTERVAL`;
- `ENABLE_SQL_QUERY=1` (для отладки), `TRACER_USE=false`;
- `PUBLIC_KEY`/`DOCUMENT_PUBLIC_LINK_JWT_SECRET` — для реальной проверки JWT (локально можно оставить пустыми, но защищённые ручки будут отклонять токены).
Готовый пример со всеми значениями приведён в `api.env.example` (рядом с этим документом) и в `.docker/.env` исходного репозитория.

View File

@ -0,0 +1,114 @@
# Эндпоинты внешних сервисов, с которыми взаимодействует documentations-api
Документ описывает HTTP-эндпоинты внешних сервисов, к которым обращается сервис документаций (оба бинарника — `cmd/api` и `cmd/filestreamer`). В отличие от фронтенда, единого декларативного реестра эндпоинтов здесь нет — каждый внешний сервис инкапсулирован в собственном клиенте в каталогах `clients/` и `pkg/`.
## Как устроено взаимодействие
Клиенты создаются при старте (`cmd/api/routes_api.go`, `cmd/filestreamer/*`) и используют базовый URL из соответствующей переменной окружения (см. `config/config.go`). Большинство клиентов построены на `go-resty/resty` (метод `SetHostURL`/`SetBaseURL`), часть — на внутренних http-обёртках `gitlab.sarex.io/platform/gotools`. Итоговый URL = `<базовый URL сервиса>` + путь из клиента.
Аутентификация исходящих запросов:
- к Sarex backend (Django) — HTTP Basic (`DJANGO_BASIC_AUTH`, а для получения пользователей — `DJANGO_BASIC_AUTH_FOR_GET_USER`); для части ручек проксируется заголовок `Identity`/`Bearer`;
- к остальным сервисам — по внутренней сети кластера, как правило без внешней авторизации.
## Базовые URL по сервисам
| Сервис | Переменная окружения | Клиент (каталог) |
| --- | --- | --- |
| Sarex backend (Django) | `DJANGO_HOST` | `clients/django`, `pkg/django`, `pkg/users`, `pkg/sarex_backend`, `clients/accounts` |
| Flows | `FLOWS_URL` | `pkg/flows` |
| Workflows | `WORKFLOW_URL` | `clients/workflow`, `pkg/workflows` |
| Workspaces | `WORKSPACE_URL` | `clients/workspace` |
| Transmittals | `TRANSMITTALS_BASE_URL` (опц.) | `pkg/transmittal` |
| Automation | `AUTOMATION_URL` | `pkg/automation` |
| Marks (штампы, HTTP) | `MARKS_PROCESSING_URL` | `pkg/marks/base` |
| Marks (штампы, RabbitMQ) | `MARKS_RABBITMQ_*` | `pkg/marks/rpc` |
| BIM-API v1 | `BIM_API_URL` | `clients/bim-api` |
| BIM-API v2 (bim-core-api) | `BIM_API_V2_URL` | `clients/bim-api-v2` |
| System log | `SYSTEM_LOG_URL` | `pkg/system_log` |
Дополнительно сервис работает с S3 (объектное хранилище, креды из `S3_SERVICE_ACCOUNT`/`S3_SERVICE_ACCOUNT_STR`) и PostgreSQL — это не HTTP-сервисы и в таблицах ниже не приводятся.
## Эндпоинты по сервисам
### Sarex backend (Django) — `DJANGO_HOST`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| GET | `/api/core/users/` | `clients/django/users.go` | Список пользователей |
| GET | `/api/core/users/{id}/` | `clients/django/users.go`, `pkg/users/client.go` | Пользователь по id |
| GET | `/api/core/users/{id}/introspect` | `clients/django/users.go` | Интроспекция пользователя |
| GET | `/api/core/users/{id}` | `pkg/django/client.go` | Пользователь по id (внутренний клиент) |
| GET | `/api/client/settings/` | `clients/django/settings.go` | Клиентские настройки |
| GET | `/api/core/service-accounts/personalized/` | `clients/django/settings.go` | Персонализированные сервисные аккаунты |
| GET | `/api/core/service_accounts/` | `clients/django/service_accounts.go`, `clients/accounts` | Сервисные аккаунты |
| GET | `/api/core/companies/` | `clients/django/companies.go` | Список компаний |
| GET | `/api/core/mrpa/{id}/` | `pkg/sarex_backend/client.go` | MRPA по id (прокидывается заголовок `Identity`) |
### Flows — `FLOWS_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| GET | `internal/v1/documents/?full=true&document_ids={id}` | `pkg/flows/client.go` | Документы в процессах (flows) по id |
| GET/POST | `internal/v1/documents/` | `pkg/flows/client.go` | Документы процессов |
### Workflows — `WORKFLOW_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `internal/v1/companies/{company_id}/workflows` | `clients/workflow/client.go` | Создать workflow обработки (BIM/PDF/DWG/DEM/DOCX и т. д.); образы задач — из `CONTAINER_REGISTRY` + `WORKFLOWS_IMAGES_VERSION` |
| GET | `v1/workflows/{id}` | `pkg/workflows/client.go` | Прочитать workflow по id |
### Workspaces — `WORKSPACE_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `internal/v2/workspaces` | `clients/workspace/client.go` | Создать воркспейс |
| DELETE | `internal/v2/documents/{ids}` | `clients/workspace/client.go` | Удалить документы воркспейса |
| PATCH | `internal/v2/documents/restore` | `clients/workspace/client.go` | Восстановить документы воркспейса |
### Transmittals — `TRANSMITTALS_BASE_URL`
Клиент создаётся только если переменная задана.
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `/internal/v1/transmittals/by_bundle_ids` | `pkg/transmittal/client.go` | Трансмитталы по списку bundle-id |
### Automation — `AUTOMATION_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| — | `/internal/v1/automations/{process_name}` | `pkg/automation/client.go` | Запуск/получение автоматизации по имени процесса |
### Marks (штампы/маркировки)
Режим выбирается флагом `USE_MARKS_RABBITMQ`.
| Метод | Путь / транспорт | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `/api/v1/marks/{bundle_id}` (HTTP, `MARKS_PROCESSING_URL`) | `pkg/marks/base/client.go` | Наложение штампов на бандл (HTTP-режим) |
| — | RabbitMQ (`MARKS_RABBITMQ_*`) | `pkg/marks/rpc` | Наложение штампов через очередь (RPC-режим, не HTTP) |
### BIM-API v1 — `BIM_API_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `/internal/v1/targets/{target_id}/bims-pdm` | `clients/bim-api/client.go` | Создать BIM для target (PDM) |
| POST | `/internal/v1/targets/{target_id}/bims-v2-pdm` | `clients/bim-api/client.go` | Создать BIM v2 для target (PDM) |
### BIM-API v2 (bim-core-api) — `BIM_API_V2_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `/internal/v1/projects/{project_id}/bims` | `clients/bim-api-v2/client.go` | Создать BIM для проекта |
### System log — `SYSTEM_LOG_URL`
| Метод | Путь | Клиент | Назначение |
| --- | --- | --- | --- |
| POST | `/api/v0/system_log` | `pkg/system_log/client.go` | Отправить запись в системный лог |
## Обработка ошибок
Клиенты, как правило, проверяют код ответа и оборачивают ошибку через `github.com/rotisserie/eris` (напр. «invalid response code %d expected 200»). Для случая недоступности исходного сервиса в самом API определён нестандартный статус `523` (`network/consts.go`, `StatusOriginIsUnreachable`).

View File

@ -0,0 +1,131 @@
# =============================================================================
# documentations-api / documentations-filestream — пример переменных окружения
# =============================================================================
# Все переменные читаются напрямую из окружения процесса библиотекой
# kelseyhightower/envconfig (config/config.go). Префикса и вложенности НЕТ
# имена плоские. Значения ниже — ориентир для локального запуска (аналог
# .docker/.env). Оба бинарника (api и filestreamer) используют один и тот же
# набор переменных.
# --- Адреса прослушивания ----------------------------------------------------
API_ADDRESS=0.0.0.0:6666 # порт основного API (cmd/api)
API_ADDRESS_FILE=0.0.0.0:7777 # порт файлового стримера (cmd/filestreamer)
# --- PostgreSQL --------------------------------------------------------------
POSTGRES_ADDRESS=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=documentations
POSTGRES_POOL_SIZE=10 # учитывается только в api; filestreamer жёстко использует 50
ENABLE_SSL=0 # 1 — TLS к БД, тогда нужен YC-PG-CERTIFICATE
# YC-PG-CERTIFICATE= # PEM CA-сертификат PostgreSQL (в проде — из секрета)
# --- S3 (объектное хранилище) ------------------------------------------------
ENABLE_S3=0 # в api при 0 сервис стартует без S3; в filestreamer S3 обязателен
S3_SERVICE_ACCOUNT=/etc/sarex/yc_s3_doc_account.json # путь к JSON сервис-аккаунта
# S3_SERVICE_ACCOUNT_STR= # альтернатива: сам JSON строкой
# --- Sarex backend (Django) --------------------------------------------------
DJANGO_HOST=https://stage.sarex.io
DJANGO_BASIC_AUTH= # basic-auth для системных вызовов Django
DJANGO_BASIC_AUTH_FOR_GET_USER= # отдельный basic-auth для получения пользователей
DJANGO_ORIGINATOR=docs_local
# --- Zitadel (опциональная аутентификация) -----------------------------------
USE_ZITADEL=0
ZITADEL_DOMAIN=idp.dev.stage.sarex.io
ZITADEL_ACCOUNT=/etc/sarex/zitadel/zitadel-account.json
# --- Внешние сервисы (базовые URL) -------------------------------------------
FILE_URL_EXTERNAL=https://stage-api.sarex.io/files
DOCUMENTATION_URL=http://localhost:8000/
WORKFLOW_URL=https://stage-api.sarex.io/workflows/api
WORKSPACE_URL=https://stage-api.sarex.io/worspace/api
WORKSPACE_V2_EXTERNAL_URL=https://stage.sarex.io/workspaces-v2/
WORKSPACE_BUNDLE_VERSION=v1
MARKS_PROCESSING_URL=http://marks-service.documentations:8000
BIM_API_URL=http://bim-api-service.bim-api-stage/
BIM_API_V2_URL=http://bim-core-api.platform.svc.cluster.local:8000/
BIM_API_URL_EXTERNAL=https://stage-api.sarex.io/bim
SYSTEM_LOG_URL=http://localhost:8888
FLOWS_URL=http://backend-service.proc.svc.cluster.local:8000
AUTOMATION_URL=http://automation-api-service.automation-stage.svc.cluster.local:8000
# TRANSMITTALS_BASE_URL=http://transmittal-service.documentations:8000 # опционально
# --- Публичные ссылки на документы -------------------------------------------
PUBLIC_LINK_HOST=https://document-link.stage.sarex.io
PUBLIC_KEY= # PEM RSA public key (PKIX) для проверки JWT
DOCUMENT_PUBLIC_LINK_JWT_SECRET= # секрет HMAC для JWT публичных ссылок/временных загрузок
DOCUMENT_PUBLIC_LINK_JWT_EXPIRATION_MINUTES=5
PUBLIC_LINK_FOLDER_CONNECTOR_ENABLED=1
# --- Подпись ссылок скачивания (signature-in-URL) ----------------------------
ENABLE_SIGNATURE_IN_URL=false # при true обязателен рабочий Valkey и SIGNATURE_SECRET_KEY
SIGNATURE_SECRET_KEY=
SIGNATURE_IN_URL_EXPIRATION_SECONDS=600
ENABLE_AUTH_JWT_IN_URL=true
# --- Valkey / Redis (кэши, кэш подписей) -------------------------------------
VALKEY_ADDR=localhost:6380 # host:port; пустая строка полностью отключает клиент
VALKEY_LOGIN=
VALKEY_PASSWORD=
VALKEY_HOST=
VALKEY_DB=0
VALKEY_CACHE_TTL=1h
VALKEY_SSL=false
VALKEY_SSL_CA_CERTS=
# --- Файловый стример / кэш --------------------------------------------------
READ_WRITE_TIMEOUT_FILE_STREAM=6h
USE_CACHE_IN_FILE_STREAMER=true
CACHE_DEFAULT_EXPIRATION=30s
CACHE_CLEANUP_INTERVAL=35s
# --- BIM / обработка ---------------------------------------------------------
USE_BIMV1_FOR_BIMV2=1
USE_BIM_INSERTER=1
USE_LEGACY_BIM_FLOW=0
LAST_MASTER_BIM=1541
LAST_SLAVE_1_BIM=5000
LAST_SLAVE_2_BIM=15000
CONVERT_DWG_TO_GEOJSON=true
CONVERT_DXF_TO_GEOJSON=true
IS_CONVERTED_PDF_UPLOADING_TO_S3=true
DELETE_S3D_AFTER_MESHOPT=false
WORKFLOW_IMAGES_VERSION=develop
WORKFLOWS_IMAGES_VERSION=develop
CONTAINER_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
WORKFLOWS_CONFIG_FILEPATH=.example.tasks_execution_config.json
# --- Штампы/маркировки: HTTP или RabbitMQ ------------------------------------
USE_MARKS_RABBITMQ=0 # 0 — ходить в MARKS_PROCESSING_URL по HTTP; 1 — через RabbitMQ
MARKS_RABBITMQ_HOST=
MARKS_RABBITMQ_PORT=
MARKS_RABBITMQ_USER=
MARKS_RABBITMQ_PASSWORD=
MARKS_RABBITMQ_API=
# --- Rate limit для /documents/metadata --------------------------------------
METADATA_RATE_LIMIT_ENABLED=true
METADATA_RATE_LIMIT_MAX_REQUESTS=10
# --- Почта (флаги; сама отправка идёт через внешние сервисы) ------------------
ENABLE_MAILGUN=true
ENABLE_SMTP=false
# --- Наблюдаемость -----------------------------------------------------------
ENVIRONMENT=stage
SENTRY_DSN=
SENTRY_DEBUG=0
NAMESPACE=local
ENABLE_SQL_QUERY=1 # логировать SQL-запросы (для локальной отладки)
TRACER_USE=false
TRACER_HOST=localhost:4317
TRACER_USE_INSECURE=true
TRACER_LOGGER_NAME=tracer_logger
SERVICE_NAME=documentations-api
SERVICE_NAME_FILESTREAM=filestream-api
# --- Только для интеграционных тестов (кодом приложения не читается) ----------
# KEY_JWT=example_jwt

View File

@ -0,0 +1,785 @@
openapi: 3.0.3
info:
title: Documentations API
version: "1.0"
description: |
REST-API сервиса документаций (`documentation-api`, бинарник `cmd/api`, образ
`documentations`, deployment `documentations-api` в неймспейсе `documentations`).
Спецификация реконструирована из исходного кода маршрутов
(`cmd/api/routes_api.go`, `cmd/api/routes_internal.go`) и middleware
(`cmd/api/bootstrap.go`, `pkg/midleware/*`). В репозитории **нет сгенерированного
swagger/openapi**, поэтому схемы тел запросов/ответов приведены обобщённо
(в коде они не описаны декларативно). Пути, методы и параметры пути —
достоверные, из роутера `gorilla/mux`.
## Базовые пути
- Публичный API: `/api/v1` (описан ниже).
- Внутренний API: `/internal/v1` (сервис-к-сервису, облегчённая авторизация;
здесь не детализируется — см. `cmd/api/routes_internal.go`).
- Потоковая отдача/приём файлов вынесены в **отдельный сервис `filestream`**
(`cmd/filestreamer`, образ `documentations-api-files`): `/api/v1/bundles/...`,
`/api/v1/documents/...`, `/api/v1/pages/...`, `/api/v1/documents/folders`,
`/api/v1/bundles_mrpas/...`, `/api/v1/public_link_mrpas/...`.
- Health-check: `GET /ping` (предоставляется каркасом роутера `rest`).
- Профилирование: `GET /debug/pprof/...` (net/http/pprof).
## Аутентификация
Основной способ — JWT в заголовке `Authorization: Bearer <token>`, проверяемый по
RSA-публичному ключу (`PUBLIC_KEY`, PEM/PKIX). Из claims извлекаются `company_ids`
и `service_accounts`. Опционально включается проверка через Zitadel (`USE_ZITADEL`),
а также разбор заголовка `Identity`. Часть ручек (пути `/public/...`,
`/public_link_mrpas/...` и запросы с `download_type=temporary`) авторизуются по
HMAC-JWT публичных ссылок (`DOCUMENT_PUBLIC_LINK_JWT_SECRET`). В сервисе filestream
ссылки скачивания дополнительно подписываются (`signature` + `expires_at` в query,
секрет `SIGNATURE_SECRET_KEY`).
## Формат ошибок
Ответы оборачиваются middleware `rest.JSONResponse`; ошибки возвращаются в JSON.
Нестандартный код `523` (`StatusOriginIsUnreachable`, `network/consts.go`)
используется, когда исходный сервис недоступен.
## Пагинация
Единого декларативного механизма пагинации в роутере нет; списки, где она нужна,
принимают параметры фильтрации в теле POST-запроса (напр. `/documents/metadata`,
`/documents/batch`). Эндпоинт `/documents/metadata` дополнительно ограничивается
rate-limit (`METADATA_RATE_LIMIT_*`).
servers:
- url: https://api.sarex.io/documentations/api/v1
description: production
- url: https://api.preprod.sarex.io/documentations/api/v1
description: preprod
- url: https://stage-api.sarex.io/documentations/api/v1
description: stage
security:
- bearerAuth: []
tags:
- name: disks
- name: documents
- name: bundles
- name: uploads
- name: permissions
- name: workspaces
- name: dashboards
- name: workflows
- name: pages
- name: marks
- name: public-links
- name: related-documents
- name: changelogs
- name: favorite-documents
- name: name-templates
- name: misc
paths:
/conversion:
post:
tags: [misc]
summary: Запустить конвертацию документа
responses:
"200": { $ref: "#/components/responses/Ok" }
/disks:
get:
tags: [disks]
summary: Список дисков (доступных пользователю)
responses:
"200": { $ref: "#/components/responses/Ok" }
post:
tags: [disks]
summary: Создать диск (требуются права администратора)
responses:
"200": { $ref: "#/components/responses/Ok" }
"403": { $ref: "#/components/responses/Forbidden" }
/disks/{disk_id}:
delete:
tags: [disks]
summary: Удалить диск (требуются права администратора)
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
"403": { $ref: "#/components/responses/Forbidden" }
/disks/{disk_id}/documents:
get:
tags: [disks, documents]
summary: Документы диска
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
post:
tags: [disks, documents]
summary: Документы диска по списку id
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/disks/{disk_id}/projects:
get:
tags: [disks]
summary: Проекты диска
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/disks/{disk_id}/service_accounts:
get:
tags: [disks, permissions]
summary: Сервисные аккаунты диска
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/disks/{disk_id}/size_migration:
get:
tags: [misc]
summary: Миграция размеров (служебное)
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/disks/{disk_id}/delete_documents_from_ws_migration:
get:
tags: [misc]
summary: Удаление документов при миграции воркспейса (служебное)
parameters: [{ $ref: "#/components/parameters/DiskId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents:
post:
tags: [documents]
summary: Создать документ/папку
responses:
"200": { $ref: "#/components/responses/Ok" }
delete:
tags: [documents]
summary: Массовое удаление документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/create_report:
post:
tags: [documents]
summary: Сформировать отчёт по метаданным документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/metadata:
post:
tags: [documents]
summary: Список метаданных документов (rate-limited)
responses:
"200": { $ref: "#/components/responses/Ok" }
"429": { $ref: "#/components/responses/TooManyRequests" }
/documents/batch:
post:
tags: [documents]
summary: Пакетное получение документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/flows:
post:
tags: [documents]
summary: Документы в трансмиттале/ревью
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/public_link:
post:
tags: [public-links]
summary: Создать публичную ссылку на документ
responses:
"200": { $ref: "#/components/responses/Ok" }
/public/documents/public_link/{id}:
get:
tags: [public-links]
summary: Прочитать публичную ссылку (публичный доступ по HMAC-JWT)
security: []
parameters: [{ $ref: "#/components/parameters/StrId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/public_link/{id}:
patch:
tags: [public-links]
summary: Обновить публичную ссылку
parameters: [{ $ref: "#/components/parameters/StrId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
delete:
tags: [public-links]
summary: Удалить публичную ссылку
parameters: [{ $ref: "#/components/parameters/StrId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/update-path:
patch:
tags: [documents]
summary: Сменить родителя документа
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/update-path:
patch:
tags: [documents]
summary: Массовая смена родителя документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/super_create:
post:
tags: [documents]
summary: Создание документа суперпользователем (требуются права администратора)
responses:
"200": { $ref: "#/components/responses/Ok" }
"403": { $ref: "#/components/responses/Forbidden" }
/documents/types:
get:
tags: [documents]
summary: Справочник типов документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/get_folders_download_url:
get:
tags: [documents]
summary: Ссылка на скачивание папок (подписанная)
responses:
"200": { $ref: "#/components/responses/Ok" }
/download_url/documents:
get:
tags: [documents]
summary: Ссылка на скачивание документа
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/name_template:
get:
tags: [documents, name-templates]
summary: Шаблон имени документа
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}:
get:
tags: [documents]
summary: Документ по id
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [documents]
summary: Переименовать/изменить документ (числовой id)
parameters: [{ $ref: "#/components/parameters/DocumentIdNum" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
delete:
tags: [documents]
summary: Удалить документ (числовой id)
parameters: [{ $ref: "#/components/parameters/DocumentIdNum" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/bundles:
get:
tags: [documents, bundles]
summary: Бандлы документа
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/add_bundle:
post:
tags: [documents, bundles]
summary: Привязать бандл к документу
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/move_bundles:
patch:
tags: [documents, bundles]
summary: Переместить бандлы
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/ancestors:
get:
tags: [documents]
summary: Предки документа
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/filetypes_by_extension:
post:
tags: [documents]
summary: Определить тип файла по расширению
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/copy:
post:
tags: [documents]
summary: Копировать документы (долгая операция, таймаут 120 мин)
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/copy_structure:
post:
tags: [documents]
summary: Копировать структуру папок
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/bin:
delete:
tags: [documents]
summary: Окончательно удалить документы из корзины
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/bin/restore:
patch:
tags: [documents]
summary: Восстановить документы из корзины
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_ids}/company:
get:
tags: [documents]
summary: Компания документов
parameters:
- name: document_ids
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/permissions:
get:
tags: [permissions]
summary: Права доступа документа
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
post:
tags: [permissions]
summary: Выдать права на документ
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/documents/{document_id}/download:
get:
tags: [documents]
summary: Скачать документ (отдаётся сервисом filestream)
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/permissions:
get:
tags: [permissions]
summary: Справочник прав доступа
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles:
post:
tags: [bundles]
summary: Создать бандл
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}:
get:
tags: [bundles]
summary: Бандл по id
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
patch:
tags: [bundles]
summary: Изменить бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
delete:
tags: [bundles]
summary: Удалить бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/download:
get:
tags: [bundles]
summary: Скачать бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/{bundle_key}/download:
get:
tags: [bundles]
summary: Скачать файл бандла по ключу
parameters:
- { $ref: "#/components/parameters/BundleId" }
- { $ref: "#/components/parameters/BundleKey" }
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/{bundle_key}/upload_single:
post:
tags: [bundles, uploads]
summary: Загрузить файл целиком (single upload)
parameters:
- { $ref: "#/components/parameters/BundleId" }
- { $ref: "#/components/parameters/BundleKey" }
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/{bundle_key}/upload_multipart:
post:
tags: [bundles, uploads]
summary: Начать multipart-загрузку файла бандла
parameters:
- { $ref: "#/components/parameters/BundleId" }
- { $ref: "#/components/parameters/BundleKey" }
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/upload_finish:
post:
tags: [bundles, uploads]
summary: Завершить загрузку бандла
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/marks:
put:
tags: [marks]
summary: Добавить штампы/QR/подписи в бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/sign:
post:
tags: [bundles, marks]
summary: Подписать бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/restart:
post:
tags: [bundles, workflows]
summary: Перезапустить workflow бандла
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/cancel_qr:
patch:
tags: [marks]
summary: Отменить QR-код
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/comment:
patch:
tags: [bundles]
summary: Обновить комментарий бандла
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/presigned_url:
get:
tags: [bundles]
summary: Presigned URL бандла
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/mrpas:
get:
tags: [bundles]
summary: MRPA бандла
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundles/{bundle_id}/copy:
post:
tags: [bundles]
summary: Копировать бандл
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/bundle/version:
get:
tags: [bundles]
summary: Версии бандла
responses:
"200": { $ref: "#/components/responses/Ok" }
/uploads/multipart/{upload_id}/complete:
post:
tags: [uploads]
summary: Завершить multipart-загрузку
parameters: [{ $ref: "#/components/parameters/UploadId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/uploads/multipart/{upload_id}/abort:
post:
tags: [uploads]
summary: Прервать multipart-загрузку
parameters: [{ $ref: "#/components/parameters/UploadId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/uploads/multipart/{upload_id}/{part_num}:
post:
tags: [uploads]
summary: Загрузить часть (part) файла
parameters:
- { $ref: "#/components/parameters/UploadId" }
- name: part_num
in: path
required: true
schema: { type: integer }
responses:
"200": { $ref: "#/components/responses/Ok" }
/workspaces:
post:
tags: [workspaces]
summary: Создать воркспейс
responses:
"200": { $ref: "#/components/responses/Ok" }
/workspaces/{ws_id}:
get:
tags: [workspaces]
summary: Документ воркспейса
parameters:
- name: ws_id
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/dashboards:
post:
tags: [dashboards]
summary: Создать дашборд
responses:
"200": { $ref: "#/components/responses/Ok" }
/dashboards/{db_id}:
get:
tags: [dashboards]
summary: Документ дашборда
parameters:
- name: db_id
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/workflows/{workflow_id}:
get:
tags: [workflows]
summary: Workflow по id
parameters:
- name: workflow_id
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/pages:
post:
tags: [pages]
summary: Создать страницу
responses:
"200": { $ref: "#/components/responses/Ok" }
/pages/{data_source}/{page_key}/download:
get:
tags: [pages]
summary: Скачать страницу
parameters:
- name: data_source
in: path
required: true
schema: { type: string }
- name: page_key
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/public/qr/{public_uuid}/document_info:
get:
tags: [marks]
summary: Публичная информация о документе по QR
security: []
parameters:
- name: public_uuid
in: path
required: true
schema: { type: string, format: uuid }
responses:
"200": { $ref: "#/components/responses/Ok" }
/related_documents:
post:
tags: [related-documents]
summary: Создать связь документов
responses:
"200": { $ref: "#/components/responses/Ok" }
get:
tags: [related-documents]
summary: Получить связанные документы
responses:
"200": { $ref: "#/components/responses/Ok" }
/related_documents/bulk_delete:
post:
tags: [related-documents]
summary: Массово удалить связи документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/templates/{bundle_id}:
get:
tags: [misc]
summary: Шаблон по бандлу
parameters: [{ $ref: "#/components/parameters/BundleId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/changelogs/create:
post:
tags: [changelogs]
summary: Создать changelog
responses:
"200": { $ref: "#/components/responses/Ok" }
/changelogs/{changelog_id}:
patch:
tags: [changelogs]
summary: Обновить changelog
parameters:
- name: changelog_id
in: path
required: true
schema: { type: string }
responses:
"200": { $ref: "#/components/responses/Ok" }
/links:
post:
tags: [misc]
summary: Создать ссылку
responses:
"200": { $ref: "#/components/responses/Ok" }
/favorite_documents:
post:
tags: [favorite-documents]
summary: Добавить документ в избранное
responses:
"200": { $ref: "#/components/responses/Ok" }
get:
tags: [favorite-documents]
summary: Список избранных документов
responses:
"200": { $ref: "#/components/responses/Ok" }
/favorite_documents/{document_id}:
delete:
tags: [favorite-documents]
summary: Убрать документ из избранного
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
/name_templates/create:
post:
tags: [name-templates]
summary: Создать шаблон имени
responses:
"200": { $ref: "#/components/responses/Ok" }
/name_templates/{document_id}:
get:
tags: [name-templates]
summary: Шаблон имени по документу
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
patch:
tags: [name-templates]
summary: Обновить шаблон имени
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
delete:
tags: [name-templates]
summary: Удалить шаблон имени
parameters: [{ $ref: "#/components/parameters/DocumentId" }]
responses:
"200": { $ref: "#/components/responses/Ok" }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: >-
JWT, подписанный ключом, соответствующим `PUBLIC_KEY` (RSA/PKIX).
Для публичных ссылок используется HMAC-JWT (`DOCUMENT_PUBLIC_LINK_JWT_SECRET`).
parameters:
DiskId:
name: disk_id
in: path
required: true
schema: { type: string }
DocumentId:
name: document_id
in: path
required: true
schema: { type: string }
DocumentIdNum:
name: document_id
in: path
required: true
description: Числовой идентификатор документа (маршрут ограничен regex `[0-9]+`)
schema: { type: integer }
BundleId:
name: bundle_id
in: path
required: true
schema: { type: string, format: uuid }
BundleKey:
name: bundle_key
in: path
required: true
schema: { type: string }
UploadId:
name: upload_id
in: path
required: true
schema: { type: string }
StrId:
name: id
in: path
required: true
schema: { type: string }
responses:
Ok:
description: Успешный ответ (тело зависит от ручки; в JSON)
content:
application/json:
schema: { type: object, additionalProperties: true }
Forbidden:
description: Недостаточно прав
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
NotFound:
description: Ресурс не найден
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
TooManyRequests:
description: Превышен лимит запросов (rate limit)
content:
application/json:
schema: { $ref: "#/components/schemas/Error" }
schemas:
Error:
type: object
properties:
error:
type: string
message:
type: string
additionalProperties: true

View File

@ -0,0 +1,156 @@
# Конфигурация проекта dps-message-hub
# Версия: 0.1.0
Документ описывает все переменные окружения и способы конфигурирования сервиса.
`dps-message-hub` (`dps_message_hub`) — это Kafka-воркер на базе [FastStream](https://faststream.airt.ai/), потребляющий сообщения об изменении ассетов и обновляющий данные разметки (`markup_event`) в PostgreSQL домена «documentations». HTTP API у сервиса нет.
> Это отдельный сервис, не путать с приложением `message-hub` (домен `planning`): у них разные префиксы переменных, набор интеграций и назначение.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/dps_message_hub/infra/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) (класс `AppSettings(BaseSettings)`).
Особенности разбора (`SettingsConfigDict`):
- `env_prefix="DPS_MESSAGE_HUB_"` — все переменные приложения начинаются с этого префикса;
- `env_nested_delimiter="__"` — вложенные секции задаются двойным подчёркиванием, напр. `DPS_MESSAGE_HUB_DOCUMENTATION_DB__HOST``documentation_db.host`;
- `env_ignore_empty=False` — пустая строка считается заданным значением (не игнорируется);
- `frozen=True` — объект настроек неизменяем после инициализации.
Настройки разбиты на три вложенные секции (модели `pydantic.BaseModel`), читаемые одним классом `AppSettings`:
- `app` (`App`) — префикс `DPS_MESSAGE_HUB_APP__`;
- `documentation_db` (`Database`) — префикс `DPS_MESSAGE_HUB_DOCUMENTATION_DB__`;
- `kafka` (`Kafka`) — префикс `DPS_MESSAGE_HUB_KAFKA__`.
Класс `Settings` (`src/dps_message_hub/infra/config.py`) — синглтон (`wiring.SingletonMeta`) поверх `AppSettings`, отдаёт настройки через свойство `.settings`.
Отдельного конфиг-файла (yaml/toml) у приложения нет. У классов настроек **не задан** `env_file`, поэтому файл `.env` автоматически не загружается — переменные нужно экспортировать в окружение процесса самостоятельно (`make config` лишь копирует `.example.env``.env` как шаблон), либо пробрасывать их в контейнер через `--env-file` (см. `Makefile`, цель `container-run`).
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса. `make config` копирует `.example.env``.env`, но приложение **не загружает `.env` автоматически** — экспортируйте сами, напр. `set -a && . ./.env && set +a` |
| Локально (контейнер) | `Makefile`: цель `container-run` пробрасывает переменные через `--env-file .env` |
| Kubernetes (Helm) | `.helm/values.yaml`: блок `universal-chart.services.api.envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов); базовый чарт — `universal-chart` (`oci://.../charts`, версия `0.1.7`) |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) и `HELM_SET_ARGS` |
Запуск процесса (`scripts/entrypoint.sh`): единый FastStream-процесс с брокером Kafka:
```
faststream run \
--factory \
--workers ${DPS_MESSAGE_HUB_NUM_WORKERS} \
'dps_message_hub.infra.app:get_app'
```
Фабрика `dps_message_hub.infra.app:get_app` (`src/dps_message_hub/infra/app.py`) собирает `FastStream`-приложение: создаёт `KafkaBroker`, подключает роутер-потребитель топика `assets` и открывает пул соединений PostgreSQL в lifespan. Отдельных точек входа для воркеров/крон-задач нет — `pyproject.toml` не содержит `[project.scripts]`.
## Переменные приложения
Дефолт `—` означает, что значение обязательно (иначе ошибка старта настроек).
### Приложение (`DPS_MESSAGE_HUB_APP__*`) — класс `App`
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DPS_MESSAGE_HUB_APP__LOG_LEVEL` | enum (`LogLevel`) | `INFO` | Уровень логирования. Допустимо: `CRITICAL`/`FATAL`/`ERROR`/`WARNING`/`WARN`/`INFO`/`DEBUG`/`NOTSET` |
| `DPS_MESSAGE_HUB_APP__IS_DEV` | bool | `False` | Признак dev-режима. Поле объявлено в настройках, но в текущем коде не используется |
| `DPS_MESSAGE_HUB_APP__BROKER_TYPE` | enum (`BrokerType`) | `—` (обязательно) | Тип брокера сообщений. Поддерживается только значение `kafka` |
### База данных PostgreSQL (`DPS_MESSAGE_HUB_DOCUMENTATION_DB__*`) — класс `Database`
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__HOST` | string | `—` | Хост PostgreSQL |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__PORT` | int | `—` | Порт PostgreSQL |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__USER` | string | `—` | Пользователь БД |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__PASSWORD` | string | `—` | Пароль пользователя БД |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__NAME` | string | `—` | Имя базы данных |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__ENABLE_SSL` | bool | `—` | Включить TLS-подключение к БД |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__SSL_MODE` | enum (`verify-full`/`verify-ca`/`""`) | `—` | Режим проверки TLS (параметр `sslmode` DSN). При `ENABLE_SSL=true` не должно быть пустым |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__SSL_ROOT_CERT_PATH` | string | `—` | Путь к CA-сертификату (параметр `sslrootcert` DSN). При `ENABLE_SSL=true` не должно быть пустым |
Валидатор `check_ssl_options_configured_when_ssl_enabled`: если `ENABLE_SSL=true`, то `SSL_MODE` и `SSL_ROOT_CERT_PATH` обязаны быть непустыми, иначе ошибка старта. Итоговый DSN собирается в вычисляемом поле `documentation_db.uri` (`postgresql://user:password@host:port/name`, при SSL добавляются `?sslmode=...&sslrootcert=...`).
### Kafka (`DPS_MESSAGE_HUB_KAFKA__*`) — класс `Kafka`
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DPS_MESSAGE_HUB_KAFKA__HOST` | string | `—` | Хост брокера Kafka |
| `DPS_MESSAGE_HUB_KAFKA__PORT` | int | `—` | Порт брокера Kafka |
| `DPS_MESSAGE_HUB_KAFKA__USERNAME` | string | `—` | Логин SASL (`SCRAM-SHA-512`) |
| `DPS_MESSAGE_HUB_KAFKA__PASSWORD` | string | `—` | Пароль SASL (`SCRAM-SHA-512`) |
| `DPS_MESSAGE_HUB_KAFKA__SSL_CAFILE` | string \| null | `None` | Путь к CA-сертификату для SSL-контекста. Если задан — используется `SASL_SSL`, иначе SASL без TLS |
| `DPS_MESSAGE_HUB_KAFKA__TOPICS` | dict (JSON) → `KafkaTopics` | `—` | Соответствие логического топика реальному имени в Kafka. Обязателен ключ `assets`, напр. `{"assets": "assets_broadcast"}` |
Формирование параметров подключения (`src/dps_message_hub/infra/app.py`): адрес брокера — вычисляемое поле `kafka.uri` (`host:port`). Безопасность через `faststream.security.SASLScram512`:
- если заданы `USERNAME` и `PASSWORD` и `SSL_CAFILE` пуст — `SASLScram512(..., use_ssl=False)`;
- если задан `SSL_CAFILE``SASLScram512(..., ssl_context=create_ssl_context(cafile=...))`.
## Переменные инфраструктуры, сборки и запуска
Не читаются классами настроек приложения, но участвуют в запуске/сборке/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `DPS_MESSAGE_HUB_NUM_WORKERS` | `scripts/entrypoint.sh`, Helm `envs` | Число воркеров FastStream (`faststream run --workers`). В Helm `_default: '2'` |
| `DPS_MESSAGE_HUB_PYTHON_IMAGE_NAME` | `Dockerfile` (ARG) | Базовый образ Python (по умолчанию `python`) |
| `DPS_MESSAGE_HUB_PYTHON_IMAGE_TAG` | `Dockerfile` (ARG) | Тег базового образа (по умолчанию `3.13-slim`) |
| `PIP_INDEX_URL`, `PIP_TRUSTED_HOST` | `Dockerfile` (ARG) | Индекс/доверенный хост pip при сборке |
Сборка (`Dockerfile`): многостадийная — стадия `builder` собирает wheel'ы из `requirements/requirements.txt`, стадия `runner` ставит их, копирует `src/` и устанавливает пакет (`pip install . --no-deps`); процесс запускается непривилегированным пользователем `dps_message_hub`. Целевая версия Python — `3.13` (`.python-version`, `requires-python >=3.13`).
## Переменные из Helm-чарта (`.helm/values.yaml`)
Сервис деплоится через зависимость `universal-chart` (`.helm/Chart.yaml`, версия `0.1.7`). Настраивается один сервис — `services.api` (тип нагрузки — deployment, `replicaCount._default: 1`). HTTP-`service` и `ingress` отключены, health-пробы (`liveness`/`readiness`, путь `/ping`) — `enabled: false` (у сервиса нет HTTP-эндпоинтов).
Обычные значения (блок `services.api.envs`) различаются по окружениям (`_default`/`stage`/`preprod`/`production`) адресами БД и Kafka, именем БД и именами топиков. В Helm дополнительно заданы (отсутствуют в `.example.env`): `DPS_MESSAGE_HUB_KAFKA__HOST`, `DPS_MESSAGE_HUB_KAFKA__PORT` (`9091`), `DPS_MESSAGE_HUB_KAFKA__SSL_CAFILE` (`/opt/config/ca.crt`), `DPS_MESSAGE_HUB_DOCUMENTATION_DB__ENABLE_SSL=true`, `SSL_MODE=verify-full`, `SSL_ROOT_CERT_PATH=/opt/config/ca.crt`.
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`_default` / `stage`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__USER` | `ya-pg-secret` / `documentations-postgresql-secret` | `username` |
| `DPS_MESSAGE_HUB_DOCUMENTATION_DB__PASSWORD` | `ya-pg-secret` / `documentations-postgresql-secret` | `password` |
| `DPS_MESSAGE_HUB_KAFKA__USERNAME` | `kafka-secret` | `username` |
| `DPS_MESSAGE_HUB_KAFKA__PASSWORD` | `kafka-secret` | `password` |
Помимо env, чарт монтирует CA-сертификат Яндекса из `ConfigMap` `ya-ca-cert` (ключ `ca.crt`) как файл `/opt/config/ca.crt` (том `cm-ya-ca-cert`, `readOnly`) — на него указывают `DPS_MESSAGE_HUB_KAFKA__SSL_CAFILE` и `DPS_MESSAGE_HUB_DOCUMENTATION_DB__SSL_ROOT_CERT_PATH`.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | NAMESPACE | CHART_VERSION | K8S_HUSTLER_BRANCH |
| --- | --- | --- | --- | --- |
| MR (`merge_request_event`) | — | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
| ветка `stage` | `stage` | `documentations` | `0.0.1-stage` | `stage` |
| ветка `master` | `preprod` | `documentations-preprod` | `0.0.1-preprod` | `preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `documentations-prod` | `0.0.1-prod` | `master` |
Ключевые переменные пайплайна: `SERVICE_NAME=dps-message-hub`, `DOCKERFILE_PATH=Dockerfile`, `CI_TRIGGER_SOURCE=app`, `RELEASE_NAME=dps-message-hub`, `CHART_NAME=dps-message-hub`, флаги `ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_DEPLOY`, `HELM_SET_ARGS` (`--set universal-chart...`). Отдельные job'ы `format` (`ruff format --diff`) и `lint` (`ruff check`) на образе `python:3.13-slim`.
## Замечания и потенциальные проблемы
- Приложение **не читает `.env`** автоматически (в `SettingsConfigDict` нет `env_file`). `make config` только создаёт `.env` из шаблона — переменные нужно экспортировать самому либо передавать контейнеру через `--env-file`.
- Большинство полей БД и Kafka **обязательны** (без дефолтов): при пустом окружении настройки не пройдут валидацию и сервис не стартует. Единственные необязательные — `APP__LOG_LEVEL`, `APP__IS_DEV`, `KAFKA__SSL_CAFILE`.
- `DPS_MESSAGE_HUB_APP__BROKER_TYPE` обязателен; поддерживается только `kafka` (иных веток в `match` нет). При другом значении брокер не будет создан.
- При `ENABLE_SSL=true` обязательно задавать `SSL_MODE` и `SSL_ROOT_CERT_PATH`, иначе валидатор настроек прервёт старт.
- `DPS_MESSAGE_HUB_KAFKA__TOPICS` обязан содержать ключ `assets` (модель `KafkaTopics`); прочие ключи игнорируются, отсутствие `assets` — ошибка старта.
- Поле `APP__IS_DEV` присутствует в настройках, но в текущем коде нигде не задействовано.
- Обработка Kafka-сообщений идёт с `auto_commit=False` и middleware `Retry` (`src/dps_message_hub/interface/middleware.py`), которая повторяет обработку **бесконечно** с экспоненциальной задержкой (до `1<<10 = 1024` сек) — «отравленное» сообщение может заблокировать партицию.
## Минимальный набор для локального запуска
Kafka и Zookeeper поднимаются через `Makefile` (цели `run-deps`/`run-zookeeper`/`run-kafka`); PostgreSQL — внешний. Минимально нужно задать:
- `DPS_MESSAGE_HUB_APP__BROKER_TYPE=kafka`;
- `DPS_MESSAGE_HUB_DOCUMENTATION_DB__HOST`, `__PORT`, `__USER`, `__PASSWORD`, `__NAME`, `__ENABLE_SSL` (для локали `false`), `__SSL_MODE`, `__SSL_ROOT_CERT_PATH` (при `ENABLE_SSL=false` можно пустыми);
- `DPS_MESSAGE_HUB_KAFKA__HOST`, `__PORT`, `__USERNAME`, `__PASSWORD`, `__TOPICS` (JSON с ключом `assets`);
- `DPS_MESSAGE_HUB_NUM_WORKERS` (для `entrypoint.sh`).
Локальный запуск: `make run` (через `entrypoint.sh`) либо `make run-dev` (`faststream run --factory --reload dps_message_hub.infra.app:get_app`). Готовые значения-примеры приведены в `dps-message-hub.env.example` рядом с этим документом.

View File

@ -0,0 +1,45 @@
# Интерфейсы сервиса dps-message-hub
# Версия: 0.1.0
Документ описывает интерфейсную поверхность сервиса: потребляемые топики Kafka и обращения к внешним зависимостям (PostgreSQL).
> `dps-message-hub` — это чистый Kafka-воркер на [FastStream](https://faststream.airt.ai/). У него **нет HTTP/REST API** (в коде нет FastAPI/Flask/aiohttp, health-роутов и т.п.), поэтому файла `openapi.yaml` для сервиса нет. Исходящих HTTP-запросов к другим сервисам он тоже не выполняет — единственный получатель данных — база PostgreSQL.
## Как устроено взаимодействие
Единое FastStream-приложение (`dps_message_hub.infra.app:get_app`, `src/dps_message_hub/infra/app.py`) объединяет:
- **Kafka** — потребитель сообщений (`KafkaBroker` + `KafkaRouter`), топик `assets` (`src/dps_message_hub/features/assets/interface/kafka.py`);
- **PostgreSQL** — пул соединений `psycopg` (`AsyncConnectionPool`), открывается/закрывается в lifespan (`src/dps_message_hub/infra/lifespan.py`, `infra/database.py`).
Каждое сообщение проходит через middleware `Retry` (`src/dps_message_hub/interface/middleware.py`): при исключении обработка повторяется бесконечно с экспоненциальной задержкой (`1 << min(10, retry_count)` секунд). Коммит оффсета ручной у потребителя `auto_commit=False`.
## Kafka-потребители (входящие сообщения)
Реальное имя топика задаётся переменной `DPS_MESSAGE_HUB_KAFKA__TOPICS` (маппинг логического имени `assets` в имя топика Kafka). Формат сообщения — `BrokerMessageDto` (`src/dps_message_hub/infra/dto.py`): поля `schema_version`, `model`, `sender`, `type`, `body`, `diff`, `timestamp`, `trace_id` (alias `xtraceId`), `user_id`, `tenants`, `tags`.
| Топик (логич.) | `group_id` | Offset reset | `auto_commit` | Обработчик | Назначение |
| --- | --- | --- | --- | --- | --- |
| `assets` | `dps_assets_consumer` | earliest | `false` | `assets` | Обновление событий разметки (`markup_event`) по изменению ассета |
Диспетчеризация внутри обработчика `assets` по полю `type` (`src/dps_message_hub/features/assets/interface/kafka.py`); `body` разбирается в модель `AssetUpdate` (поля `id`, `attributes[]`), `diff` — произвольный `dict`:
| `type` сообщения | Условие (`diff`) | Действие | Назначение |
| --- | --- | --- | --- |
| `model_updated` | `diff is None` | — | Пропуск (нет изменений) |
| `model_updated` | в `diff` есть ключ `resource_id` | `unlink_asset(asset_id)` | Отвязка ассета: деактивация его событий разметки |
| `model_updated` | в `diff` есть ключ `attributes` | `update_markup_events(asset_id, attributes)` | Пересоздание событий разметки по обновлённым атрибутам |
| `model_deleted` | — | `unlink_asset(asset_id)` | Отвязка ассета при удалении модели |
Бизнес-логика (`src/dps_message_hub/features/assets/application/asset.py`): для обновляемых атрибутов существующие активные `markup_event` деактивируются (`inactivation_time`), затем в транзакции вставляются новые записи. Тип атрибута (поле `type`) определяет целевую колонку значения (`value_int`/`value_float`/`value_string`/`value_option_id`/`unit_option_id`/`value_dt`/`value_date`) — маппинг в `features/assets/domain/entities.py`.
## Внешние зависимости (инфраструктура)
| Зависимость | Назначение |
| --- | --- |
| Kafka | Источник сообщений (топик `assets`). Подключение `SASLScram512`, при заданном `KAFKA__SSL_CAFILE` — по `SASL_SSL` |
| PostgreSQL | Хранилище данных (`psycopg` async, таблица `markup_event`). Операции: `SELECT`, `UPDATE`, массовая вставка `COPY ... FROM STDIN` (`features/assets/infra/database.py`) |
## Исходящие HTTP-запросы к внешним сервисам
Отсутствуют. Сервис не содержит HTTP-клиентов (`httpx`/`aiohttp`/`requests`) и не обращается к другим сервисам по HTTP; все побочные эффекты — запись в PostgreSQL.

View File

@ -0,0 +1,42 @@
# =============================================================================
# dps-message-hub — пример конфигурации (.env)
# Версия: 0.1.0
# Скопируйте в .env (`make config`) и заполните значения.
# ВНИМАНИЕ: приложение НЕ загружает .env автоматически (в классах настроек нет
# env_file/dotenv). Экспортируйте переменные сами, напр.:
# set -a && . ./.env && set +a
# либо запускайте контейнер через `--env-file .env` (см. Makefile).
# =============================================================================
# --- Инфраструктура запуска (не читается классом настроек) ---
# Число воркеров faststream (scripts/entrypoint.sh: faststream run --workers)
DPS_MESSAGE_HUB_NUM_WORKERS=1
# --- Приложение (префикс DPS_MESSAGE_HUB_APP__) ---
# CRITICAL | FATAL | ERROR | WARNING | WARN | INFO | DEBUG | NOTSET
DPS_MESSAGE_HUB_APP__LOG_LEVEL=INFO
DPS_MESSAGE_HUB_APP__IS_DEV=true
# Тип брокера сообщений (поддерживается только kafka)
DPS_MESSAGE_HUB_APP__BROKER_TYPE=kafka
# --- База данных PostgreSQL (префикс DPS_MESSAGE_HUB_DOCUMENTATION_DB__) ---
DPS_MESSAGE_HUB_DOCUMENTATION_DB__HOST=
DPS_MESSAGE_HUB_DOCUMENTATION_DB__PORT=6432
DPS_MESSAGE_HUB_DOCUMENTATION_DB__USER=
DPS_MESSAGE_HUB_DOCUMENTATION_DB__PASSWORD=
DPS_MESSAGE_HUB_DOCUMENTATION_DB__NAME=
DPS_MESSAGE_HUB_DOCUMENTATION_DB__ENABLE_SSL=false
# verify-full | verify-ca | "" (обязателен при ENABLE_SSL=true)
DPS_MESSAGE_HUB_DOCUMENTATION_DB__SSL_MODE=
# Путь к CA-сертификату (обязателен при ENABLE_SSL=true)
DPS_MESSAGE_HUB_DOCUMENTATION_DB__SSL_ROOT_CERT_PATH=
# --- Kafka (префикс DPS_MESSAGE_HUB_KAFKA__) ---
DPS_MESSAGE_HUB_KAFKA__HOST=localhost
DPS_MESSAGE_HUB_KAFKA__PORT=9092
DPS_MESSAGE_HUB_KAFKA__USERNAME=
DPS_MESSAGE_HUB_KAFKA__PASSWORD=
# Путь к CA-сертификату для SSL-контекста (SASL_SSL). Если пусто — SASL без SSL
# DPS_MESSAGE_HUB_KAFKA__SSL_CAFILE=/opt/config/ca.crt
# Соответствие логического топика `assets` реальному имени топика Kafka (JSON)
DPS_MESSAGE_HUB_KAFKA__TOPICS='{"assets": "assets_broadcast_test"}'

View File

@ -0,0 +1,66 @@
# Конфигурация проекта documentation-frontend
Документ описывает сборку, рантайм-конфигурацию и деплой микрофронтенда `documentation-frontend` (образ `documentation-frontend-app`, деплой `frontend` в домене `documentations`).
## Способ конфигурирования
Это фронтенд-модуль (Module Federation remote), поэтому в отличие от backend-сервисов он **не** читает переменные окружения в рантайме. Единственный параметр конфигурации, влияющий на поведение, — окружение сборки `BUILD_ENV`, которое webpack «зашивает» в бандл на этапе сборки.
- Значение берётся из `process.env.BUILD_ENV` при запуске webpack.
- Webpack подставляет его как глобальную константу `BUILD_ENV` через `DefinePlugin` (`webpack.config.js`).
- Допустимые значения проверяются в `env.js`: `local`/`stage`/`preprod`/`prod`/`contour`. Если значение не входит в набор — сборка падает с ошибкой.
- `build.config.js` по `BUILD_ENV` выбирает режим webpack (`mode`/`devtool`): `local`/`stage` → `development` + `eval-source-map`, `prod`/`preprod`/`contour` → `production` + `source-map`.
- В рантайме `BUILD_ENV` определяет базовые хосты сервисов (`module/api/hosts.ts`, `resolveHost`) и тип http-сервиса (`module/api/http-service.ts`: при `local``setTypeOfHttpService("original")`). Подробности по хостам и эндпоинтам — в `frontend.ENDPOINTS.md`.
## Переменные сборки
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `BUILD_ENV` | `env.js`, `build.config.js`, `webpack.config.js` (`DefinePlugin`), `Dockerfile` (`ARG`), `.gitlab-ci.yml` (`--build-arg`) | Окружение сборки: `local`/`stage`/`preprod`/`prod`/`contour`. Определяет режим сборки и базовые хосты API |
| `NPM_NEXUS_TOKEN` | `Dockerfile` (`ARG`), `.npmrc`, `.gitlab-ci.yml` (`--build-arg`) | Токен доступа к приватному npm-реестру (Nexus) при `npm i` |
Отдельного `.env`-файла в репозитории нет; переменные передаются как build-arg'и Docker/CI.
## Сборка (`package.json`, `Dockerfile`)
Скрипты npm:
| Скрипт | Команда | Назначение |
| --- | --- | --- |
| `build-module` | `webpack --config webpack.config.js` | Сборка модуля в `dist` (используется в образе) |
| `serve-module` | `webpack serve --config webpack.config.js` | Dev-сервер (порт `9002`, https) |
| `lint` | `eslint ./module/**/*.ts(x) --fix` | Линтинг |
| `start` | `BUILD_ENV=local run-p serve-module storybook` | Локальный запуск (dev-сервер + Storybook) |
| `storybook` / `build-storybook` | `start-storybook` / `build-storybook` | Storybook |
Сборка образа (`Dockerfile`, multi-stage):
1. Стадия `static` (`node:16`): `npm i --legacy-peer-deps` с `NPM_NEXUS_TOKEN`, затем `npm run lint` и `BUILD_ENV=$BUILD_ENV npm run build-module` → артефакты в `/app/dist`.
2. Финальная стадия (`nginx:1.19.6`): копирует `dist` в `/dist` и `nginx/nginx.conf` в `/etc/nginx/nginx.conf`.
Module Federation (`webpack.config.js`, `ModuleFederationPlugin`): имя remote — `srx_documentations`, `filename: module/remoteEntry.js`. Экспонируемые модули: `./DocumentationsPage`, `./DocumentSelect`, `./CreateDocDialog`, `./DownloadFilesDialog`, `./FileBindingsDialog`. Shared-зависимости (singleton): `react`, `react-dom`, `@material-ui/core`, `@material-ui/styles`, `@sarex-team/sdk-js`, `@sarex-team/translator`, `@sarex-team/ui-kit`, `mobx`, `mobx-react-lite`.
## Деплой (Helm, `.helm/values.yaml`)
Чарт использует общий `universal-chart`. Ключевые значения для сервиса `frontend`:
- `deployment.name._default`: `documentation-frontend-static`; порт контейнера `80`.
- `replicaCount`: `stage` — 1, `preprod` — 2, `production` — 2.
- Пробы `liveness`/`readiness`: `httpGet /ping` на порту `80`.
- `resources.requests`: `memory 100Mi`, `cpu 100m`.
- `image.name._default`: `cr.yandex/crp3ccidau046kdj8g9q/documentation-frontend-static:latest` (в CI переопределяется на собранный `IMAGE_NAME` через `HELM_SET_ARGS`).
- `service`: `ClusterIP`, порт `80``stage``8080`), `targetPort 80`, `portName http`.
- `imagePullSecrets.name._default`: `dockerhub`.
## CI/CD (`.gitlab-ci.yml`)
Пайплайн подключает шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`). `SERVICE_NAME`: `documentation-frontend-app`. Окружение переключается по ветке/тегу (`workflow.rules`):
| Условие | STAND | Namespace | `BUILD_ENV` | `global.env` | `CHART_VERSION` |
| --- | --- | --- | --- | --- | --- |
| ветка `stage` | `stage` | `documentations` | `stage` | `stage` | `0.0.1-stage` |
| ветка `master` | `preprod` | `documentations-preprod` | `preprod` | `preprod` | `0.0.1-preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `documentations-prod` | `prod` | `production` | `0.0.1-prod` |
| merge request | — | — | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE: "false"`) |
Каждая ветка задаёт `BUILD_ARGS` (`--build-arg BUILD_ENV=... --build-arg NPM_NEXUS_TOKEN=...`) и `HELM_SET_ARGS` (образ, `global.env`, `commitSha`, `gitlabUri`, `gitlabJobUrl`, `owner`), а также `K8S_HUSTLER_BRANCH` соответствующего окружения (`universal-chart-stage`/`-preprod`/`-production`).

View File

@ -0,0 +1,224 @@
# Эндпоинты, с которыми взаимодействует documentation-frontend
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `documentation-frontend`, образ `documentation-frontend-app`, деплой `frontend`). Модуль публикуется как remote для Module Federation (`webpack.config.js`, имя `srx_documentations`).
## Как устроено взаимодействие
Все запросы описаны декларативно в реестре `module/api/endpoints.ts` (объект `endpoints`). Каждый эндпоинт задаётся структурой `Endpoint`:
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
- `method` — HTTP-метод (`GET`/`POST`/`PUT`/`PATCH`/`DELETE`);
- `path(args)` — функция, возвращающая путь запроса (с подстановкой параметров/query);
- `body(args)` — опционально, формирование тела запроса;
- `responseType` — опционально, тип ответа (напр. `blob`);
- `accessToken(args)` — опционально, явная передача access-токена в заголовки;
- `cache`, `queryOptions` — опции кеширования/повторов;
- `showErrorNotification` — показывать ли уведомление об ошибке (по умолчанию `true`).
Запрос выполняется единой функцией `fetch(endpoint, params, controller)` (`module/api/endpoints.ts`), которая через `httpService` (`module/api/http-service.ts`, обёртка `createHttpService` из `@sarex-team/sdk-js` поверх `axios`) отправляет запрос на базовый хост сервиса. Базовый хост выбирается по паре «`service` + окружение»: `httpService` создаётся с картой хостов `apiHosts` и текущим `buildEnv`, и разрешает хост внутри себя. Тот же алгоритм продублирован в экспортируемом хелпере `resolveHost(service)` из `module/api/hosts.ts`. Итоговый URL = `<базовый хост сервиса>` + `path` эндпоинта.
Окружение определяется глобальной константой `BUILD_ENV`, которую webpack подставляет в бандл через `DefinePlugin` (`webpack.config.js`) из переменной сборки `process.env.BUILD_ENV`. Допустимые значения проверяются в `env.js`: `local`/`stage`/`preprod`/`prod`/`contour`. В режиме `local` тип http-сервиса переключается на `"original"` (`module/api/http-service.ts`).
Ошибки маппируются в человекочитаемые сообщения в `module/api/errors.ts` (`resolveNetworkErrorByCode`, `resolveNetworkError`).
## Базовые хосты по сервисам и окружениям
Значения из `module/api/hosts.ts` (объект `apiHosts`). Итоговый URL = `<базовый хост сервиса>` + `path` эндпоинта.
| Сервис (`service`) | Назначение | `local` | `stage` | `preprod` | `prod` | `contour` |
| --- | --- | --- | --- | --- | --- | --- |
| `sarex` | Локальный сервис данных (`/api/core`, `/api/client`) | `/sarex-backend` | `/` | `/` | `/` | `/` |
| `documentations` | Сервис документации (документы, бандлы, файлы) | `https://stage-api.sarex.io/documentations` | `https://stage-api.sarex.io/documentations` | `https://api.preprod.sarex.io/documentations` | `https://api.sarex.io/documentations` | `/documentations` |
| `sarexApi` | Gateway/API Sarex (`/gateway`, `/eav`, `/cde`, `/transmittals`, `/flows`, `/issues`) | `https://stage-api.sarex.io` | `https://stage-api.sarex.io` | `https://api.preprod.sarex.io` | `https://api.sarex.io` | `/` |
| `workspaces` | Сервис рабочих областей | `https://stage-api.sarex.io/workspaces` | `https://stage-api.sarex.io/workspaces` | `https://api.preprod.sarex.io/workspaces` | `https://api.sarex.io/workspaces` | `/workspaces` |
| `workflows` | Сервис обработки документов | `https://stage-api.sarex.io/workflows` | `https://stage-api.sarex.io/workflows` | `https://api.preprod.sarex.io/workflows` | `https://api.sarex.io/workflows` | `/workflows` |
| `processes` | Сервис рабочих процессов (flows, reviews) | `https://stage-api.sarex.io/flows` | `https://stage-api.sarex.io/flows` | `https://api.preprod.sarex.io/flows` | `https://api.sarex.io/flows` | `/flows` |
| `remarks` | Сервис замечаний | `https://stage-api.sarex.io/remarks` | `https://stage-api.sarex.io/remarks` | `https://api.preprod.sarex.io/remarks` | `https://api.sarex.io/remarks` | `/remarks` |
| `files` | Сервис файлов | `https://stage-api.sarex.io/files` | `https://stage-api.sarex.io/files` | `https://api.preprod.sarex.io/files` | `https://api.sarex.io/files` | `/files` |
| `google` | Временное хранилище (GCS) | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` |
| `bim` | BIM-API | `https://stage-bim-api.sarex.io` | `https://stage-bim-api.sarex.io` | `https://bim-api.preprod.sarex.io` | `https://bim-api.sarex.io` | `""` |
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://idp.dev.stage.sarex.io` | `https://login.preprod.sarex.io` | `https://login.sarex.io` | `""` |
> Хосты `google`, `bim` и `zitadel` заданы в карте хостов, но напрямую в реестре `endpoints` не используются — они задействованы через SDK/вьюер (`@sarex-team/sdk-js`) и механизм аутентификации. В окружении `local` сервис `sarex` проксируется на `/sarex-backend`, в `stage`/`preprod`/`prod` — на `/` (относительные пути), в `contour` все сервисы работают по относительным путям изолированного контура. Отдельного `module-hosts.ts` (карты хостов удалённых модулей) в репозитории нет.
## Эндпоинты по сервисам
### `sarex` — Локальный сервис данных
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getSettings` | GET | `/api/client/settings/` | Клиентские настройки (кешируется, `stateTime: 5`) |
| `getUser` | GET | `/api/core/users/{userId}/` | Пользователь по id |
| `getUsersByCompanyId` | GET | `/api/core/users/?company={companyId}&limit={limit}&offset={offset}` | Пользователи компании (пагинация) |
| `getTargets` | GET | `/api/core/targets/` | Список таргетов |
| `getCompanies` | GET | `/api/core/companies/` | Список компаний |
| `getDepartmentById` | GET | `/api/core/admin/departments?company={companyId}` | Отделы компании |
| `getUsersPositionById` | GET | `/api/core/admin/positions/?company={companyId}` | Должности компании |
| `getMrpaList` | POST | `/api/core/mrpa/list/` | Список МЧД (фильтр по пользователю/компании) |
| `getByFullUrl` | GET | `{url}` | Запрос по произвольному URL |
### `documentations` — Сервис документации
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getDisks` | GET | `/api/v1/disks` | Список дисков |
| `getDocumentTypes` | GET | `/api/v1/documents/types` | Справочник типов документов |
| `getAllPermmission` | GET | `/api/v1/permissions` | Все права доступа |
| `getDocPermission` | GET | `/api/v1/documents/{id}/permissions` | Права доступа документа |
| `postPermission` | POST | `/api/v1/documents/{id}/permissions` | Выдать права сервисному аккаунту |
| `postBundle` | POST | `/api/v1/bundles` | Создать бандл |
| `postFile` | POST | `/api/v1/bundles/{bundleId}/{fileKey}?single_upload=1` | Загрузить файл (single upload) |
| `uploadFileStart` | POST | `/api/v1/bundles/{bundleId}/{key}/upload_multipart` | Начать multipart-загрузку файла |
| `uploadFolderStart` | POST | `/api/v1/bundles/{bundleId}/{key}/upload_multipart?upload_path={folderPath}` | Начать multipart-загрузку с указанием пути |
| `uploadPart` | PUT | `/api/v1/bundles/{bundleId}/{bundleKey}?part_number={partNumber}` | Загрузить часть файла |
| `bundleComplite` | POST | `/api/v1/bundles/{bundleId}/upload_finish` | Завершить загрузку бандла |
| `bundleCompleteUpload` | POST | `/api/v1/bundles/{bundleId}/upload_finish` | Завершить загрузку бандла (дубль ключа) |
| `getFile` | GET | `/api/v1/bundles/{bundleId}/{bundleKey}` | Получить файл бандла |
| `updateComment` | PATCH | `/api/v1/bundles/{bundleId}/comment` | Обновить комментарий бандла |
| `getBundles` | GET | `/api/v1/documents/{id}/bundles` | Бандлы документа |
| `addBundle` | POST | `/api/v1/documents/{documentId}/add_bundle` | Привязать бандл к документу |
| `moveBundles` | PATCH | `/api/v1/documents/{documentId}/move_bundles` | Переместить бандлы |
| `removeBundle` | DELETE | `/api/v1/bundles/{id}` | Удалить бандл |
| `postWorkspace` | POST | `/api/v1/workspaces` | Создать рабочую область |
| `getDocumentById` | GET | `/api/v1/documents/{id}` | Документ по id |
| `getDocumentWithBundles` | GET | `/api/v1/documents/{id}?extend=bundles` | Документ с бандлами |
| `changeDocument` | PATCH | `/api/v1/documents/{documentId}` | Переименовать документ |
| `changeDocumentName` | PATCH | `/api/v1/documents/{id}` | Переименовать документ |
| `updatePath` | PATCH | `/api/v1/documents/{id}/update-path` | Сменить родителя документа |
| `updateDocumentsPaths` | PATCH | `/api/v1/documents/update-path` | Массовая смена родителя |
| `deleteDocument` | DELETE | `/api/v1/documents/{id}` | Удалить документ |
| `deleteDocuments` | DELETE | `/api/v1/documents?document_ids={ids}` | Удалить несколько документов |
| `getFolderChildrenWithActiveProcesses` | POST | `/api/v1/documents/flows` | Дети папки с активными процессами |
| `downloadFile` | GET | `/api/v1/bundles/{lastBundleId}/{key}/download` | Скачать файл (с флагами `include_original_pdf`/`include_printable_pdf`) |
| `downloadFiles` | GET | `/api/v1/download_url/documents?document_ids={documentIds}` | Получить ссылку на скачивание документов |
| `downloadAllFiles` | GET | `/api/v1/bundles/{lastBundleId}/download` | Скачать все файлы бандла |
| `downloadFolder` | GET | `/api/v1/documents/{docId}/download?depth={depth}` | Скачать папку |
| `getFoldersDownloadUrl` | GET | `/api/v1/documents/get_folders_download_url?document_ids={ids}` | Ссылка на скачивание папок |
| `conversionFile` | POST | `/api/v1/conversion` | Конвертация документа (в IFC) |
| `addMarks` | PUT | `/api/v1/bundles/{bundleId}/marks` | Добавить штампы/QR/подписи |
| `sign` | POST | `/api/v1/bundles/{bundleId}/sign` | Подписать бандл |
| `cancelQrCode` | PATCH | `/api/v1/bundles/{bundleId}/cancel_qr` | Отменить QR-код |
| `restartWorkflow` | POST | `/api/v1/bundles/{bundleId}/restart` | Перезапустить workflow бандла |
| `getPublicLink` | GET | `/api/v1/public/documents/public_link/{public_link_id}` | Получить публичную ссылку |
| `createPublicLink` | POST | `/api/v1/documents/public_link` | Создать публичную ссылку |
| `updatePublicLink` | PATCH | `/api/v1/documents/public_link/{public_link_id}` | Обновить публичную ссылку |
| `deletePublicLink` | DELETE | `/api/v1/documents/public_link/{public_link_id}` | Удалить публичную ссылку |
| `removeDoc` | DELETE | `/api/v1/documents/bin?parent_id={id}` | Переместить в корзину |
| `recoveryDocument` | PATCH | `/api/v1/documents/bin/restore?parent_id={id}` | Восстановить из корзины |
| `copyFolderStructure` | POST | `/api/v1/documents/copy_structure` | Копировать структуру папок |
| `getTemplateJSON` | GET | `/api/v1/templates/{bundleId}` | JSON-шаблон бандла |
| `uploadSingleTemplate` | POST | `/api/v1/bundles/{bundleId}/{key}/upload_single` | Загрузить файл шаблона |
| `createReport` | POST | `/api/v1/documents/create_report` | Сформировать отчёт по документам |
| `updateChangelog` | PATCH | `api/v1/changelogs/{bundleId}` | Обновить запись журнала изменений |
| `createChangelog` | POST | `api/v1/changelogs/create` | Создать запись журнала изменений |
| `getFavorites` | GET | `/api/v1/favorite_documents?company_id={companyId}` | Избранные документы |
| `createFavoriteDocument` | POST | `/api/v1/favorite_documents` | Добавить документ в избранное |
| `deleteFavoriteDocument` | DELETE | `/api/v1/favorite_documents/{documentId}` | Убрать документ из избранного |
| `getNearestNameTemplate` | GET | `/api/v1/documents/{documentId}/name_template` | Ближайший шаблон именования (без уведомления об ошибке) |
| `getDocumentNameTemplate` | GET | `/api/v1/name_templates/{documentId}` | Шаблон именования документа (без уведомления об ошибке) |
| `createNameTemplate` | POST | `/api/v1/name_templates/create` | Создать шаблон именования |
| `updateNameTemplate` | PATCH | `/api/v1/name_templates/{documentId}` | Обновить шаблон именования |
| `deleteNameTemplate` | DELETE | `/api/v1/name_templates/{documentId}` | Удалить шаблон именования |
| `createLink` | POST | `/api/v1/links` | Создать ярлык (ссылку на документ) |
| `getBundleMrpas` | GET | `/api/v1/bundles/{bundleId}/mrpas` | МЧД бандла |
### `sarexApi` — Gateway/API Sarex
Через этот сервис проходят запросы к `/gateway`, `/eav`, `/cde`, а также к сабпутям других доменов, доступным через общий шлюз: `/transmittals`, `/flows`, `/issues`.
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getDocuments` | GET | `/gateway/api/v1/disks/{id}/documents?parent_id={parentId}&child_id={childId}` | Документы диска (по родителю/потомку) |
| `getFolderChildren` | GET | `/gateway/api/v1/disks/{diskId}/documents?parent_id={documentId}` | Дети папки |
| `getSearch` | GET | `/gateway/api/v4/disks/{diskId}/documents?...` | Поиск/фильтрация документов (root_document, limit, filters, bookmark) |
| `createDocument` | POST | `/gateway/api/v1/documents` | Создать документ/папку/проект |
| `fetchDocumentPaths` | POST | `/gateway/api/v1/documents/ancestors` | Предки документов |
| `getAttributesByDocument` | GET | `/gateway/api/v1/documents/{id}/attributes` | Атрибуты документа |
| `updateAttributes` | PUT | `/gateway/api/v1/documents/{id}/attributes` | Обновить атрибуты документа |
| `addAttributes` | POST | `/gateway/eav/api/v0/entity/` | Создать сущность атрибутов (EAV) |
| `getDefaultAttributes` | GET | `/eav/api/v0/schema/?model=document&company_id={companyId}&type_identifier={docType}` | Схема атрибутов по типу |
| `getAttributes` | GET | `/eav/api/v0/attribute/?company_id={params}` | Атрибуты компании |
| `getAttributesWithParams` | GET | `/eav/api/v0/schema/?model=document&{params}` | Схема атрибутов с параметрами |
| `updateSubscription` | POST | `/gateway/api/v1/subscription/` | Создать/обновить подписку |
| `deleteSubscription` | DELETE | `/gateway/api/v1/documents/{documentId}/subscription/` | Удалить подписку |
| `getActivityLog` | GET | `/gateway/api/v1/system_log/?model_names=document&...` | Журнал активности документа |
| `fetchResourceByDocumentId` | GET | `/gateway/api/v1/resources-rpc/resource-by-document-id/{id}/` | Ресурс по id документа |
| `getUsersWithTransmittalProjectPermissions` | GET | `/gateway/api/v2/users/?limit=5000&offset=0&resource_id={projectId}&permissions={permissions}` | Пользователи с правами в проекте |
| `getRemovedDocuments` | GET | `/gateway/api/v1/documents/bin?parent_id={id}{params}` | Удалённые документы в папке |
| `getRemovedFilteredDocuments` | GET | `/gateway/api/v1/documents/bin{params}` | Удалённые документы (фильтр) |
| `getTemplates` | GET | `/gateway/api/v1/disks/{diskId}/flat_documents?type={type}` | Плоский список документов по типу |
| `getFileSize` | GET | `/gateway/api/v1/documents/size?disk_id={diskId}&document_id={documentId}` | Размер документа |
| `completeUpload` | POST | `{uploadUrl}/complete` | Завершение загрузки (по переданному URL) |
| `createTransmittal` | POST | `/transmittals/api/v1/transmittals/create` | Создать трансмиттал |
| `getTransmittalById` | GET | `/transmittals/api/v1/transmittals/{transmittalId}` | Трансмиттал по id |
| `getTransmittalsByBundleId` | POST | `/transmittals/internal/v1/transmittals/by_bundle_ids` | Трансмитталы по id бандлов |
| `getTemplateListForSelect` | GET | `/transmittals/api/v1/transmittal_templates/select?resource={resourceId}` | Список шаблонов трансмитталов для выбора |
| `getSingleTemplate` | GET | `/transmittals/api/v1/transmittal_templates/{templateId}?resource={resourceId}` | Шаблон трансмиттала по id |
| `createPrescriptionDocument` | POST | `/issues/api/prescriptions/{prescriptionId}/generate/` | Сгенерировать документ по предписанию |
| `loadReviewData` | GET | `/flows/api/v1/documents/?bundle_ids={ids}&limit=10000&full=true` | Данные согласований по бандлам (с явным access-токеном) |
| `searchAssets` | POST | `eav/api/v4/assets/search/` | Поиск активов по id |
| `getProjectAssets` | GET | `eav/api/v4/assets/?linkable_to_project={resourceId}&parentId=null` | Активы проекта |
| `getAssetsList` | GET | `eav/api/v4/assets/?tenant_id={tenantId}&linkable_to_project={resourceId}&depth=0&...` | Список активов (поиск/пагинация) |
| `getAssets` | GET | `eav/api/v4/assets/?{params}` | Активы по произвольным параметрам |
| `getAssetLevels` | GET | `eav/api/v4/assets/?tenant_id={tenantId}&parent_id={parentAssetId}&path_contains={id}` | Уровни активов |
| `getBindings` | GET | `/cde/app/v1/bundles/{bundleId}/bindings` | Привязки бандла |
| `createSession` | POST | `/cde/app/v1/s32d/sessions/` | Создать S32D-сессию |
| `getSessionList` | GET | `/cde/app/v1/s32d/sessions/` | Список S32D-сессий |
| `uploadS32DSingle` | PUT | `/cde/app/v1/s32d/sessions/{sessionId}/archive` | Загрузить архив S32D (single) |
| `s32dMultipartInit` | POST | `/cde/app/v1/s32d/sessions/{sessionId}/archive/multipart` | Начать multipart-загрузку архива S32D |
| `s32dMultipartUploadPart` | PUT | `/cde/app/v1/s32d/sessions/{sessionId}/archive/multipart/{uploadId}/parts/{partNumber}` | Загрузить часть архива S32D |
| `s32dMultipartComplete` | POST | `/cde/app/v1/s32d/sessions/{sessionId}/archive/multipart/{uploadId}/complete` | Завершить multipart-загрузку S32D |
| `startS32DProcess` | POST | `/cde/app/v1/s32d/sessions/{sessionId}/process` | Запустить обработку S32D |
### `processes` — Сервис рабочих процессов (flows)
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getProcesses` | GET | `/api/v1/flows/?{query}` | Список процессов (flows) |
| `createReview` | POST | `/api/v1/reviews/` | Создать review |
| `deleteReview` | DELETE | `/api/v1/reviews/{id}/` | Удалить review |
| `activateReview` | PATCH | `/api/v1/reviews/{id}/approve/` | Активировать/утвердить review |
| `createReviewDocuments` | POST | `/api/v1/documents/` | Добавить документы в review |
### `workflows` — Сервис обработки документов
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getWorkflow` | GET | `/api/v1/workflows/{workflowId}` | Workflow по id |
| `getWorkflows` | POST | `/api/v1/workflows/batch` | Пакетное получение workflow |
### `workspaces` — Сервис рабочих областей
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getWorkspaces` | GET | `/api/v1/workspaces/{uuid}` | Рабочая область по uuid |
### `remarks` — Сервис замечаний
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `getRemarksTotalCount` | GET | `/api/v1/total_count` | Общее число замечаний |
### `files` — Сервис файлов
| Ключ | Метод | Путь | Назначение |
| --- | --- | --- | --- |
| `downloadBundlesMrpas` | POST | `/api/v1/bundles_mrpas/` | Скачать МЧД бандлов (ответ `blob`) |
## Обработка ошибок
Маппинг ошибок выполняется в `module/api/errors.ts`. Функция `fetch` (`module/api/endpoints.ts`) перехватывает `AxiosError` и вызывает:
- `resolveNetworkErrorByCode(service, code, endpoint, method, data)` — при HTTP-ответе с кодом ≥ 400 и при отмене запроса (`CanceledError`/`ERR_CANCELED`, внутренний код `-1` → «Запрос был отменен»);
- `resolveNetworkError(service, error)` — при сетевой ошибке без ответа сервера.
Базовый маппинг кодов (`httpCodeToError`): `400` — «некорректный формат запроса», `401` — «ошибка авторизации», `403` — «доступ запрещен», `404` — «ресурс не найден», `500` — «ошибка сервера». Для неизвестного кода подбирается ближайший (`4xx` → `400`, иначе → `500`). К сообщению добавляется человекочитаемое имя сервиса из `getServiceToName()` (напр. `documentations`/`sarexApi` → «Сервис документации», `sarex` → «Локальный сервис данных», `processes` → «Сервис рабочих процессов», `workflows` → «Сервис обработки документов», `remarks` → «Сервис замечаний», `workspaces` → «Сервис рабочих областей», `files` → «Сервис файлов»).
Для части кодов сообщение уточняется по эндпоинту и методу:
- `401` — набор сообщений `error401Messages` (истёкшая/невалидная сессия, завершённая сессия); по умолчанию — «Ваш токен невалиден, обновите страницу».
- `403` — тип определяется `determine403ErrorType(endpoint, method)` (напр. чтение/создание/редактирование/удаление/перемещение документа, скачивание, загрузка файла, управление доступом, изменение атрибутов, создание review/трансмиттала, доступ к диску/проекту), сообщения — `error403Messages`.
- `400` — тип определяется `determine400ErrorType(endpoint, method, data)` с анализом текста `data.message` (конфликт имени, дубликат, отсутствие/некорректность расширения, некорректный формат), сообщения — `error400Messages`.
- `409` — тип определяется `determine409ErrorType(endpoint, method, data)` (дубликаты имён документов/папок/ярлыков, конфликт при `copy_structure`), сообщения — `error409Messages`.
По умолчанию у запросов включён показ уведомления об ошибке (`showErrorNotification !== false`); отдельные эндпоинты отключают его (`getNearestNameTemplate`, `getDocumentNameTemplate`). Типы кодов ошибок описаны в `module/api/types.ts`.

View File

@ -0,0 +1,241 @@
# Конфигурация проекта pdm
Документ описывает все переменные окружения и способы конфигурирования сервиса **pdm** (Go). Сервис деплоится в namespace `documentations` как деплоймент `pdm` (образ `pdmv2`). Это шлюз/агрегатор поверх Postgres и множества внутренних сервисов Sarex (документации, ресурсы, ремарки, вложения, состояния, подписки, EAV, инспекции, релизы, BIM, трансмитталы и др.).
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` (функция `NewConfig`) через библиотеку [`cleanenv`](https://github.com/ilyakaznacheev/cleanenv) — вызовом `cleanenv.ReadEnv(cfg)`.
Особенности разбора:
- **Плоские имена переменных без общего префикса** — каждое поле помечено тегом `env:"..."` (напр. `POSTGRES_ADDRESS`, `RESOURCES_URL`). Вложенности/делимитера, как в pydantic-settings, здесь нет.
- **Обязательность** задаётся тегом `env-required:"true"` — при отсутствии такой переменной приложение не стартует (`config error`). В таблицах ниже дефолт `—` означает обязательное поле.
- **Значения по умолчанию** задаются тегом `env-default:"..."`.
- `cleanenv.ReadEnv` читает **только переменные окружения процесса** — конфиг-файла (yaml/toml) и авто-загрузки `.env` нет. Единственный файловый источник — JSON сервисного аккаунта S3 (`S3_SERVICE_ACCOUNT`).
Отдельно от env читается JSON-файл доступа к S3 — путь берётся из `S3_SERVICE_ACCOUNT`, разбор в `pkg/s3` (`NewFromConfigFile`). Формат файла (`.example.s3config.json`):
```json
{ "endpoint": "", "access_key_id": "", "secret_access_key": "", "use_ssl": true }
```
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса. `make config` копирует `.example.env``.env` и `.example.s3config.json``.s3config.json` (только если файлов ещё нет), но приложение **не загружает `.env` автоматически**его нужно экспортировать самому. В репозитории для этого есть `.envrc` (`use flake` + `dotenv`) под direnv |
| Локально (live-reload) | `make run-dev``air` (`.air.toml`), пересборка `./cmd/httpserver/main.go` |
| Kubernetes (Helm) | `.helm/values.yaml`: блок `services.api.envs` (обычные значения, ключ `_default` и переопределения по окружениям `stage`/`preprod`/`production`) и `services.api.secretEnvs` (значения из k8s-секретов через `secretKeyRef`). Используется зонтичный `universal-chart` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна в `workflow.rules` (по ветке/тегу), общие шаблоны из `generic/common-ci` |
Точки входа (`cmd/`):
| Команда | Точка входа | Назначение |
| --- | --- | --- |
| `make run` / `make build` | `cmd/httpserver/main.go` | HTTP API (Fiber). Читает конфиг и вызывает `internal/app/http.New(cfg).Run()` |
| — | `cmd/example/main.go`, `cmd/test/main.go` | Вспомогательные утилиты (не участвуют в деплое) |
Порядок инициализации в `internal/app/http/httpserver.go` (`App.Run`): трейсер (при `TRACER_USE=true`) → подключение к Postgres → инициализация HTTP-клиентов внешних сервисов → репозитории/usecase/сервисы → опциональный Valkey → сборка Fiber-приложения (`internal/controller/http/v1.Setup`) → `app.Listen(":8080")`.
## Переменные приложения
Все переменные читаются `config/config.go`. Дефолт `—` означает, что значение обязательно (`env-required:"true"`) и его отсутствие приводит к ошибке старта.
### App / Log
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `APP_NAME` | string | — | Имя приложения |
| `APP_VERSION` | string | — | Версия приложения |
| `LOG_LEVEL` | string | — | Уровень логирования (`pkg/logging`, напр. `DEBUG`/`INFO`) |
### Postgres (`POSTGRES`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
| `POSTGRES_DB` | string | — | Имя базы данных |
| `POSTGRES_USER` | string | — | Пользователь БД |
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
| `POSTGRES_POOL_SIZE` | int32 | — | Размер пула соединений (pgxpool) |
| `ENABLE_OBSERVABILITY` | bool | `false` | Инструментирование пула Postgres трейсингом (`otelpgx`) |
> DSN собирается в `Config.GetPostgresConnectionUrl()` как `postgres://user:password@address:port/db`**без параметра `sslmode`**. Отдельный флаг `ENABLE_SSL` из Helm кодом не читается (см. «Замечания»).
### HTTP (`HTTP`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `HTTP_PORT` | string | — | Порт HTTP. **Обязателен по тегу, но фактически не используется** — сервер слушает `:8080` (хардкод в `httpserver.go`) |
| `PUBLIC_KEY` | string (PEM) | `""` | Публичный ключ (PKIX) для проверки JWT. Формально необязателен, но при пустом/некорректном значении приложение падает (`panic` в `v1.Setup`) |
| `HTTP_BODY_LIMIT` | int | `268435456` (256 MB) | Максимальный размер тела запроса, байт |
| `HTTP_READ_BUFFER_SIZE` | int | `98304` (96 KB) | Размер буфера чтения запроса, байт |
### Auth и хосты внешних сервисов
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_BASIC_AUTH` | string | — | Basic-токен для авторизации в бэкенде Sarex/Django (клиенты `targets`, `users`) |
| `DJANGO_HOST` | string | — | Базовый URL Django/бэкенда. Используется сразу двумя секциями — `USERS.UserHost` и `SA.SAHost` (accounts/companies/django-клиенты) |
| `NOTES_URL` | string | — | Сервис заметок (`notes`) |
| `FLOWS_URL` | string | — | Сервис процессов (`flows`) |
| `RESOURCES_URL` | string | — | Сервис ресурсов/IAM (`resources`) |
| `REMARKS_URL` | string | — | Сервис замечаний (`remarks`) |
| `ATTACHMENTS_URL` | string | — | Сервис вложений (`attachments`) |
| `STATES_URL` | string | — | Сервис состояний/рабочих областей (`workspaces`) |
| `SUBSCRIPTIONS_URL` | string | — | Сервис подписок (`subscriptions`) |
| `EAV_URL` | string | — | Сервис атрибутов EAV |
| `INSPECTIONS_URL` | string | — | Сервис инспекций |
| `SYSTEM_LOG_URL` | string | — | Сервис системного лога |
| `TARGET_URL` | string | — | Сервис таргетов |
| `DOCUMENTATION_URL` | string | — | Сервис документаций (documentation-api-v2) |
| `BIM_V2_HOST` | string | — | BIM core API v2 |
| `NOTES_URL` | string | — | (см. выше) |
| `DRAWINGS_INTERNAL_URL` | string | — | Внутренний URL сервиса чертежей |
| `RELEASES_URL` | string | — | URL GitLab для получения релизов |
| `RELEASES_TOKEN` | string | — | Токен доступа к GitLab (`RELEASES_URL`) |
### Ресурсы и фильтр разрешений (`RESOURCES`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_PERMISSIONS_FILTER` | bool | `false` | Включить фильтрацию по разрешениям на уровне сервиса документов |
| `PERMISSIONS_FILTER_COMPANIES` | string (JSON-массив) | `[133, 256, 247, 219, 248, 194, 242, 260, 252, 255, 239, 125, 116, 92, 311, 170]` | Список ID компаний, к которым применяется фильтр. Парсится `json.Unmarshal` в `[]uint64` |
### Thumbnails (`ATTACHMENTS`, `STATES`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WIDTH_THUMB_ATTACHMENTS` | int | `100` | Ширина превью вложений |
| `HEIGHT_THUMB_ATTACHMENTS` | int | `100` | Высота превью вложений |
| `WIDTH_THUMB_STATES` | int | `100` | Ширина превью состояний |
| `HEIGHT_THUMB_STATES` | int | `100` | Высота превью состояний |
### Subscriptions / System log — доп. поля
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_SUBSCRIPTIONS` | bool | `true` | Включить интеграцию с подписками в сервисе документов |
| `API_HOST_PREFIX` | string | `""` | Префикс хоста API (напр. `/gateway`), используется сервисом системного лога |
### S3 (`S3`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `S3_SERVICE_ACCOUNT` | string | — | Путь к JSON-файлу с доступом к S3 (`endpoint`, `access_key_id`, `secret_access_key`, `use_ssl`). Разбирается в `pkg/s3` |
### Transmittals (`Transmittals`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `TRANSMITTALS_ENABLE` | bool | `true` | Включить клиент трансмитталов; при `false` используется stub-реализация |
| `TRANSMITTALS_BASE_URL` | string | — | Базовый URL сервиса трансмитталов. Обязателен даже при `TRANSMITTALS_ENABLE=false` |
### Observability / Tracer
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `OBSERVABILITY_COLLECTOR_ENDPOINT` | string | `""` | Эндпоинт OTLP-коллектора (метрики/наблюдаемость) |
| `TRACER_USE` | bool | `false` | Включить трейсинг OpenTelemetry (`golang-fiber-otel-tools`) |
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора трейсов |
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
| `SERVICE_NAME` | string | `Pdm` | Имя сервиса в трейсах |
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя логгера OTel |
### Valkey (`VALKEY`) — кэш пользователей
Подключение опционально: если `VALKEY_ADDR` пуст — клиент не создаётся (кэш пользователей отключён). Ошибки подключения/пинга не фатальны (лог `Warn`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `VALKEY_ADDR` | string | `""` | Адрес Valkey (при пустом — кэш выключен) |
| `VALKEY_LOGIN` | string | `""` | Логин |
| `VALKEY_HOST` | string | `""` | Хост |
| `VALKEY_PASSWORD` | string | `""` | Пароль |
| `VALKEY_DB` | int | `0` | Номер БД |
| `VALKEY_SSL` | bool | `false` | Использовать TLS |
| `VALKEY_SSL_CA_CERTS` | string | `""` | Путь к CA-сертификату для TLS |
## Переменные инфраструктуры и сборки
Не читаются кодом приложения (`config/config.go`), но участвуют в сборке/деплое:
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `GITLAB_CREDENTIALS` | `Dockerfile` (build-arg), `.gitlab-ci.yml` (`BUILD_ARGS`) | Учётные данные для доступа к приватным Go-модулям `gitlab.sarex.io` при сборке |
| `SERVICE_NAME` (CI) | `.gitlab-ci.yml` | `pdmv2` — имя сервиса в пайплайне (не путать с `SERVICE_NAME` трейсера) |
| `DOCKERFILE_PATH`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` | Путь к Dockerfile, источник триггера |
| `ENABLE_LINTER`, `ENABLE_BUILD_CHART`, `ENABLE_BUILD_IMAGE`, `ENABLE_STATE_UPDATE`, `ENABLE_DEPLOY` | `.gitlab-ci.yml` | Флаги стадий пайплайна |
| `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `IMAGE_NAME` | `.gitlab-ci.yml` | Параметры окружения/деплоя Helm |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Обычные значения задаются в `services.api.envs` (ключ `_default` + переопределения по `stage`/`preprod`/`production`) и содержат переменные приложения, описанные выше, различаясь адресами БД/сервисов, `LOG_LEVEL`, доменами и т.п.
Значения из секретов (`services.api.secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secretName`, prod/по умолчанию) | Ключ (`secretKey`) |
| --- | --- | --- |
| `POSTGRES_DB` | `documentations-postgresql-secret` (preprod: `ya-pg-secret`) | `database` |
| `POSTGRES_PORT` | `documentations-postgresql-secret` | `port` |
| `POSTGRES_ADDRESS` | `documentations-postgresql-secret` | `host` |
| `POSTGRES_USER` | `documentations-postgresql-secret` | `username` |
| `POSTGRES_PASSWORD` | `documentations-postgresql-secret` | `password` |
| `YC-PG-CERTIFICATE` | `documentations-postgresql-secret` (preprod: `yc-pg-certificate`) | `ca.crt` |
| `DJANGO_BASIC_AUTH` | `django-auth` | `key` |
| `PUBLIC_KEY` | `public-key` | `key` |
| `RELEASES_TOKEN` | `releases-token` | `key` |
| `VALKEY_ADDR` | `valkey-secret` | `url` |
| `VALKEY_LOGIN` | `valkey-secret` | `login` |
| `VALKEY_PASSWORD` | `valkey-secret` | `password` |
| `VALKEY_HOST` | `valkey-secret` | `host` |
| `VALKEY_PORT` | `valkey-secret` | `port` |
| `VALKEY_CA_CERTS` | `valkey-secret` | `cert` |
Помимо env, чарт монтирует секрет `documentations-yc-s3` как том в `/etc/sarex/yc-s3-storage` (readOnly). Именно на файл `/etc/sarex/yc-s3-storage/yc-s3-service-account.json` указывает `S3_SERVICE_ACCOUNT` в prod-конфигурации.
Прочие значения чарта (не переменные приложения): `deployment.*` (имя `pdm-api`, реплики `stage=3`/`preprod=2`/`production=8`, ресурсы `cpu=1`, `memory=2Gi`), `image.name` (`cr.yandex/.../pdm_v2`), `service.*` (порт `8080`), `imagePullSecrets` (`dockerhub`), `probes.*` (startup/liveness/readiness по `/internal/healthz/*` на порту `8080`).
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml` ref `apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace | `universal-chart.global.env` |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `documentations` | `stage` |
| ветка `master` | `preprod` | `documentations-preprod` | `preprod` |
| тег (`CI_COMMIT_TAG`) | `prod` | `documentations-prod` | `production` |
| merge request | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
Общие переменные: `SERVICE_NAME=pdmv2`, `RELEASE_NAME=pdmv2`, `CHART_NAME=pdmv2`, `CHART_VERSION=0.0.1-<env>`, `DOCKERFILE_PATH=Dockerfile`.
## Замечания и потенциальные проблемы
- **`HTTP_PORT` фактически игнорируется.** Поле обязательно (`env-required`), но сервер жёстко слушает `:8080` (`app.Listen(":8080")` в `httpserver.go`). Реальный порт задаётся только этим хардкодом; в Helm `HTTP_PORT` и `service.port` совпадают со `8080`, поэтому расхождение незаметно.
- **`PUBLIC_KEY` де-факто обязателен.** По тегам он необязателен (`env:"PUBLIC_KEY"`), но `v1.Setup` при пустом/битом PEM вызывает `panic` (`failed to parse PEM block...`). Для локального запуска нужен валидный публичный ключ.
- **SSL к Postgres в коде не настраивается.** DSN формируется без `sslmode` (`GetPostgresConnectionUrl`). Helm-переменные `ENABLE_SSL` и секрет `YC-PG-CERTIFICATE` кодом **не читаются** — подключение к БД идёт без TLS-параметров на уровне DSN.
- **Множество Helm-переменных не читается приложением.** В `services.api.envs`/`secretEnvs` присутствуют переменные, отсутствующие в `config/config.go`, — вероятно, унаследованы от `documentation-api`: `API_ADDRESS`, `API_ADDRESS_FILE`, `ENABLE_SSL`, `ENABLE_S3`, `FILE_URL_EXTERNAL`, `WORKFLOW_URL`, `WORKSPACE_URL`, `BIM_API_URL`, `BIM_API_V2_URL`, `BIM_API_URL_EXTERNAL`, `WORKSPACE_BUNDLE_VERSION`, `WORKFLOW_IMAGES_VERSION`/`WORKFLOWS_IMAGES_VERSION`, `NAMESPACE`, `DJANGO_ORIGINATOR`, `USE_EXPERIMENTAL`, `READ_WRITE_TIMEOUT_FILE_STREAM`, `CACHE_DEFAULT_EXPIRATION`, `CACHE_CLEANUP_INTERVAL`, `USE_CACHE_IN_FILE_STREAMER`, `SENTRY_DSN`, `SENTRY_DEBUG`, `ENVIRONMENT`, `YC-PG-CERTIFICATE`. Они не влияют на работу pdm.
- **Несовпадение имён Valkey.** Код ждёт `VALKEY_SSL_CA_CERTS` (`config.go`), а Helm-секрет прокидывает `VALKEY_CA_CERTS`; также Helm задаёт `VALKEY_PORT`, который код не читает (адрес берётся целиком из `VALKEY_ADDR`). В результате CA-сертификат и порт из секрета до приложения не доходят.
- **Секции `USERS`/`SA` используют один и тот же env `DJANGO_HOST`.** Оба поля (`UserHost`, `SAHost`) читают одну переменную.
- **`config.env` в репозитории содержит реальные учётные данные** (пароль Postgres, `DJANGO_BASIC_AUTH`, токены) — это конфигурация для отладки, не шаблон. Для примеров использовать `.example.env`; `config.env` не должен попадать в окружения и подлежит ротации секретов.
- **`ENABLE_SQL_QUERY` из `config.env` кодом не читается** — в `config/config.go` такого поля нет.
- **`cleanenv.ReadEnv` не загружает `.env` автоматически.** `make config` лишь создаёт файлы-шаблоны (`.env`, `.s3config.json`) при их отсутствии; переменные нужно экспортировать вручную (например, через `direnv`/`.envrc`).
- **v0-роутер (echo) не подключён.** В `cmd/httpserver/main.go` используется только `internal/controller/http/v1.Setup` (Fiber). Пакет `internal/controller/http/v0` (на `labstack/echo`) в рантайме не задействован.
## Минимальный набор для локального запуска
Приложение поднимается через `make run` (или `make run-dev` с `air`). Минимально необходимо задать (обязательные поля `config/config.go`):
- `APP_NAME`, `APP_VERSION`, `LOG_LEVEL`;
- `POSTGRES_ADDRESS`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_PORT`, `POSTGRES_POOL_SIZE`;
- `HTTP_PORT` (любой — фактически используется `:8080`), а также **валидный** `PUBLIC_KEY` (иначе `panic`);
- `DJANGO_BASIC_AUTH`, `DJANGO_HOST`;
- хосты внешних сервисов: `NOTES_URL`, `FLOWS_URL`, `RESOURCES_URL`, `REMARKS_URL`, `ATTACHMENTS_URL`, `STATES_URL`, `SUBSCRIPTIONS_URL`, `EAV_URL`, `INSPECTIONS_URL`, `SYSTEM_LOG_URL`, `TARGET_URL`, `DOCUMENTATION_URL`, `BIM_V2_HOST`, `DRAWINGS_INTERNAL_URL`;
- `RELEASES_URL`, `RELEASES_TOKEN`;
- `S3_SERVICE_ACCOUNT` (путь к `.s3config.json`) и заполненный сам JSON-файл;
- `TRANSMITTALS_BASE_URL` (обязателен даже при выключенных трансмитталах).
Необязательные (есть дефолты): `ENABLE_OBSERVABILITY`, `HTTP_BODY_LIMIT`, `HTTP_READ_BUFFER_SIZE`, `ENABLE_PERMISSIONS_FILTER`, `PERMISSIONS_FILTER_COMPANIES`, `USE_SUBSCRIPTIONS`, `WIDTH_THUMB_*`/`HEIGHT_THUMB_*`, `API_HOST_PREFIX`, `TRANSMITTALS_ENABLE`, `TRACER_*`, `SERVICE_NAME`, `VALKEY_*`, `OBSERVABILITY_COLLECTOR_ENDPOINT`.
Готовые значения-примеры приведены в `pdm.env.example` (на основе `.example.env` репозитория).

View File

@ -0,0 +1,116 @@
# Пример переменных окружения сервиса pdm (документируемый образ pdmv2).
# Основан на .example.env репозитория; переменные читаются config/config.go
# библиотекой cleanenv (cleanenv.ReadEnv). Значения-заглушки, замените своими.
# Файл автоматически НЕ загружается приложением — переменные нужно экспортировать
# в окружение процесса (напр. через direnv/.envrc или `set -a && . ./.env`).
# App
APP_NAME=pdm
APP_VERSION=0.1.0
# Logger
LOG_LEVEL=DEBUG
# Postgres
POSTGRES_ADDRESS=127.0.0.1
POSTGRES_DB=documentations
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password
POSTGRES_PORT=5432
POSTGRES_POOL_SIZE=10
ENABLE_OBSERVABILITY=false
# Http
# ВНИМАНИЕ: HTTP_PORT читается как обязательный, но сервер всё равно слушает :8080 (хардкод).
HTTP_PORT=8001
# PUBLIC_KEY — публичный ключ в формате PEM (PKIX) для проверки JWT.
# Формально не обязателен по тегам, но при пустом/некорректном значении приложение падает (panic).
PUBLIC_KEY=
HTTP_BODY_LIMIT=268435456
HTTP_READ_BUFFER_SIZE=98304
# Auth (Basic-токен для походов в Django/бэкенд Sarex)
DJANGO_BASIC_AUTH=
# Users / service accounts (один и тот же хост используется как DJANGO_HOST)
DJANGO_HOST=https://stage.sarex.io
# Notes
NOTES_URL=https://stage-api.sarex.io/notes
# Flows
FLOWS_URL=https://stage-api.sarex.io/flows
# Resources
RESOURCES_URL=http://localhost:9000
ENABLE_PERMISSIONS_FILTER=false
PERMISSIONS_FILTER_COMPANIES=
# Remarks
REMARKS_URL=https://stage-api.sarex.io/remarks
# Attachments
ATTACHMENTS_URL=http://localhost:8000
WIDTH_THUMB_ATTACHMENTS=100
HEIGHT_THUMB_ATTACHMENTS=100
# States (workspaces)
STATES_URL=https://stage-api.sarex.io/workspaces
WIDTH_THUMB_STATES=100
HEIGHT_THUMB_STATES=100
# Subscriptions
USE_SUBSCRIPTIONS=true
SUBSCRIPTIONS_URL=https://stage-api.sarex.io/subscriptions
# Eav
EAV_URL=http://stage-api.sarex.io/eav
# Inspections
INSPECTIONS_URL=https://stage-api.sarex.io/inspections
# System log
SYSTEM_LOG_URL=http://localhost:8888
API_HOST_PREFIX=/gateway
# Target
TARGET_URL=https://stage.sarex.io
# Documentation api
DOCUMENTATION_URL=http://localhost:6666/
# Bim v2
BIM_V2_HOST=http://localhost:8888/
# Observability (OTLP-коллектор)
OBSERVABILITY_COLLECTOR_ENDPOINT=
# Releases (GitLab)
RELEASES_URL=https://gitlab.com
RELEASES_TOKEN=
# Drawings
DRAWINGS_INTERNAL_URL=http://localhost:6666
# S3 (путь к JSON-файлу с сервисным аккаунтом, см. .example.s3config.json)
S3_SERVICE_ACCOUNT=.s3config.json
# Transmittals
TRANSMITTALS_ENABLE=true
TRANSMITTALS_BASE_URL=http://transmittal-service.transmittal-api-stage
# Tracer (OpenTelemetry)
TRACER_USE=false
TRACER_HOST=localhost:4317
TRACER_USE_INSECURE=true
SERVICE_NAME=Pdm
TRACER_LOGGER_NAME=tracer_logger
# Valkey (кэш пользователей, опционально; при пустом VALKEY_ADDR не подключается)
VALKEY_ADDR=
VALKEY_LOGIN=
VALKEY_HOST=
VALKEY_PASSWORD=
VALKEY_DB=0
VALKEY_SSL=false
VALKEY_SSL_CA_CERTS=

View File

@ -0,0 +1,918 @@
openapi: 3.0.3
info:
title: PDM API
version: "0.1.0"
description: |
REST API сервиса **pdm** (`pdm/pdm`, образ `pdmv2`) — шлюз/агрегатор над
Postgres и множеством внутренних сервисов Sarex: документации, ресурсы и
разрешения, замечания, вложения, состояния/рабочие области, подписки,
атрибуты (EAV), инспекции, релизы, заметки, чертежи, BIM, трансмитталы,
системный лог.
Сервис написан на Go (**Fiber v2**). Приложение собирается в
`internal/controller/http/v1.Setup` (`internal/controller/http/v1/router.go`),
точка входа — `cmd/httpserver/main.go`. Роутинг делится на группы:
- публичный API — префикс `/api` с версиями `v1`/`v2`/`v3`/`v4`
(`group.Group("v1")` и т.д.);
- внутренний API — префикс `/internal` (healthcheck, pprof, служебные
ручки), без аутентификации на уровне приложения.
Готовой спецификации (swagger) в репозитории нет — данный документ
восстановлен из роутеров `internal/controller/http/**/router.go`.
Сервер слушает порт `8080` (хардкод в `internal/app/http/httpserver.go`).
### Аутентификация
Аутентификация применяется middleware `pkg/httpserver/middleware/auth.go` ко
всем путям с префиксом `/api`. Токен передаётся заголовком
`Authorization: Bearer <jwt>`. Поддерживаются два режима:
1. **sarex-backend** (по умолчанию) — если заголовка `Identity` нет, подпись
основного JWT проверяется публичным ключом из `PUBLIC_KEY` (PKIX). Из
claims извлекаются `user_id`, `is_superuser`, `company_ids`,
`service_accounts`, `permissions` и т.д.
2. **Zitadel** — если передан дополнительный заголовок
`Identity: Bearer <jwt>`, полезная нагрузка берётся из этого токена
(`urn:zitadel:iam:user:metadata`); основной токен кладётся как
`access_token`. Подпись identity-токена приложением не проверяется
(`ParseUnverified`) — доверие обеспечивается сетевым слоем.
При отсутствии заголовка `Authorization`, неверной схеме (не `Bearer`) или
ошибке разбора токена возвращается **401 Unauthorized** (пустое тело).
Пути `/internal/*` аутентификации на уровне приложения не требуют.
### Пагинация
Списочные эндпоинты используют пагинацию через query-параметры `limit` и
`offset` (напр. `internal/controller/http/v1/document/downloaded.go`,
`flat.go`). Единого конверта ответа нет — формат зависит от эндпоинта.
### Обработка ошибок
Ошибки бизнес-слоя оборачиваются в `AppError`
(`internal/app_errors/errors.go`) и сериализуются как JSON
`{ "message": "...", "error_code": "GW-XXXX" }`. HTTP-статус выбирается по
`error_code` в `internal/app_errors/middleware.go`:
| error_code | Статус | Значение |
| --- | --- | --- |
| `GW-0001` | 404 | Ресурс не найден |
| `GW-0002` | 401 | Не аутентифицирован |
| `GW-0003` | 403 | Нет доступа |
| `GW-0004` | 400 | Некорректный запрос |
| `GW-0014` | 400 | Ошибка валидации состояния |
| `GW-0000` | 500 | Системная ошибка |
Не все роутеры используют обёртку `middleware.ErrorHandler` — часть
обработчиков возвращает ошибки/статусы напрямую, поэтому формат ответа об
ошибке может отличаться от `AppError`.
### Замечания (расхождения кода)
- Пути с сегментом `*` (напр. `/api/v1/disks/*/documents`,
`/api/v1/documents/*/attributes`, `/api/v1/targets/*/remarks/*`) — это
«жадные» wildcard-сегменты Fiber, захватывающие путь документа/цели
целиком (включая `/`). В спецификации они представлены параметром пути.
- Часть эндпоинтов завершается слэшем (`/`), часть — нет; поведение
определяется определениями групп во Fiber.
- v0-роутер (`internal/controller/http/v0`, на `labstack/echo`) в рантайме
не подключён.
contact:
name: pdm
url: https://gitlab.com/sarex-team/pdm
servers:
- url: http://pdm-api.documentations:8080
description: Stage (внутренний адрес в кластере, namespace documentations)
- url: http://pdm-api.documentations-preprod:8080
description: Preprod (внутренний адрес в кластере)
- url: http://pdm-api.documentations-prod:8080
description: Production (внутренний адрес в кластере)
security:
- bearerAuth: []
tags:
- name: documents
description: Документы, диски, проекты, атрибуты (v1/v2/v3/v4)
- name: resources
description: Ресурсы и разрешения (v1/v2)
- name: remarks
description: Замечания по таргетам
- name: attachments
description: Вложения
- name: states
description: Состояния рабочих областей
- name: subscriptions
description: Подписки
- name: system_log
description: Системный лог
- name: targets
description: Таргеты
- name: inspections
description: Инспекции
- name: users
description: Пользователи
- name: eav
description: Атрибуты (EAV)
- name: releases
description: Релизы
- name: notes
description: Заметки
- name: drawings
description: Чертежи (сечения и экспорты)
- name: internal
description: Служебные эндпоинты (без аутентификации)
paths:
# ---------------- v1: documents ----------------
/api/v1/disks/{diskPath}/documents:
get:
tags: [documents]
summary: Список документов по диску (v1)
parameters:
- $ref: '#/components/parameters/DiskPath'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/disks/{diskPath}/downloaded_documents:
get:
tags: [documents]
summary: Список скачанных документов диска
parameters:
- $ref: '#/components/parameters/DiskPath'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/disks/{diskPath}/flat_documents:
get:
tags: [documents]
summary: Плоский список документов диска
parameters:
- $ref: '#/components/parameters/DiskPath'
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/disks/{diskId}/thumbnails:
post:
tags: [documents]
summary: Превью документов диска
parameters:
- name: diskId
in: path
required: true
schema: { type: string }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/projects/:
get:
tags: [documents]
summary: Получить проект по ID документа
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents:
post:
tags: [documents]
summary: Создать документ
responses:
'200': { $ref: '#/components/responses/Ok' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/{documentPath}/attributes:
get:
tags: [documents]
summary: Атрибуты документа
parameters:
- $ref: '#/components/parameters/DocumentPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
put:
tags: [documents]
summary: Создать/обновить атрибуты документа
parameters:
- $ref: '#/components/parameters/DocumentPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/{documentPath}/subscription/:
delete:
tags: [documents]
summary: Удалить подписку по ID документа
parameters:
- $ref: '#/components/parameters/DocumentPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/bin:
get:
tags: [documents]
summary: Список удалённых документов (корзина)
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/approving_users:
post:
tags: [documents]
summary: Согласующие пользователи
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/bundle_versions:
post:
tags: [documents]
summary: Версии бандлов документов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/ancestors:
post:
tags: [documents]
summary: Предки документов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/size:
get:
tags: [documents]
summary: Размер папки
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/related_documents:
get:
tags: [documents]
summary: Связанные документы
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [documents]
summary: Создать связи документов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/documents/related_documents/bulk_delete:
post:
tags: [documents]
summary: Массовое удаление связей документов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: remarks ----------------
/api/v1/targets/{targetPath}/remarks:
post:
tags: [remarks]
summary: Создать замечание для таргета
parameters:
- $ref: '#/components/parameters/TargetPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/targets/{targetPath}/remarks/{remarkPath}:
get:
tags: [remarks]
summary: Получить замечание
parameters:
- $ref: '#/components/parameters/TargetPath'
- $ref: '#/components/parameters/RemarkPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
patch:
tags: [remarks]
summary: Обновить замечание
parameters:
- $ref: '#/components/parameters/TargetPath'
- $ref: '#/components/parameters/RemarkPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: resources ----------------
/api/v1/resources/:
get:
tags: [resources]
summary: Список ресурсов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resources/users-with-resources/:
post:
tags: [resources]
summary: Пользователи с ресурсами
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resources/permissions-bulk/:
patch:
tags: [resources]
summary: Массовое обновление разрешений
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resources/{uuid}/:
get:
tags: [resources]
summary: Ресурс по UUID
parameters:
- name: uuid
in: path
required: true
schema: { type: string, format: uuid }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resource-permissions/:
get:
tags: [resources]
summary: Разрешения на ресурсы
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [resources]
summary: Массовое создание разрешений на ресурс
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resource-permissions/{id}/:
delete:
tags: [resources]
summary: Удалить разрешение на ресурс
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/bulk_delete/resource-permissions/:
post:
tags: [resources]
summary: Массовое удаление разрешений на ресурс
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/company-resource-permissions/:
get:
tags: [resources]
summary: Разрешения компаний на ресурсы
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [resources]
summary: Создать разрешение компании на ресурс
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/company-resource-permissions/{id}/:
delete:
tags: [resources]
summary: Удалить разрешение компании на ресурс
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resources-rpc/resource-by-document-id/{id}/:
get:
tags: [resources]
summary: Ресурс по ID документа
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/resources-rpc/parent-document-by-resource-id/{uuid}/:
get:
tags: [resources]
summary: Родительская папка проекта по UUID ресурса
parameters:
- name: uuid
in: path
required: true
schema: { type: string, format: uuid }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: attachments ----------------
/api/v1/attachments:
post:
tags: [attachments]
summary: Создать вложение
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [attachments]
summary: Список вложений
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/attachments/{id}:
get:
tags: [attachments]
summary: Вложение по ID
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
tags: [attachments]
summary: Удалить вложение
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: states (workspace) ----------------
/api/v1/workspace/{ws_id}:
post:
tags: [states]
summary: Создать состояние с вложением
parameters:
- $ref: '#/components/parameters/WsId'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [states]
summary: Состояния с вложениями
parameters:
- $ref: '#/components/parameters/WsId'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/workspace/{ws_id}/dynamic_states:
post:
tags: [states]
summary: Создать динамические состояния
parameters:
- $ref: '#/components/parameters/WsId'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [states]
summary: Динамические состояния
parameters:
- $ref: '#/components/parameters/WsId'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/workspace/states/{id}:
get:
tags: [states]
summary: Состояние по ID
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/workspace/dynamic_states/{id}:
get:
tags: [states]
summary: Динамическое состояние по ID
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: subscriptions ----------------
/api/v1/subscription/:
post:
tags: [subscriptions]
summary: Создать подписку
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: system_log ----------------
/api/v1/system_log/:
get:
tags: [system_log]
summary: Отфильтрованный системный лог
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
post:
tags: [system_log]
summary: Записать в системный лог
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: targets ----------------
/api/v1/targets/:
get:
tags: [targets]
summary: Список таргетов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/targets/{targetPath}/:
get:
tags: [targets]
summary: Таргет по ID
parameters:
- $ref: '#/components/parameters/TargetPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: inspections ----------------
/api/v1/inspections/:
post:
tags: [inspections]
summary: Создать инспекцию
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [inspections]
summary: Список инспекций
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/{id}:
get:
tags: [inspections]
summary: Инспекция по ID
parameters:
- $ref: '#/components/parameters/IdInt'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
patch:
tags: [inspections]
summary: Обновить инспекцию
parameters:
- $ref: '#/components/parameters/IdInt'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
tags: [inspections]
summary: Удалить инспекцию
parameters:
- $ref: '#/components/parameters/IdInt'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/created_at/daterange:
get:
tags: [inspections]
summary: Диапазон дат создания
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/inspection_date/daterange:
get:
tags: [inspections]
summary: Диапазон дат инспекций
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/available_responsible_users:
get:
tags: [inspections]
summary: Доступные ответственные пользователи
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/available_inspection_dates:
get:
tags: [inspections]
summary: Доступные даты инспекций
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/inspections/export:
get:
tags: [inspections]
summary: Экспорт инспекций
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: users / eav / releases / notes ----------------
/api/v1/users/:
get:
tags: [users]
summary: Пользователи по разрешению на ресурс
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/attribute/:
get:
tags: [eav]
summary: Получить атрибуты (EAV)
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/releases/{id}:
get:
tags: [releases]
summary: Список релизов по ID проекта
parameters:
- $ref: '#/components/parameters/IdInt'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/notes/{service_id}/{entity_id}/{instance_id}/:
get:
tags: [notes]
summary: Заметки по сервису/сущности/инстансу
parameters:
- { name: service_id, in: path, required: true, schema: { type: string } }
- { name: entity_id, in: path, required: true, schema: { type: string } }
- { name: instance_id, in: path, required: true, schema: { type: string } }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v1: drawings ----------------
/api/v1/drawings/cross-sections:
post:
tags: [drawings]
summary: Создать сечение
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [drawings]
summary: Сечения по ID инстанса
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/drawings/cross-sections/{cross_section_id}/data:
get:
tags: [drawings]
summary: Данные сечения
parameters:
- { name: cross_section_id, in: path, required: true, schema: { type: string } }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/drawings/cross-sections/{cross_section_id}:
delete:
tags: [drawings]
summary: Удалить сечение
parameters:
- { name: cross_section_id, in: path, required: true, schema: { type: string } }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/drawings/exports:
post:
tags: [drawings]
summary: Создать экспорт
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
get:
tags: [drawings]
summary: Список экспортов
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v1/drawings/exports/{export_id}:
delete:
tags: [drawings]
summary: Удалить экспорт
parameters:
- { name: export_id, in: path, required: true, schema: { type: string } }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v2 ----------------
/api/v2/resources/:
get:
tags: [resources]
summary: Расширенный список ресурсов (v2)
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v2/resources:
post:
tags: [resources]
summary: Создать расширенный ресурс (v2)
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v2/resources/{id}:
get:
tags: [resources]
summary: Расширенный ресурс по ID (v2)
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
patch:
tags: [resources]
summary: Обновить расширенный ресурс (v2)
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
delete:
tags: [resources]
summary: Удалить расширенный ресурс (v2)
parameters:
- $ref: '#/components/parameters/IdPathPlain'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v2/users/:
get:
tags: [users]
summary: Пользователи по ресурсу (v2)
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v2/disks/{diskPath}/documents:
get:
tags: [documents]
summary: Список документов по диску (v2)
parameters:
- $ref: '#/components/parameters/DiskPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- v3 / v4 ----------------
/api/v3/disks/{diskPath}/documents:
get:
tags: [documents]
summary: Упрощённый список документов по диску (v3)
parameters:
- $ref: '#/components/parameters/DiskPath'
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
/api/v4/disks/{disk_id}/documents:
get:
tags: [documents]
summary: Поиск документов по диску (v4)
parameters:
- { name: disk_id, in: path, required: true, schema: { type: string } }
responses:
'200': { $ref: '#/components/responses/Ok' }
'401': { $ref: '#/components/responses/Unauthorized' }
# ---------------- internal ----------------
/internal/v1/documents/approving_users:
post:
tags: [internal]
summary: Согласующие пользователи (внутренний, без аутентификации)
security: []
responses:
'200': { $ref: '#/components/responses/Ok' }
/internal/healthz/startup:
get:
tags: [internal]
summary: Startup-проба (БД, опционально Valkey)
security: []
responses:
'200': { description: OK }
'503': { description: Service Unavailable }
/internal/healthz/live:
get:
tags: [internal]
summary: Liveness-проба
security: []
responses:
'200': { description: OK }
/internal/healthz/ready:
get:
tags: [internal]
summary: Readiness-проба
security: []
responses:
'200': { description: OK }
'503': { description: Service Unavailable }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
JWT в заголовке `Authorization: Bearer <jwt>`. В режиме Zitadel
дополнительно передаётся заголовок `Identity: Bearer <jwt>`.
parameters:
Limit:
name: limit
in: query
required: false
schema: { type: integer, format: int64, minimum: 0 }
description: Размер страницы
Offset:
name: offset
in: query
required: false
schema: { type: integer, format: int64, minimum: 0 }
description: Смещение
DiskPath:
name: diskPath
in: path
required: true
schema: { type: string }
description: Жадный wildcard-сегмент Fiber (может содержать `/`) — путь/ID диска
DocumentPath:
name: documentPath
in: path
required: true
schema: { type: string }
description: Жадный wildcard-сегмент Fiber — путь/ID документа
TargetPath:
name: targetPath
in: path
required: true
schema: { type: string }
description: Жадный wildcard-сегмент Fiber — путь/ID таргета
RemarkPath:
name: remarkPath
in: path
required: true
schema: { type: string }
description: Жадный wildcard-сегмент Fiber — путь/ID замечания
WsId:
name: ws_id
in: path
required: true
schema: { type: string }
description: UUID рабочей области
IdPath:
name: id
in: path
required: true
schema: { type: string }
IdPathPlain:
name: id
in: path
required: true
schema: { type: string }
IdInt:
name: id
in: path
required: true
schema: { type: integer }
description: Числовой идентификатор (Fiber-ограничение `:id<int>`)
responses:
Ok:
description: Успешный ответ (структура зависит от эндпоинта)
content:
application/json:
schema: {}
BadRequest:
description: Некорректный запрос
content:
application/json:
schema: { $ref: '#/components/schemas/AppError' }
Unauthorized:
description: Не аутентифицирован (пустое тело)
Forbidden:
description: Нет доступа
content:
application/json:
schema: { $ref: '#/components/schemas/AppError' }
NotFound:
description: Ресурс не найден
content:
application/json:
schema: { $ref: '#/components/schemas/AppError' }
schemas:
AppError:
type: object
description: Формат ошибки бизнес-слоя (`internal/app_errors/errors.go`)
properties:
message:
type: string
example: not found
error_code:
type: string
description: Внутренний код ошибки (`GW-XXXX`)
enum: [GW-0000, GW-0001, GW-0002, GW-0003, GW-0004, GW-0014]
example: GW-0001
required: [message, error_code]

187
apps/flows/.env.example Normal file
View File

@ -0,0 +1,187 @@
# =============================================================================
# flows-backend (.env.example)
# =============================================================================
# Приложение читает переменные окружения напрямую через pydantic BaseSettings
# (src/flow/config.py). У верхнеуровневого класса Settings префикса нет — его
# поля задаются переменными с именем поля в ВЕРХНЕМ регистре (напр. BASE_HOST).
# Вложенные секции конфигурируются отдельными классами со своим env_prefix
# (PG_, DJANGO_, DOCUMENTATION_, RABBITMQ_, TRACING_ и т.д.).
#
# Приложение НЕ загружает .env автоматически (нет python-dotenv/env_file) —
# экспортируйте переменные в окружение самостоятельно, напр.:
# set -a && . ./.env && set +a
# =============================================================================
# App / общие настройки (класс Settings, без префикса)
SERVICE_NAME=review-service
SERVICE_HOST=0.0.0.0
SERVICE_PORT=8000
# Префикс за реверс-прокси (в кластере: /flows)
PROXY_PATH_PREFIX=
API_PREFIX=/api/v1
API_INTERNAL_PREFIX=/internal/v1
BASE_HOST=https://lk.sarex.io
DEBUG=False
# Проверка подписи JWT (True в кластере; False удобно для локальной разработки)
JWT_AUTH_ENABLE=True
# Таймаут gunicorn (используется в entrypoint.sh, не кодом)
TIMEOUT=120
# Feature-флаги
ENABLE_MAILINGS=True
ENABLE_MAILGUN=True
ENABLE_CELERY=True
ENABLE_EVENTS=True
ENABLE_ANALYTICS=False
SYNC_RESOURCE_ID=False
# Logger (класс LoggerSettings, префикс LOG_)
LOG_LEVEL=INFO
# Database — основной PostgreSQL (класс PostgresSettings, префикс PG_)
PG_HOST=127.0.0.1
PG_PORT=6432
PG_LOGIN=flow
PG_PASSWORD=password
PG_DB=flows_db
# Documentation PG — БД сервиса документаций (класс DocumentationDBSettings, префикс DOCUMENTATION_PG_)
DOCUMENTATION_PG_HOST=127.0.0.1
DOCUMENTATION_PG_PORT=6432
DOCUMENTATION_PG_USERNAME=flow
DOCUMENTATION_PG_PASSWORD=password
DOCUMENTATION_PG_DATABASE=flows_db
# RabbitMQ (класс RabbitSettings, префикс RABBITMQ_)
RABBITMQ_HOST=localhost
RABBITMQ_PORT=5672
RABBITMQ_USERNAME=flow
RABBITMQ_PASSWORD=flow
RABBITMQ_VHOST=flows
# Celery (класс CelerySettings, префикс CELERY_; брокер берётся из RABBITMQ_*)
CELERY_QUEUE=flow
# Sarex backend (Django) (класс DjangoSettings, префикс DJANGO_)
DJANGO_USE=True
DJANGO_HOST=http://localhost:8000/api
DJANGO_TIMEOUT=60
# base64(login:password) для Basic-auth
DJANGO_TOKEN=
# Documentation service (класс DocumentationSettings, префикс DOCUMENTATION_)
DOCUMENTATION_USE=True
DOCUMENTATION_HOST=https://api.sarex.io/documentations/api/v1
DOCUMENTATION_EXTERNAL_HOST=https://api.sarex.io/documentations/api/v1
DOCUMENTATION_TIMEOUT=60
# EAV service (класс EAVSettings, префикс EAV_)
EAV_HOST=http://eav-service.eav-prod
EAV_TIMEOUT=60
# Planning management / MSP (класс PlanningManagementSettings, префикс PLANNING_)
PLANNING_USE=True
PLANNING_HOST=https://api.sarex.io/api/pm/msp
PLANNING_TIMEOUT=60
# Checklists service (класс CheckListsSettings, префикс CHECKLIST_)
CHECKLIST_USE=True
CHECKLIST_HOST=https://stage-api.sarex.io/checklists
CHECKLIST_TIMEOUT=60
# Workflows service (класс WorkflowsSettings, префикс WORKFLOWS_)
WORKFLOWS_USE=True
WORKFLOWS_HOST=https://lk.sarex.io/workflows/api/v1
WORKFLOWS_TIMEOUT=60
# Gateway / Resources (поля класса Settings, используются при SYNC_RESOURCE_ID=1)
GATEWAY_URL=https://stage-api.sarex.io/gateway
RESOURCE_URL=https://stage-api.sarex.io/resources
# Event bus (класс EventBusSettings, префикс EVENTS_)
EVENTS_HOST=ws://localhost:8000/ws
EVENTS_CONNECTION_TIMEOUT=5
EVENTS_COUNT_RETRIES=100
# Admin panel (класс AdminPanelSettings, префикс ADMIN_PANEL_)
ADMIN_PANEL_SECRET_KEY=hex
ADMIN_PANEL_TOKEN_MAX_AGE=86400
# Auth (RSA public key для проверки JWT; в кластере монтируется как JWT_PUBLIC_KEY)
JWT_PUBLIC_KEY=
# Sentry (класс SentrySettings, префикс SENTRY_)
SENTRY_DSN=
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=1.0
SENTRY_SEND_DEFAULT_PII=True
# Почта: SMTP (поля класса Settings; альтернатива Mailgun)
SMTP_HOST=
SMTP_PORT=
FROM_EMAIL=
# OpenTelemetry / трейсинг (класс TraceSettings, префикс TRACING_)
TRACING_USE=False
TRACING_HOST=localhost:4317
TRACING_INSECURE=False
TRACING_SERVICE_NAME=flows
TRACING_ENVIRONMENT=prod
TRACING_MODULE=flows
TRACING_TEAM=team_proc
TRACING_COMPONENT=backend
# Прочее (не читается приложением, задаётся в инфраструктуре)
# ENABLE_METRICS=0
# =============================================================================
# Переменные ТОЛЬКО для celery-воркера и scheduler
# (src/worker/notifications_config.py, src/worker/sync_config.py)
# =============================================================================
# Flows DB (класс FlowsDatabase, префикс FLOWS_DB_)
FLOWS_DB_HOST=127.0.0.1
FLOWS_DB_PORT=6432
FLOWS_DB_DB=flows_db
FLOWS_DB_USERNAME=flow
FLOWS_DB_PASSWORD=password
# Issues DB (класс IssuesDatabase, префикс ISSUES_DB_)
ISSUES_DB_HOST=127.0.0.1
ISSUES_DB_PORT=6432
ISSUES_DB_DB=issues_db
ISSUES_DB_USERNAME=issues
ISSUES_DB_PASSWORD=password
# RFI DB (класс RFIDatabase, префикс RFI_DB_)
RFI_DB_HOST=127.0.0.1
RFI_DB_PORT=6432
RFI_DB_DB=rfi_db
RFI_DB_USERNAME=rfi
RFI_DB_PASSWORD=password
# Django-клиент воркера (класс DjangoClient, префикс DJANGO_)
DJANGO_BASE_HOST=https://lk.sarex.io
# DJANGO_HOST — см. выше
# DJANGO_AUTH — base64(login:password); в кластере берётся из секрета django
DJANGO_AUTH=
# Resources-клиент воркера (класс ResourcesClient, префикс RESOURCES_)
RESOURCES_HOST=http://iams.iam.svc.cluster.local:8080
# Flows-клиент воркера (класс FlowsClient, префикс FLOWS_)
FLOWS_HOST=https://api.sarex.io/flows
# Настройки рассылок воркера (класс NotificationGlobalSettings, префикс NOTIFICATION_SETTINGS_)
NOTIFICATION_SETTINGS_ENABLE_MAILINGS=True
NOTIFICATION_SETTINGS_USE_MAILGUN=True
# Mailgun (класс MailgunClient, префикс MAILGUN_)
MAILGUN_HOST=https://api.mailgun.net/v3/mg.sarex.io
MAILGUN_API_KEY=
# Отправка уведомлений через Workflows (класс WorkflowsNotificationsSettings, префикс WORKFLOWS_NOTIFICATIONS_)
WORKFLOWS_NOTIFICATIONS_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
WORKFLOWS_NOTIFICATIONS_SMTP_HOST=127.0.0.1
WORKFLOWS_NOTIFICATIONS_SMTP_PORT=42069
WORKFLOWS_NOTIFICATIONS_FROM_EMAIL=hello@sarex.io

293
apps/flows/CONFIGURATION.md Normal file
View File

@ -0,0 +1,293 @@
# Конфигурация проекта flows-backend
Документ описывает все переменные окружения и способы конфигурирования сервиса.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/flow/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) (класс `Settings` и набор вложенных классов `*Settings`).
Особенности разбора:
- у верхнеуровневого класса `Settings` **префикса нет** и не задан `env_nested_delimiter`его собственные поля задаются переменными с именем поля в верхнем регистре (напр. `BASE_HOST`, `SERVICE_PORT`, `PROXY_PATH_PREFIX`);
- каждая вложенная секция — это **отдельный класс** `BaseSettings` со своим `env_prefix` (`class Config: env_prefix = "..."`), который читает переменные окружения независимо. Поэтому переменные «плоские» с префиксами: `PG_HOST`, `DJANGO_HOST`, `RABBITMQ_PORT`, `TRACING_USE` и т.д. — двойного подчёркивания для вложенности здесь нет;
- отсутствие обязательного поля без дефолта приводит к ошибке старта; большинство полей приложения имеют дефолты (см. таблицы ниже).
Отдельного конфиг-файла (yaml/toml) у приложения нет. Приложение **не загружает `.env` автоматически**`config.py` не задан `env_file`, зависимости `python-dotenv` нет) — переменные нужно экспортировать в окружение самому, напр. `set -a && . ./.env && set +a`.
Воркер (`src/worker`) использует **собственные** классы настроек (`src/worker/notifications_config.py`, `src/worker/sync_config.py`, `src/worker/celery.py`) с частично другими префиксами (`FLOWS_DB_`, `ISSUES_DB_`, `RFI_DB_`, `RESOURCES_`, `MAILGUN_`, `WORKFLOWS_NOTIFICATIONS_` и т.д.).
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально | Переменные окружения процесса. `.env.example` — шаблон; приложение его **не** загружает автоматически, экспортируйте вручную |
| Контейнер | `Dockerfile` / `Dockerfile.worker`; запуск через `entrypoint.sh` (сначала `alembic upgrade head`, затем gunicorn) |
| Kubernetes (Helm, репозиторий) | `.helm/values.yaml` (`universal-chart`): блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) для сервисов `backend`, `worker`, `scheduler` |
| Kubernetes (kustomize, infra) | `iac/apps/flows/base/*.yaml`: env в `backend-deployment.yaml` / `celery-deployment.yaml`; секреты инжектируются агентом **HashiCorp Vault** (`vault.hashicorp.com/agent-inject-*`) и подгружаются в окружение перед стартом |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`), общий шаблон `generic/common-ci` (`universal-pipeline.yaml`) |
Способы запуска процессов:
| Процесс | Команда | Назначение |
| --- | --- | --- |
| HTTP API | `gunicorn ... flow.main:app` (`entrypoint.sh`) | Публичный и внутренний REST API (FastAPI) |
| Celery worker | `celery -A src.worker worker` | Обработчик фоновых задач (рассылки, синхронизация) |
| Celery beat (scheduler) | `celery -A src.worker beat -l INFO` | Периодические задачи (см. `beat_schedule` ниже) |
| Alembic | `alembic upgrade head``entrypoint.sh`) | Миграции БД при старте контейнера |
Порядок запуска в контейнере (`entrypoint.sh`): миграции (`alembic upgrade head`), затем `gunicorn -w 3 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 --timeout $TIMEOUT ... flow.main:app`.
Периодические задачи воркера (`src/worker/celery.py`, `beat_schedule`):
| Задача | Расписание (UTC) | Назначение |
| --- | --- | --- |
| `sync_reviews` | `*/7` минут | Синхронизация review |
| `notify_users` | пн–пт, 06:00 | Рассылка уведомлений пользователям |
| `notify_admins_about_empty_steps` | пн–пт, 05:30 | Уведомление админов о пустых шагах |
## Переменные приложения (API)
Дефолт `—` означает, что значение обязательно (иначе ошибка старта).
### Общие (`Settings`, без префикса)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SERVICE_NAME` | string | `review-service` | Имя сервиса |
| `SERVICE_HOST` | string | `0.0.0.0` | Адрес прослушивания (в кластере переопределяется внешним URL API) |
| `SERVICE_PORT` | int | `8000` | Порт |
| `PROXY_PATH_PREFIX` | string | `""` | Root path за реверс-прокси (в кластере `/flows`). Влияет на `root_path` FastAPI и на префикс админки |
| `API_PREFIX` | string | `/api/v1` | Префикс публичного API |
| `API_INTERNAL_PREFIX` | string | `/internal/v1` | Префикс внутреннего API |
| `BASE_HOST` | string | `https://lk.sarex.io` | Базовый внешний URL (для ссылок/писем) |
| `GATEWAY_URL` | string | `https://stage-api.sarex.io/gateway` | URL gateway (используется при `SYNC_RESOURCE_ID=1`) |
| `RESOURCE_URL` | string | `https://stage-api.sarex.io/resources` | URL сервиса ресурсов/IAM (используется при `SYNC_RESOURCE_ID=1`) |
| `REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр образов |
| `JWT_AUTH_ENABLE` | bool | `True` | Включить аутентификацию по JWT. `False` — все запросы идут от дефолтного пользователя (удобно локально) |
| `DEBUG` | bool | `False` | Режим отладки |
| `ENABLE_MAILINGS` | bool | `True` | Включить рассылки |
| `ENABLE_MAILGUN` | bool | `True` | Использовать Mailgun (иначе — SMTP) |
| `ENABLE_CELERY` | bool | `True` | Включить постановку задач в Celery |
| `ENABLE_EVENTS` | bool | `True` | Включить событийную шину |
| `ENABLE_ANALYTICS` | bool | `False` | Отправлять данные в аналитику |
| `SYNC_RESOURCE_ID` | bool | `False` | Определять `resource_id` через gateway/resources при создании review/документов |
| `SMTP_HOST` | string \| null | `None` | SMTP-хост (альтернатива Mailgun) |
| `SMTP_PORT` | int \| null | `None` | SMTP-порт |
| `FROM_EMAIL` | string \| null | `None` | Адрес отправителя писем |
### Logger (`LoggerSettings`, префикс `LOG_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `LOG_LEVEL` | string | `INFO` | Уровень логирования (`INFO`/`DEBUG`/…); JSON-формат вывода |
| `LOG_FORMAT` | string | JSON-шаблон | Формат строки лога |
### Auth
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `JWT_PUBLIC_KEY` | string | — | Публичный RSA-ключ для проверки подписи JWT. В кластере монтируется из секрета и экспортируется как `JWT_PUBLIC_KEY` перед стартом (см. `entrypoint`/Vault) |
> Аутентификация выполняется в `src/flow/middleware.py` (`TokenUserMiddleware`). При наличии заголовка `identity` полезная нагрузка берётся из Zitadel-токена (`urn:zitadel:iam:user:metadata`), иначе — из основного `Authorization: Bearer <jwt>`. Подпись проверяется публичным ключом. Пути `/docs/`, `/openapi.json/`, `/internal/` из проверки исключены.
### Database — основной PostgreSQL (`PostgresSettings`, префикс `PG_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `PG_HOST` | string | `""` | Хост PostgreSQL |
| `PG_PORT` | string | `6432` | Порт PostgreSQL (обычно pgbouncer) |
| `PG_LOGIN` | string | `""` | Пользователь БД |
| `PG_PASSWORD` | string | `""` | Пароль БД |
| `PG_DB` | string | `""` | Имя базы данных |
> Итоговый DSN собирается свойством `PostgresSettings.url`: `postgresql://{login}:{password}@{host}:{port}/{db}`.
### Documentation PG (`DocumentationDBSettings`, префикс `DOCUMENTATION_PG_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DOCUMENTATION_PG_HOST` | string | `""` | Хост БД документаций |
| `DOCUMENTATION_PG_PORT` | string | `""` | Порт |
| `DOCUMENTATION_PG_USERNAME` | string | `""` | Пользователь |
| `DOCUMENTATION_PG_PASSWORD` | string | `""` | Пароль |
| `DOCUMENTATION_PG_DATABASE` | string | `""` | Имя базы |
### RabbitMQ (`RabbitSettings`, префикс `RABBITMQ_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `RABBITMQ_HOST` | string | `localhost` | Хост |
| `RABBITMQ_PORT` | string | `5672` | Порт |
| `RABBITMQ_USERNAME` | string | `flow` | Пользователь |
| `RABBITMQ_PASSWORD` | string | `flow` | Пароль |
| `RABBITMQ_VHOST` | string | `api` | Виртуальный хост (в кластере `flows`/`flow_preprod`/`flow_prod`) |
### Celery (`CelerySettings`, префикс `CELERY_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `CELERY_QUEUE` | string | `flow` | Очередь задач. Брокер — из `RABBITMQ_*` |
### HTTP-клиенты внешних сервисов
Каждый клиент — отдельный класс с полями `use`/`host`/`timeout` (и своим префиксом). Соединение создаётся httpx-клиентом.
| Секция / префикс | Переменные | Назначение |
| --- | --- | --- |
| Sarex backend (Django) — `DJANGO_` | `DJANGO_USE` (`True`), `DJANGO_HOST` (`http://localhost:8000/api`), `DJANGO_TIMEOUT` (`60`), `DJANGO_TOKEN` (base64 `login:password` для Basic-auth) | Основной backend Sarex |
| Documentations — `DOCUMENTATION_` | `DOCUMENTATION_USE` (`True`), `DOCUMENTATION_HOST`, `DOCUMENTATION_EXTERNAL_HOST`, `DOCUMENTATION_TIMEOUT` (`60`) | Сервис документаций (внутренний и внешний хост) |
| EAV — `EAV_` | `EAV_HOST` (`http://eav-service.eav-prod`), `EAV_TIMEOUT` (`60`) | Сервис EAV (атрибуты) |
| Planning / MSP — `PLANNING_` | `PLANNING_USE` (`True`), `PLANNING_HOST` (`https://api.sarex.io/api/pm/msp`), `PLANNING_TIMEOUT` (`60`) | Планирование |
| Checklists — `CHECKLIST_` | `CHECKLIST_USE` (`True`), `CHECKLIST_HOST`, `CHECKLIST_TIMEOUT` (`60`) | Сервис чек-листов |
| Workflows — `WORKFLOWS_` | `WORKFLOWS_USE` (`True`), `WORKFLOWS_HOST` (`https://lk.sarex.io/workflows/api/v1`), `WORKFLOWS_TIMEOUT` (`60`) | Сервис workflows |
### Event bus (`EventBusSettings`, префикс `EVENTS_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `EVENTS_HOST` | string | `ws://localhost:8000/ws` | Адрес WebSocket событийной шины |
| `EVENTS_CONNECTION_TIMEOUT` | int | `5` | Таймаут подключения (сек) |
| `EVENTS_COUNT_RETRIES` | int | `100` | Число попыток переподключения |
### Admin panel (`AdminPanelSettings`, префикс `ADMIN_PANEL_`)
Админка (`sqladmin`) монтируется по пути `/api/admin/` (с учётом `PROXY_PATH_PREFIX`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ADMIN_PANEL_SECRET_KEY` | string | `hex` | Секретный ключ сессии админки |
| `ADMIN_PANEL_TOKEN_MAX_AGE` | int | `86400` | Время жизни токена (сек) |
### Sentry (`SentrySettings`, префикс `SENTRY_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SENTRY_DSN` | string | `""` | DSN Sentry |
| `SENTRY_ENVIRONMENT` | string | `production` | Окружение |
| `SENTRY_TRACES_SAMPLE_RATE` | float | `1.0` | Доля трейсов |
| `SENTRY_SEND_DEFAULT_PII` | bool | `True` | Отправлять PII |
### OpenTelemetry / трейсинг (`TraceSettings`, префикс `TRACING_`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `TRACING_USE` | bool | `False` | Включить трейсинг (при `True` инициализируется OTLP + middleware) |
| `TRACING_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора |
| `TRACING_INSECURE` | bool | `False` | Подключение без TLS |
| `TRACING_SERVICE_NAME` | string | `flows` | Имя сервиса в трейсах |
| `TRACING_ENVIRONMENT` | string | `prod` | Окружение (`stage`/`preprod`/`prod`) |
| `TRACING_MODULE` | string | `flows` | Атрибут `module` |
| `TRACING_TEAM` | string | `team_proc` | Атрибут `team` |
| `TRACING_COMPONENT` | string | `backend` | Атрибут `component` |
## Переменные только для воркера и scheduler
Читаются классами из `src/worker/*`, а не основным приложением.
### Базы данных воркера
| Секция / префикс | Переменные | Назначение |
| --- | --- | --- |
| Flows DB — `FLOWS_DB_` | `FLOWS_DB_HOST`, `FLOWS_DB_PORT`, `FLOWS_DB_DB`, `FLOWS_DB_USERNAME`, `FLOWS_DB_PASSWORD` | БД flows (для задач синхронизации/рассылок) |
| Issues DB — `ISSUES_DB_` | `ISSUES_DB_HOST`, `ISSUES_DB_PORT`, `ISSUES_DB_DB`, `ISSUES_DB_USERNAME`, `ISSUES_DB_PASSWORD` | БД issues |
| RFI DB — `RFI_DB_` | `RFI_DB_HOST`, `RFI_DB_PORT`, `RFI_DB_DB`, `RFI_DB_USERNAME`, `RFI_DB_PASSWORD` | БД RFI |
Все пять полей каждой БД обязательны (без дефолтов).
### Клиенты и рассылки воркера
| Секция / префикс | Переменные | Назначение |
| --- | --- | --- |
| Django-клиент — `DJANGO_` | `DJANGO_BASE_HOST`, `DJANGO_HOST`, `DJANGO_AUTH` (Basic-auth) | Получение пользователей/токенов |
| Resources-клиент — `RESOURCES_` | `RESOURCES_HOST` | Пользователи, сгруппированные по ресурсам |
| Flows-клиент — `FLOWS_` | `FLOWS_HOST` | Внутренние вызовы flows API (`switch_to_next_step`) |
| Глобальные настройки рассылок — `NOTIFICATION_SETTINGS_` | `NOTIFICATION_SETTINGS_ENABLE_MAILINGS` (`True`), `NOTIFICATION_SETTINGS_USE_MAILGUN` (`True`) | Флаги рассылок |
| Mailgun — `MAILGUN_` | `MAILGUN_HOST`, `MAILGUN_API_KEY`, `MAILGUN_SENT_FROM` (`hello@sarex.io`) | Отправка писем через Mailgun |
| Workflows — `WORKFLOWS_` | `WORKFLOWS_HOST`, `WORKFLOWS_TIMEOUT` (`60`) | Постановка job в workflows |
| Уведомления через Workflows — `WORKFLOWS_NOTIFICATIONS_` | `WORKFLOWS_NOTIFICATIONS_TAG` (`email`), `WORKFLOWS_NOTIFICATIONS_REGISTRY`, `WORKFLOWS_NOTIFICATIONS_SMTP_HOST`, `WORKFLOWS_NOTIFICATIONS_SMTP_PORT`, `WORKFLOWS_NOTIFICATIONS_FROM_EMAIL` | Параметры job-рассылки |
## Переменные инфраструктуры, сборки и деплоя
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `TIMEOUT` | `entrypoint.sh` | Таймаут gunicorn-воркеров (сек), напр. `120` (stage) / `900` (prod) |
| `ENABLE_METRICS` | `.helm/values.yaml` | Флаг метрик (`0`/`1`), кодом не читается |
| `PIP_INDEX_URL` / `--extra-index-url` | `requirements.txt` | Приватный индекс пакетов Nexus (`fastapi-otel-tools`) |
| `SERVICE_HOST` (в кластере) | `.helm/values.yaml`, kustomize | В кластере в `SERVICE_HOST` кладётся внешний URL API (`https://api.sarex.io/flows/api/v1`), переопределяя дефолт `0.0.0.0` |
| `SAREX_MAILER_HOST` | `.helm/values.yaml` | Хост mailer-сервиса |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт `universal-chart` описывает три сервиса — `backend`, `worker`, `scheduler`. Обычные значения задаются в блоке `envs` (с ключами по окружениям `_default`/`stage`/`preprod`/`production`), значения из секретов — в блоке `secretEnvs` (монтируются как env через `secretKeyRef`).
Значения из секретов (backend):
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `PG_DB` | `postgres-secret` / `flows-postgresql-secret` | `database` |
| `PG_LOGIN` | `postgres-secret` / `flows-postgresql-secret` | `username` |
| `PG_PASSWORD` | `postgres-secret` / `flows-postgresql-secret` | `password` |
| `PG_HOST` | `postgres-secret` / `flows-postgresql-secret` | `host` |
| `SENTRY_DSN` | `sentry-secret` | `dsn` |
| `SENTRY_ENVIRONMENT` | `sentry-secret` | `env` |
| `DJANGO_TOKEN` | `django-secret` | `token` |
| `RABBITMQ_USERNAME` | `rabbitmq-secret` / `flows-rabbitmq-secret` | `username` |
| `RABBITMQ_PASSWORD` | `rabbitmq-secret` / `flows-rabbitmq-secret` | `password` |
| `ADMIN_PANEL_SECRET_KEY` | `admin-secret` | `key` |
| `JWT_PUBLIC_KEY` | `jwt-secret` | `public_key` |
| `DOCUMENTATION_PG_*` | `documentations-postgresql-secret` / `documentations-postgres-secret` | `database`/`host`/`port`/`username`/`password` |
Воркер дополнительно получает секреты `FLOWS_DB_*`, `ISSUES_DB_*`, `RFI_DB_*` (из соответствующих postgres-секретов), `DJANGO_AUTH` (`django-secret.token`), `MAILGUN_API_KEY` (`mailgun-secret.api-key`).
Чарт также монтирует CA-сертификат PostgreSQL (`pg-cert` → `/root/.postgresql/root.crt`).
## Переменные из kustomize-манифестов (`iac/apps/flows`)
Инфраструктурный репозиторий разворачивает те же образы через kustomize (`base` + оверлеи `brusnika-stage`, `brusnika-prod`, `yc-k8s-test`). Секреты инжектируются агентом **HashiCorp Vault** (аннотации `vault.hashicorp.com/agent-inject-*`) и подгружаются в окружение из файлов `/vault/secrets/*` перед запуском `entrypoint.sh`:
| Секрет Vault | Переменные |
| --- | --- |
| `secrets/data/postgresql/apps/flows` | `PG_DB`, `PG_LOGIN`, `PG_HOST`, `PG_PORT`, `PG_PASSWORD`, `DOCUMENTATION_PG_*` |
| `secrets/data/rabbitmq/apps/flows` | `RABBITMQ_USERNAME`, `RABBITMQ_PASSWORD`, `RABBITMQ_VHOST`, `RABBITMQ_HOST`, `RABBITMQ_PORT` |
| `secrets/data/vault/common/django_auth` | `DJANGO_TOKEN` |
| `secrets/data/vault/common/rsa_keys` | `JWT_PUBLIC_KEY` (public_key) |
Остальные значения (`LOG_LEVEL`, `BASE_HOST`, `DJANGO_HOST`, `DOCUMENTATION_HOST`, `EAV_HOST`, `GATEWAY_URL`, `RESOURCE_URL`, `SERVICE_HOST`, `WORKFLOWS_HOST`, `CHECKLIST_HOST`, `SMTP_HOST`/`SMTP_PORT`, `FROM_EMAIL`, `ENABLE_*`, `SYNC_RESOURCE_ID`, `TIMEOUT` и т.д.) задаются напрямую в блоке `env` deployment-манифеста оверлея.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace | CHART_VERSION |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `proc` | `0.0.1-stage` |
| ветка `master` | `preprod` | `flows-preprod` | `0.0.1-preprod` |
| тег (`CI_COMMIT_TAG`) | `production` | `flows-prod` | `0.0.1-prod` |
Ключевые переменные пайплайна: `SERVICE_NAME=flows-backend`, `DOCKERFILE_PATH=Dockerfile`, `IMAGE_NAME_WORKER` (образ воркера, собирается job-ом `build_worker` из `Dockerfile.worker`), `HELM_SET_ARGS` (проброс образов backend/worker/scheduler и метаданных коммита в чарт).
## Замечания и потенциальные проблемы
- Приложение **не загружает `.env` автоматически** — переменные нужно экспортировать в окружение (см. `.env.example`).
- У `Settings` нет `env_nested_delimiter`, поэтому вложенные секции конфигурируются **плоскими** переменными со своими префиксами (`PG_`, `DJANGO_`, `RABBITMQ_`, …), а не через `__`.
- Поле `service_host` (дефолт `0.0.0.0`) и переменная `SERVICE_HOST` совпадают по имени: в кластере в `SERVICE_HOST` кладётся внешний URL API, что переопределяет адрес прослушивания в объекте настроек. Реальный адрес/порт прослушивания при запуске в контейнере задаёт gunicorn (`-b 0.0.0.0:8000` в `entrypoint.sh`), а не поле настроек.
- Почта: при `ENABLE_MAILGUN=1` используется Mailgun (`MAILGUN_*` — в основном на стороне воркера), иначе — SMTP (`SMTP_HOST`/`SMTP_PORT`/`FROM_EMAIL`).
- Healthcheck-эндпоинта у сервиса нет; в чарте probes (`liveness`/`readiness`) отключены.
- Воркер использует отдельные классы настроек (pydantic v1 стиль `class Config`), у которых поля БД **обязательны** — при запуске воркера без `FLOWS_DB_*`/`ISSUES_DB_*`/`RFI_DB_*` будет ошибка.
## Минимальный набор для локального запуска
Минимально необходимо задать:
- `SERVICE_PORT` (по умолчанию `8000`), `PROXY_PATH_PREFIX` (пусто локально)
- `JWT_AUTH_ENABLE=False` (чтобы не требовался `JWT_PUBLIC_KEY`) — иначе задайте `JWT_PUBLIC_KEY`
- `PG_HOST`, `PG_PORT`, `PG_LOGIN`, `PG_PASSWORD`, `PG_DB`
- `RABBITMQ_HOST`, `RABBITMQ_PORT`, `RABBITMQ_USERNAME`, `RABBITMQ_PASSWORD`, `RABBITMQ_VHOST` (если `ENABLE_CELERY=1`/`ENABLE_EVENTS=1`)
- хосты внешних сервисов, которые реально используются: `DJANGO_HOST`, `DOCUMENTATION_HOST`, `EAV_HOST`, `CHECKLIST_HOST`, `WORKFLOWS_HOST`, `PLANNING_HOST`
- при `SYNC_RESOURCE_ID=1``GATEWAY_URL`, `RESOURCE_URL`
- `TRACING_USE=False` (иначе — `TRACING_HOST`, `TRACING_SERVICE_NAME`)
- для воркера — `FLOWS_DB_*`, `ISSUES_DB_*`, `RFI_DB_*`, `MAILGUN_*` или SMTP
Готовые значения-примеры для всех переменных приведены в `.env.example`.

142
apps/flows/ENDPOINTS.md Normal file
View File

@ -0,0 +1,142 @@
# Эндпоинты, с которыми взаимодействует flows-frontend
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `flows-frontend`).
## Как устроено взаимодействие
В отличие от единого реестра эндпоинтов, запросы во `flows-frontend` выполняются **точечно** из MobX-сторов (`module/store/stores/*.ts`) через общий HTTP-клиент `httpService`.
`httpService` создаётся в `module/api/http-service.ts` фабрикой `createHttpService` из `@sarex-team/sdk-js`. Клиент предоставляет методы `getRequest`, `postRequest`, `putRequest`, `patchRequest`, `deleteRequest`, каждый из которых принимает объект вида:
```ts
httpService.getRequest({
service: "flows", // логическое имя сервиса (ключ из hosts.ts)
url: `/flows/${id}/?full=true`, // путь запроса относительно базового хоста сервиса
data: { ... }, // тело запроса (для post/put/patch)
// ...прочие опции axios
});
```
Базовый хост сервиса подставляется по ключу `service` из `module/api/hosts.ts` в зависимости от `BUILD_ENV`. Итоговый URL = `<базовый хост сервиса>` + `url`.
Окружение выбирается переменной `BUILD_ENV` (`module/env.js`): одно из `local`, `stage`, `prod`, `preprod`, `contour`, `severstal`, `uralchem`. В `webpack.config.js` значение прокидывается в бандл через `DefinePlugin`. Значение по умолчанию при резолве хоста — `prod`.
Подключаемый удалённый модуль (`documentations`) описан отдельно в `module/api/modules-hosts.ts` и резолвится функцией `getModuleHost` (Module Federation, `remoteEntry.js`).
## Базовые хосты по сервисам и окружениям
Значения из `module/api/hosts.ts`. Показаны `stage` и `prod`; дополнительно определены `local`, `preprod` и `contour``contour` — относительные пути для изолированного контура; в `local` сервис `sarex` проксируется на `/sarex-backend`).
| Сервис (`service`) | Назначение | `stage` | `prod` |
| --- | --- | --- | --- |
| `flows` | Сервис процессов согласования (flows, reviews, steps, statuses) | `https://stage-api.sarex.io/flows/api/v1` | `https://api.sarex.io/flows/api/v1` |
| `sarex` | Локальный backend (Django `core`/`client`) | `""` (относительные пути) | `""` |
| `gateway_api_v1` | Gateway API v1 (ресурсы) | `https://stage-api.sarex.io/gateway/api/v1` | `https://api.sarex.io/gateway/api/v1` |
| `gateway_api_v2` | Gateway API v2 (пользователи) | `https://stage-api.sarex.io/gateway/api/v2` | `https://api.sarex.io/gateway/api/v2` |
| `documentations` | Сервис документации (диски, документы) | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` |
| `eav_api_v0` | Сервис EAV (атрибуты/схемы) | `https://stage-api.sarex.io/eav/api/v0` | `https://api.sarex.io/eav/api/v0` |
| `checklists` | Сервис чек-листов | `https://stage-api.sarex.io/checklists/api/v1` | `https://api.sarex.io/checklists/api/v1` |
| `transmittals` | Сервис передачи документации (шаблоны) | `https://stage-api.sarex.io/transmittals/api/v1` | `https://api.sarex.io/transmittals/api/v1` |
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
Удалённые модули (`module/api/modules-hosts.ts`):
| Модуль | `stage` | `prod` |
| --- | --- | --- |
| `documentations` | `https://stage-modules.sarex.io/documentations/static/module/remoteEntry.js` | `https://modules.sarex.io/documentations/static/module/remoteEntry.js` |
## Эндпоинты по сервисам
### `flows` — Сервис процессов согласования
Источник: `module/store/stores/processes.ts`, `module/store/stores/resources.ts`.
| Метод (`*Request`) | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/flows/{id}/?full=true` | `processes.loadFlow` | Маршрут по id (с шагами/статусами) |
| POST | `/flows/filter/` | `processes` (фильтр) | Список маршрутов по фильтру |
| POST | `/flows/count_flows_by_resource/` | `processes` | Количество маршрутов по ресурсам |
| POST | `/flows/count_flows_by_resource/` | `processes.getCountByResourceId` | Количество маршрутов для одного ресурса |
| POST | `/flows/` | `processes.createFlow` | Создать маршрут |
| POST | `/flows/{id}/copy/?full=true` | `processes` (копирование) | Копировать маршрут |
| PUT | `/flows/{id}/?full=true` | `processes` (обновление) | Обновить маршрут |
| PATCH | `/flows/bulk-update/` | `processes.bulkUpdateFlow` | Массовое обновление маршрутов |
| POST | `/steps/` | `processes.createStep` | Создать шаг |
| PUT | `/steps/{id}/?full=true` | `processes.updateStep` | Обновить шаг |
| PATCH | `/steps/{stepId}/update_reviewers/` | `processes` | Обновить согласующих шага |
| GET | `/steps/{id}/active_reviews/` | `processes.getActiveReviewsForReviewer` | Активные review на шаге |
| POST | `/statuses/` | `processes.createStatus` | Создать статус |
| DELETE | `/statuses/{id}/` | `processes.deleteStatus` | Удалить статус |
| POST | `/reviews/count_by_resource_id/` | `resources` | Количество review по ресурсу |
### `sarex` — Локальный backend (Django)
Источник: `module/store/stores/users.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/api/client/settings/` | `users.getCurrentUser` | Настройки/данные текущего пользователя |
| GET | `/api/core/users/?company={id}&{query}` | `users.getUsers` / `getUsersByCompanyId` / `getAllUsersByCompanyId` | Пользователи компании |
| GET | `/api/core/admin/departments/?{query}` | `users.fetchDepartmentsSA` | Департаменты (service account) |
| GET | `/api/core/admin/departments/?company={id}&{query}` | `users.fetchDepartmentsByCompanyId` | Департаменты компании |
| GET | `/api/core/admin/positions/?{query}` | `users.fetchPositionsSA` | Должности (service account) |
| GET | `/api/core/admin/positions/?company={id}&{query}` | `users.fetchPositionsByCompanyId` | Должности компании |
### `gateway_api_v1` — Gateway API v1 (ресурсы)
Источник: `module/store/stores/resources.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/resources/?{query}` | `resources` | Список ресурсов (по фильтру) |
| GET | `/resources/?company_id={id}` | `resources` | Ресурсы компании |
### `gateway_api_v2` — Gateway API v2 (пользователи)
Источник: `module/store/stores/users.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/users/?{query}` | `users.getUsersByResourceId` | Пользователи по ресурсу |
### `documentations` — Сервис документации
Источник: `module/store/stores/documents.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/disks/{id}/documents` | `documents.fetchDocumentsByDiskId` | Документы диска |
### `eav_api_v0` — Сервис EAV (атрибуты)
Источник: `module/store/stores/attributes.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| GET | `/schema/?model_name=flow&company_id={id}` | `attributes.fetchFlowsAttributes` | Схема атрибутов для модели `flow` |
### `checklists` — Сервис чек-листов
Источник: `module/store/stores/checklists.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| POST | `/checklists/filter/` | `checklists.fetchChecklists` | Список чек-листов по фильтру |
### `transmittals` — Сервис передачи документации
Источник: `module/store/stores/transmittals.ts`.
| Метод | Путь | Стор / метод | Назначение |
| --- | --- | --- | --- |
| POST | `/transmittal_templates` | `transmittals` | Шаблоны трансмитталов (пагинация по `next`, фильтр по `resources`) |
### `zitadel` — IdP
Хост определён в `hosts.ts` для аутентификации через SDK; прямых вызовов из сторов в текущей версии модуля нет (используется инфраструктурой `@sarex-team/sdk-js`).
## Замечания
- Единого файла-реестра эндпоинтов (`endpoints.ts`) во `flows-frontend` нет — вызовы разбросаны по сторам `module/store/stores/*`. При добавлении нового запроса указывайте `service` строго из ключей `hosts.ts`.
- Часть путей содержит завершающий слэш и query-параметры прямо в строке `url` (напр. `/flows/{id}/?full=true`) — это соответствует поведению backend (`flows-backend`), где роуты объявлены со слэшем на конце.
- Сервис `sarex` в `stage`/`prod` имеет пустой базовый хост (`""`), то есть запросы идут по относительным путям того же origin; в `local` он проксируется на `/sarex-backend`, в `contour` — на относительные пути контура.

1736
apps/flows/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

75
apps/iam/.env.example Normal file
View File

@ -0,0 +1,75 @@
# iams-v2 — пример переменных окружения.
# Конфиг читается из окружения (github.com/sethvargo/go-envconfig).
# Перед разбором подхватывается env-файл через godotenv: путь из ENV_FILE, иначе ".env".
# Значения ниже — дефолты из кода и примеры для локального запуска.
# App
ENVIRONMENT=local
LOG_LEVEL=info
SERVICE_NAME=iams
SERVICE_VERSION=0.0.0
# HTTP (prefix HTTP_)
HTTP_PORT=8080
# ReadBufferSize (Fiber/fasthttp), байты
HTTP_READ_BUFFER_SIZE=131072
# Database (prefix DB_)
DB_DSN=postgres://postgres:secret@localhost:5432/postgres?sslmode=disable
DB_MIGRATIONS_PATH=migrations
# Auth — внешний контур /external/api/* (prefix AUTH_)
# false ⇒ группа /external/api не регистрируется, JWT не разбирается
AUTH_ENABLED=false
# Sonyflake — machine id для генерации resource.id (prefix SONYFLAKE_)
SONYFLAKE_MACHINE_ID=1
# Zitadel Management API (prefix ZITADEL_)
# при ENABLED=true обязательны HOST, ACCESS_TOKEN, ORG_RULES_FILE
ZITADEL_ENABLED=false
ZITADEL_HOST=
ZITADEL_ACCESS_TOKEN=
ZITADEL_ORG_RULES_FILE=config/zitadel/org-rules-stage.json
# S3 (presigned GET для вложений виджета) (prefix S3_)
# при ENABLED=true обязательны ENDPOINT_URL, BUCKET_NAME, ACCESS_KEY_ID, SECRET_ACCESS_KEY
S3_ENABLED=false
S3_ENDPOINT_URL=https://storage.yandexcloud.net
S3_BUCKET_NAME=
S3_REGION=ru-central1
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_PRESIGN_EXPIRES=1h
# Kafka (prefix KAFKA_)
KAFKA_ENABLED=false
KAFKA_BROKERS=localhost:9092
KAFKA_SECURITY_PROTOCOL=PLAINTEXT
KAFKA_SASL_MECHANISM=
KAFKA_SASL_PLAIN_USERNAME=
KAFKA_SASL_PLAIN_PASSWORD=
KAFKA_SSL_CAFILE=
# Политика имён топиков (prefix KAFKA_TOPIC_)
# По умолчанию topic = event_type. PREFIX добавляется ко всем именам.
KAFKA_TOPIC_PREFIX=
# Путь к JSON-файлу {event_type: topic} с переопределениями
KAFKA_TOPIC_OVERRIDES_FILE=
# Legacy-топик (BrokerMessage пользователя)
KAFKA_TOPIC_LEGACY_AMS_SYNC=ams-sync
# OpenTelemetry (prefix OTEL_)
OTEL_ENABLED=false
OTEL_HOST=localhost
OTEL_PORT=4317
OTEL_INSECURE=true
OTEL_SERVICE_NAME=iams
# Переменная выбора env-файла (читается до разбора конфига)
# ENV_FILE=.env
# --- Вспомогательное (не читается конфигом приложения) ---
# DSN для интеграционных тестов (общий контейнер Postgres): см. Makefile/README
# TEST_DB_DSN=postgres://postgres:secret@localhost:5432/postgres?sslmode=disable
# Включение Kafka в docker compose для сервиса http
# COMPOSE_KAFKA_ENABLED=false

174
apps/iam/CONFIGURATION.md Normal file
View File

@ -0,0 +1,174 @@
# Конфигурация проекта iams-v2
Документ описывает все переменные окружения и способы конфигурирования сервиса **iams** (`platform/iams-v2`) — сервиса управления пользователями, ресурсами и правами доступа (IAM).
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `internal/app/config.go` (функция `app.Load`) через библиотеку [`github.com/sethvargo/go-envconfig`](https://github.com/sethvargo/go-envconfig) (структура `Config`).
Особенности разбора:
- вложенные секции задаются полями-структурами с тегом `env:", prefix=<PREFIX>_"`, напр. `DB_``Config.DB`, `S3_``Config.S3`;
- у большинства полей задан дефолт через `env:"NAME, default=..."`; поля без дефолта при отсутствии остаются нулевыми, а обязательность проверяется отдельными валидаторами (см. ниже);
- перед разбором окружения подхватывается env-файл через [`godotenv`](https://github.com/joho/godotenv): путь берётся из переменной `ENV_FILE`, иначе `.env`. Отсутствие файла **не** является ошибкой (ошибка `godotenv.Load` игнорируется), реальные значения читаются из окружения процесса.
Отдельного конфиг-файла (yaml/toml) у приложения нет, за исключением двух внешних файлов, путь к которым задаётся переменными: `ZITADEL_ORG_RULES_FILE` (JSON-правила организаций Zitadel) и `KAFKA_TOPIC_OVERRIDES_FILE` (JSON-переопределения имён топиков).
Валидация на старте (`app.Load`):
- при `S3_ENABLED=true` обязательны `S3_ENDPOINT_URL`, `S3_BUCKET_NAME`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` — иначе ошибка старта;
- при `ZITADEL_ENABLED=true` обязательны `ZITADEL_HOST`, `ZITADEL_ACCESS_TOKEN`, `ZITADEL_ORG_RULES_FILE` — иначе ошибка старта;
- при заданном `KAFKA_TOPIC_OVERRIDES_FILE` файл читается и парсится как JSON `{event_type: topic}` — при ошибке чтения/парсинга сервис не стартует.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (бинарник) | Переменные окружения процесса + env-файл (`.env` или `ENV_FILE`), который автоматически загружается `godotenv` |
| Локально (docker compose) | `docker-compose.yml`: сервис `http` получает `DB_DSN`, `KAFKA_ENABLED`, `S3_ENABLED` из окружения хоста; Postgres — из блока `environment` |
| Kubernetes (Helm) | `.helm/values.yaml` (universal-chart): блоки `envs` (обычные значения по окружениям `_default`/`stage`/`production`), `secretEnvs` (значения из k8s-секретов) и `volumes` (монтирование CA-сертификата Kafka) |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переключение стенда по ветке/тегу (`workflow.rules`), общие шаблоны из `generic/common-ci`, `HELM_SET_ARGS` |
Способы запуска процессов (`cmd/*`):
| Команда | Точка входа | Назначение |
| --- | --- | --- |
| `server` (docker `ENTRYPOINT`, `make build`) | `cmd/server/main.go` | HTTP API (Fiber). Флаг `-env-file=PATH` переопределяет `ENV_FILE` |
| `cli migrate` | `cmd/cli/main.go` | Прогон миграций БД (`golang-migrate`). Требует `DB_DSN`; путь миграций — `DB_MIGRATIONS_PATH` |
| `seed` | `cmd/seed/main.go` | Наполнение БД тестовыми данными (флаги `--seed`, `--resources`, `--users`, `--tenant-id`, `--type-id`) |
Docker-образ (`docker/httpserver/Dockerfile`) собирает статический бинарник `server` (scratch-образ) и копирует каталог `config/` (файлы правил Zitadel). Миграции применяются отдельной командой `cli migrate` (в локальном сценарии — целью `make postgres-up`).
## Переменные приложения
В столбце «Переменная» указано полное имя (с учётом префикса секции). Дефолт `—` означает, что в коде значения по умолчанию нет (поле остаётся нулевым, если не задано).
### App (верхний уровень, без префикса)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENVIRONMENT` | string | `local` | Окружение развёртывания (`local`/`stage`/`prod`) |
| `LOG_LEVEL` | string | `info` | Уровень логирования (`debug`/`info`/`warn`/`error`) |
| `SERVICE_NAME` | string | `iams` | Имя сервиса |
| `SERVICE_VERSION` | string | `0.0.0` | Версия сервиса |
### HTTP (`HTTP_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `HTTP_PORT` | string | `8080` | Порт HTTP-сервера |
| `HTTP_READ_BUFFER_SIZE` | int | `131072` | Размер буфера чтения запроса (Fiber/fasthttp), байты |
### Database (`DB_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DB_DSN` | string | — | DSN PostgreSQL (`postgres://user:pass@host:port/db?sslmode=...`). Обязателен для работы БД и команды `cli migrate` |
| `DB_MIGRATIONS_PATH` | string | `migrations` | Путь к каталогу SQL-миграций |
### Auth (`AUTH_*`)
Конфигурация внешнего контура `/external/api/*`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `AUTH_ENABLED` | bool | `false` | При `false` внешний контур не регистрируется и пользовательская авторизация не выполняется; внутренние эндпоинты работают без изменений |
### Sonyflake (`SONYFLAKE_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SONYFLAKE_MACHINE_ID` | uint16 | `1` | Machine ID генератора идентификаторов `resource.id` для новых записей |
### Zitadel (`ZITADEL_*`)
Management API (Service User с правами администратора). При `ENABLED=true` `HOST`, `ACCESS_TOKEN`, `ORG_RULES_FILE` обязательны.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ZITADEL_ENABLED` | bool | `false` | Включить интеграцию с Zitadel |
| `ZITADEL_HOST` | string | — | Хост Zitadel (напр. `https://login.sarex.io`) |
| `ZITADEL_ACCESS_TOKEN` | string | — | Access token сервисного пользователя |
| `ZITADEL_ORG_RULES_FILE` | string | — | Путь к JSON-файлу правил организаций (`config/zitadel/org-rules-<env>.json`) |
### S3 (`S3_*`)
Presigned GET для вложений виджета в приватном S3-совместимом бакете. При `ENABLED=true` `ENDPOINT_URL`, `BUCKET_NAME`, `ACCESS_KEY_ID`, `SECRET_ACCESS_KEY` обязательны.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `S3_ENABLED` | bool | `false` | Включить S3-интеграцию |
| `S3_ENDPOINT_URL` | string | — | Эндпоинт S3 (напр. `https://storage.yandexcloud.net`) |
| `S3_BUCKET_NAME` | string | — | Имя бакета |
| `S3_REGION` | string | `ru-central1` | Регион |
| `S3_ACCESS_KEY_ID` | string | — | Access key |
| `S3_SECRET_ACCESS_KEY` | string | — | Secret key |
| `S3_PRESIGN_EXPIRES` | duration | `1h` | Срок жизни presigned-ссылки (Go duration, напр. `1h`, `15m`) |
### Kafka (`KAFKA_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_ENABLED` | bool | `false` | Включить Kafka-продюсер |
| `KAFKA_BROKERS` | string | `localhost:9092` | Список брокеров |
| `KAFKA_SECURITY_PROTOCOL` | string | `PLAINTEXT` | Протокол (`PLAINTEXT`/`SASL_SSL`/...) |
| `KAFKA_SASL_MECHANISM` | string | — | SASL-механизм (напр. `SCRAM-SHA-512`) |
| `KAFKA_SASL_PLAIN_USERNAME` | string | — | SASL-логин |
| `KAFKA_SASL_PLAIN_PASSWORD` | string | — | SASL-пароль |
| `KAFKA_SSL_CAFILE` | string | — | Путь к CA-сертификату для SSL |
Политика имён топиков (`KAFKA_TOPIC_*`):
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_TOPIC_PREFIX` | string | — | Префикс, добавляемый ко всем именам топиков. По умолчанию `topic = event_type` |
| `KAFKA_TOPIC_OVERRIDES_FILE` | string | — | Путь к JSON-файлу `{event_type: topic}` с переопределениями. Читается на старте |
| `KAFKA_TOPIC_LEGACY_AMS_SYNC` | string | `ams-sync` | Legacy-топик для `BrokerMessage` пользователя |
### OpenTelemetry (`OTEL_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `OTEL_ENABLED` | bool | `false` | Включить трейсинг |
| `OTEL_HOST` | string | `localhost` | Хост OTLP/gRPC-коллектора |
| `OTEL_PORT` | string | `4317` | Порт коллектора |
| `OTEL_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
| `OTEL_SERVICE_NAME` | string | `iams` | Имя сервиса в трейсах |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт — `universal-chart` (сервис `iams`). Обычные значения задаются в блоке `envs` с профилями `_default`/`stage`/`production` и содержат те же переменные `ENVIRONMENT`, `LOG_LEVEL`, `AUTH_ENABLED`, `HTTP_*`, `DB_MIGRATIONS_PATH`, `ZITADEL_*`, `S3_*`, `KAFKA_*`, `OTEL_*` (различаются адресами БД/брокеров/коллектора, бакетом, доменом Zitadel и путём к файлу правил).
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `DB_DSN` | `iams-secret` | `db-dsn` |
| `ZITADEL_ACCESS_TOKEN` | `iams-secret` | `zitadel-access-token` |
| `KAFKA_SASL_PLAIN_USERNAME` | `iams-secret` | `kafka-sasl-plain-username` |
| `KAFKA_SASL_PLAIN_PASSWORD` | `iams-secret` | `kafka-sasl-plain-password` |
| `S3_ACCESS_KEY_ID` | `yc-s3-secret` | `key_id` |
| `S3_SECRET_ACCESS_KEY` | `yc-s3-secret` | `access_key` |
Помимо env, чарт монтирует CA-сертификат Kafka из секрета `ya-ca-secret` как файл `/etc/ca-certificates/Yandex/ca-cert` — именно на него указывает `KAFKA_SSL_CAFILE` в конфигурации стенда/прода.
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, порт `8080`, `replicaCount`: `_default=1`, `production=4`), `image.name` (`cr.yandex/.../iams:latest`), `service.*` (порт/targetPort `8080`).
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace | Chart version |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `platform` | `0.0.1-stage` |
| тег (`CI_COMMIT_TAG`) | `production` | `iam` | `0.0.1-prod` |
| merge request | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
Ключевые переменные пайплайна: `SERVICE_NAME=iams`, `DOCKERFILE_PATH=./docker/httpserver/Dockerfile`, `RELEASE_NAME`, `CHART_NAME`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` (образ, `global.env`, `commitSha`, ссылки на GitLab). Джобы `lint` (`go vet` + `golangci-lint`) и `test` (`go test ./...`) выполняются на merge request.
## Замечания и минимальный набор для локального запуска
- Приложение **загружает env-файл автоматически** (`godotenv`): достаточно положить `.env` рядом с бинарником или указать путь через `ENV_FILE` / флаг `-env-file`. Отсутствие файла не является ошибкой.
- Три интеграции выключены по умолчанию (`ENABLED=false`): `AUTH`, `ZITADEL`, `S3`, `KAFKA`, `OTEL`. Включение любой из `S3`/`ZITADEL` требует заполнения обязательных полей (см. валидаторы выше), иначе сервис не стартует.
- Для локального запуска минимально необходимо задать `DB_DSN` (Postgres поднимается через `docker compose up -d postgres`, миграции — `make postgres-up``cli migrate`). Остальные секции можно оставить с дефолтами (`ENABLED=false`).
- Готовые значения-примеры для всех переменных приведены в `.env.example`.

207
apps/iam/ENDPOINTS.md Normal file
View File

@ -0,0 +1,207 @@
# Эндпоинты сервиса iams-v2
Документ описывает все HTTP-эндпоинты, которые **предоставляет** сервис `iams` (IAM: пользователи, ресурсы/проекты, права доступа). В отличие от фронтенд-модулей, здесь описан контракт самого сервиса.
## Как устроено взаимодействие
Сервер — Fiber (`internal/controller/http/server.go`). Маршруты регистрируются двумя наборами:
- **Внутренний контур** (`RegisterAPIRoutes`) — без аутентификации на уровне приложения, доступ ограничивается сетевым слоем (не публикуется через ingress). Базовые группы: `/api/v0`, `/api/admin/v0`, `/api/v1`, `/api/v2`.
- **Внешний контур** (`RegisterExternalAPIRoutes`) — регистрируется только при `AUTH_ENABLED=true`. Перед маршрутами выполняется разбор JWT (`AuthMiddleware`) и пометка контекста (`ExternalAPIMiddleware`, для админ-группы дополнительно `ExternalUserAdminAPIMiddleware`). Базовые группы: `/external/api/v0`, `/external/api/admin/v0`, `/external/api/v1`, `/external/api/v2`. Внешний контур публикует **подмножество** внутренних маршрутов, часть — только на чтение.
Глобальные middleware: `requestid`, `recover`, OTel (при `OTEL_ENABLED` и заданном `OTEL_SERVICE_NAME`), логирование.
### Аутентификация (внешний контур)
Токен передаётся заголовком `Authorization: Bearer <jwt>` (`internal/controller/http/jwt/parser.go`). **Подпись токена не проверяется** — доверие делегируется вышестоящему gateway/virtual service. Схема разбора выбирается по заголовку `Identity`:
- заголовок `Identity` отсутствует/пуст → legacy-формат (Django SimpleJWT);
- заголовок `Identity` задан → Zitadel (клейм `urn:zitadel:iam:user:metadata`, значения полей — base64).
### Формат ответов и ошибок
Успешные ответы — JSON (списки: `{ count, results }`; часть эндпоинтов возвращает объект напрямую). Ошибки — JSON `{ "error": "...", "id": "..." }` (`httpx.Response`). Доменный `Kind` маппится на HTTP-статус (`httpx/error_resolve.go`): `InvalidArgument/OutOfRange`→`400`, `Unauthenticated`→`401`, `PermissionDenied`→`403`, `NotFound`→`404`, `Aborted/AlreadyExists`→`409`, `PreconditionFailed`→`412`, `ResourceExhausted`→`429`, `Unavailable`→`503`, `Internal/Unknown/DataLoss`→`500`. Идентификатор запроса — заголовок из `requestid`.
### Пагинация и фильтры
Списки — через query `limit`/`offset` (по умолчанию `limit=1000`). Дополнительные фильтры задаются query-параметрами (напр. для `/resource`: `parent_id`, `type`, `tenant_id`, `name`, `code`, `public_id`, `target_id`, `service_accounts`; для `/users`: `username`, `search`, `is_active`, `is_staff`, `is_superuser`, `service_account_id`, `id`, `company_id`, `departments`, `positions`, `groups`).
## Служебные эндпоинты
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/health` | Health-check: проверка БД и (при включении) Kafka. `200` — ok, `503` — недоступность зависимости |
## Внутренний контур
### Users (`/api/v0/users`, `/api/admin/v0`, `/api/v1`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/users` | Список пользователей (фильтры, пагинация) |
| POST | `/api/v0/users` | Создать пользователя (если включён use case) |
| PATCH | `/api/v0/users` | Массовое обновление пользователей (legacy bulk_update) |
| GET | `/api/v0/users/:id` | Пользователь по id |
| PUT | `/api/v0/users/:id` | Полное обновление пользователя |
| PATCH | `/api/v0/users/:id` | Частичное обновление пользователя |
| DELETE | `/api/v0/users/:id` | Удалить пользователя |
| POST | `/api/admin/v0/users/activation` | Активация/деактивация пользователей компании |
| POST | `/api/v1/users-with-resources` | Пользователи с их ресурсами (тело: `tenant_id`, `id[]`) |
| GET | `/api/v1/users-grouped-by-resource/` | Пользователи, сгруппированные по ресурсу (если включён use case) |
### Permission check (`/api/v0`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| POST | `/api/v0/permissions-check` | Проверка набора прав пользователя (`user_id`, `checks[]`) |
### Groups (`/api/v0/groups`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/groups` | Список групп |
| POST | `/api/v0/groups` | Создать группу |
| POST | `/api/v0/groups/search` | Поиск групп (пагинация, сортировка, фильтры) |
| GET | `/api/v0/groups/:id` | Группа по id |
| PUT | `/api/v0/groups/:id` | Обновить группу |
| PATCH | `/api/v0/groups/:id` | Частично обновить группу |
| DELETE | `/api/v0/groups/:id` | Удалить группу |
### Positions (`/api/v0/positions`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/positions` | Список должностей |
| POST | `/api/v0/positions` | Создать должность |
| GET | `/api/v0/positions/:id` | Должность по id |
| PUT | `/api/v0/positions/:id` | Обновить должность |
| PATCH | `/api/v0/positions/:id` | Частично обновить должность |
| DELETE | `/api/v0/positions/:id` | Удалить должность |
### Departments (`/api/v0/departments`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/departments` | Список подразделений |
| POST | `/api/v0/departments` | Создать подразделение |
| GET | `/api/v0/departments/:id` | Подразделение по id |
| PUT | `/api/v0/departments/:id` | Обновить подразделение |
| PATCH | `/api/v0/departments/:id` | Частично обновить подразделение |
| DELETE | `/api/v0/departments/:id` | Удалить подразделение |
### Django permissions (`/api/v0/permissions`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/permissions` | Список permission'ов |
| POST | `/api/v0/permissions` | Создать permission |
| POST | `/api/v0/permissions/search` | Поиск permission'ов |
| GET | `/api/v0/permissions/:id` | Permission по id |
| DELETE | `/api/v0/permissions/:id` | Удалить permission |
### Resource types v0 (`/api/v0/resource-types`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/resource-types` | Список типов ресурсов |
| POST | `/api/v0/resource-types` | Создать тип |
| GET | `/api/v0/resource-types/:id` | Тип по id |
| PUT | `/api/v0/resource-types/:id` | Обновить тип |
| PATCH | `/api/v0/resource-types/:id` | Частично обновить тип |
| DELETE | `/api/v0/resource-types/:id` | Удалить тип |
### Resources v0 (`/api/v0/resources`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/resources` | Список ресурсов (legacy v0) |
| POST | `/api/v0/resources` | Создать ресурс |
| GET | `/api/v0/resources/:id` | Ресурс по id |
| PUT | `/api/v0/resources/:id` | Обновить ресурс |
| PATCH | `/api/v0/resources/:id` | Частично обновить ресурс |
| DELETE | `/api/v0/resources/:id` | Удалить ресурс |
### Projects v0 (`/api/v0/projects`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/projects` | Список проектов |
| POST | `/api/v0/projects` | Создать проект |
| GET | `/api/v0/projects/:publicID` | Проект по public id |
| PUT | `/api/v0/projects/:publicID` | Обновить проект |
| PATCH | `/api/v0/projects/:publicID` | Частично обновить проект |
| DELETE | `/api/v0/projects/:publicID` | Удалить проект |
### Widgets v0 (`/api/v0/widgets`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v0/widgets` | Список виджетов |
| POST | `/api/v0/widgets` | Создать виджет |
| GET | `/api/v0/widgets/:publicID` | Виджет по public id |
| PUT | `/api/v0/widgets/:publicID` | Обновить виджет |
| PATCH | `/api/v0/widgets/:publicID` | Частично обновить виджет |
| DELETE | `/api/v0/widgets/:publicID` | Удалить виджет |
### Resources v1 (`/api/v1/resource`, `/api/v1/targets`, `/api/v1/service-accounts`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v1/resource` | Список ресурсов (v1) |
| POST | `/api/v1/resource` | Создать ресурс |
| GET | `/api/v1/resource/:publicID` | Ресурс по public id |
| PUT | `/api/v1/resource/:publicID` | Обновить ресурс |
| PATCH | `/api/v1/resource/:publicID` | Частично обновить ресурс |
| DELETE | `/api/v1/resource/:publicID` | Удалить ресурс |
| GET | `/api/v1/targets/:target_id/resource` | Ресурс по target id (только внутренний) |
| GET | `/api/v1/resources-grouped-by-sa` | Ресурсы, сгруппированные по сервисному аккаунту (если включено) |
| GET | `/api/v1/service-accounts` | Список сервисных аккаунтов |
### Widgets v2 (`/api/v2/resource`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v2/resource` | Список виджетов (v2) |
| POST | `/api/v2/resource` | Создать виджет |
| GET | `/api/v2/resource/:publicID` | Виджет по public id |
| PUT | `/api/v2/resource/:publicID` | Обновить виджет |
| PATCH | `/api/v2/resource/:publicID` | Частично обновить виджет |
| DELETE | `/api/v2/resource/:publicID` | Удалить виджет |
### Permissions v1 (`/api/v1`)
| Метод | Путь | Назначение |
| --- | --- | --- |
| GET | `/api/v1/resource_permission` | Список прав на ресурсы |
| POST | `/api/v1/resource_permission` | Создать права (пакет `items[]`) |
| GET | `/api/v1/resource_permission/:id` | Право по id |
| DELETE | `/api/v1/resource_permission/:id` | Удалить право |
| PATCH | `/api/v1/permissions-bulk` | Массовое изменение прав (`tenant_id`, `permissions`) |
| GET | `/api/v1/company_resource_permission` | Список прав компании на ресурсы |
| POST | `/api/v1/company_resource_permission` | Создать права компании (пакет `items[]`) |
| GET | `/api/v1/company_resource_permission/:id` | Право компании по id |
| DELETE | `/api/v1/company_resource_permission/:id` | Удалить право компании |
## Внешний контур (`/external/api/*`, только при `AUTH_ENABLED=true`)
Публикуется подмножество внутренних маршрутов; часть — только на чтение. Пути идентичны внутренним, но с префиксом `/external/api`.
| Группа | Маршруты | Отличия от внутреннего контура |
| --- | --- | --- |
| `/external/api/v0/users` | `GET /`, `GET /:id`, `PATCH /` | Только чтение + массовый PATCH (bulk) |
| `/external/api/admin/v0/users` | `POST /activation`, полный CRUD `/users` | Полный доступ (админ-группа) |
| `/external/api/admin/v0/positions` | `/positions` (CRUD) | Как внутренний |
| `/external/api/admin/v0/departments` | `/departments` (CRUD) | Как внутренний |
| `/external/api/admin/v0/groups` | `/groups` (CRUD + search) | Как внутренний |
| `/external/api/admin/v0/permissions` | `/permissions` (django) | Как внутренний |
| `/external/api/v0/resource-types` | `/resource-types` (CRUD) | Как внутренний |
| `/external/api/v0/resources` | `/resources` (CRUD, v0) | Как внутренний |
| `/external/api/v0/projects` | `GET /`, `GET /:publicID` | Только чтение |
| `/external/api/v0/widgets` | `/widgets` (CRUD) | Как внутренний |
| `/external/api/v1/resource` | `GET /`, `GET /:publicID` | Только чтение |
| `/external/api/v2/resource` | `/resource` (виджеты v2, CRUD) | Как внутренний |
| `/external/api/v1/resource_permission` | CRUD-подмножество | Как внутренний |
| `/external/api/v1/permissions-bulk` | `PATCH /` | Как внутренний |
| `/external/api/v1/company_resource_permission` | CRUD-подмножество | Как внутренний |
> Часть маршрутов регистрируется условно — только если соответствующий хендлер/use case подключён при инициализации сервера (проверки `Has*` в хендлерах). При отсутствии сервиса группа не регистрируется.

1170
apps/iam/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
# Общие настройки процесса
PYTHONPATH=src
PICCOLO_CONF=db.config
# App
DEBUG=true
SERVICE_URL=https://stage.sarex.io
# HTTP App
HTTP_APP_HOST=0.0.0.0
HTTP_APP_PORT=8000
HTTP_APP_ROOT_PATH=""
HTTP_APP_WORKERS=1
HTTP_APP_ADMIN_ENABLE=true
# Database
DATABASE_HOST=postgres
DATABASE_PORT=5432
DATABASE_NAME=postgres
DATABASE_USER=postgres
DATABASE_PASSWORD=postgres
# Kafka
KAFKA_HOST=host
KAFKA_USERNAME=username
KAFKA_PASSWORD=password
# Если задан KAFKA_SSL_CERT (для http-приложения) или KAFKA_SSL_CAFILE (для kafka-consumer) —
# подключение идёт по SASL-SCRAM-SHA-512 поверх TLS, иначе без SSL.
KAFKA_SSL_CERT=""
KAFKA_SSL_CAFILE=ssl_cafile
KAFKA_EAV_ASSETS_TOPIC=eav_assets_topic
# OpenTelemetry
OTEL_ENABLE=false
OTEL_URL=http://signoz-otel-collector-external.signoz.svc.cluster.local:4317
OTEL_SERVICE_NAME=inspections-backend.inspections-stage
OTEL_INSECURE=true
# Auth (JWT)
# Если false — middleware аутентификации не подключается, используется дефолтный пользователь.
JWT_AUTH_ENABLE=false
# Notifications
NOTIFICATIONS_ENABLE=false
NOTIFICATIONS_EMAIL_FROM=hello@sarex.io
# Sarex backend
SAREX_BACKEND_URL=https://stage.sarex.io
SAREX_BACKEND_TIMEOUT=30
# base64(login:password)
SAREX_BACKEND_AUTH=base64(login:password)
# EAV (сервис атрибутов)
EAV_URL=https://stage-api.sarex.io/eav
EAV_TIMEOUT=30
# Workflows (процессы / отправка email)
WORKFLOWS_URL=http://workflows-api-service.processing-stage
WORKFLOWS_TIMEOUT=30
WORKFLOWS_EMAIL_DOCKER_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/notification:email
# Мобильное приложение (версии)
MOBILE_APP_CURRENT_VERSION=1.0.0
MOBILE_APP_RECOMMENDED_VERSION=1.0.0
MOBILE_APP_REQUIRED_VERSION=1.0.0

View File

@ -0,0 +1,216 @@
# Конфигурация проекта inspections-backend
Документ описывает все переменные окружения и способы конфигурирования сервиса.
## Способы конфигурирования
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/) (класс `Config` и вложенные `*Config`).
Особенности разбора:
- у приложения **нет единого общего префикса** — каждая секция задаётся собственным `env_prefix` в своём `SettingsConfigDict` (напр. `HTTP_APP_`, `DATABASE_`, `KAFKA_`, `OTEL_`, `JWT_AUTH_`, `NOTIFICATIONS_`, `SAREX_BACKEND_`, `EAV_`, `WORKFLOWS_`, `MOBILE_APP_`);
- две переменные верхнего уровня (`DEBUG`, `SERVICE_URL`) читаются без префикса;
- вложенности через разделитель нет — плоские имена вида `<PREFIX><FIELD>`, напр. `DATABASE_HOST``database.host`;
- почти у всех полей есть значения по умолчанию, поэтому отсутствие переменной обычно не приводит к ошибке старта — берётся дефолт из кода. В таблицах ниже приведены дефолты из `src/config.py`.
Отдельного конфиг-файла (yaml/toml) у приложения нет. Часовой пояс приложения зафиксирован в коде: `app_timezone = ZoneInfo("Europe/Moscow")`.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально (uv) | `Makefile` через `SET_ENV` делает `set -a; source .env; set +a` перед запуском команд. Шаблон переменных — `.env.template` (в репозитории; `.env*` в `.gitignore`, кроме `.env.template`) |
| Docker | `docker/http/Dockerfile` и `docker/kafka/Dockerfile` фиксируют `PYTHONPATH=src` и `PICCOLO_CONF=db.config`; прикладные переменные пробрасываются рантаймом (k8s) |
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) для сервисов `api` и `kafka-app` |
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`), сборка образа и деплой универсального чарта |
Способы запуска процессов:
| Команда (`Makefile`) | Точка входа | Назначение |
| --- | --- | --- |
| `make run` | `src/cmd/http/main.py` | HTTP API (uvicorn, `app.http:create_app`, `factory=True`) |
| — (kafka-consumer) | `src/cmd/kafka/main.py` | Обработчик Kafka-событий (FastStream) |
| `make migrate` | `piccolo migrations forwards all` | Применение миграций Piccolo |
| `make migrations` | `piccolo migrations new inspections --auto` | Генерация новой миграции |
| `make sync_eav` | `src/cmd/scripts/sync_eav.py` | Разовая синхронизация EAV |
| `make playground` | `piccolo playground run` | Локальный playground Piccolo (sqlite) |
| `make format` / `make format-check` | `ruff` | Форматирование/проверка кода |
Порядок запуска в контейнере HTTP (`docker/http/entrypoint.sh`): сначала выполняются миграции (`piccolo migrations forwards all`), затем стартует `src/cmd/http/main.py`. Контейнер kafka-consumer (`docker/kafka/entrypoint.sh`) запускает только `src/cmd/kafka/main.py` (миграции не применяет).
## Переменные приложения
В столбце «Переменная» указано полное имя (префикс + поле). Дефолт `—` означает, что значения по умолчанию у поля нет.
### App (верхний уровень)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DEBUG` | bool | `True` | Режим отладки: логирование SQL-запросов Piccolo, `reload=True` у uvicorn, непродакшн-режим админки |
| `SERVICE_URL` | string | `https://stage.sarex.io` | Внешний базовый URL Sarex, используется при формировании ссылок (уведомления, экспорт) |
### HTTP App (`HTTP_APP_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `HTTP_APP_HOST` | string | `0.0.0.0` | Адрес прослушивания |
| `HTTP_APP_PORT` | int | `8000` | Порт |
| `HTTP_APP_ROOT_PATH` | string | `""` | Root path (префикс за реверс-прокси, напр. `/inspections`) |
| `HTTP_APP_WORKERS` | int | `1` | Число воркеров uvicorn |
| `HTTP_APP_ADMIN_ENABLE` | bool | `True` | Монтировать ли Piccolo-admin по пути `/admin/` |
### Database (`DATABASE_*`)
PostgreSQL через Piccolo (`PostgresEngine`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DATABASE_HOST` | string | `postgres` | Хост PostgreSQL |
| `DATABASE_PORT` | int | `5432` | Порт PostgreSQL |
| `DATABASE_NAME` | string | `postgres` | Имя базы данных |
| `DATABASE_USER` | string | `postgres` | Пользователь БД |
| `DATABASE_PASSWORD` | string | `postgres` | Пароль пользователя БД |
### Kafka (`KAFKA_*`)
Используется FastStream (`KafkaBroker`). Продюсер — в HTTP-приложении (публикация событий инспекций), консьюмер — отдельный процесс (`kafka-app`), подписан на топик EAV-ассетов.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_HOST` | string | `""` | Bootstrap-сервер (`bootstrap_servers=[host]`) |
| `KAFKA_USERNAME` | string | `""` | Пользователь (SASL) |
| `KAFKA_PASSWORD` | string | `""` | Пароль (SASL) |
| `KAFKA_SSL_CERT` | string | `""` | CA-сертификат как строка (`cadata`). Если задан — HTTP-приложение подключается по `SASLScram512` поверх TLS, иначе без SSL |
| `KAFKA_SSL_CAFILE` | string | `""` | Путь к CA-файлу (`cafile`). Если задан — kafka-consumer подключается по `SASLScram512` поверх TLS, иначе без SSL |
| `KAFKA_EAV_ASSETS_TOPIC` | string | `""` | Топик событий EAV-ассетов, на который подписан consumer |
> Механизм безопасности отличается между процессами: HTTP-приложение (`app/http.py`) смотрит на `KAFKA_SSL_CERT` (строка сертификата), а kafka-consumer (`app/kafka.py`) — на `KAFKA_SSL_CAFILE` (путь к файлу).
### OpenTelemetry (`OTEL_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `OTEL_ENABLE` | bool | `False` | Включить трейсинг/структурированное логирование. При `True` отключается `access_log` uvicorn |
| `OTEL_URL` | string | `http://signoz-otel-collector-external.signoz.svc.cluster.local:4317` | Адрес OTLP-коллектора |
| `OTEL_SERVICE_NAME` | string | `inspections-backend.inspections-stage` | Имя сервиса в трейсах |
| `OTEL_INSECURE` | bool | `True` | Небезопасное (без TLS) подключение к коллектору |
### Auth (`JWT_AUTH_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `JWT_AUTH_ENABLE` | bool | `False` | Подключать ли middleware `TokenUserMiddleware`. При `False` используется дефолтный пользователь из `entity/context.py` (для локальной разработки) |
> Middleware декодирует JWT **без проверки подписи** (`verify_signature: False`). При наличии заголовка `identity` полезная нагрузка берётся из него (метаданные Zitadel), иначе — из `authorization`. Роуты `/docs/`, `/openapi.json/`, `/admin/`, `/internal/` исключены из проверки.
### Notifications (`NOTIFICATIONS_*`)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `NOTIFICATIONS_ENABLE` | bool | `False` | Включить отправку уведомлений об инспекциях |
| `NOTIFICATIONS_EMAIL_FROM` | string | `hello@sarex.io` | Адрес отправителя писем |
### Sarex backend (`SAREX_BACKEND_*`)
HTTP-клиент основного бэкенда Sarex (данные пользователей и т.п.).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SAREX_BACKEND_URL` | string | `https://stage.sarex.io` | Базовый URL |
| `SAREX_BACKEND_TIMEOUT` | int | `30` | Таймаут запроса (сек) |
| `SAREX_BACKEND_AUTH` | string (base64) | `base64(login:password)` | Basic-auth в виде base64(`login:password`) |
### EAV (`EAV_*`)
HTTP-клиент сервиса атрибутов (EAV).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `EAV_URL` | string | `https://stage-api.sarex.io/eav` | Базовый URL |
| `EAV_TIMEOUT` | int | `30` | Таймаут запроса (сек) |
### Workflows (`WORKFLOWS_*`)
HTTP-клиент сервиса процессов; используется, в т.ч. для запуска отправки email.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `WORKFLOWS_URL` | string | `http://workflows-api-service.processing-stage` | Базовый URL |
| `WORKFLOWS_TIMEOUT` | int | `30` | Таймаут запроса (сек) |
| `WORKFLOWS_EMAIL_DOCKER_IMAGE` | string | `cr.yandex/crp3ccidau046kdj8g9q/notification:email` | Docker-образ шага отправки email |
### Mobile App (`MOBILE_APP_*`)
Значения отдаются эндпоинтом `GET /api/v1/mobile-app/version/`.
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `MOBILE_APP_CURRENT_VERSION` | string | `""` | Текущая версия |
| `MOBILE_APP_RECOMMENDED_VERSION` | string | `""` | Рекомендуемая версия |
| `MOBILE_APP_REQUIRED_VERSION` | string | `""` | Минимально требуемая версия |
## Переменные инфраструктуры и сборки
Не читаются кодом приложения через `pydantic-settings`, но участвуют в запуске/сборке/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `PYTHONPATH=src` | `.env.template`, `Dockerfile` | Корень пакета приложения |
| `PICCOLO_CONF=db.config` | `.env.template`, `Dockerfile` | Модуль конфигурации Piccolo (`APP_REGISTRY`, `DB`, `ADMIN_ASGI_APP`) |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт — обёртка над зависимостью `universal-chart` (`oci://cr.yandex/crp3ccidau046kdj8g9q/charts`, версия `0.1.7`). Описаны два сервиса: `api` (HTTP) и `kafka-app` (consumer), у каждого свои блоки `envs` и `secretEnvs`. Значения различаются по окружениям через ключи `_default`/`stage`/`preprod`/`production`.
Обычные значения (`envs`) содержат те же переменные приложения, что и выше (различаются `SERVICE_URL`, `EAV_URL`, `WORKFLOWS_URL`, `OTEL_*`, `KAFKA_EAV_ASSETS_TOPIC`, `HTTP_APP_ROOT_PATH=/inspections`, `HTTP_APP_WORKERS=3`, `JWT_AUTH_ENABLE=true`, `NOTIFICATIONS_ENABLE` и т.п.). Отличия от дефолтов кода: в проде `DEBUG=false`, `OTEL_ENABLE=true`, `JWT_AUTH_ENABLE=true`.
Значения из секретов (`secretEnvs`, монтируются как env через `secretKeyRef`):
| Переменная | Секрет (`_default` / `stage`) | Ключ (`secretKey`) |
| --- | --- | --- |
| `DATABASE_USER` | `ya-pg-secret` / `inspections-postgresql-secret` | `username` |
| `DATABASE_PORT` | `ya-pg-secret` / `inspections-postgresql-secret` | `port` |
| `DATABASE_NAME` | `ya-pg-secret` / `inspections-postgresql-secret` | `database` |
| `DATABASE_HOST` | `ya-pg-secret` / `inspections-postgresql-secret` | `host` |
| `DATABASE_PASSWORD` | `ya-pg-secret` / `inspections-postgresql-secret` | `password` |
| `KAFKA_HOST` | `yc-kafka-secret` / `inspections-kafka-secret` | `host` |
| `KAFKA_USERNAME` | `yc-kafka-secret` / `inspections-kafka-secret` | `username` |
| `KAFKA_PASSWORD` | `yc-kafka-secret` / `inspections-kafka-secret` | `password` |
| `KAFKA_SSL_CERT` | `inspections-kafka-secret` | `cert` |
| `SAREX_BACKEND_AUTH` | `sarex-backend-auth-secret` | `key` |
Помимо env, у сервиса `kafka-app` смонтирован CA-сертификат Yandex как файл `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt` (секрет `yc-ch-certificate`, ключ `certificate`) — на него указывает `KAFKA_SSL_CAFILE`.
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, реплики, ресурсы, probes), `image.*`, `service.*`, `imagePullSecrets`, у `kafka-app``command` (`python src/cmd/kafka/main.py`) и `volumes`.
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
| Условие | STAND | Namespace | Release / Chart |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `proc` | `inspections-backend` |
| ветка `master` | `preprod` | `inspections-preprod` | `inspections-backend` |
| тег (`CI_COMMIT_TAG`) | `production` | `inspections-prod` | `sarex-inspections` |
| `merge_request_event` | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
Ключевые переменные пайплайна: `SERVICE_NAME=inspections-backend`, `DOCKERFILE_PATH=./docker/http/Dockerfile`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` (проброс `image.name`, `global.env`, метаданных коммита для сервисов `api` и `kafka-app`). Job `lint` прогоняет `ruff check` и `ruff format --check` на образе `uv 0.7.13 / python3.13`.
## Замечания
- Приложение **не загружает `.env` автоматически** — переменные экспортируются в окружение (в `Makefile` — через `set -a; source .env; set +a`, в k8s — через env-блоки чарта).
- Аутентификация в middleware декодирует JWT **без проверки подписи**; безопасность обеспечивается сетевым слоем/ingress. При `JWT_AUTH_ENABLE=false` активен захардкоженный дефолтный пользователь (`entity/context.py`), пригодный только для локальной разработки.
- Большинство полей конфигурации имеют дефолты — при отсутствии переменной сервис стартует со значением из кода. Для реального окружения значения задаются в `.helm/values.yaml`.
- Для экспорта поддерживается единственный формат — `xlsx` (`InspectionExportType`).
## Минимальный набор для локального запуска
Скопировать `.env.template` в `.env`, поднять PostgreSQL и (при необходимости обработки событий) Kafka, применить миграции (`make migrate`) и запустить API (`make run`). Минимально стоит задать:
- `DEBUG`, `SERVICE_URL`
- `HTTP_APP_HOST`, `HTTP_APP_PORT`
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
- `JWT_AUTH_ENABLE=false` (для дебага без токена)
- `KAFKA_*` (если нужен consumer/публикация событий)
- `SAREX_BACKEND_*`, `EAV_*`, `WORKFLOWS_*` (для интеграций)
- `OTEL_ENABLE=false` локально

View File

@ -0,0 +1,115 @@
# Эндпоинты, с которыми взаимодействует inspections-frontend
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `inspections-frontend`).
## Как устроено взаимодействие
Запросы выполняются через общий `httpService` (`module/api/http-service.ts`), созданный фабрикой `createHttpService` из `@sarex-team/sdk-js` (поверх `axios`). У сервиса есть методы `getRequest`, `postRequest`, `patchRequest`, `deleteRequest`, каждый из которых принимает объект с полями:
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
- `url` — путь запроса (дописывается к базовому хосту сервиса);
- `data` — тело запроса (для POST/PATCH);
- `axiosConfig` — доп. настройки axios (`params`, `paramsSerializer` и т.п.);
- `showErrorNotification` — показывать ли уведомление об ошибке.
Базовый хост подставляется по паре «`service` + окружение». Окружение определяется глобальной переменной сборки `BUILD_ENV` (`local`/`stage`/`preprod`/`prod`/`contour`); при её отсутствии используется `prod` (`const buildEnv = BUILD_ENV ?? "prod"`). В режиме `local` для http-сервиса выставляется `type: "original"`. `BUILD_ENV` задаётся при сборке (напр. `BUILD_ENV=stage npm start`) и прокидывается через webpack DefinePlugin.
Определения запросов сгруппированы по файлам в `module/api/` (`inspections.ts`, `assets.ts`, `Issues.ts`, `premises-api.ts`) и по стор-файлам в `module/Inspections/store/` (`inspections.ts`, `filters.ts`, `resource.ts`, `calendar.ts`, `issuesStore.ts`).
## Базовые хосты по сервисам и окружениям
Значения из `module/api/hosts.ts`. Итоговый URL = `<базовый хост сервиса>` + `url` запроса.
| Сервис (`service`) | Назначение | `stage` | `prod` |
| --- | --- | --- | --- |
| `inspections` | Сервис событий/инспекций (этот бэкенд) | `https://stage-api.sarex.io/inspections/api/v1` | `https://api.sarex.io/inspections/api/v1` |
| `sarexApi` | Gateway/API Sarex (`/gateway`) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
| `sarex` | Локальный сервис данных (`/api/core`, `/api/client`) | `""` (относительные пути) | `""` |
| `eavV0` | Сервис атрибутов EAV, API v0 | `https://stage-api.sarex.io/eav/api/v0` | `https://api.sarex.io/eav/api/v0` |
| `eavV4` | Сервис атрибутов EAV, API v4 (ассеты) | `https://stage-api.sarex.io/eav/api/v4` | `https://api.sarex.io/eav/api/v4` |
| `issues` | Сервис замечаний | `https://stage-api.sarex.io/issues/api` | `https://api.sarex.io/issues/api` |
| `premises` | Сервис помещений | `https://stage-api.sarex.io/premises/api/v1` | `https://api.sarex.io/premises/api/v1` |
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
> Также определены окружения `local`, `preprod` и `contour`. В `local` сервис `sarex` проксируется на `/sarex-backend`, а `inspections`/`eav`/`issues`/`premises` указывают на стейдж. В `contour` все хосты пустые (относительные пути для изолированного контура). Сервис `zitadel` в hosts объявлен, но прямых запросов из модуля к нему нет — аутентификация обрабатывается на уровне SDK/платформы.
## Эндпоинты по сервисам
### `inspections` — Сервис событий/инспекций
Базовый хост уже включает `/api/v1`, поэтому в путях ниже он не повторяется.
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| GET | `/inspections/` | `store/resource.ts` | Создание/список событий |
| POST | `/inspections/` | `store/resource.ts` | Создать событие |
| POST | `/inspections/filter/` | `store/resource.ts`, `store/calendar.ts` | Список событий с фильтрами и пагинацией в теле |
| GET | `/inspections/{id}/` | `api/inspections.ts`, `store/resource.ts` | Событие по id |
| PATCH | `/inspections/{id}/` | `api/inspections.ts` | Частичное обновление события |
| GET | `/inspections/types/` | `api/inspections.ts` | Типы событий компании (query `company_id`) |
| POST | `/inspections/status-count/` | `store/inspections.ts` | Счётчики по статусам (фильтры в теле) |
| POST | `/inspections/filter-options/` | `store/filters.ts` | Доступные значения фильтров |
| GET | `/inspections/aggregate/created_at/minmax/` | `store/filters.ts` | Мин/макс по дате создания |
| GET | `/inspections/aggregate/inspection_dt/minmax/` | `store/filters.ts` | Мин/макс по дате проведения |
| GET | `/inspections/change-history/` | `api/inspections.ts` | История изменений (пагинация, фильтры в query) |
| GET | `/inspections/export/` | `store/inspections.ts` | Экспорт событий (xlsx) |
| POST | `/inspections/unavailable-dates/` | `api/inspections.ts` | Недоступные даты для исполнителей |
| POST | `/inspections/unavailable-users/` | `api/inspections.ts` | Недоступные исполнители на интервал |
### `sarexApi` — Gateway/API Sarex
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| GET | `/gateway/api/v1/resources/?company_id={companyId}` | `store/inspections.ts` | Ресурсы (проекты) компании |
| GET | `/gateway/api/v2/users/` | `store/issuesStore.ts`, `store/resource.ts` | Пользователи (с пагинацией/фильтрами) |
| GET | `/gateway/api/v1/attachments/?company_id={companyId}&instance_id={id}&model_name=inspection` | `store/resource.ts` | Вложения события |
| POST | `/gateway/api/v1/attachments/` | `store/resource.ts` | Создать вложение |
| DELETE | `/gateway/api/v1/attachments/{id}` | `store/resource.ts` | Удалить вложение |
### `sarex` — Локальный сервис данных
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| GET | `/api/client/settings/` | `store/inspections.ts` | Клиентские настройки |
| GET | `/api/core/users/` | `store/inspections.ts` | Пользователи |
| GET | `/api/core/admin/departments/?company={companyId}` | `store/inspections.ts` | Отделы компании |
| GET | `/api/core/admin/positions/?company={companyId}` | `store/inspections.ts` | Должности компании |
### `eavV4` — Сервис атрибутов EAV (ассеты)
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| POST | `/assets/search/` | `api/assets.ts` | Поиск ассетов по списку id |
| GET | `/assets/` | `api/assets.ts` | Список ассетов (фильтры/пагинация/теги в query) |
> Теги ассетов (`EnumAssetTags`): `location`, `project_structure`, `events.can_be_selected`, `remarks.can_be_selected`.
### `eavV0` — Сервис атрибутов EAV (атрибуты)
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| GET | `/attribute/?company_id={companyId}` | `store/inspections.ts` | Атрибуты компании |
### `issues` — Сервис замечаний
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| POST | `/issues/` | `api/Issues.ts` | Создать замечание |
| GET | `/issues/` | `api/Issues.ts` | Список замечаний (фильтры по компании/ресурсу/событию/типам) |
| POST | `/attachments/` | `api/Issues.ts`, `store/issuesStore.ts` | Прикрепить медиа к замечанию |
| GET | `/companies/{companyId}/status-model/` | `api/Issues.ts` | Статусная модель замечаний компании |
| GET | `/issue-types/` | `api/Issues.ts` | Типы замечаний компании |
### `premises` — Сервис помещений
Базовый хост уже включает `/api/v1`.
| Метод | Путь | Где вызывается | Назначение |
| --- | --- | --- | --- |
| GET | `/premises/{id}/` | `api/premises-api.ts` | Помещение по id |
| POST | `/premises/filter/` | `api/premises-api.ts` | Помещения по фильтру (пагинация в query) |
| POST | `/premise_types/filter/` | `api/premises-api.ts` | Типы помещений по фильтру (пагинация в query) |
## Обработка ошибок
Показ уведомлений об ошибках управляется флагом `showErrorNotification` в параметрах запроса (включается точечно для части запросов). Для сериализации query-параметров-массивов местами используется `query-string` с `arrayFormat: "comma"` (напр. в `api/Issues.ts`). Часть запросов в `api/assets.ts` оборачивает ошибку в `throw new Error(...)`.

File diff suppressed because it is too large Load Diff

101
apps/issues/.env.example Normal file
View File

@ -0,0 +1,101 @@
# Django
DJANGO_SETTINGS_MODULE=config.settings.production
DJANGO_ADMIN_SECRET_KEY=''
DJANGO_TOKEN=django-token
# Environment
ENVIRONMENT=production
ENVIRONMENT_CLIENT=stage
# Database (PostgreSQL)
DATABASE_NAME=postgres
DATABASE_USER=postgres
DATABASE_PASSWORD=password
DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
# Sarex auth (basic)
SAREX_USERNAME=
SAREX_PASSWORD=
# External services
AERO_HOST=https://stage.sarex.io
AERO_PUBLIC_HOST=https://stage.sarex.io
BASE_AERO_URL=https://stage.sarex.io
BASE_AUTH_URL=https://stage.sarex.io
SAREX_API=https://stage.sarex.io
SAREX_HOST=https://stage.sarex.io
SERVICE_URL=https://stage.sarex.io
GATEWAY_URL=https://stage-api.sarex.io/gateway
DOCUMENTATIONS_URL=http://documentations-api-svc.documentations.svc.cluster.local:8000
WORKFLOWS_URL=http://workflows-api-service.platform.svc.cluster.local:8000
WORKFLOWS_HOST=http://workflows-api-service.platform.svc.cluster.local:8000
RESOURCES_API_HOST=http://iams.platform.svc.cluster.local:8080
REVIEW_HOST=https://stage-api.sarex.io/flows
INSPECTION_HOST=https://stage-api.sarex.io/inspections
EAV_HOST=http://eav-service.eav-stage
# RabbitMQ / Celery broker
RABBITMQ_USERNAME=mcc
RABBITMQ_PASSWORD=mcc
RABBITMQ_HOSTNAME=rabbitmq-service
RABBITMQ_VHOST=api
# Redis (Celery result backend)
REDIS_HOST=redis
REDIS_DB=0
# Kafka
KAFKA_HOST=
KAFKA_USERNAME=
KAFKA_PASSWORD=
KAFKA_SSL_CAFILE=/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
KAFKA_EAV_ASSETS_TOPIC=assets-broadcast-test
KAFKA_ISSUES_TOPIC=issues-broadcast
# S3 (Yandex Cloud) — общий бакет
YC_S3_ACCESS_KEY_ID=
YC_S3_SECRET_ACCESS_KEY=
YC_S3_BUCKET_NAME=
YC_S3_ENDPOINT_URL=
YC_S3_VERIFY=true
# S3 — бакет предписаний
PRESCRIPTION_S3_ACCESS_KEY_ID=
PRESCRIPTION_S3_SECRET_ACCESS_KEY=
PRESCRIPTION_S3_BUCKET=
PRESCRIPTION_S3_ENDPOINT_URL=
# Email
ENABLE_MAILGUN=True
EMAIL_FROM=hello@sarex.io
EMAIL_DOCKER_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/notification:email
MAILGUN_BASE_URL=https://api.mailgun.net/v3/mg.sarex.io
MAILGUN_API_KEY=
USE_NOTIFICATIONS=True
# SMTP (альтернатива Mailgun)
SMTP_HOST=
SMTP_PORT=
# Prescriptions workflow
PRESCRIPTION_WF_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/rendering-template:develop
PRESCRIPTION_WF_RESULT_PATH=prescriptions-storage-stage
PRESCRIPTION_WF_CALLBACK=cr.yandex/crp3ccidau046kdj8g9q/webhook-caller:develop
PRESCRIPTION_INTERNAL_HOST=http://issues-backend-service.proc.svc.cluster.local:80/internal
DOCX_TO_PDF_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/docx-to-pdf:latest
# Export
EXPORT_WF_CROPPING_DOCKER_IMAGE=cr.yandex/crp3ccidau046kdj8g9q/crop-issue-pin-area:stage
# OpenTelemetry
USE_OTEL=False
SERVICE_NAME=issues-backend.sarex-issues
TRACER_ENDPOINT=localhost:4375
USE_INSECURE=True
MODULE=issues
TEAM=proc_team
COMPONENT=backend
# uWSGI / infra
API_ADDRESS=8000
SENTRY_KEY=

View File

@ -0,0 +1,245 @@
# Конфигурация проекта issues-backend
Документ описывает все переменные окружения и способы конфигурирования сервиса замечаний (Issues).
## Способы конфигурирования
Сервис настраивается **через переменные окружения**. Это Django-приложение; настройки читаются в `src/config/settings/base.py` и `src/config/settings/production.py` напрямую через `os.getenv(...)`. В начале `base.py` вызывается `load_dotenv()` ([`python-dotenv`](https://pypi.org/project/python-dotenv/)), поэтому при локальном запуске файл `.env` из рабочего каталога **подхватывается автоматически**.
Активный модуль настроек задаётся переменной `DJANGO_SETTINGS_MODULE` (в контейнере/Helm — `config.settings.production`) либо флагом `--settings=config.settings.production` у `manage.py`. Модуль `production.py` импортирует всё из `base.py` и переопределяет `DEBUG=False`, `ALLOWED_HOSTS`, `SIMPLE_JWT`, `LOGGING` и часть внешних хостов.
Отдельного конфиг-файла (yaml/toml) у приложения нет. Дополнительно на инфраструктурном уровне используются `config/settings/base.py` для Celery/Kafka/OTel и Helm-чарт для задания переменных в Kubernetes.
Источники переменных по способам запуска:
| Способ запуска | Откуда берутся переменные |
| --- | --- |
| Локально | Переменные окружения процесса и файл `.env` (грузится `load_dotenv()` в `base.py`) |
| Локально (Kafka) | `docker compose --file local-kafka-docker-compose.yml up -d` поднимает брокер; консьюмер — `manage.py consume_kafka` |
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) для каждого сервиса (`api`, `celery`, `celery-beat`, `kafka-app`) |
| CI/CD (GitLab) | `.gitlab-ci.yml`: общий шаблон `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`); окружение выбирается по ветке/тегу |
Способы запуска процессов:
| Процесс | Команда | Назначение |
| --- | --- | --- |
| HTTP API | `uwsgi` (entrypoint) / `manage.py runserver` | REST API (DRF), OpenAPI-схема через drf-spectacular |
| Celery worker | `celery -A config worker -l info -E --concurrency=2` | Обработчик фоновых задач (`issues.tasks`, `issues.notifications`, `prescriptions.tasks`) |
| Celery beat | `celery -A config beat -l info` | Периодические задачи (ежедневный инкремент счётчиков, отчёт о просрочках) |
| Kafka consumer | `python3 run_kafka_app.py` / `manage.py consume_kafka` | Консьюмер Kafka (топики ассетов и замечаний) |
Порядок старта в контейнере задаётся `compose/server/entrypoint.sh` (миграции + запуск uWSGI по `compose/server/uwsgi.ini`). Базовый образ — `python:3.10-slim-bookworm` (`compose/server/Dockerfile`).
## Переменные приложения
Дефолт `—` означает, что явного значения по умолчанию в коде нет (`os.getenv` вернёт `None`); для корректной работы переменную нужно задать.
### Django и окружение
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DJANGO_SETTINGS_MODULE` | string | `config.settings.production` (Helm) | Модуль настроек Django |
| `DJANGO_ADMIN_SECRET_KEY` | string | `''` | `SECRET_KEY` Django |
| `DJANGO_TOKEN` | string | `django-token` | Служебный токен |
| `ENVIRONMENT` | string | `production` | Окружение развёртывания (в т.ч. атрибут OTel) |
| `ENVIRONMENT_CLIENT` | string | `production` | Клиентское окружение (`stage`/`preprod`/`production`) |
### База данных (PostgreSQL)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `DATABASE_NAME` | string | — | Имя базы данных |
| `DATABASE_USER` | string | — | Пользователь БД |
| `DATABASE_PASSWORD` | string | — | Пароль пользователя БД |
| `DATABASE_HOST` | string | — | Хост PostgreSQL |
| `DATABASE_PORT` | int | — | Порт PostgreSQL |
> Движок — `django.db.backends.postgresql`. В Kubernetes значения приходят из секрета (`issues-postgresql-secret` для stage, `ya-pg-secret` для preprod/production), CA-сертификат монтируется как `/root/.postgresql/ca.crt`.
### Внешние сервисы (URL)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `SAREX_API` | string | — | Базовый API Sarex (`SAREX_HOST` по умолчанию равен ему) |
| `SAREX_HOST` | string | `= SAREX_API` | Хост Sarex |
| `AERO_HOST` | string | `https://stage.sarex.io` | Хост Aero |
| `AERO_PUBLIC_HOST` | string | `https://stage.sarex.io``production.py` — из env) | Публичный хост Aero |
| `BASE_AERO_URL` | string | `https://lk.sarex.io` | Базовый URL Aero |
| `BASE_AUTH_URL` | string | `https://lk.sarex.io` | Базовый URL аутентификации |
| `SERVICE_URL` | string | `https://lk.sarex.io` | URL сервиса |
| `GATEWAY_URL` | string | `https://lk.sarex.io` | URL gateway |
| `DOCUMENTATIONS_URL` | string | `https://lk.sarex.io` | URL сервиса документаций |
| `WORKFLOWS_URL` | string | `https://lk.sarex.io` | URL сервиса workflows |
| `WORKFLOWS_HOST` | string | `https://lk.sarex.io` | Хост workflows |
| `RESOURCES_API_HOST` | string | `https://lk.sarex.io``production.py``http://sarex-resources-service.resources-prod`) | Хост сервиса ресурсов (IAM) |
| `REVIEW_HOST` | string | `https://lk.sarex.io` | Хост сервиса review/flows |
| `INSPECTION_HOST` | string | `https://lk.sarex.io` | Хост сервиса инспекций |
| `EAV_HOST` | string | `http://eav-service.eav-stage` | Хост сервиса атрибутов (EAV) |
| `SAREX_USERNAME` | string | — | Логин для basic-auth Sarex |
| `SAREX_PASSWORD` | string | — | Пароль для basic-auth Sarex |
### RabbitMQ и Celery
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `RABBITMQ_USERNAME` | string | `mcc` | Пользователь брокера |
| `RABBITMQ_PASSWORD` | string | `mcc` | Пароль брокера |
| `RABBITMQ_HOSTNAME` | string | `rabbitmq-service` | Хост брокера |
| `RABBITMQ_VHOST` | string | `api` | Виртуальный хост |
| `REDIS_HOST` | string | `redis` | Хост Redis (result backend) |
| `REDIS_DB` | int | `0` | Номер БД Redis |
> `CELERY_BROKER_URL` собирается как `amqp://{user}:{password}@{hostname}/{vhost}` + `?heartbeat=30`. `CELERY_RESULT_BACKEND``redis://{REDIS_HOST}:6379/{REDIS_DB}`. Расписание beat: инкремент счётчиков `1:00`, отчёт о просрочках `6:00` (`Europe/Moscow`).
### Kafka
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `KAFKA_HOST` | string | — | Адрес брокера Kafka |
| `KAFKA_USERNAME` | string | — | Пользователь |
| `KAFKA_PASSWORD` | string | — | Пароль |
| `KAFKA_SSL_CAFILE` | string | — | Путь к CA-сертификату (в Helm — YandexInternalRootCA) |
| `KAFKA_EAV_ASSETS_TOPIC` | string | — | Топик трансляции ассетов (EAV) |
| `KAFKA_ISSUES_TOPIC` | string | — | Топик трансляции замечаний |
### S3 (Yandex Cloud)
Основное хранилище (`django-storages`, `S3Boto3Storage`):
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `YC_S3_ACCESS_KEY_ID` | string | — | Access key |
| `YC_S3_SECRET_ACCESS_KEY` | string | — | Secret key |
| `YC_S3_BUCKET_NAME` | string | — | Имя бакета |
| `YC_S3_ENDPOINT_URL` | string | — | Эндпоинт S3 |
| `YC_S3_VERIFY` | bool | `None` | Проверять TLS-сертификат (`"true"` → `True`) |
Хранилище предписаний (отдельный бакет):
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `PRESCRIPTION_S3_ACCESS_KEY_ID` | string | — | Access key |
| `PRESCRIPTION_S3_SECRET_ACCESS_KEY` | string | — | Secret key |
| `PRESCRIPTION_S3_BUCKET` | string | — | Имя бакета |
| `PRESCRIPTION_S3_ENDPOINT_URL` | string | — | Эндпоинт S3 |
### Почта (Mailgun / SMTP)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `ENABLE_MAILGUN` | bool | `True` | Использовать Mailgun |
| `MAILGUN_BASE_URL` | string | `https://api.mailgun.net/v3/mg.sarex.io` | URL API Mailgun |
| `MAILGUN_API_KEY` | string | (задан дефолт в коде) | API-ключ Mailgun (в проде — из секрета) |
| `EMAIL_FROM` | string | `hello@sarex.io` | Адрес отправителя |
| `EMAIL_DOCKER_IMAGE` | string | `cr.yandex/.../notification:email` | Образ сервиса нотификаций |
| `USE_NOTIFICATIONS` | bool | `True` | Включить отправку уведомлений (`False`/`false`/`0` → выкл.) |
| `SMTP_HOST` | string | `None``production.py``""`) | SMTP-хост (альтернатива Mailgun) |
| `SMTP_PORT` | int | `None` | SMTP-порт |
### Предписания (workflow)
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `PRESCRIPTION_WF_IMAGE` | string | — | Образ workflow генерации предписаний |
| `DOCX_TO_PDF_IMAGE` | string | — | Образ конвертера DOCX→PDF |
| `PRESCRIPTION_WF_RESULT_PATH` | string | — | Путь/бакет результата |
| `PRESCRIPTION_WF_CALLBACK` | string | — | Образ webhook-caller |
| `PRESCRIPTION_INTERNAL_HOST` | string | — | Внутренний хост колбэков предписаний |
| `EXPORT_WF_CROPPING_DOCKER_IMAGE` | string | `cr.yandex/.../crop-issue-pin-area:prod` | Образ кропа области пина для экспорта |
### OpenTelemetry
Трейсинг подключается только если `USE_OTEL` истинно (`django_otel_tools`).
| Переменная | Тип | Значение по умолчанию | Назначение |
| --- | --- | --- | --- |
| `USE_OTEL` | bool | `False` | Включить трейсинг/логирование через OTel |
| `SERVICE_NAME` | string | `issues-backend.sarex-issues` | Имя сервиса в трейсах |
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
| `USE_INSECURE` | bool | `False` | Небезопасное (без TLS) подключение к коллектору |
| `MODULE` | string | `issues` | Атрибут трейсов |
| `TEAM` | string | `proc_team` | Атрибут трейсов |
| `COMPONENT` | string | `backend` | Атрибут трейсов |
## Переменные инфраструктуры
Не читаются кодом приложения напрямую (или используются вспомогательными компонентами), но участвуют в запуске/деплое.
| Переменная | Где используется | Назначение |
| --- | --- | --- |
| `API_ADDRESS` | Helm (`envs`) | Порт uWSGI (`8000`) |
| `SENTRY_KEY` | Helm (`envs`) | DSN Sentry (задан для stage) |
| `SAREX_MAILER_URL` | Helm (`envs`) | URL сервиса рассылок (`http://mailer-service.mailer:8000`) |
| `MAILGUN_HOST` | Helm (`envs`) | Хост Mailgun на уровне чарта |
| `NPM_TOKEN`, `BUILD_ENV` | CI/Dockerfile | Сборка (актуально для фронтенда) |
## Переменные из Helm-чарта (`.helm/values.yaml`)
Чарт — обёртка над `universal-chart`. Определены четыре сервиса: `api`, `celery`, `celery-beat`, `kafka-app`. Обычные значения (`envs`) задаются для окружений `_default`/`stage`/`preprod`/`production` (различаются адресами БД/сервисов, топиками Kafka, образами, `SERVICE_NAME`, `TRACER_ENDPOINT`).
Значения из секретов (блок `secretEnvs`, монтируются через `secretKeyRef`):
| Переменная | Секрет (stage / preprod-prod) | Ключ |
| --- | --- | --- |
| `KAFKA_USERNAME` | `issues-kafka-secret` / `yc-kafka-secret` | `username` |
| `KAFKA_PASSWORD` | `issues-kafka-secret` / `yc-kafka-secret` | `password` |
| `KAFKA_HOST` | `issues-kafka-secret` / `yc-kafka-secret` | `host` |
| `SAREX_USERNAME` | `sarex-auth` | `username` |
| `SAREX_PASSWORD` | `sarex-auth` | `password` |
| `DATABASE_HOST` | `issues-postgresql-secret` / `ya-pg-secret` | `host` |
| `DATABASE_NAME` | `issues-postgresql-secret` / `ya-pg-secret` | `database` |
| `DATABASE_PORT` | `issues-postgresql-secret` / `ya-pg-secret` | `port` |
| `DATABASE_USER` | `issues-postgresql-secret` / `ya-pg-secret` | `username` |
| `DATABASE_PASSWORD` | `issues-postgresql-secret` / `ya-pg-secret` | `password` |
| `YC_S3_ACCESS_KEY_ID` | `issues-s3-secret` / `yc-s3-secret` | `key_id` |
| `YC_S3_SECRET_ACCESS_KEY` | `issues-s3-secret` / `yc-s3-secret` | `access_key` |
| `YC_S3_BUCKET_NAME` | `issues-s3-secret` / `yc-s3-secret` | `storage_bucket_name` |
| `YC_S3_ENDPOINT_URL` | `issues-s3-secret` / `yc-s3-secret` | `endpoint_url` |
| `RABBITMQ_VHOST` | `issues-rabbitmq-secret` / `rabbitmq-secret` | `vhost` |
| `RABBITMQ_USERNAME` | `issues-rabbitmq-secret` / `rabbitmq-secret` | `user` |
| `RABBITMQ_HOSTNAME` | `issues-rabbitmq-secret` / `rabbitmq-secret` | `host` |
| `RABBITMQ_PASSWORD` | `issues-rabbitmq-secret` / `rabbitmq-secret` | `password` |
| `MAILGUN_API_KEY` | `mailgun-secret` | `api-key` |
| `DJANGO_TOKEN` | `django-secret` | `token` |
| `DJANGO_ADMIN_SECRET_KEY` | `django-admin-secret` | `secret_key` |
| `PRESCRIPTION_S3_ACCESS_KEY_ID` | `prescription-s3-secret` | `key_id` |
| `PRESCRIPTION_S3_SECRET_ACCESS_KEY` | `prescription-s3-secret` | `access_key` |
| `PRESCRIPTION_S3_BUCKET` | `prescription-s3-secret` | `storage_bucket_name` |
| `PRESCRIPTION_S3_ENDPOINT_URL` | `prescription-s3-secret` | `endpoint_url` |
Дополнительно чарт монтирует конфиг uWSGI (`uwsgi-configmap` → `/opt/server/uwsgi.ini`, только сервис `api`), CA-сертификат PostgreSQL (`yc-ch-certificate` → `/root/.postgresql/ca.crt`) и внутренний CA Яндекса (`YandexInternalRootCA.crt`).
## Переменные в CI (`.gitlab-ci.yml`)
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`). Основные переменные: `SERVICE_NAME=issues`, `DOCKERFILE_PATH=./compose/server/Dockerfile`. Окружение выбирается по ветке/тегу:
| Условие | STAND | Namespace | CHART_VERSION |
| --- | --- | --- | --- |
| ветка `stage` | `stage` | `proc` | `0.0.1-stage` |
| ветка `master` | `preprod` | `issues-preprod` | `0.0.1-preprod` |
| тег (`CI_COMMIT_TAG`) | `production` | `issues-prod` | `0.0.1-prod` |
`HELM_SET_ARGS` для каждого окружения проставляет образы четырёх сервисов (`api`, `celery`, `celery-beat`, `kafka-app`), `universal-chart.global.env` и метаданные коммита (`commitSha`, `gitlabUri`, `gitlabJobUrl`, `owner`).
## Замечания и потенциальные проблемы
- В отличие от FastAPI-сервисов, переменные не имеют единого префикса и читаются напрямую через `os.getenv`. Файл `.env` подхватывается автоматически (`load_dotenv()` в `base.py`).
- `DEBUG` в `base.py` установлен в `True`; в `production.py` переопределяется на `False`. Для боевого окружения обязателен модуль `config.settings.production`.
- `MAILGUN_API_KEY` имеет захардкоженный дефолт в коде — в реальных окружениях его нужно переопределять секретом.
- Ряд переменных без дефолта (`DATABASE_*`, `KAFKA_*`, `YC_S3_*`, `SAREX_USERNAME`/`SAREX_PASSWORD`, `PRESCRIPTION_WF_*`) обязательны для полноценной работы соответствующих подсистем.
- Переменные `SAREX_MAILER_URL`, `MAILGUN_HOST`, `SENTRY_KEY`, `API_ADDRESS` задаются в Helm, но не читаются кодом приложения напрямую.
## Минимальный набор для локального запуска
Postgres, RabbitMQ, Redis и Kafka поднимаются локально; приложение — `python ./src/manage.py runserver --settings=config.settings.production`, консьюмер — `manage.py consume_kafka`. Минимально необходимо задать:
- `DJANGO_ADMIN_SECRET_KEY`
- `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`, `DATABASE_HOST`, `DATABASE_PORT`
- `RABBITMQ_USERNAME`, `RABBITMQ_PASSWORD`, `RABBITMQ_HOSTNAME`, `RABBITMQ_VHOST`, `REDIS_HOST`
- `KAFKA_HOST`, `KAFKA_USERNAME`, `KAFKA_PASSWORD`, `KAFKA_EAV_ASSETS_TOPIC`, `KAFKA_ISSUES_TOPIC` (для консьюмера)
- `YC_S3_*` (для работы с файлами) и при необходимости `PRESCRIPTION_S3_*`
- внешние URL: `SAREX_API`, `AERO_HOST`, `GATEWAY_URL`, `DOCUMENTATIONS_URL`, `WORKFLOWS_URL`, `RESOURCES_API_HOST`, `EAV_HOST`, `INSPECTION_HOST`, `REVIEW_HOST`
- почта: `ENABLE_MAILGUN` + `MAILGUN_*` **или** `SMTP_HOST`/`SMTP_PORT`
- `USE_OTEL=False` для локальной разработки
Готовые значения-примеры приведены в `.env.example`.

138
apps/issues/ENDPOINTS.md Normal file
View File

@ -0,0 +1,138 @@
# Эндпоинты, с которыми взаимодействует issues-frontend
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `issues-frontend`).
## Как устроено взаимодействие
Запросы сгруппированы по доменным API-объектам в каталоге `module/api/` (`IssuesApi`, `CoreApi`, `PrescriptionsApi`, `InspectionsApi`, `AttributesApi`, `AssetsApi`, `ContractsApi`, `ResourcesApi`, `TemplatesApi`, `PremisesApi`, `DocumentationApi`). Каждый метод вызывает единый `httpService` (`module/api/http-service.ts`).
`httpService` создаётся фабрикой `createHttpService` из `@sarex-team/sdk-js` и принимает карту хостов `hosts` (`module/api/hosts.ts`) и текущее окружение `BUILD_ENV`. Вызов задаётся объектом:
- `service` — логическое имя сервиса (ключ из `hosts`, см. таблицу ниже);
- метод — `getRequest`/`postRequest`/`putRequest`/`patchRequest`/`deleteRequest`;
- `url` — путь запроса (дописывается к базовому хосту сервиса);
- `axiosConfig` — доп. настройки axios (`params`, `paramsSerializer`, `responseType` и т.п.);
- `data` — тело запроса;
- `showErrorNotification`, `queryKey`, `cache` — опции показа ошибок, ключа кеша и кеширования.
Итоговый URL = `<базовый хост сервиса для BUILD_ENV>` + `url`. Базовый хост выбирается по `BUILD_ENV` (`local`/`stage`/`prod`/`preprod`/`contour`).
## Базовые хосты по сервисам и окружениям
Значения из `module/api/hosts.ts`. Приведены `stage` и `prod`; в `contour` используются относительные пути, в `preprod` — домен `api.preprod.sarex.io`, в `local` — как в `stage`, но `sarex` проксируется на `/sarex-backend`.
| Сервис (`service`) | Назначение | `stage` | `prod` |
| --- | --- | --- | --- |
| `issues` | Сервис замечаний (issues-backend, собственный API) | `https://stage-api.sarex.io/issues/api` | `https://api.sarex.io/issues/api` |
| `prescriptions` | Предписания (issues-backend) | `https://stage-api.sarex.io/issues/api/prescriptions` | `https://api.sarex.io/issues/api/prescriptions` |
| `sarexApi` | Gateway/API Sarex (`/gateway`, `/issues`, `/inspections`, `/contracts`) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
| `sarex` | Локальный сервис данных (`/api/core`, `/api/client`) | `""` (относительные пути) | `""` |
| `gateway` | Gateway Sarex | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
| `documentations` | Сервис документации | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` |
| `eav` | Сервис атрибутов/ассетов (EAV) | `https://stage-api.sarex.io/eav/api` | `https://api.sarex.io/eav/api` |
| `files` | Сервис файлов | `https://stage-api.sarex.io/files/api/v1` | `https://api.sarex.io/files/api/v1` |
| `premises` | Сервис помещений | `https://stage-api.sarex.io/premises/api/v1` | `https://api.sarex.io/premises/api/v1` |
| `workspaces` | Сервис рабочих областей | `https://stage-api.sarex.io/workspaces` | `https://api.sarex.io/workspaces` |
| `workflows` | Сервис обработки документов | `https://stage-api.sarex.io/workflows` | `https://api.sarex.io/workflows` |
| `comparisons` | Сервис сравнений | `https://stage-api.sarex.io/comparisons` | `https://api.sarex.io/comparisons` |
| `remarks` | Сервис замечаний (remarks) | `https://stage-api.sarex.io/remarks` | `https://api.sarex.io/remarks` |
| `bim` / `bimv2` | BIM-API | `https://stage-api.sarex.io/bim` (`/bimv2`) | `https://api.sarex.io/bim` (`/bimv2`) |
| `google` | Временное хранилище (GCS) | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` |
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
> Часть сервисов объявлена в карте хостов, но напрямую в `module/api/*` не вызывается (`workspaces`, `workflows`, `comparisons`, `remarks`, `bim`, `bimv2`, `google`, `zitadel`) — они используются инфраструктурой SDK / другими слоями. Сервис `prescriptions` объявлен в хостах, но методы предписаний фактически ходят через `sarexApi` по пути `/issues/api/prescriptions`.
Подключаемый удалённый модуль `documentations` описан отдельно в `module/api/module-hosts.ts` (`remoteEntry.js` микрофронтенда documentations).
## Эндпоинты по сервисам
### `issues` — Сервис замечаний (собственный API)
Файл `module/api/issuesApi.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `IssuesApi.getStatusModels` | GET | `/companies/{companyId}/status-model/` | Модель статусов компании (по `resource_id`, `issue_type_id`) |
| `IssuesApi.getTypes` | GET | `/issue-types/` | Типы замечаний компании (`company_id`) |
| `IssuesApi.getIssue` | GET | `/issues/{public_id}/` | Замечание по публичному id |
| `IssuesApi.editIssue` | PATCH | `/issues/{publicId}/` | Редактировать замечание |
| `IssuesApi.getChanges` | GET | `/issue-changes/` | История изменений замечания (`issue_id`) |
### `sarexApi` — Gateway/API Sarex
Файлы `issuesApi.ts`, `prescriptionsApi.ts`, `inspections.ts`, `contractsApi.ts`, `attributes.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `IssuesApi.postComment` | POST | `/issues/api/comments/` | Добавить комментарии |
| `IssuesApi.deleteComment` | DELETE | `/issues/api/comments/{id}/` | Удалить комментарий |
| `IssuesApi.postAttachment` | POST | `/issues/api/attachments/` | Загрузить вложение (multipart) |
| `IssuesApi.deleteAttachment` | DELETE | `/issues/api/attachments/{id}/` | Удалить вложение (`issue_public_id`) |
| `PrescriptionsApi.postPrescription` | POST | `/issues/api/prescriptions/` | Создать предписание |
| `PrescriptionsApi.getPrescriptions` | GET | `/issues/api/prescriptions/` | Список предписаний (сериализованные фильтры в query) |
| `InspectionsApi.getInspections` | GET | `/inspections/api/v1/inspections/light/` | Список инспекций (`company_id`, `limit`, `offset`) |
| `InspectionsApi.getInspectionTypes` | GET | `/inspections/api/v1/inspections/types/` | Типы инспекций (`company_id`) |
| `ContractsApi.getContracts` | GET | `/contracts/api/v0/contracts/` | Договоры (`tenant_id`, `contractor_id`, `resource_id`) |
| `AttributesApi.getDocumentAttributes` | GET | `/gateway/api/v1/documents/{documentId}/attributes/` | Атрибуты документа |
### `sarex` — Локальный сервис данных
Файл `module/api/coreApi.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `CoreApi.getUserSettings` | GET | `/api/client/settings/` | Клиентские настройки |
| `CoreApi.getUsers` | GET | `/api/core/users/` | Пользователи компании (`company`, `limit`, `offset`, `show_inactive`) |
| `CoreApi.getDepartments` | GET | `/api/core/admin/departments/` | Отделы компании |
| `CoreApi.getPositions` | GET | `/api/core/admin/positions/` | Должности компании |
### `gateway` — Gateway Sarex
Файлы `coreApi.ts`, `resources.ts`, `templatesApi.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `CoreApi.getGatewayUsers` | GET | `/api/v2/users/` | Пользователи по ресурсу/правам (`resource_id`, `permissions`, `limit`, `offset`) |
| `ResourcesApi.getResourceFullInfo` | GET | `/api/v2/resources/{resourceId}/` | Полная информация о ресурсе |
| `TemplatesApi.getTemplates` | GET | `/api/v1/disks/{diskId}/flat_documents/` | Плоский список документов диска (`type`) |
### `documentations` — Сервис документации
Файлы `documentation.ts`, `templatesApi.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `DocumentationApi.getDocumentById` | GET | `/documents/{docId}` | Документ по id (опц. `extend`) |
| `TemplatesApi.getDisks` | GET | `/disks` | Список дисков |
### `eav` — Атрибуты и ассеты (EAV)
Файлы `assets.ts`, `attributes.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `AssetsApi.getAssetsPost` | POST | `/v4/assets/search/` | Поиск ассетов |
| `AssetsApi.getAssetsGet` | GET | `/v4/assets/` | Список ассетов |
| `AttributesApi.getAttributes` | GET | `/v0/attribute/` | Атрибуты компании (`company_id`) |
### `files` — Сервис файлов
Файл `documentation.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `DocumentationApi.getPage` | GET | `/pages/{sourceId}/{pageId}` | Страница файла (ответ `blob`) |
### `premises` — Сервис помещений
Файл `premises-api.ts`.
| Метод API | HTTP | Путь | Назначение |
| --- | --- | --- | --- |
| `PremisesApi.getPremise` | GET | `/premises/{id}/` | Помещение по id |
| `PremisesApi.getPremisesFilter` | POST | `/premises/filter/` | Фильтрация помещений (`limit`, `offset` в query) |
| `PremisesApi.getPremiseTypesFilter` | POST | `/premise_types/filter/` | Фильтрация типов помещений (`limit`, `offset` в query) |
## Обработка ошибок
Глобальная обработка выполняется в `module/api/http-service.ts`: `httpService` обёрнут в `Proxy`, который для методов запросов (`getRequest`/`postRequest`/`putRequest`/`patchRequest`/`deleteRequest`) перехватывает ошибку и при статусе `403` показывает toast-уведомление (`react-toastify`) с текстом `detail` из ответа либо сообщением «У вас недостаточно прав для выполнения данного действия», после чего пробрасывает ошибку дальше. Для отдельных запросов показ уведомлений включается флагом `showErrorNotification: true`. В окружении `local` SDK переключается в режим `original` (`setSharedHttpServiceConfig({ type: "original" })`).

3278
apps/issues/openapi.yaml Normal file

File diff suppressed because it is too large Load Diff