Merge branch 'docs' into 'master'
Add example `.env` files and configuration documentation for `measurements`... See merge request infra/iac!2
This commit is contained in:
commit
d00f91688a
85
apps/ams-sync/.env.example
Normal file
85
apps/ams-sync/.env.example
Normal 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
|
||||||
218
apps/ams-sync/CONFIGURATION.md
Normal file
218
apps/ams-sync/CONFIGURATION.md
Normal 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`
|
||||||
99
apps/ams-sync/ENDPOINTS.md
Normal file
99
apps/ams-sync/ENDPOINTS.md
Normal 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 | Источник пользователей и компаний для миграции/синхронизации |
|
||||||
46
apps/attachments/.env.example
Normal file
46
apps/attachments/.env.example
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Attachments — пример конфигурации (.env)
|
||||||
|
# Скопируйте в .env и заполните значения.
|
||||||
|
# =============================================================================
|
||||||
|
# --- Приложение (необязательные, есть значения по умолчанию) ---
|
||||||
|
# Версия: 0.11.1
|
||||||
|
|
||||||
|
# API=/api
|
||||||
|
# NAME=Attachments
|
||||||
|
# VERSION=0.0.1
|
||||||
|
# DESCRIPTION=Attachments
|
||||||
|
|
||||||
|
# --- База данных PostgreSQL (обязательные) ---
|
||||||
|
DATABASE_NAME=attachments
|
||||||
|
DATABASE_USER=postgres
|
||||||
|
DATABASE_PASSWORD=change_me
|
||||||
|
DATABASE_HOST=db
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
DATABASE_SSL_MODE=disable
|
||||||
|
|
||||||
|
# Пароль суперпользователя PostgreSQL для контейнера db (docker-compose)
|
||||||
|
POSTGRES_PASSWORD=change_me
|
||||||
|
|
||||||
|
# --- S3 (Yandex Object Storage) ---
|
||||||
|
# Вариант 1: путь к JSON с реквизитами сервисного аккаунта.
|
||||||
|
# Файл должен содержать: {"endpoint": "...", "access_key_id": "...", "secret_access_key": "..."}
|
||||||
|
YANDEX_S3_ACCOUNT_PATH=/etc/sarex/yc-s3-storage/yc-s3-service-account.json
|
||||||
|
|
||||||
|
# Вариант 2: задать реквизиты напрямую (если не используете JSON-файл).
|
||||||
|
# YANDEX_S3_ENDPOINT_URL=storage.yandexcloud.net
|
||||||
|
# YANDEX_S3_ACCESS_KEY_ID=change_me
|
||||||
|
# YANDEX_S3_SECRET_ACCESS_KEY=change_me
|
||||||
|
|
||||||
|
YANDEX_S3_VERIFY=true
|
||||||
|
# YANDEX_S3_USE_SSL=true
|
||||||
|
# YANDEX_S3_REGION=ru-central1
|
||||||
|
BUCKET_NAME=attachments-stage-2
|
||||||
|
|
||||||
|
# --- Логирование (префикс LOG_) ---
|
||||||
|
# LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# --- Трейсинг / OpenTelemetry (префикс TRACING_) ---
|
||||||
|
# TRACING_USE=false
|
||||||
|
# TRACING_HOST=localhost:4317
|
||||||
|
# TRACING_SERVICE_NAME=attachments
|
||||||
|
# TRACING_INSECURE=false
|
||||||
129
apps/attachments/CONFIGURATION.md
Normal file
129
apps/attachments/CONFIGURATION.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Конфигурация проекта Attachments
|
||||||
|
# Версия: 0.11.1
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Настройка сервиса выполняется **только через переменные окружения**. Отдельного файла с настройками (yaml/toml) в приложении нет — за конфигурацию отвечает `internal/config/settings.py` на базе `pydantic.BaseSettings`.
|
||||||
|
|
||||||
|
Источники переменных окружения по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (docker-compose) | Файл `.env` в корне (`env_file: .env` в `docker-compose.yml`), плюс `POSTGRES_PASSWORD` для контейнера БД |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна и build-args |
|
||||||
|
|
||||||
|
Дополнительно секреты доступа к S3 не задаются напрямую, а читаются из JSON-файла (см. `YANDEX_S3_ACCOUNT_PATH` ниже).
|
||||||
|
|
||||||
|
## Переменные приложения (класс `Settings`)
|
||||||
|
|
||||||
|
Читаются напрямую по имени (регистрозависимо, `case_sensitive = True`). Переменные без значения по умолчанию **обязательны** — без них приложение не стартует.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обязательна | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `API` | str | нет | `/api` | Префикс всех HTTP-роутов |
|
||||||
|
| `NAME` | str | нет | `Attachments` | Имя сервиса (title в FastAPI/OpenAPI) |
|
||||||
|
| `VERSION` | str | нет | `0.0.1` | Версия сервиса |
|
||||||
|
| `DESCRIPTION` | str | нет | `Attachments` | Описание сервиса |
|
||||||
|
| `YANDEX_S3_ENDPOINT_URL` | str | да* | — | Endpoint S3 (без схемы; `https://`/`http://` добавляется в коде по `YANDEX_S3_USE_SSL`) |
|
||||||
|
| `YANDEX_S3_ACCESS_KEY_ID` | str | да* | — | Access Key ID для S3 |
|
||||||
|
| `YANDEX_S3_SECRET_ACCESS_KEY` | str | да* | — | Secret Access Key для S3 |
|
||||||
|
| `YANDEX_S3_USE_SSL` | bool | нет | `True` | Использовать ли HTTPS при обращении к S3 |
|
||||||
|
| `YANDEX_S3_REGION` | str | нет | `ru-central1` | Регион S3 |
|
||||||
|
| `YANDEX_S3_VERIFY` | bool | да | — | Проверять ли SSL-сертификат S3 |
|
||||||
|
| `BUCKET_NAME` | str | да | — | Имя бакета для вложений |
|
||||||
|
| `DATABASE_NAME` | str | да | — | Имя базы данных PostgreSQL |
|
||||||
|
| `DATABASE_USER` | str | да | — | Пользователь БД |
|
||||||
|
| `DATABASE_PASSWORD` | str | да | — | Пароль пользователя БД |
|
||||||
|
| `DATABASE_HOST` | str | да | — | Хост БД |
|
||||||
|
| `DATABASE_PORT` | int | да | — | Порт БД |
|
||||||
|
| `DATABASE_SSL_MODE` | str | да | — | Режим SSL при подключении к БД (напр. `disable`, `require`, `verify-full`) |
|
||||||
|
|
||||||
|
\* Три переменные `YANDEX_S3_ENDPOINT_URL`, `YANDEX_S3_ACCESS_KEY_ID`, `YANDEX_S3_SECRET_ACCESS_KEY` формально обязательны, но при старте приложения они **проставляются автоматически** функцией `json_config_s3_account_settings()` из JSON-файла (см. `YANDEX_S3_ACCOUNT_PATH`). Задавать их вручную не нужно, если задан путь к файлу.
|
||||||
|
|
||||||
|
## Настройки S3 через JSON-файл
|
||||||
|
|
||||||
|
| Переменная | Обязательна | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `YANDEX_S3_ACCOUNT_PATH` | да | Путь к JSON-файлу с реквизитами сервисного аккаунта S3 |
|
||||||
|
|
||||||
|
При старте функция `json_config_s3_account_settings()` читает файл по этому пути и выставляет переменные окружения `YANDEX_S3_ENDPOINT_URL`, `YANDEX_S3_ACCESS_KEY_ID`, `YANDEX_S3_SECRET_ACCESS_KEY`.
|
||||||
|
|
||||||
|
Ожидаемая структура JSON-файла:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"endpoint": "storage.yandexcloud.net",
|
||||||
|
"access_key_id": "<ключ>",
|
||||||
|
"secret_access_key": "<секрет>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
В Kubernetes файл монтируется из секрета `attachments-s3-secret` (в prod — `yc-s3`) в `/etc/sarex/yc-s3-storage/yc-s3-service-account.json`.
|
||||||
|
|
||||||
|
## Логирование (класс `LoggerSettings`, префикс `LOG_`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | str | `INFO` | Уровень логирования (`DEBUG`, `INFO`, `WARNING`, ...). При неизвестном значении откатывается на `INFO` |
|
||||||
|
| `LOG_FORMAT` | str | JSON-шаблон с полями `timestamp`, `level`, `message` | Формат строк лога (используется `pythonjsonlogger`) |
|
||||||
|
|
||||||
|
## Трейсинг / OpenTelemetry (класс `TraceSettings`, префикс `TRACING_`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `TRACING_USE` | bool | `False` | Включает OTEL-трейсинг, логгер и middleware |
|
||||||
|
| `TRACING_HOST` | str | `localhost:4317` | Адрес OTLP-коллектора |
|
||||||
|
| `TRACING_SERVICE_NAME` | str | `attachments` | Имя сервиса в трейсах |
|
||||||
|
| `TRACING_INSECURE` | bool | `False` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
|
||||||
|
Трейсинг активируется только при `TRACING_USE=true`.
|
||||||
|
|
||||||
|
## Переменные инфраструктуры и сборки
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но нужны для запуска/сборки/деплоя.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POSTGRES_PASSWORD` | `docker-compose.yml` (контейнер `db`) | Пароль суперпользователя PostgreSQL при локальном запуске |
|
||||||
|
| `PIP_EXTRA_INDEX_URL` | `docker/Dockerfile` (build-arg) | Доп. индекс pip для установки приватных пакетов при сборке образа |
|
||||||
|
| `GITLAB_PYPI_EXTRA_INDEX_URL` | `.gitlab-ci.yml` | Значение, пробрасываемое в `PIP_EXTRA_INDEX_URL` при сборке в CI |
|
||||||
|
|
||||||
|
### Переменные CI/CD (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Служебные переменные пайплайна: `SERVICE_NAME`, `DOCKERFILE_PATH`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE`, а также подставляемые по окружениям `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`. Окружение выбирается по ветке/тегу: `stage` → ветка `stage`, `preprod` → ветка `master`, `production` → git-тег.
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Обычные переменные (`envs`):
|
||||||
|
|
||||||
|
| Переменная | Значение | Примечание |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `API_ADDRESS` | `0.0.0.0:8000` | Задаётся в чарте, но **кодом приложения не читается** |
|
||||||
|
| `POSTGRES_POOL_SIZE` | `10` | Задаётся в чарте, но **кодом приложения не читается** |
|
||||||
|
| `DATABASE_SSL_MODE` | `verify-full` | |
|
||||||
|
| `YANDEX_S3_VERIFY` | `true` | |
|
||||||
|
| `YANDEX_S3_ACCOUNT_PATH` | `/etc/sarex/yc-s3-storage/yc-s3-service-account.json` | |
|
||||||
|
| `BUCKET_NAME` | `attachments-stage-2` / `attachments-prod-2` | Зависит от окружения |
|
||||||
|
|
||||||
|
Переменные из секретов (`secretEnvs`, секрет `attachments-postgresql-secret` / `ya-pg-secret`):
|
||||||
|
|
||||||
|
| Переменная | Ключ секрета | Примечание |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DATABASE_PORT` | `port` | |
|
||||||
|
| `DATABASE_HOST` | `host` | |
|
||||||
|
| `DATABASE_USER` | `username` | |
|
||||||
|
| `DATABASE_PASSWORD` | `password` | |
|
||||||
|
| `DATABASE_NAME` | `database` | |
|
||||||
|
| `YC-PG-CERTIFICATE` | `ca.crt` | CA-сертификат PostgreSQL; также монтируется файлом в `/root/.postgresql/root.crt` |
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Для запуска через `docker-compose` в файле `.env` достаточно задать (см. `.env.example`):
|
||||||
|
|
||||||
|
- `POSTGRES_PASSWORD` — для контейнера БД
|
||||||
|
- `DATABASE_*` — параметры подключения к БД
|
||||||
|
- `BUCKET_NAME`, `YANDEX_S3_VERIFY`
|
||||||
|
- `YANDEX_S3_ACCOUNT_PATH` **или** напрямую `YANDEX_S3_ENDPOINT_URL` + `YANDEX_S3_ACCESS_KEY_ID` + `YANDEX_S3_SECRET_ACCESS_KEY`
|
||||||
15
apps/auth-flow/.env.example
Normal file
15
apps/auth-flow/.env.example
Normal 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) во время работы.
|
||||||
162
apps/auth-flow/CONFIGURATION.md
Normal file
162
apps/auth-flow/CONFIGURATION.md
Normal 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`) — обновление токенов не выполняется в фоне.
|
||||||
80
apps/auth-flow/ENDPOINTS.md
Normal file
80
apps/auth-flow/ENDPOINTS.md
Normal 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
64
apps/bim/.env.example
Normal 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
189
apps/bim/CONFIGURATION.md
Normal 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
55
apps/bim/ENDPOINTS.md
Normal 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
1158
apps/bim/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
80
apps/cde/.env.example
Normal file
80
apps/cde/.env.example
Normal 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
215
apps/cde/CONFIGURATION.md
Normal 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
130
apps/cde/ENDPOINTS.md
Normal 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
298
apps/cde/openapi.yaml
Normal 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
|
||||||
31
apps/checklists/.env.example
Normal file
31
apps/checklists/.env.example
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Piccolo (обязательно для запуска)
|
||||||
|
PYTHONPATH=src
|
||||||
|
PICCOLO_CONF=db.config
|
||||||
|
|
||||||
|
# App
|
||||||
|
DEBUG=true
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# OpenTelemetry
|
||||||
|
OTEL_ENABLE=false
|
||||||
|
OTEL_URL=http://signoz-otel-collector-external.signoz.svc.cluster.local:4317
|
||||||
|
OTEL_SERVICE_NAME=checklists-backend.checklists-stage
|
||||||
|
OTEL_INSECURE=true
|
||||||
|
|
||||||
|
# Auth (JWT)
|
||||||
|
# При JWT_AUTH_ENABLE=false middleware отключён и используется дефолтный пользователь
|
||||||
|
JWT_AUTH_ENABLE=false
|
||||||
|
JWT_AUTH_PUBLIC_KEY=key
|
||||||
171
apps/checklists/CONFIGURATION.md
Normal file
171
apps/checklists/CONFIGURATION.md
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# Конфигурация проекта checklists-backend
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/).
|
||||||
|
|
||||||
|
В отличие от единого класса настроек, конфигурация разбита на несколько независимых классов `BaseSettings`, у каждого — **свой** `env_prefix` (плоские имена, без вложенного разделителя):
|
||||||
|
|
||||||
|
| Класс | `env_prefix` | Раздел |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Config` | *(нет префикса)* | `debug` |
|
||||||
|
| `HTTPAppConfig` | `HTTP_APP_` | Параметры HTTP-приложения/uvicorn |
|
||||||
|
| `DatabaseConfig` | `DATABASE_` | Подключение к PostgreSQL |
|
||||||
|
| `OTELConfig` | `OTEL_` | Трейсинг/логи OpenTelemetry |
|
||||||
|
| `JWTAuthConfig` | `JWT_AUTH_` | Аутентификация по JWT |
|
||||||
|
|
||||||
|
Подклассы подключаются к корневому `Config` как поля со значениями по умолчанию (`http_app`, `database`, `otel`, `jwt_auth`) и читают окружение в момент импорта. Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Особенности:
|
||||||
|
|
||||||
|
- **`.env` не загружается автоматически** — в `config.py` не задан `env_file`, зависимости `python-dotenv` нет. Файл `.env.template` — это шаблон; переменные нужно экспортировать в окружение самому (напр. `set -a && . ./.env && set +a`) либо задавать через `--env`/манифесты k8s.
|
||||||
|
- Помимо переменных приложения, для запуска нужны две инфраструктурные переменные Piccolo: `PYTHONPATH=src` и `PICCOLO_CONF=db.config` (заданы в `.env.template` и в `Dockerfile`).
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (`make run`) | Переменные окружения процесса. `.env.template` — шаблон, приложение его **не подхватывает** автоматически |
|
||||||
|
| Контейнер | `docker/http/Dockerfile` задаёт `PYTHONPATH`/`PICCOLO_CONF`; прочие переменные пробрасываются при запуске |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) чарта `universal-chart` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) и `HELM_SET_ARGS` |
|
||||||
|
|
||||||
|
Способы запуска (`Makefile`):
|
||||||
|
|
||||||
|
| Команда | Точка входа | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `make run` | `src/cmd/http/main.py` → `uvicorn` (factory `app.http:create_app`) | HTTP API |
|
||||||
|
| `make migrate` | `piccolo migrations forwards all` | Применение миграций БД |
|
||||||
|
| `make migrations` | `piccolo migrations new checklists --auto` | Генерация новой миграции |
|
||||||
|
| `make format` / `make format-check` | `ruff` | Форматирование/линт |
|
||||||
|
|
||||||
|
Порядок запуска в контейнере (`docker/http/entrypoint.sh`): сначала выполняются миграции (`piccolo migrations forwards all`), затем стартует приложение (`src/cmd/http/main.py`). Uvicorn запускается в режиме фабрики; `reload` включается при `DEBUG=true`, число воркеров — из `HTTP_APP_WORKERS`.
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
В столбце «Переменная» указано полное имя (префикс + поле). Дефолт `—` означает, что значение обязательно.
|
||||||
|
|
||||||
|
### App (`Config`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DEBUG` | bool | `True` | Режим отладки. Влияет на `reload` uvicorn, логирование SQL-запросов Piccolo (`log_queries`/`log_responses`), а также на `production`-флаг и `debug` piccolo-admin |
|
||||||
|
|
||||||
|
### HTTP-приложение (`HTTP_APP_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `HTTP_APP_HOST` | string | `0.0.0.0` | Адрес прослушивания |
|
||||||
|
| `HTTP_APP_PORT` | int | `8000` | Порт |
|
||||||
|
| `HTTP_APP_ROOT_PATH` | string | `""` | Root path (префикс за реверс-прокси; в k8s — `/checklists`) |
|
||||||
|
| `HTTP_APP_WORKERS` | int | `1` | Число воркеров uvicorn |
|
||||||
|
| `HTTP_APP_ADMIN_ENABLE` | bool | `True` | Монтировать ли piccolo-admin по пути `/admin/` |
|
||||||
|
|
||||||
|
### Database (`DATABASE_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DATABASE_HOST` | string | `postgres` | Хост PostgreSQL |
|
||||||
|
| `DATABASE_PORT` | int | `5432` | Порт PostgreSQL |
|
||||||
|
| `DATABASE_NAME` | string | `postgres` | Имя базы данных |
|
||||||
|
| `DATABASE_USER` | string | `postgres` | Пользователь БД |
|
||||||
|
| `DATABASE_PASSWORD` | string | `postgres` | Пароль пользователя БД |
|
||||||
|
|
||||||
|
Подключение собирается в `src/db/config.py` (`PostgresEngine`). SSL-параметров в настройках нет; в prod TLS обеспечивается на уровне подключения/CA-сертификата (см. `docker/http/ca.crt`).
|
||||||
|
|
||||||
|
### OpenTelemetry (`OTEL_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `OTEL_ENABLE` | bool | `False` | Включить трейсинг/логи OTEL. При `True` подключаются `fastapi-otel-tools` и инструментирование FastAPI |
|
||||||
|
| `OTEL_URL` | string | `http://signoz-otel-collector-external.signoz.svc.cluster.local:4317` | Адрес OTLP-коллектора |
|
||||||
|
| `OTEL_SERVICE_NAME` | string | `checklists-backend.checklists-stage` | Имя сервиса в трейсах |
|
||||||
|
| `OTEL_INSECURE` | bool | `True` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
|
||||||
|
> При `OTEL_ENABLE=true` `access_log` uvicorn отключается (логи идут через OTEL-обработчик).
|
||||||
|
|
||||||
|
### Auth (`JWT_AUTH_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `JWT_AUTH_ENABLE` | bool | `False` | Включить `JWTAuthMiddleware`. При `False` middleware не подключается, и в контекст подставляется дефолтный пользователь (для локальной разработки) |
|
||||||
|
| `JWT_AUTH_PUBLIC_KEY` | string | `key` | Публичный RSA-ключ для JWT (алгоритм `RS512`) |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры и сборки
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PYTHONPATH` | `.env.template`, `Dockerfile` | Путь к исходникам (`src`) |
|
||||||
|
| `PICCOLO_CONF` | `.env.template`, `Dockerfile` | Путь к конфигу Piccolo (`db.config`) |
|
||||||
|
| `SERVICE_NAME` | `.gitlab-ci.yml` | Имя сервиса (`checklists-backend`) |
|
||||||
|
| `DOCKERFILE_PATH` | `.gitlab-ci.yml` | Путь к Dockerfile (`./docker/http/Dockerfile`) |
|
||||||
|
| `IMAGE_NAME` | `.gitlab-ci.yml` (`HELM_SET_ARGS`) | Имя собираемого образа |
|
||||||
|
| `CHART_NAME` / `CHART_VERSION` / `RELEASE_NAME` | `.gitlab-ci.yml` | Параметры релиза Helm |
|
||||||
|
|
||||||
|
Базовый образ — `python:3.13-slim-bookworm`; менеджер зависимостей — `uv` (`uv sync --locked`). В образ добавляется CA-сертификат Yandex (`docker/http/ca.crt`).
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Сервис деплоится подключаемым чартом `universal-chart` (OCI-зависимость). Окружение выбирается ключом `universal-chart.global.env` (`stage`/`preprod`/`production`); для каждой переменной значение берётся из блока с ключом текущего окружения либо из `_default`.
|
||||||
|
|
||||||
|
Обычные значения (блок `envs`) переопределяют дефолты кода, в частности:
|
||||||
|
|
||||||
|
| Переменная | Значение в чарте |
|
||||||
|
| --- | --- |
|
||||||
|
| `HTTP_APP_ROOT_PATH` | `/checklists` |
|
||||||
|
| `HTTP_APP_WORKERS` | `3` |
|
||||||
|
| `HTTP_APP_ADMIN_ENABLE` | `true` |
|
||||||
|
| `DATABASE_PORT` | `6432` (PgBouncer) |
|
||||||
|
| `DATABASE_NAME` | `checklists_db` (stage), `checklists` (preprod/production) |
|
||||||
|
| `OTEL_ENABLE` | `true` |
|
||||||
|
| `OTEL_URL` | `http://otel-collector.opentelemetry-collector.svc.cluster.local:4317` |
|
||||||
|
| `OTEL_SERVICE_NAME` | `checklists-backend.proc` (stage), `…checklists-preprod`, `…checklists-prod` |
|
||||||
|
| `JWT_AUTH_ENABLE` | `true` |
|
||||||
|
| `DEBUG` | `false` |
|
||||||
|
|
||||||
|
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DATABASE_USER` | `checklists-postgresql-secret` (stage) / `ya-pg-secret` (preprod, production) | `user` |
|
||||||
|
| `DATABASE_PASSWORD` | `checklists-postgresql-secret` / `ya-pg-secret` | `password` |
|
||||||
|
| `DATABASE_HOST` | `checklists-postgresql-secret` / `ya-pg-secret` | `host` |
|
||||||
|
| `JWT_AUTH_PUBLIC_KEY` | `jwt-secret` | `public-key` |
|
||||||
|
|
||||||
|
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, реплики — 1 по умолчанию, 2 в production, ресурсы), `image.*`, `service.*` (ClusterIP, порт `80` → `8000`). Проверки `liveness`/`readiness` отключены.
|
||||||
|
|
||||||
|
## Переменные в CI (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает общие шаблоны из `generic/common-ci` (`universal-pipeline.yaml`, `common-security-scan.yaml`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||||
|
|
||||||
|
| Условие | STAND | Namespace |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `proc` |
|
||||||
|
| ветка `master` | `preprod` | `checklists-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `checklists-prod` |
|
||||||
|
|
||||||
|
Ключевые переменные пайплайна: `SERVICE_NAME`, `DOCKERFILE_PATH`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` (проброс `image.name`, `global.env`, метаданных коммита). Job `lint` прогоняет `ruff check`/`ruff format --check` на образе `uv`.
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- Приложение **не загружает `.env` автоматически** — переменные нужно экспортировать в окружение вручную либо задавать в манифестах.
|
||||||
|
- Аутентификация JWT в коде выполняет разбор токена с `options={"verify_signature": False}` в обоих режимах (sarex-backend и Zitadel) — подпись фактически не проверяется на уровне приложения, доверие обеспечивается сетевым слоем (Istio). При `JWT_AUTH_ENABLE=false` middleware не подключается и используется дефолтный пользователь из `entity/context.py`.
|
||||||
|
- Внутренние эндпоинты (`/internal/*`) аутентификации на уровне приложения не требуют.
|
||||||
|
- SSL-настроек подключения к БД в коде нет; в prod используется PgBouncer (`DATABASE_PORT=6432`) и CA-сертификат, вшитый в образ.
|
||||||
|
- Piccolo-admin доступен по `/admin/` при `HTTP_APP_ADMIN_ENABLE=true`; в режиме `DEBUG=false` он поднимается в `production`-режиме.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Нужен доступный PostgreSQL. Помимо `PYTHONPATH=src` и `PICCOLO_CONF=db.config`, для запуска достаточно значений по умолчанию — обязательных переменных без дефолта нет. Практически стоит задать:
|
||||||
|
|
||||||
|
- `DEBUG` (`true` локально)
|
||||||
|
- `HTTP_APP_HOST`, `HTTP_APP_PORT`
|
||||||
|
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
|
||||||
|
- `JWT_AUTH_ENABLE` (`false` для локальной разработки — тогда используется дефолтный пользователь)
|
||||||
|
- `OTEL_ENABLE` (`false` локально)
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example`.
|
||||||
963
apps/checklists/openapi.yaml
Normal file
963
apps/checklists/openapi.yaml
Normal file
@ -0,0 +1,963 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Checklists
|
||||||
|
version: "0.1.0"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **checklists-backend** — управление чек-листами
|
||||||
|
(`Checklist`) и их результатами (`ChecklistResult`).
|
||||||
|
|
||||||
|
Сервис написан на Python (**FastAPI** + ORM **Piccolo**). Приложение
|
||||||
|
собирается фабрикой `create_app` в `src/app/http.py`. Роутинг состоит из
|
||||||
|
двух групп:
|
||||||
|
|
||||||
|
- публичный API — префикс `/api/v1` (`controller/http/api`);
|
||||||
|
- внутренний API — префикс `/internal/v1` (`controller/http/internal_api`),
|
||||||
|
предназначен для вызовов внутри кластера (через ingress не публикуется).
|
||||||
|
|
||||||
|
Интерактивная документация (ReDoc) доступна по `/docs/`, схема —
|
||||||
|
по `/openapi.json/` (с учётом `root_path`). Админ-панель Piccolo монтируется
|
||||||
|
по `/admin/` при `HTTP_APP_ADMIN_ENABLE=true`.
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Аутентификация включается флагом `JWT_AUTH_ENABLE`. При включённом
|
||||||
|
`JWTAuthMiddleware` (`controller/http/middlewares.py`) публичные эндпоинты
|
||||||
|
требуют заголовок `Authorization: Bearer <jwt>`. Поддерживаются два режима:
|
||||||
|
|
||||||
|
1. **Zitadel** — если передан дополнительный заголовок `identity`
|
||||||
|
(`Identity <jwt>`), полезная нагрузка (`user_id`, `company_ids`) берётся
|
||||||
|
из этого токена (`urn:zitadel:iam:user:metadata`).
|
||||||
|
2. **sarex-backend** — если заголовка `identity` нет, разбирается основной
|
||||||
|
токен (алгоритм `RS512`, ключ `JWT_AUTH_PUBLIC_KEY`).
|
||||||
|
|
||||||
|
В обоих режимах разбор выполняется с `verify_signature=False` — подпись на
|
||||||
|
уровне приложения не проверяется, доверие обеспечивается сетевым слоем.
|
||||||
|
Внутренние эндпоинты (`/internal/*`) и пути `/docs/`, `/openapi.json/`,
|
||||||
|
`/admin/*` аутентификацию пропускают. При `JWT_AUTH_ENABLE=false` middleware
|
||||||
|
не подключается и используется дефолтный пользователь.
|
||||||
|
|
||||||
|
### Пагинация
|
||||||
|
Списочные ответы используют пагинацию limit/offset и оборачиваются в
|
||||||
|
`PaginatedResponse` — `{ count, result }`, где `count` — число объектов в
|
||||||
|
текущем ответе, `result` — сами объекты. Параметры: `limit` (по умолчанию
|
||||||
|
`100`), `offset` (по умолчанию `0`), сортировка — `order_by`/`ascending`.
|
||||||
|
|
||||||
|
### Обработка ошибок
|
||||||
|
Доменные ошибки (`controller/http/errors.py`) возвращаются как
|
||||||
|
`application/json` с телом `{ "detail": "<текст>" }`. Маппинг:
|
||||||
|
|
||||||
|
- `ResourceNotPermittedError` → **403**;
|
||||||
|
- `ResourceNotFoundError` → **404**;
|
||||||
|
- `StateConflictError` → **409**;
|
||||||
|
- `ChecklistResultValidationError` → **422**;
|
||||||
|
- прочее → **500**.
|
||||||
|
|
||||||
|
Ошибки валидации тела/параметров запроса (Pydantic) отдаются FastAPI в
|
||||||
|
стандартном формате `422` (`HTTPValidationError`). Все публичные эндпоинты
|
||||||
|
(`/api/*`) при невалидном/отсутствующем токене возвращают `401`.
|
||||||
|
|
||||||
|
contact:
|
||||||
|
name: checklists-backend
|
||||||
|
url: https://gitlab/proc/checklists-backend
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://api.sarex.io/checklists
|
||||||
|
description: Production (ingress, root_path=/checklists)
|
||||||
|
- url: https://stage-api.sarex.io/checklists
|
||||||
|
description: Stage (ingress, root_path=/checklists)
|
||||||
|
- url: http://checklists-backend-service.proc.svc.cluster.local
|
||||||
|
description: Внутрикластерный адрес (ClusterIP, порт 80 → 8000). Единственный способ достучаться до /internal/v1
|
||||||
|
- url: http://localhost:8000
|
||||||
|
description: Локальный запуск (Uvicorn, порт по умолчанию 8000)
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: Checklists
|
||||||
|
description: Чек-листы — создание, просмотр, поиск, удаление
|
||||||
|
- name: Checklist results
|
||||||
|
description: Результаты чек-листов — создание, просмотр, обновление, удаление
|
||||||
|
- name: internal
|
||||||
|
description: Внутренние эндпоинты (только внутри кластера)
|
||||||
|
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
|
||||||
|
paths:
|
||||||
|
# ==========================================================================
|
||||||
|
# Checklists
|
||||||
|
# ==========================================================================
|
||||||
|
/api/v1/checklists/:
|
||||||
|
post:
|
||||||
|
tags: [Checklists]
|
||||||
|
summary: Создать чек-лист
|
||||||
|
operationId: createChecklist
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistCreate'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Созданный чек-лист
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistReadFull'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
get:
|
||||||
|
tags: [Checklists]
|
||||||
|
summary: Список чек-листов
|
||||||
|
operationId: listChecklists
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/OrderByChecklist' }
|
||||||
|
- { $ref: '#/components/parameters/Ascending' }
|
||||||
|
- { $ref: '#/components/parameters/Limit' }
|
||||||
|
- { $ref: '#/components/parameters/Offset' }
|
||||||
|
- name: company_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Фильтр по ID компаний
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Страница чек-листов (компактное представление)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedResponse_ChecklistReadCompact'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/api/v1/checklists/filter/:
|
||||||
|
post:
|
||||||
|
tags: [Checklists]
|
||||||
|
summary: Список чек-листов (фильтры в теле)
|
||||||
|
description: Аналог `GET /api/v1/checklists/`, но фильтры и пагинация передаются в теле запроса.
|
||||||
|
operationId: filterChecklists
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistFiltersWithPagination'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Страница чек-листов (компактное представление)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedResponse_ChecklistReadCompact'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/api/v1/checklists/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [Checklists]
|
||||||
|
summary: Чек-лист по id
|
||||||
|
operationId: getChecklist
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/InstanceId' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Чек-лист (полное представление)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistReadFull'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
delete:
|
||||||
|
tags: [Checklists]
|
||||||
|
summary: Удалить чек-лист
|
||||||
|
operationId: deleteChecklist
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/InstanceId' }
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Чек-лист удалён
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# Checklist results
|
||||||
|
# ==========================================================================
|
||||||
|
/api/v1/results/:
|
||||||
|
post:
|
||||||
|
tags: [Checklist results]
|
||||||
|
summary: Создать результат чек-листа
|
||||||
|
description: |
|
||||||
|
Создаёт результат по `checklist_id`. Данные чек-листа переносятся бэком
|
||||||
|
автоматически; в теле передаются метаданные и список значений инпутов
|
||||||
|
(`input_values`).
|
||||||
|
operationId: createChecklistResult
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultCreate'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Созданный результат
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultRead'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
get:
|
||||||
|
tags: [Checklist results]
|
||||||
|
summary: Список результатов чек-листов
|
||||||
|
operationId: listChecklistResults
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/OrderByChecklistResult' }
|
||||||
|
- { $ref: '#/components/parameters/Ascending' }
|
||||||
|
- { $ref: '#/components/parameters/Limit' }
|
||||||
|
- { $ref: '#/components/parameters/Offset' }
|
||||||
|
- { $ref: '#/components/parameters/FilterResultId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterChecklistId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterCreatorId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterIsDraft' }
|
||||||
|
- { $ref: '#/components/parameters/FilterIsLocked' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Страница результатов
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedResponse_ChecklistResultRead'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/api/v1/results/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [Checklist results]
|
||||||
|
summary: Результат чек-листа по id
|
||||||
|
operationId: getChecklistResult
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/InstanceId' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Результат чек-листа
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultRead'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
patch:
|
||||||
|
tags: [Checklist results]
|
||||||
|
summary: Обновить результат чек-листа
|
||||||
|
description: Частичное обновление значений инпутов и флага черновика.
|
||||||
|
operationId: updateChecklistResult
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/InstanceId' }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultPartialUpdate'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Обновлённый результат
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultRead'
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
delete:
|
||||||
|
tags: [Checklist results]
|
||||||
|
summary: Удалить результат чек-листа
|
||||||
|
operationId: deleteChecklistResult
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/InstanceId' }
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Результат удалён
|
||||||
|
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||||
|
'403': { $ref: '#/components/responses/Forbidden' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# Internal
|
||||||
|
# ==========================================================================
|
||||||
|
/internal/v1/results/:
|
||||||
|
get:
|
||||||
|
tags: [internal]
|
||||||
|
summary: Список результатов (внутренний)
|
||||||
|
description: Внутрикластерный эндпоинт. Аутентификация на уровне приложения не выполняется.
|
||||||
|
operationId: internalListChecklistResults
|
||||||
|
security: []
|
||||||
|
parameters:
|
||||||
|
- { $ref: '#/components/parameters/OrderByChecklistResult' }
|
||||||
|
- { $ref: '#/components/parameters/Ascending' }
|
||||||
|
- { $ref: '#/components/parameters/Limit' }
|
||||||
|
- { $ref: '#/components/parameters/Offset' }
|
||||||
|
- { $ref: '#/components/parameters/FilterResultId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterChecklistId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterCreatorId' }
|
||||||
|
- { $ref: '#/components/parameters/FilterIsDraft' }
|
||||||
|
- { $ref: '#/components/parameters/FilterIsLocked' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Страница результатов
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PaginatedResponse_ChecklistResultRead'
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/internal/v1/results/lock:
|
||||||
|
patch:
|
||||||
|
tags: [internal]
|
||||||
|
summary: Массовое обновление блокировки результатов
|
||||||
|
description: Устанавливает флаг `is_locked` для списка результатов по их id. Внутрикластерный эндпоинт.
|
||||||
|
operationId: internalBulkUpdateLock
|
||||||
|
security: []
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultBulkLockingPartialUpdate'
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Флаги блокировки обновлены
|
||||||
|
'422': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
JWT в заголовке `Authorization: Bearer <token>`. Алгоритм `RS512`,
|
||||||
|
ключ `JWT_AUTH_PUBLIC_KEY`. Разбор выполняется без проверки подписи
|
||||||
|
(`verify_signature=False`).
|
||||||
|
identityToken:
|
||||||
|
type: apiKey
|
||||||
|
in: header
|
||||||
|
name: identity
|
||||||
|
description: |
|
||||||
|
Опциональный заголовок `identity` (`Identity <jwt>`) для режима Zitadel.
|
||||||
|
При его наличии полезная нагрузка (`user_id`, `company_ids`) берётся из
|
||||||
|
этого токена.
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
InstanceId:
|
||||||
|
name: instance_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
Limit:
|
||||||
|
name: limit
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Максимальное количество объектов
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 100
|
||||||
|
Offset:
|
||||||
|
name: offset
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Количество пропущенных объектов
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
default: 0
|
||||||
|
Ascending:
|
||||||
|
name: ascending
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Сортировка по возрастанию
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
OrderByChecklist:
|
||||||
|
name: order_by
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Поле для сортировки
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [id]
|
||||||
|
default: id
|
||||||
|
OrderByChecklistResult:
|
||||||
|
name: order_by
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Поле для сортировки
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum: [id]
|
||||||
|
default: id
|
||||||
|
FilterResultId:
|
||||||
|
name: id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Фильтр по ID результата
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
FilterChecklistId:
|
||||||
|
name: checklist_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Фильтр по ID чек-листа
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
FilterCreatorId:
|
||||||
|
name: creator_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Фильтр по ID создателя результата
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
FilterIsDraft:
|
||||||
|
name: is_draft
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Признак «чернового» результата
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
nullable: true
|
||||||
|
FilterIsLocked:
|
||||||
|
name: is_locked
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: Признак блокировки результата
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
nullable: true
|
||||||
|
|
||||||
|
responses:
|
||||||
|
Unauthorized:
|
||||||
|
description: Токен не предоставлен или невалиден
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPError'
|
||||||
|
Forbidden:
|
||||||
|
description: Недостаточно прав
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPError'
|
||||||
|
NotFound:
|
||||||
|
description: Запрошенный ресурс не найден
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPError'
|
||||||
|
Conflict:
|
||||||
|
description: Конфликт состояния
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPError'
|
||||||
|
ValidationError:
|
||||||
|
description: |
|
||||||
|
Ошибка валидации тела/параметров запроса (FastAPI/Pydantic) либо
|
||||||
|
доменная ошибка валидации результата (`{ detail }`).
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
# ---- Общие ----
|
||||||
|
HTTPError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
detail:
|
||||||
|
type: string
|
||||||
|
description: Описание ошибки
|
||||||
|
required: [detail]
|
||||||
|
|
||||||
|
HTTPValidationError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
detail:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
|
||||||
|
ValidationError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
loc:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
required: [loc, msg, type]
|
||||||
|
|
||||||
|
# ---- Ограничения инпутов ----
|
||||||
|
InputChoiceOption:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: Уникальный идентификатор опции
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Название опции
|
||||||
|
example: "Да"
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description: Порядковый номер опции
|
||||||
|
example: 1
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Цвет опции (hex или именованный html-цвет)
|
||||||
|
example: "#00aa00"
|
||||||
|
selected_tip:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Заметка при выборе опции
|
||||||
|
example: 'Рекомендуется добавить комментарий при ответе "Нет"'
|
||||||
|
alt_name:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Название опции для записи в историю
|
||||||
|
example: "Одобрено"
|
||||||
|
required: [id, name, order, selected_tip]
|
||||||
|
|
||||||
|
InputChoiceConstraints:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [choice]
|
||||||
|
options:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
description: Список опций для выбора
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/InputChoiceOption'
|
||||||
|
required: [type, options]
|
||||||
|
|
||||||
|
InputStringConstraints:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [string]
|
||||||
|
min_length:
|
||||||
|
type: integer
|
||||||
|
minimum: 0
|
||||||
|
default: 0
|
||||||
|
description: Минимально допустимое количество символов
|
||||||
|
max_length:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
default: 1000
|
||||||
|
description: Максимально допустимое количество символов
|
||||||
|
required: [type]
|
||||||
|
|
||||||
|
InputConstraints:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/InputChoiceConstraints'
|
||||||
|
- $ref: '#/components/schemas/InputStringConstraints'
|
||||||
|
discriminator:
|
||||||
|
propertyName: type
|
||||||
|
mapping:
|
||||||
|
choice: '#/components/schemas/InputChoiceConstraints'
|
||||||
|
string: '#/components/schemas/InputStringConstraints'
|
||||||
|
|
||||||
|
# ---- Чек-лист (создание) ----
|
||||||
|
ChecklistInputCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
maxLength: 1024
|
||||||
|
description: Название
|
||||||
|
example: "Комментарий"
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description: Порядковый номер
|
||||||
|
is_required:
|
||||||
|
type: boolean
|
||||||
|
description: Обязательное ли поле для заполнения
|
||||||
|
constraints:
|
||||||
|
$ref: '#/components/schemas/InputConstraints'
|
||||||
|
required: [name, order, is_required, constraints]
|
||||||
|
|
||||||
|
ChecklistItemCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 8192
|
||||||
|
description: Описание шага
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description: Порядковый номер
|
||||||
|
inputs:
|
||||||
|
type: array
|
||||||
|
description: Список элементов ввода
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistInputCreate'
|
||||||
|
required: [description, order, inputs]
|
||||||
|
|
||||||
|
ChecklistCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
maxLength: 250
|
||||||
|
description: Название
|
||||||
|
example: "Чек-лист проверки документов"
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 8192
|
||||||
|
description: Описание чек-листа
|
||||||
|
company_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
description: ID компании
|
||||||
|
example: 1
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
description: Список шагов чек-листа
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistItemCreate'
|
||||||
|
required: [name, description, company_id, items]
|
||||||
|
|
||||||
|
# ---- Чек-лист (чтение) ----
|
||||||
|
ChecklistInputRead:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
maxLength: 1024
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
is_required:
|
||||||
|
type: boolean
|
||||||
|
constraints:
|
||||||
|
$ref: '#/components/schemas/InputConstraints'
|
||||||
|
required: [id, created_at, updated_at, name, order, is_required, constraints]
|
||||||
|
|
||||||
|
ChecklistItemRead:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
maxLength: 8192
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
inputs:
|
||||||
|
type: array
|
||||||
|
description: Список элементов ввода
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistInputRead'
|
||||||
|
required: [id, created_at, updated_at, description, order, inputs]
|
||||||
|
|
||||||
|
ChecklistReadCompact:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
company_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
required: [id, created_at, updated_at, name, description, company_id]
|
||||||
|
|
||||||
|
ChecklistReadFull:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/ChecklistReadCompact'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
description: Список шагов чек-листа
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistItemRead'
|
||||||
|
required: [items]
|
||||||
|
|
||||||
|
ChecklistFiltersWithPagination:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
order_by:
|
||||||
|
type: string
|
||||||
|
enum: [id]
|
||||||
|
default: id
|
||||||
|
ascending:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
default: 100
|
||||||
|
offset:
|
||||||
|
type: integer
|
||||||
|
default: 0
|
||||||
|
company_id:
|
||||||
|
type: array
|
||||||
|
nullable: true
|
||||||
|
description: Фильтр по ID компаний
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
|
||||||
|
# ---- Результаты чек-листов ----
|
||||||
|
ChecklistResultInputCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
input_id:
|
||||||
|
type: integer
|
||||||
|
description: ID инпута, для которого устанавливается значение
|
||||||
|
example: 1
|
||||||
|
value:
|
||||||
|
description: 'Значение (тип зависит от инпута: id опции для choice, строка для string)'
|
||||||
|
nullable: true
|
||||||
|
example: "Да"
|
||||||
|
required: [input_id, value]
|
||||||
|
|
||||||
|
ChecklistResultCreate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
entity_type:
|
||||||
|
type: string
|
||||||
|
description: Сущность, для которой создан результат
|
||||||
|
example: "review"
|
||||||
|
entity_id:
|
||||||
|
type: string
|
||||||
|
description: ID сущности, для которой создан результат
|
||||||
|
example: "1"
|
||||||
|
checklist_id:
|
||||||
|
type: integer
|
||||||
|
description: ID чек-листа
|
||||||
|
example: 1
|
||||||
|
accessible_by:
|
||||||
|
type: array
|
||||||
|
description: SA ID роли/места/пользователя, которым доступен результат
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
is_draft:
|
||||||
|
type: boolean
|
||||||
|
description: Является ли результат черновым
|
||||||
|
input_values:
|
||||||
|
type: array
|
||||||
|
description: Список устанавливаемых значений
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultInputCreate'
|
||||||
|
required: [entity_type, entity_id, checklist_id, accessible_by, is_draft, input_values]
|
||||||
|
|
||||||
|
ChecklistResultPartialUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
input_values:
|
||||||
|
type: array
|
||||||
|
description: Список устанавливаемых значений
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultInputCreate'
|
||||||
|
is_draft:
|
||||||
|
type: boolean
|
||||||
|
description: Является ли результат черновым
|
||||||
|
required: [input_values, is_draft]
|
||||||
|
|
||||||
|
ChecklistResultInput:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
input_id:
|
||||||
|
type: integer
|
||||||
|
description: ID инпута, для которого создан результат
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
description: Название
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description: Порядковый номер
|
||||||
|
value:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Введённое значение (строка или UUID выбранной опции)
|
||||||
|
value_text:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Текстовое представление введённого значения
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
description: Цвет значения
|
||||||
|
required: [input_id, name, order, value, value_text, color]
|
||||||
|
|
||||||
|
ChecklistResultItem:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
description: Описание шага
|
||||||
|
order:
|
||||||
|
type: integer
|
||||||
|
description: Порядковый номер
|
||||||
|
inputs:
|
||||||
|
type: array
|
||||||
|
description: Список введённых значений
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultInput'
|
||||||
|
required: [description, order, inputs]
|
||||||
|
|
||||||
|
ChecklistResultRead:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
entity_type:
|
||||||
|
type: string
|
||||||
|
example: "review"
|
||||||
|
entity_id:
|
||||||
|
type: string
|
||||||
|
example: "1"
|
||||||
|
checklist_id:
|
||||||
|
type: integer
|
||||||
|
accessible_by:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
is_draft:
|
||||||
|
type: boolean
|
||||||
|
company_id:
|
||||||
|
type: integer
|
||||||
|
description: ID компании
|
||||||
|
is_locked:
|
||||||
|
type: boolean
|
||||||
|
description: Заблокирован ли результат для изменений
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
description: Список шагов чек-листа
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultItem'
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- created_at
|
||||||
|
- updated_at
|
||||||
|
- entity_type
|
||||||
|
- entity_id
|
||||||
|
- checklist_id
|
||||||
|
- accessible_by
|
||||||
|
- is_draft
|
||||||
|
- company_id
|
||||||
|
- is_locked
|
||||||
|
- items
|
||||||
|
|
||||||
|
ChecklistResultBulkLockingPartialUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ids:
|
||||||
|
type: array
|
||||||
|
description: Список id результатов, которым нужно обновить флаг
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
example: [1, 2, 3]
|
||||||
|
is_locked:
|
||||||
|
type: boolean
|
||||||
|
description: Заблокирован ли результат для изменений
|
||||||
|
required: [ids, is_locked]
|
||||||
|
|
||||||
|
# ---- Пагинация ----
|
||||||
|
PaginatedResponse_ChecklistReadCompact:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
description: Количество объектов
|
||||||
|
example: 1
|
||||||
|
result:
|
||||||
|
type: array
|
||||||
|
description: Объекты
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistReadCompact'
|
||||||
|
required: [count, result]
|
||||||
|
|
||||||
|
PaginatedResponse_ChecklistResultRead:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
description: Количество объектов
|
||||||
|
example: 1
|
||||||
|
result:
|
||||||
|
type: array
|
||||||
|
description: Объекты
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ChecklistResultRead'
|
||||||
|
required: [count, result]
|
||||||
54
apps/comparisons/.env.example
Normal file
54
apps/comparisons/.env.example
Normal 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
|
||||||
148
apps/comparisons/CONFIGURATION.md
Normal file
148
apps/comparisons/CONFIGURATION.md
Normal 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`.
|
||||||
77
apps/comparisons/ENDPOINTS.md
Normal file
77
apps/comparisons/ENDPOINTS.md
Normal 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`.
|
||||||
793
apps/comparisons/openapi.yaml
Normal file
793
apps/comparisons/openapi.yaml
Normal 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
|
||||||
19
apps/contracts/.env.example
Normal file
19
apps/contracts/.env.example
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# App
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
ADDRESS=:8080
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
# Публичный RSA-ключ (PEM) для проверки JWT.
|
||||||
|
# Читается напрямую через os.Getenv("PUBLIC_KEY") в cmd/http/main.go.
|
||||||
|
PUBLIC_KEY=
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# DSN подключения к PostgreSQL (pgx), напр. postgres://postgres:admin@127.0.0.1:5432/postgres?sslmode=disable
|
||||||
|
DB_URL=postgres://postgres:admin@127.0.0.1:5432/postgres?sslmode=disable
|
||||||
|
DB_POOL_SIZE=10
|
||||||
|
|
||||||
|
# CLI (миграции) — cmd/cli
|
||||||
|
# Путь к каталогу с миграциями (по умолчанию migrations)
|
||||||
|
DB_MIGRATIONS_PATH=migrations
|
||||||
|
# Необязательно: путь к .env-файлу для cli (эквивалент флага -env-file)
|
||||||
|
# ENV_FILE=.env
|
||||||
135
apps/contracts/CONFIGURATION.md
Normal file
135
apps/contracts/CONFIGURATION.md
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
# Конфигурация проекта contracts
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса `contracts` (Go, HTTP API + CLI миграций).
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется библиотекой [`github.com/sethvargo/go-envconfig`](https://github.com/sethvargo/go-envconfig) по структуре `Config` в `internal/app/http/config.go`. Дополнительно `.env`-файл автоматически подгружается через [`github.com/joho/godotenv`](https://github.com/joho/godotenv):
|
||||||
|
|
||||||
|
- HTTP-процесс (`cmd/http/main.go`) вызывает `godotenv.Load(".env")` перед разбором конфигурации — если файл `.env` есть в рабочем каталоге, его переменные попадают в окружение;
|
||||||
|
- CLI-процесс (`cmd/cli/main.go`) загружает файл из `ENV_FILE` (или `.env` по умолчанию), путь можно задать флагом `-env-file`.
|
||||||
|
|
||||||
|
Особенности разбора (`go-envconfig`):
|
||||||
|
|
||||||
|
- глобального префикса нет — верхнеуровневые поля читаются по своим именам (`LOG_LEVEL`, `ADDRESS`);
|
||||||
|
- вложенные секции задаются префиксом на уровне структуры: `Database` → `env:", prefix=DB_"`, `Auth` → `env:", prefix=AUTH_"`;
|
||||||
|
- значения по умолчанию заданы в тегах через `default=…`; поля без `default` при отсутствии переменной остаются пустыми (нулевым значением типа), а не приводят к панике на этапе разбора — ошибки всплывают позже (например, невалидный `DB_URL` или пустой `PUBLIC_KEY`).
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (бинарник) | `.env` в рабочем каталоге (авто-загрузка `godotenv`) + переменные окружения процесса |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yml`: сервис `contracts` берёт переменные из `env_file: .env`; поднимается вместе с `postgres` |
|
||||||
|
| Kubernetes (Helm) | `.helm/values-<env>.yaml`: блоки `envs` (обычные значения) и `secrets` (значения из k8s-секретов); шаблон `.helm/templates/deployment.yaml` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci` и переменные `workflow.rules` (namespace, release, chart) |
|
||||||
|
|
||||||
|
Способы запуска процессов:
|
||||||
|
|
||||||
|
| Команда | Точка входа | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `http` | `cmd/http/main.go` | HTTP API (Fiber v3), слушает `ADDRESS` |
|
||||||
|
| `cli migrate` | `cmd/cli/main.go` | Применение миграций БД (`golang-migrate`), каталог `DB_MIGRATIONS_PATH` |
|
||||||
|
|
||||||
|
Порядок запуска в контейнере (`entrypoint.sh`): сначала `./cli migrate`, затем `./http`.
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
В столбце «Переменная» указано полное имя (с учётом префикса секции). Дефолт `—` означает, что значения по умолчанию нет.
|
||||||
|
|
||||||
|
### App
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | `debug` | Уровень логирования (zap): `debug`/`info`/`warn`/`error` и т.п. |
|
||||||
|
| `ADDRESS` | string | `:8080` | Адрес и порт прослушивания HTTP-сервера (Fiber) |
|
||||||
|
|
||||||
|
### Database (`DB_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DB_URL` | string | — | DSN подключения к PostgreSQL (`pgxpool.ParseConfig`), напр. `postgres://user:pass@host:5432/db?sslmode=verify-full` |
|
||||||
|
| `DB_POOL_SIZE` | int32 | `10` | Максимальный размер пула соединений (`pgxpool.Config.MaxConns`) |
|
||||||
|
|
||||||
|
### Auth (`AUTH_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `AUTH_PUBLIC_KEY` | string | — | Публичный RSA-ключ (PEM) для проверки JWT. См. замечание ниже — фактически используется `PUBLIC_KEY` |
|
||||||
|
| `PUBLIC_KEY` | string | — | Публичный RSA-ключ (PEM). Читается напрямую в `cmd/http/main.go` через `os.Getenv("PUBLIC_KEY")` и записывается в `config.Auth.PublicKey`, перекрывая `AUTH_PUBLIC_KEY` |
|
||||||
|
|
||||||
|
> При старте `AuthProvider` парсит ключ (`pem.Decode` + `x509.ParsePKIXPublicKey`). Если `PUBLIC_KEY` пустой или невалидный — приложение падает с `panic` ещё до старта HTTP-сервера.
|
||||||
|
|
||||||
|
## Переменные CLI (миграции)
|
||||||
|
|
||||||
|
Читаются в `cmd/cli/main.go` (структура `cliConfig`, префикс `DB_`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DB_URL` | string | — | DSN подключения к PostgreSQL для применения миграций (обязателен, иначе ошибка `DB_URL is required`) |
|
||||||
|
| `DB_MIGRATIONS_PATH` | string | `migrations` | Путь к каталогу с SQL-миграциями (`golang-migrate`) |
|
||||||
|
| `ENV_FILE` | string | `.env` | Путь к `.env`-файлу, из которого CLI загружает переменные (можно задать флагом `-env-file=PATH`) |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры и сборки
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная / параметр | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | `docker-compose.yml` | Параметры локального контейнера PostgreSQL (`postgres`/`admin`/`postgres`) |
|
||||||
|
| build-stage `golang:1.24` | `Dockerfile` | Базовый образ для сборки бинарников `http` и `cli` |
|
||||||
|
| runtime `alpine:latest` | `Dockerfile` | Финальный образ; копируются `http`, `cli`, `migrations/`, `entrypoint.sh`; открыт порт `8080` |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)
|
||||||
|
|
||||||
|
Обычные значения задаются в блоке `envs` (в текущих values он пуст: `envs: []`). Значения из секретов (блок `secrets`) монтируются как env через `secretKeyRef` в `.helm/templates/deployment.yaml`:
|
||||||
|
|
||||||
|
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DB_URL` | `ya-pg-secret` | `db_url` |
|
||||||
|
| `PUBLIC_KEY` | `public-key` | `key` |
|
||||||
|
|
||||||
|
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, образ, порт, реплики, ресурсы, `service_name`/`service_port`), `api.*` (host/prefix/path ingress), `imagePullSecrets`.
|
||||||
|
|
||||||
|
Помимо env, чарт монтирует CA-сертификат PostgreSQL из секрета `ya-pg-secret` (ключ `certificate`) как файл `/opt/.postgresql/root.crt` (см. `deployment.yaml`). Секрет `ya-pg-secret` при отсутствии создаётся шаблоном `ya-pg-secret.yaml` со случайными значениями и политикой `helm.sh/resource-policy: keep`.
|
||||||
|
|
||||||
|
Параметры окружений (`.helm/values-<env>.yaml`):
|
||||||
|
|
||||||
|
| Окружение | `api.host` | `deployment.service_port` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| stage | `stage-api.sarex.io` | `8080` |
|
||||||
|
| preprod | `api.preprod.sarex.io` | `80` |
|
||||||
|
| production | `api.sarex.io` | `8080` |
|
||||||
|
|
||||||
|
## Переменные в CI (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает общие шаблоны из `generic/common-ci` (`universal-pipeline-*`, `common-security-scan`, `common-build`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||||
|
|
||||||
|
| Условие | STAND | Namespace | Chart version |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| ветка `master` | `preprod` | `contracts-preprod` | `0.0.1-preprod` |
|
||||||
|
| ветка `stage` | `stage` | `contracts-stage` | `0.0.1-stage` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `prod` | `contracts-prod` | `0.0.1-prod` |
|
||||||
|
|
||||||
|
Общие переменные пайплайна: `RELEASE_NAME=contracts`, `CHART_NAME=contracts`, `IMAGE_PATH=deployment.image`, `HELM_SET_ARGS="--set deployment.image=${IMAGE_NAME}"`, `DOCKERFILE_PATH=Dockerfile`, флаги `ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_STATE_UPDATE`/`ENABLE_DEPLOY` (`true`), `ENABLE_LINTER` (`false`). Для merge request-ов пайплайн запускается без деплоя.
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **Дублирование ключа авторизации.** В `Config` объявлено поле `Auth.PublicKey` с тегом `AUTH_PUBLIC_KEY`, но `cmd/http/main.go` дополнительно читает `os.Getenv("PUBLIC_KEY")` и перезаписывает им значение. В Helm секрет прокидывается как `PUBLIC_KEY`. Практически используется именно `PUBLIC_KEY`; `AUTH_PUBLIC_KEY` в текущем деплое не задаётся.
|
||||||
|
- **`.env` загружается автоматически** (в отличие от Python-сервисов): `godotenv.Load(".env")` в HTTP-процессе и `godotenv.Load(ENV_FILE|.env)` в CLI. Файл `.env` при этом попадает под `.gitignore` (`*.env`) и в репозиторий не коммитится.
|
||||||
|
- **Пустой `PUBLIC_KEY` — фатально.** `auth.New` делает `panic`, если ключ не удаётся распарсить как PEM/PKIX. Для локального запуска нужен валидный публичный ключ.
|
||||||
|
- **`DB_URL` обязателен и для http, и для cli.** Невалидный DSN приводит к ошибке `pgxpool.ParseConfig`/подключения; в CLI пустой `DB_URL` даёт явную ошибку `DB_URL is required`.
|
||||||
|
- **`envs: []` в values.** Все прикладные переменные в k8s сейчас приходят только из секретов (`DB_URL`, `PUBLIC_KEY`); `LOG_LEVEL`/`ADDRESS` используют дефолты (`debug`, `:8080`).
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
PostgreSQL поднимается через `docker-compose up postgres`, приложение — сборкой `cmd/http` (или целиком через docker-compose). Минимально необходимо задать:
|
||||||
|
|
||||||
|
- `DB_URL` — DSN до PostgreSQL (для локали обычно `?sslmode=disable`)
|
||||||
|
- `PUBLIC_KEY` — валидный публичный RSA-ключ (PEM) для проверки JWT
|
||||||
|
- при необходимости: `LOG_LEVEL`, `ADDRESS`, `DB_POOL_SIZE` (иначе применяются дефолты)
|
||||||
|
- для миграций (`cli migrate`): `DB_URL` и, при нестандартном расположении, `DB_MIGRATIONS_PATH`
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example`.
|
||||||
64
apps/contracts/ENDPOINTS.md
Normal file
64
apps/contracts/ENDPOINTS.md
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует contracts-frontend
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `contracts-frontend`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Запросы сгруппированы по доменам в каталоге `src/shared/api/fetch/*.api.ts`. Каждая функция вызывает соответствующий метод `httpService` (`src/shared/api/http-service.ts`), который создаётся фабрикой `createHttpService` из `@sarex-team/sdk-js` (поверх `axios`). Для запроса указываются:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (добавляется к базовому хосту сервиса);
|
||||||
|
- `data` — тело запроса (для `post`/`put`);
|
||||||
|
- `axiosConfig.params` — query-параметры;
|
||||||
|
- `cache`, `queryKey` — опции кеширования (react-query-подобные ключи из `src/shared/api/keys/*`);
|
||||||
|
- `isCSRF` — включение CSRF-обработки (для `departments`).
|
||||||
|
|
||||||
|
Базовый хост подставляется по значению `service` и текущему окружению `__BUILD_ENV__` (`local`/`stage`/`preprod`/`prod`, по умолчанию `prod`; см. `http-service.ts`). В режиме `local` для http-сервиса устанавливается тип `zitadel` (`setTypeOfHttpService("zitadel")`). Итоговый URL = `<базовый хост сервиса>` + `url`.
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `src/shared/api/hosts.ts`. Ниже перечислены сервисы, **фактически используемые** запросами модуля; в реестре хостов определены и другие сервисы (`bim`, `bimv2`, `workflows`, `workspaces`, `documentations`, `comparisons`, `remarks`, `projects`, `eavV1`, `notifications`, `google`, `sarexApi`, `zitadel`), но обращений к ним в `fetch/*` нет.
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | `stage` | `prod` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `contracts` | Сервис договоров (contracts-backend) | `https://stage-api.sarex.io/contracts` | `https://api.sarex.io/contracts` |
|
||||||
|
| `sarex` | Локальный backend Sarex (core/admin) | `""` (относительные пути) | `""` |
|
||||||
|
| `gateway` | Gateway/API Sarex (ресурсы/проекты) | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||||
|
|
||||||
|
> Также определены окружения `local` и `preprod`. В `local` сервисы проксируются на относительные пути (`contracts` → `/sarex-contracts`, `sarex` → `/sarex-backend`, `gateway` → `/sarex-gateway`, `zitadel` → `/zitadel`). Значения `preprod` используют домен `api.preprod.sarex.io`.
|
||||||
|
|
||||||
|
## Эндпоинты по сервисам
|
||||||
|
|
||||||
|
### `contracts` — Сервис договоров
|
||||||
|
|
||||||
|
Определены в `src/shared/api/fetch/contract.api.ts`.
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchContractsByResourceId` | GET | `/api/v0/contracts` | Список договоров (query: `limit`, `offset`, `resource_id`, `tenant_id`) |
|
||||||
|
| `fetchCreateContractByResourceId` | POST | `/api/v0/contracts` | Создать договор |
|
||||||
|
| `fetchUpdateContract` | PUT | `/api/v0/contracts/{contract.id}` | Обновить договор по id |
|
||||||
|
|
||||||
|
> Функция удаления `fetchDeleteContract` (`DELETE /api/v0/contracts/{contractId}`) присутствует в коде, но закомментирована.
|
||||||
|
|
||||||
|
### `sarex` — Локальный backend Sarex (core/admin)
|
||||||
|
|
||||||
|
Определены в `company.api.ts`, `contractor.api.ts`, `department.api.ts`.
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchCompanies` | GET | `/api/core/admin/companies/` | Список компаний (кешируется, ключ `companies`) |
|
||||||
|
| `fetchContractors` | GET | `/api/core/admin/contractors/?company_id={companyId}` | Контрагенты компании (кешируется, ключ `contractors`) |
|
||||||
|
| `fetchDepartments` | GET | `/api/core/admin/departments/` | Отделы (кешируется, ключ `departments`, `isCSRF: true`) |
|
||||||
|
|
||||||
|
### `gateway` — Gateway/API Sarex
|
||||||
|
|
||||||
|
Определён в `project.api.ts`.
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchProjects` | GET | `/api/v1/resources/?company_id={companyId}` | Список ресурсов/проектов компании (кешируется, ключ `projects`) |
|
||||||
|
|
||||||
|
## Обработка запросов и кеширование
|
||||||
|
|
||||||
|
Кеширование включается флагом `cache: true` с ключом `queryKey` (значения ключей — в `src/shared/api/keys/*.ts`: `companies`, `projects`, `contractors`, `departments`). Обработка ошибок и авторизация (в т.ч. режим `zitadel` для `local`) выполняются внутри `httpService` из `@sarex-team/sdk-js`.
|
||||||
404
apps/contracts/openapi.yaml
Normal file
404
apps/contracts/openapi.yaml
Normal file
@ -0,0 +1,404 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Contracts Service API
|
||||||
|
version: "1.0.0"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **contracts** (`platform/contracts`) — управление
|
||||||
|
договорами (контрактами): создание (в т.ч. массовое), получение по id,
|
||||||
|
списочный вывод с фильтрами и пагинацией, обновление.
|
||||||
|
|
||||||
|
Сервис написан на Go (**Fiber v3**). Приложение собирается в
|
||||||
|
`internal/app/http/app.go` (`New`). Роутинг вложен под общий префикс
|
||||||
|
`/api/v0` (`app.server.Group("/api/v0")`), внутри — группа
|
||||||
|
`/contracts` (`internal/controller/http/v0`).
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Все эндпоинты группы `/api/v0/contracts` защищены middleware
|
||||||
|
(`internal/adapter/auth/middleware.go`). Поддерживаются два режима:
|
||||||
|
|
||||||
|
1. **Zitadel** — если передан заголовок `Identity: Bearer <jwt>`,
|
||||||
|
пользователь берётся из полезной нагрузки этого токена
|
||||||
|
(`urn:zitadel:iam:user:metadata`). Подпись на уровне приложения
|
||||||
|
не проверяется (валидность обеспечивается сетевым слоем/Istio).
|
||||||
|
2. **sarex-backend** — если заголовка `Identity` нет, подпись основного
|
||||||
|
токена `Authorization: Bearer <jwt>` проверяется публичным RSA-ключом
|
||||||
|
(`PUBLIC_KEY`).
|
||||||
|
|
||||||
|
Корневой эндпоинт `GET /api/v0/` (health/ping) аутентификации не требует.
|
||||||
|
|
||||||
|
### Идентификаторы
|
||||||
|
Идентификатор договора — **ULID** (строка), парсится через
|
||||||
|
`ulid.Parse`. `resource_id` — **UUID**.
|
||||||
|
|
||||||
|
### Пагинация
|
||||||
|
Списочный вывод использует `limit`/`offset` (query-параметры, по умолчанию
|
||||||
|
`limit=100`, `offset=0`).
|
||||||
|
|
||||||
|
### Обработка ошибок
|
||||||
|
Ошибки возвращаются с соответствующим HTTP-статусом; тело — либо строка
|
||||||
|
с описанием, либо `{"error": "..."}` (при внутренней панике). Коды:
|
||||||
|
`400` — некорректный запрос/невалидный id, `401` — проблемы аутентификации,
|
||||||
|
`403` — пользователь не состоит в компании (`tenant_id`), `404` — договор
|
||||||
|
не найден, `500` — внутренняя ошибка, `501` — метод не реализован.
|
||||||
|
|
||||||
|
### Замечания (расхождения кода)
|
||||||
|
- `PATCH` и `DELETE` (как по коллекции, так и по id) возвращают
|
||||||
|
**`501 Not Implemented`** — обработчики-заглушки.
|
||||||
|
- `POST /api/v0/contracts` принимает **как одиночный объект, так и массив**:
|
||||||
|
тип создания выбирается по форме тела (объект → создание одного договора,
|
||||||
|
массив → пакетное создание).
|
||||||
|
- `GET /api/v0/contracts` (список) требует query-параметр `tenant_id`
|
||||||
|
(middleware `UserInCompanyMiddleware` проверяет, что пользователь состоит
|
||||||
|
в этой компании; иначе `400`/`403`).
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://api.sarex.io/contracts
|
||||||
|
description: production
|
||||||
|
- url: https://stage-api.sarex.io/contracts
|
||||||
|
description: stage
|
||||||
|
- url: https://api.preprod.sarex.io/contracts
|
||||||
|
description: preprod
|
||||||
|
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: contracts
|
||||||
|
description: Договоры
|
||||||
|
- name: service
|
||||||
|
description: Служебные эндпоинты
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/v0/:
|
||||||
|
get:
|
||||||
|
tags: [service]
|
||||||
|
summary: Health / ping
|
||||||
|
description: Возвращает `200 OK` без тела. Аутентификация не требуется.
|
||||||
|
security: []
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
|
||||||
|
/api/v0/contracts:
|
||||||
|
post:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Создать договор или несколько договоров
|
||||||
|
description: |
|
||||||
|
Принимает либо одиночный объект `CreateContractRequest`, либо массив
|
||||||
|
таких объектов. Форма тела определяет режим создания.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/components/schemas/CreateContractRequest"
|
||||||
|
- type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/CreateContractRequest"
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Договор(ы) создан(ы)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
oneOf:
|
||||||
|
- $ref: "#/components/schemas/ContractResponse"
|
||||||
|
- type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ContractResponse"
|
||||||
|
"400":
|
||||||
|
description: Некорректное тело запроса / ошибка валидации
|
||||||
|
"401":
|
||||||
|
description: Ошибка аутентификации
|
||||||
|
"500":
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
get:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Список договоров
|
||||||
|
description: |
|
||||||
|
Возвращает договоры с фильтрацией и пагинацией. Требует `tenant_id`
|
||||||
|
(проверяется принадлежность пользователя к компании).
|
||||||
|
parameters:
|
||||||
|
- name: tenant_id
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Идентификатор компании/арендатора
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 100
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
default: 0
|
||||||
|
- name: resource_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
- name: contractor_id
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Список договоров
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ContractPaginatedResponse"
|
||||||
|
"400":
|
||||||
|
description: Некорректные параметры запроса / отсутствует tenant_id
|
||||||
|
"401":
|
||||||
|
description: Ошибка аутентификации
|
||||||
|
"403":
|
||||||
|
description: Пользователь не состоит в указанной компании
|
||||||
|
"500":
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
put:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Пакетное обновление договоров
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/UpdateContractRequest"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Договоры обновлены
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ContractResponse"
|
||||||
|
"400":
|
||||||
|
description: Некорректное тело запроса
|
||||||
|
"401":
|
||||||
|
description: Ошибка аутентификации
|
||||||
|
"500":
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
patch:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Пакетное частичное обновление (не реализовано)
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: Not Implemented
|
||||||
|
delete:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Пакетное удаление (не реализовано)
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: Not Implemented
|
||||||
|
|
||||||
|
/api/v0/contracts/{id}:
|
||||||
|
parameters:
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Идентификатор договора (ULID)
|
||||||
|
get:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Получить договор по id
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Договор
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ContractResponse"
|
||||||
|
"400":
|
||||||
|
description: Невалидный id
|
||||||
|
"401":
|
||||||
|
description: Ошибка аутентификации
|
||||||
|
"404":
|
||||||
|
description: Договор не найден
|
||||||
|
"500":
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
put:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Обновить договор
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UpdateContractRequest"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Договор обновлён
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/ContractResponse"
|
||||||
|
"400":
|
||||||
|
description: Невалидный id / некорректное тело
|
||||||
|
"401":
|
||||||
|
description: Ошибка аутентификации
|
||||||
|
"404":
|
||||||
|
description: Договор не найден
|
||||||
|
"500":
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
patch:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Частичное обновление (не реализовано)
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: Not Implemented
|
||||||
|
delete:
|
||||||
|
tags: [contracts]
|
||||||
|
summary: Удалить договор (не реализовано)
|
||||||
|
responses:
|
||||||
|
"501":
|
||||||
|
description: Not Implemented
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
Основной токен `Authorization: Bearer <jwt>` (проверяется по RSA-ключу).
|
||||||
|
Опционально может передаваться заголовок `Identity: Bearer <jwt>`
|
||||||
|
(режим Zitadel), который имеет приоритет.
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
Contractor:
|
||||||
|
type: object
|
||||||
|
description: Произвольный JSON-объект с данными контрагента (в БД — JSONB).
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
CreateContractRequest:
|
||||||
|
type: object
|
||||||
|
required: [number, tenant_id, started_at, deadline_at]
|
||||||
|
properties:
|
||||||
|
number:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
description: Номер договора
|
||||||
|
tenant_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Идентификатор компании/арендатора
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
contractor:
|
||||||
|
$ref: "#/components/schemas/Contractor"
|
||||||
|
started_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deadline_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
cost:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: 0
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
UpdateContractRequest:
|
||||||
|
type: object
|
||||||
|
required: [number, tenant_id, started_at, deadline_at]
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: ULID договора
|
||||||
|
number:
|
||||||
|
type: string
|
||||||
|
minLength: 1
|
||||||
|
tenant_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
contractor:
|
||||||
|
$ref: "#/components/schemas/Contractor"
|
||||||
|
started_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deadline_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
cost:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
minimum: 0
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
ContractResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
description: ULID договора
|
||||||
|
number:
|
||||||
|
type: string
|
||||||
|
tenant_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
resource_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
contractor:
|
||||||
|
$ref: "#/components/schemas/Contractor"
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
started_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deadline_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
cost:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
ContractPaginatedResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
offset:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ContractResponse"
|
||||||
162
apps/control-interface/ENDPOINTS.md
Normal file
162
apps/control-interface/ENDPOINTS.md
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует srx-admin
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается приложение `srx-admin` (панель администрирования, деплой `control-interface`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
`srx-admin` — это монорепозиторий (`admin-monorepo`) c двумя фронтенд-сервисами и общим пакетом:
|
||||||
|
|
||||||
|
- `services/admin` — хост-приложение (основной админ-интерфейс);
|
||||||
|
- `services/assets` — федеративный модуль (Module Federation), встраиваемый в хост;
|
||||||
|
- `packages/app-kit` — общий пакет с реестром API-функций и таблицей хостов.
|
||||||
|
|
||||||
|
Запросы описаны не единым реестром, а по доменам — в файлах `shared/api/fetch/*.api.ts`. Каждый домен экспортирует фабрику (например `UserApi`, `AssetApi`, `ProjectApi`), которая принимает `httpService` и возвращает набор методов. Внутри метода вызывается `httpService.getRequest`/`postRequest`/`putRequest`/`patchRequest`/`deleteRequest` со структурой:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (с подстановкой параметров прямо в строку или через `axiosConfig.params`);
|
||||||
|
- `data` — тело запроса (для POST/PUT/PATCH);
|
||||||
|
- `axiosConfig`, `cache`, `queryKey`, `controller`, `isCSRF` — опции axios, кеширования, ключа запроса, отмены и CSRF-токена.
|
||||||
|
|
||||||
|
`httpService` создаётся в `shared/api/http-service.ts` через `createHttpService` из `@sarex-team/sdk-js`. Базовый хост подставляется по логическому имени `service` из `packages/app-kit/src/shared/api/hosts.ts` в зависимости от окружения сборки `__ENDPOINT__` (`BUILD_ENV`, по умолчанию `prod`). Итоговый URL = `<базовый хост сервиса>` + `url`.
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `packages/app-kit/src/shared/api/hosts.ts`. Ниже приведены `stage` и `prod`; дополнительно определены окружения `local`, `contour` и `preprod` (см. примечание). Сервисы, к которым `srx-admin` реально обращается, отмечены значком «●» в колонке «Используется».
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | Используется | `stage` | `prod` |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `iam` | IAM: пользователи, отделы, должности, группы, права | ● | `https://stage-api.sarex.io/iam` | `https://api.sarex.io/iam` |
|
||||||
|
| `eavV1` | EAV: ассеты, атрибуты, права на ассеты, модули | ● | `https://stage-api.sarex.io/eav` | `https://api.sarex.io/eav` |
|
||||||
|
| `gateway` | Gateway: ресурсы (проекты) и права на ресурсы | ● | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||||
|
| `sarex` | Локальный сервис данных (`/api/core`, `/api/pm`, `/api/commons`) | ● | `""` (относительные пути) | `""` |
|
||||||
|
| `bimv2` | BIM v2: модели статусов | ● | `https://stage-api.sarex.io/bimv2` | `https://api.sarex.io/bimv2` |
|
||||||
|
| `premises` | Сервис помещений | ● | `https://stage-api.sarex.io/premises` | `https://api.sarex.io/premises` |
|
||||||
|
| `notifications` | Лямбда уведомлений (email) | ● | `https://stage-api.sarex.io/lambdas/notification` | `https://api.sarex.io/lambdas/notification` |
|
||||||
|
| `documentations` | Сервис документации | | `https://stage-api.sarex.io/documentations` | `https://api.sarex.io/documentations` |
|
||||||
|
| `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` | Сервис замечаний | | `https://stage-api.sarex.io/remarks` | `https://api.sarex.io/remarks` |
|
||||||
|
| `projects` | Сервис проектов | | `https://stage-api.sarex.io/projects` | `https://api.sarex.io/projects` |
|
||||||
|
| `bim` | BIM-API | | `https://stage-api.sarex.io/bim` | `https://api.sarex.io/bim` |
|
||||||
|
| `sarexApi` | Gateway/API Sarex (корень) | | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||||
|
| `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` |
|
||||||
|
|
||||||
|
> В окружении `local` сервисы проксируются на относительные пути (`sarex` → `/sarex-backend`, `gateway` → `/sarex-gateway`, `eavV1` → `/sarex-eav-v1`, `notifications` → `/sarex-notifications`, `iam` → `/iam`, `premises` → `/premises` и т. д.). Окружение `contour` использует относительные пути для изолированного контура. Подключаемые удалённые модули (Module Federation) описаны отдельно в `services/*/config/endpoints.ts` (см. раздел «Удалённые модули»).
|
||||||
|
|
||||||
|
## Эндпоинты по сервисам
|
||||||
|
|
||||||
|
### `iam` — IAM (пользователи, отделы, должности, группы, права)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchUsers` | GET | `/api/admin/v0/users/?{query}` | Список пользователей компании (пагинация, поиск, фильтры) |
|
||||||
|
| `fetchCreateUser` | POST | `/api/admin/v0/users/` | Создать пользователя |
|
||||||
|
| `fetchUpdateUser` | PATCH | `/api/admin/v0/users/{id}/` | Обновить пользователя |
|
||||||
|
| `fetchBulkUpdateUsers` | PATCH | `/api/admin/v0/users/` | Массовое обновление пользователей |
|
||||||
|
| `fetchBulkUpdateUsersActivation` | POST | `/api/admin/v0/users/activation/` | Массовая активация/деактивация пользователей |
|
||||||
|
| `fetchDepartments` | GET | `/api/admin/v0/departments/` | Список отделов (пагинация, поиск, фильтр по компании) |
|
||||||
|
| `fetchCreateDepartment` | POST | `/api/admin/v0/departments/` | Создать отдел (CSRF) |
|
||||||
|
| `fetchUpdateDepartment` | PUT | `/api/admin/v0/departments/{id}/` | Обновить отдел (CSRF) |
|
||||||
|
| `fetchDeleteDepartment` | DELETE | `/api/admin/v0/departments/{id}/` | Удалить отдел (CSRF) |
|
||||||
|
| `fetchPositions` | GET | `/api/admin/v0/positions` | Список должностей (пагинация, поиск, фильтр по компании) |
|
||||||
|
| `fetchCreatePosition` | POST | `/api/admin/v0/positions/` | Создать должность (CSRF) |
|
||||||
|
| `fetchUpdatePosition` | PUT | `/api/admin/v0/positions/{id}/` | Обновить должность (CSRF) |
|
||||||
|
| `fetchDeletePosition` | DELETE | `/api/admin/v0/positions/{id}/` | Удалить должность (CSRF) |
|
||||||
|
| `fetchGroups` | POST | `/api/admin/v0/groups/search/` | Поиск функциональных групп (фильтры, пагинация, сортировка) |
|
||||||
|
| `createGroup` | POST | `/api/admin/v0/groups` | Создать группу |
|
||||||
|
| `updateGroup` | PATCH | `/api/admin/v0/groups/{id}` | Обновить группу |
|
||||||
|
| `deleteGroup` | DELETE | `/api/admin/v0/groups/{id}` | Удалить группу |
|
||||||
|
| `fetchPermissions` | POST | `/api/admin/v0/permissions/search/` | Поиск прав (фильтры, пагинация, сортировка) |
|
||||||
|
| `createPermission` | POST | `/api/admin/v0/permissions` | Создать право |
|
||||||
|
| `deletePermission` | DELETE | `/api/admin/v0/permissions/{id}` | Удалить право |
|
||||||
|
|
||||||
|
### `eavV1` — EAV (ассеты, атрибуты, права на ассеты, модули)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchGetAssetsV4` | GET | `/api/v4/assets/` | Список ассетов (v4, параметры фильтрации) |
|
||||||
|
| `fetchGetAssetsV5` | GET | `/api/v2/assets/` | Список ассетов (v5) |
|
||||||
|
| `fetchCreateBulkAssetsV4` | POST | `/api/v4/assets/` | Массовое создание ассетов (v4) |
|
||||||
|
| `fetchCreateBulkAssetsV5` | POST | `/api/v2/assets/` | Массовое создание ассетов (v5) |
|
||||||
|
| `fetchUpdateBulkAssetsV4` | PATCH | `/api/v4/assets/` | Массовое обновление ассетов (v4) |
|
||||||
|
| `fetchUpdateBulkAssetsV5` | PATCH | `/api/v2/assets/` | Массовое обновление ассетов (v5) |
|
||||||
|
| `fetchDeleteAssetV4` | DELETE | `/api/v4/assets/{assetId}/` | Удалить ассет (v4) |
|
||||||
|
| `fetchDeleteAssetV5` | DELETE | `/api/v2/assets/{assetId}/` | Удалить ассет (v5) |
|
||||||
|
| `fetchCopyRootAsset` | POST | `/api/v4/assets/{asset_id}/copy/` | Копировать корневой ассет |
|
||||||
|
| `fetchCopyAssets` | POST | `/api/v2/assets/copy-to-destination-bulk/` | Массовое копирование ассетов в назначения |
|
||||||
|
| `fecthGetAssetPermissions` | GET | `/api/v4/permissions/?asset_id={id}&service_account_id={id}` | Права доступа ассета |
|
||||||
|
| `fetchPostCreateAssetPermissions` | POST | `/api/v4/permissions/` | Создать права на ассет |
|
||||||
|
| `fetchPostUpdateAssetPermissions` | PATCH | `/api/v4/permissions/` | Обновить права на ассет |
|
||||||
|
| `fetchDeleteAssetPermissions` | DELETE | `/api/v4/permissions/{permissionId}/` | Удалить права на ассет |
|
||||||
|
| `fetchGetAssetPermissionsTree` | GET | `/api/v4/permissions/relative/?asset_id={id}` | Дерево наследуемых прав ассета |
|
||||||
|
| `fetchAttributes` | GET | `/api/v1/attribute/` | Список атрибутов компании (пагинация, поиск, фильтр по id) |
|
||||||
|
| `createAttribute` | POST | `/api/v1/attribute/` | Создать атрибут |
|
||||||
|
| `updateAttribute` | PUT | `/api/v1/attribute/{id}/` | Обновить атрибут |
|
||||||
|
| `deleteAttribute` | DELETE | `/api/v1/attribute/{attributeId}/` | Удалить атрибут |
|
||||||
|
| `fetchModules` | GET | `/api/v1/modules/` | Список модулей (CSRF) |
|
||||||
|
|
||||||
|
### `gateway` — Gateway (ресурсы/проекты, права на ресурсы)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchProjects` | GET | `/api/v2/resources?show_all=true&limit=10000&company_id={id}` | Список проектов компании (кешируется) |
|
||||||
|
| `fetchGetProjectByProjectId` | GET | `/api/v2/resources/{projectId}` | Проект по id (кешируется) |
|
||||||
|
| `fetchCreateProject` | POST | `/api/v2/resources` | Создать проект/ресурс |
|
||||||
|
| `fetchUpdateProjectByProjectId` | PATCH | `/api/v2/resources/{projectId}` | Обновить проект |
|
||||||
|
| `fetchDeleteProjectByProjectId` | DELETE | `/api/v2/resources/{projectId}` | Удалить проект |
|
||||||
|
| `fetchParentDocumentByResourceId` | GET | `/api/v1/resources-rpc/parent-document-by-resource-id/{resourceId}` | Родительский документ по resource id |
|
||||||
|
| `fetchResources` | GET | `/api/v1/resources/?company_id={id}` | Список ресурсов компании (кешируется) |
|
||||||
|
| `fetchCreatePermission` | POST | `/api/v1/resource-permissions/` | Выдать права на ресурсы сервисному аккаунту |
|
||||||
|
| `fetchResourcesByUsersId` | POST | `/api/v1/resources/users-with-resources/` | Ресурсы по набору пользователей |
|
||||||
|
| `fetchBulkUpdateUsersPermissions` | PATCH | `/api/v1/resources/permissions-bulk/` | Массовое обновление прав на ресурсы |
|
||||||
|
| `fetchBulkUpdateUsersCompanyResourcesPermission` | POST | `/api/v1/company-resource-permissions/bulk/` | Массовая выдача прав на ресурсы компании |
|
||||||
|
|
||||||
|
### `sarex` — Локальный сервис данных
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchCoordinates` | GET | `/api/commons/cs/` | Справочник систем координат (кешируется) |
|
||||||
|
| `fetchLinksToPlanningByProjectId` | GET | `/api/pm/msp/projects/?resource_id={projectId}&strict=true` | Связи проекта с планированием (кешируется) |
|
||||||
|
| `fetchBulkUpdateUserNotifications` | PATCH | `/api/core/users/bulk/notifications/` | Массовое переключение уведомлений пользователей |
|
||||||
|
| `getMrpas` | POST | `/api/core/mrpa/list/` | Список МРПА (пагинация, фильтры, агрегации) |
|
||||||
|
| `createMrpa` | POST | `/api/core/mrpa/` | Загрузить МРПА (multipart/form-data) |
|
||||||
|
| `deleteMrpa` | DELETE | `/api/core/mrpa/{id}/` | Удалить МРПА |
|
||||||
|
|
||||||
|
### `bimv2` — BIM v2 (модели статусов)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchCreateCompanyStatusModel` | POST | `/api/v1/companies/{companyId}/status_model` | Создать модель статусов компании |
|
||||||
|
| `fetchGetCompanyStatusModels` | GET | `/api/v1/companies/{companyId}/status_model` | Модели статусов компании |
|
||||||
|
| `fetchGetBIMStatusModels` | GET | `/api/v1/bims/{bimId}/status_models` | Модели статусов BIM |
|
||||||
|
| `fetchUpdateBIMStatusModel` | POST | `/api/v1/bims/{bimId}/status_model` | Обновить модель статусов BIM |
|
||||||
|
| `fetchGetBIMStatuses` | POST | `/api/v1/bims/{bimId}/statuses?{search}` | Статусы BIM (с фильтром) |
|
||||||
|
|
||||||
|
### `premises` — Сервис помещений
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getPremises` | POST | `/api/v1/premises/filter/` | Помещения по локациям и ресурсу |
|
||||||
|
|
||||||
|
### `notifications` — Лямбда уведомлений
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchSendEmail` | POST | `/` | Отправить email-уведомление (from `hello@sarex.io`) |
|
||||||
|
|
||||||
|
## Удалённые модули (Module Federation)
|
||||||
|
|
||||||
|
Помимо HTTP-API, `srx-admin` подгружает удалённые микрофронтенды через `remoteEntry.js`. Адреса заданы в `services/admin/config/endpoints.ts` и `services/assets/config/endpoints.ts` (объект `moduleEndpoints`).
|
||||||
|
|
||||||
|
| Модуль | `stage` / `local` | `prod` | `contour` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `documentations` | `https://stage-modules.sarex.io/documentations/static/module/remoteEntry.js` | `https://modules.sarex.io/documentations/static/module/remoteEntry.js` | `/documentations/static/module/remoteEntry.js` |
|
||||||
|
| `assets` | `https://stage-modules.sarex.io/control-interface/modules/assets/remoteEntry.js` | `https://modules.sarex.io/control-interface/modules/assets/remoteEntry.js` | `/control-interface/modules/assets/remoteEntry.js` |
|
||||||
|
|
||||||
|
> В `preprod` используются хосты вида `https://modules.preprod.sarex.io/...`.
|
||||||
|
|
||||||
|
## Обработка ошибок и авторизация
|
||||||
|
|
||||||
|
Запросы выполняются через `httpService` (`@sarex-team/sdk-js` поверх `axios`). Для части эндпоинтов (`iam`: отделы, должности, создание пользователей/групп; `eavV1`: модули) передаётся флаг `isCSRF: true` — добавляется CSRF-токен. Ошибки обрабатываются на уровне SDK и сторов приложения; человекочитаемые сообщения задаются в сторах (`errorMessage`), например «Произошла ошибка при запросе пользователей» / «мест работы» / «ролей» / «функциональных групп».
|
||||||
50
apps/cross-section/ENDPOINTS.md
Normal file
50
apps/cross-section/ENDPOINTS.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует cross-section
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `cross-section`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Запросы описаны в двух API-объектах в `module/api/endpoints.ts`: `crossSectionApi` (поперечные сечения) и `exportsApi` (экспорт и скачивание вложений). Каждый метод вызывает соответствующий хелпер `httpService` (`getRequest`/`postRequest`/`deleteRequest`) со структурой:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (с подстановкой параметров/query);
|
||||||
|
- `data` — опционально, тело запроса (для `POST`/`PUT`).
|
||||||
|
|
||||||
|
`httpService` создаётся функцией `createHttpService` из `@sarex-team/sdk-js` в `module/api/http-service.ts`. Базовый хост подставляется по ключу `service` из `module/api/hosts.ts` в зависимости от `BUILD_ENV` (по умолчанию `prod`). В окружении `local` тип сервиса переключается на `original` (`setTypeOfHttpService("original")`). Итоговый URL = `<базовый хост сервиса>` + `url` эндпоинта.
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `module/api/hosts.ts`. Определены окружения `local`, `stage`, `prod`, `preprod`.
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | `local` | `stage` | `prod` | `preprod` |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `gateway` | Gateway/API Sarex (используется всеми эндпоинтами модуля) | `https://stage-api.sarex.io/gateway/` | `https://stage-api.sarex.io/gateway/` | `https://api.sarex.io/gateway/` | `https://api.preprod.sarex.io/gateway/` |
|
||||||
|
| `drawings` | Сервис чертежей | `https://stage-api.sarex.io/drawings/` | `https://stage-api.sarex.io/drawings/` | `https://api.sarex.io/drawings/` | `https://api.preprod.sarex.io/drawings/` |
|
||||||
|
| `sarex` | Локальный сервис данных (относительные пути) | `https://stage.sarex.io/` | `""` | `""` | `""` |
|
||||||
|
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` | `https://login.preprod.sarex.io` |
|
||||||
|
|
||||||
|
> Фактически все эндпоинты модуля обращаются к сервису `gateway`. Сервисы `drawings`, `sarex` и `zitadel` объявлены в реестре хостов, но напрямую в `endpoints.ts` не используются.
|
||||||
|
|
||||||
|
## Эндпоинты по сервисам
|
||||||
|
|
||||||
|
### `gateway` — Gateway/API Sarex
|
||||||
|
|
||||||
|
#### `crossSectionApi` — поперечные сечения
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getCrossSections` | GET | `api/v1/drawings/cross-sections?instance_id={uuid}` | Список поперечных сечений по инстансу |
|
||||||
|
| `getCrossSectionData` | GET | `api/v1/drawings/cross-sections/{uuid}/data` | Данные поперечного сечения по uuid |
|
||||||
|
| `createCrossSections` | POST | `api/v1/drawings/cross-sections` | Создать поперечное сечение (тело — `model`) |
|
||||||
|
| `removeCrossSections` | DELETE | `api/v1/drawings/cross-sections/{uuid}/` | Удалить поперечное сечение |
|
||||||
|
|
||||||
|
#### `exportsApi` — экспорт
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `createExport` | POST | `api/v1/drawings/exports` | Создать экспорт (тело: `company_id`, `cross_section_id`, `file_type`; по умолчанию `file_type = "dwg"`) |
|
||||||
|
| `downloadExport` | GET | `api/v1/attachments/{attachment_id}` | Скачать вложение экспорта по id |
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
В модуле нет отдельного слоя маппинга ошибок (аналога `module/api/errors.ts`): обработка HTTP-ошибок выполняется на уровне `httpService` из `@sarex-team/sdk-js`. Каждый метод возвращает `response.data` (для `createExport` — весь ответ).
|
||||||
259
apps/django/.env.example
Normal file
259
apps/django/.env.example
Normal 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
|
||||||
357
apps/django/CONFIGURATION.md
Normal file
357
apps/django/CONFIGURATION.md
Normal 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
127
apps/django/ENDPOINTS.md
Normal 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/…` | Внутренние вызовы (настройки, токены) |
|
||||||
417
apps/django/FRONTEND_REQUESTS.md
Normal file
417
apps/django/FRONTEND_REQUESTS.md
Normal 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
589
apps/django/openapi.yaml
Normal 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 }
|
||||||
16
apps/document-link/.env.example
Normal file
16
apps/document-link/.env.example
Normal 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=
|
||||||
95
apps/document-link/CONFIGURATION.md
Normal file
95
apps/document-link/CONFIGURATION.md
Normal 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`).
|
||||||
66
apps/document-link/ENDPOINTS.md
Normal file
66
apps/document-link/ENDPOINTS.md
Normal 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` | «Что-то пошло не так» |
|
||||||
229
apps/documentations/api-v2.CONFIGURATION.md
Normal file
229
apps/documentations/api-v2.CONFIGURATION.md
Normal 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` (с учётом замечаний выше).
|
||||||
88
apps/documentations/api-v2.ENDPOINTS.md
Normal file
88
apps/documentations/api-v2.ENDPOINTS.md
Normal 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).
|
||||||
87
apps/documentations/api-v2.env.example
Normal file
87
apps/documentations/api-v2.env.example
Normal 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
|
||||||
1258
apps/documentations/api-v2.openapi.yaml
Normal file
1258
apps/documentations/api-v2.openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
345
apps/documentations/api.CONFIGURATION.md
Normal file
345
apps/documentations/api.CONFIGURATION.md
Normal 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` исходного репозитория.
|
||||||
114
apps/documentations/api.ENDPOINTS.md
Normal file
114
apps/documentations/api.ENDPOINTS.md
Normal 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`).
|
||||||
131
apps/documentations/api.env.example
Normal file
131
apps/documentations/api.env.example
Normal 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
|
||||||
785
apps/documentations/api.openapi.yaml
Normal file
785
apps/documentations/api.openapi.yaml
Normal 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
|
||||||
156
apps/documentations/dps-message-hub.CONFIGURATION.md
Normal file
156
apps/documentations/dps-message-hub.CONFIGURATION.md
Normal 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` рядом с этим документом.
|
||||||
45
apps/documentations/dps-message-hub.ENDPOINTS.md
Normal file
45
apps/documentations/dps-message-hub.ENDPOINTS.md
Normal 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.
|
||||||
42
apps/documentations/dps-message-hub.env.example
Normal file
42
apps/documentations/dps-message-hub.env.example
Normal 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"}'
|
||||||
66
apps/documentations/frontend.CONFIGURATION.md
Normal file
66
apps/documentations/frontend.CONFIGURATION.md
Normal 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`).
|
||||||
224
apps/documentations/frontend.ENDPOINTS.md
Normal file
224
apps/documentations/frontend.ENDPOINTS.md
Normal 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`.
|
||||||
241
apps/documentations/pdm.CONFIGURATION.md
Normal file
241
apps/documentations/pdm.CONFIGURATION.md
Normal 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` репозитория).
|
||||||
116
apps/documentations/pdm.env.example
Normal file
116
apps/documentations/pdm.env.example
Normal 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=
|
||||||
918
apps/documentations/pdm.openapi.yaml
Normal file
918
apps/documentations/pdm.openapi.yaml
Normal 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]
|
||||||
30
apps/drawings/.env.example
Normal file
30
apps/drawings/.env.example
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# drawings-api — пример переменных окружения.
|
||||||
|
# Конфигурация читается через github.com/kelseyhightower/envconfig
|
||||||
|
# (config/config.go). Приложение НЕ загружает .env автоматически —
|
||||||
|
# переменные нужно экспортировать в окружение процесса самому,
|
||||||
|
# напр.: set -a && . ./.env && set +a
|
||||||
|
|
||||||
|
# API
|
||||||
|
API_ADDRESS=localhost:6666
|
||||||
|
|
||||||
|
# Postgres
|
||||||
|
POSTGRES_USER=user
|
||||||
|
POSTGRES_PASSWORD=password
|
||||||
|
POSTGRES_DB=drawings
|
||||||
|
POSTGRES_ADDRESS=localhost:6432
|
||||||
|
POSTGRES_POOL_SIZE=10
|
||||||
|
# TLS-подключение к БД. При ENABLE_SSL=true используется сертификат
|
||||||
|
# из YC-PG-CERTIFICATE (PEM-содержимое, не путь к файлу)
|
||||||
|
ENABLE_SSL=false
|
||||||
|
YC-PG-CERTIFICATE=
|
||||||
|
|
||||||
|
# Workflow (интеграция с workflows-api через sdk-go)
|
||||||
|
WORKFLOW_HOST=http://workflows-api-service.proc/
|
||||||
|
CONTAINER_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
|
||||||
|
IMAGE_NAME_EXPORT_TO_DWG=cross-sections-to-dwg
|
||||||
|
IMAGE_TAG=develop
|
||||||
|
TASK_VERSION=1
|
||||||
|
# Внутренний URL самого drawings-api — на него workflow вызывает webhook
|
||||||
|
DRAWING_INTERNAL_URL=http://drawings-api-service.aero/
|
||||||
|
# URL сервиса attachments (передаётся в задачу экспорта)
|
||||||
|
ATTACHMENT_URL=http://attachments-service.documentations.svc.cluster.local:80
|
||||||
120
apps/drawings/CONFIGURATION.md
Normal file
120
apps/drawings/CONFIGURATION.md
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
# Конфигурация проекта drawings-api
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис написан на **Go** (`gitlab.com/sarex-team/rnd/drawings-api`) и настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (`envconfig.Process("", &Config)`).
|
||||||
|
|
||||||
|
Особенности разбора:
|
||||||
|
|
||||||
|
- **префикса нет** — переменные читаются по именам из тега `envconfig:"..."` (напр. `POSTGRES_ADDRESS`, `API_ADDRESS`);
|
||||||
|
- вложенных секций через разделитель нет: `Config` — плоская композиция трёх структур (`Postgres`, `API`, `Workflow`), у каждого поля своё явное имя переменной;
|
||||||
|
- часть полей имеет дефолт через тег `default:"..."` (напр. `CONTAINER_REGISTRY`, `TASK_VERSION`); поля без дефолта при отсутствии переменной получают нулевое значение типа (пустая строка / `0` / `false`), ошибки старта из-за «обязательности» нет;
|
||||||
|
- при ошибке разбора (`envconfig.Process`) приложение завершается с `logger.Fatalf` (`config.MustParse`).
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет. Файл `.env` в репозитории — только шаблон; приложение его **не загружает автоматически** (в коде нет чтения `.env`/dotenv), переменные нужно экспортировать в окружение самому.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (бинарник) | Переменные окружения процесса. `.env` — шаблон, экспортируется вручную, напр. `set -a && . ./.env && set +a`. Сборка — `make drawings-api` / `make migrations` |
|
||||||
|
| Локально (контейнер) | `Dockerfile` (multi-stage, `golang:1.22`) + `entrypoint.sh`. Переменные пробрасываются через `--env`/`--env-file` при запуске контейнера |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) чарта `universal-chart` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`) и выбор окружения по ветке/тегу через `workflow.rules` |
|
||||||
|
|
||||||
|
Порядок запуска в контейнере (`entrypoint.sh`): сначала применяются миграции (`migrations migrate`), затем стартует основной бинарник (`drawings-api`).
|
||||||
|
|
||||||
|
Точки входа (`cmd/`):
|
||||||
|
|
||||||
|
| Команда | Точка входа | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `drawings-api` | `cmd/drawings-api` | HTTP API-сервер (gorilla/mux) |
|
||||||
|
| `migrations migrate` | `cmd/migrations` | Применение миграций БД (`robinjoseph08/go-pg-migrations`) |
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Все переменные ниже читаются кодом приложения (`config/config.go`). В столбце «Значение по умолчанию» указан дефолт из тега `default:"..."`; `—` означает, что дефолта нет (при отсутствии переменной поле получает нулевое значение типа).
|
||||||
|
|
||||||
|
### API (`API`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `API_ADDRESS` | string | — | Адрес прослушивания HTTP-сервера, напр. `0.0.0.0:8080` |
|
||||||
|
|
||||||
|
### Postgres (`Postgres`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POSTGRES_USER` | string | — | Пользователь БД |
|
||||||
|
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||||
|
| `POSTGRES_DB` | string | — | Имя базы данных |
|
||||||
|
| `POSTGRES_ADDRESS` | string | — | Адрес PostgreSQL в формате `host:port` |
|
||||||
|
| `POSTGRES_POOL_SIZE` | int | — | Размер пула соединений (`pg.Options.PoolSize`) |
|
||||||
|
| `ENABLE_SSL` | bool | — | Подключение к БД по TLS. При `true` строится `tls.Config` из `YC-PG-CERTIFICATE` |
|
||||||
|
| `YC-PG-CERTIFICATE` | string | — | PEM-содержимое CA-сертификата PostgreSQL (не путь к файлу). Используется только при `ENABLE_SSL=true` |
|
||||||
|
|
||||||
|
> При `ENABLE_SSL=true` из содержимого `YC-PG-CERTIFICATE` собирается пул корневых сертификатов; `ServerName` берётся из хостовой части `POSTGRES_ADDRESS`, при этом в коде выставлен `InsecureSkipVerify: true`. Имя переменной `YC-PG-CERTIFICATE` содержит дефисы (нестандартно для env), но именно так указано в теге `envconfig`.
|
||||||
|
|
||||||
|
### Workflow (`Workflow`)
|
||||||
|
|
||||||
|
Интеграция с сервисом workflows через `gitlab.com/sarex-team/sdk-go/pkg/workflows`: создание workflow экспорта cross-section в DWG (задача парсинга + задача webhook-уведомления).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `WORKFLOW_HOST` | string | — | Базовый URL сервиса workflows |
|
||||||
|
| `CONTAINER_REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр образов для задач workflow |
|
||||||
|
| `IMAGE_NAME_EXPORT_TO_DWG` | string | — | Имя образа задачи экспорта cross-section в DWG |
|
||||||
|
| `IMAGE_TAG` | string | — | Тег образов задач workflow |
|
||||||
|
| `TASK_VERSION` | string | `1` | Версия задачи экспорта (параметр `version`) |
|
||||||
|
| `DRAWING_INTERNAL_URL` | string | — | Внутренний URL самого drawings-api; на него workflow вызывает webhook `POST {DRAWING_INTERNAL_URL}internal/v1/exports/{export_id}/webhook` |
|
||||||
|
| `ATTACHMENT_URL` | string | — | URL сервиса attachments (передаётся в задачу экспорта как `attachment_url`) |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Деплой выполняется через `universal-chart` (`Chart.yaml`, зависимость `universal-chart`). Сервис `drawings-api` слушает порт `8080`; probes настроены на `/ping` (в чарте выключены). Обычные значения задаются в блоке `envs`, значения из секретов — в `secretEnvs`. Значения различаются по окружениям через ключи `_default` / `stage` / `preprod` / `production`.
|
||||||
|
|
||||||
|
Обычные значения (`envs`) — те же переменные приложения, что описаны выше (`API_ADDRESS`, `ENABLE_SSL`, `WORKFLOW_HOST`, `CONTAINER_REGISTRY`, `IMAGE_NAME_EXPORT_TO_DWG`, `IMAGE_TAG`, `TASK_VERSION`, `DRAWING_INTERNAL_URL`, `ATTACHMENT_URL`), различаются адресами сервисов, тегами образов и флагом `ENABLE_SSL` по окружениям.
|
||||||
|
|
||||||
|
Значения из секретов (`secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Переменная | Секрет (`_default`) | Секрет (`stage`) | Ключ |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POSTGRES_USER` | `ya-pg-secret` | `drawings-postgresql-secret` | `username` |
|
||||||
|
| `POSTGRES_PASSWORD` | `ya-pg-secret` | `drawings-postgresql-secret` | `password` |
|
||||||
|
| `POSTGRES_DB` | `ya-pg-secret` | `drawings-postgresql-secret` | `database` |
|
||||||
|
| `POSTGRES_POOL_SIZE` | `ya-pg-secret` | `drawings-postgresql-secret` | `pool-size` |
|
||||||
|
| `POSTGRES_ADDRESS` | `ya-pg-secret` | `drawings-postgresql-secret` | `address` |
|
||||||
|
| `YC-PG-CERTIFICATE` | `yc-pg-certificate` | `drawings-postgresql-secret` | `ca.crt` |
|
||||||
|
|
||||||
|
## Переменные в 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 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `aero` | `0.0.1-stage` | `universal-chart-stage` |
|
||||||
|
| ветка `master` | `preprod` | `drawings-preprod` | `0.0.1-preprod` | `universal-chart-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `drawings-prod` | `0.0.1-prod` | `universal-chart-production` |
|
||||||
|
| merge request | — | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
|
||||||
|
|
||||||
|
Ключевые переменные пайплайна: `SERVICE_NAME=drawings-api`, `DOCKERFILE_PATH=./Dockerfile`, `RELEASE_NAME=drawings-api`, `CHART_NAME=${SERVICE_NAME}`, `BUILD_ARGS` (`--build-arg CI_COMMIT_SHORT_SHA=…`), `HELM_SET_ARGS` (`--set universal-chart.services.drawings-api.image.name.<env>=…`, `--set universal-chart.global.env=<env>`, а также `commitSha`/`gitlabUri`/`gitlabJobUrl`/`owner`).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **Нет обязательности полей.** В отличие от pydantic-конфигов других сервисов, `envconfig` не помечает поля обязательными — при отсутствии переменной поле молча получает нулевое значение. Например, пустой `POSTGRES_ADDRESS` не вызовет ошибку старта конфига, но приведёт к ошибке при подключении к БД.
|
||||||
|
- **Имя `YC-PG-CERTIFICATE` с дефисами** нестандартно для переменных окружения, но именно так задано в теге `envconfig` и в Helm-секрете. В отличие от других сервисов, здесь это **содержимое** сертификата (PEM), а не путь к файлу.
|
||||||
|
- **TLS к БД с `InsecureSkipVerify: true`.** При `ENABLE_SSL=true` корневой сертификат подхватывается, но проверка имени/цепочки фактически ослаблена флагом `InsecureSkipVerify`.
|
||||||
|
- **Webhook-петля.** `DRAWING_INTERNAL_URL` должен указывать на сам drawings-api внутри кластера — по нему workflow дергает `POST internal/v1/exports/{export_id}/webhook` для перевода экспорта в статус `done`. Неверный URL оставит экспорты в статусе `running`.
|
||||||
|
- **`.env` не загружается автоматически** — переменные нужно экспортировать вручную либо задавать через окружение контейнера.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Минимально необходимо задать:
|
||||||
|
|
||||||
|
- `API_ADDRESS` (напр. `localhost:6666`)
|
||||||
|
- `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_ADDRESS`, `POSTGRES_POOL_SIZE`, `ENABLE_SSL` (`false` локально; тогда `YC-PG-CERTIFICATE` не нужен)
|
||||||
|
- `WORKFLOW_HOST`, `IMAGE_NAME_EXPORT_TO_DWG`, `IMAGE_TAG`, `DRAWING_INTERNAL_URL`, `ATTACHMENT_URL` (для сценариев экспорта; `CONTAINER_REGISTRY` и `TASK_VERSION` имеют дефолты)
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example`.
|
||||||
426
apps/drawings/openapi.yaml
Normal file
426
apps/drawings/openapi.yaml
Normal file
@ -0,0 +1,426 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Drawings API
|
||||||
|
version: "0.0.1"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **drawings-api** (`gitlab.com/sarex-team/rnd/drawings-api`) —
|
||||||
|
управление разрезами (cross-sections) чертежей, их данными и экспортом
|
||||||
|
в DWG через сервис workflows.
|
||||||
|
|
||||||
|
Сервис написан на **Go** (gorilla/mux, go-pg). Роутер собирается в
|
||||||
|
`cmd/drawings-api/bootstrap.go`. Помимо служебных эндпоинтов
|
||||||
|
(`/ping`, `/metrics`) есть две группы бизнес-маршрутов с одинаковым
|
||||||
|
набором операций:
|
||||||
|
|
||||||
|
- `/api/v1/*` — публичный роутинг;
|
||||||
|
- `/internal/v1/*` — внутренний роутинг (набор тот же плюс webhook
|
||||||
|
экспорта, вызываемый воркером workflow).
|
||||||
|
|
||||||
|
На все бизнес-маршруты навешены middleware: JSON-ответ (`rest.JSONResponse`),
|
||||||
|
request-id (`reqid.Middleware`) и логирование. Явной аутентификации в коде
|
||||||
|
сервиса нет — доступ ограничивается на уровне ingress/сети кластера.
|
||||||
|
|
||||||
|
### Экспорт в DWG
|
||||||
|
`POST /exports` создаёт запись экспорта и запускает workflow из двух задач
|
||||||
|
(парсинг cross-section в DWG + webhook-уведомление). По завершении workflow
|
||||||
|
вызывает `POST /internal/v1/exports/{export_id}/webhook`, который переводит
|
||||||
|
экспорт в статус `done`.
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: /api/v1
|
||||||
|
description: Публичный префикс
|
||||||
|
- url: /internal/v1
|
||||||
|
description: Внутренний префикс
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: service
|
||||||
|
description: Служебные эндпоинты
|
||||||
|
- name: cross-sections
|
||||||
|
description: Разрезы чертежей
|
||||||
|
- name: exports
|
||||||
|
description: Экспорт разрезов в DWG
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/ping:
|
||||||
|
get:
|
||||||
|
tags: [service]
|
||||||
|
summary: Healthcheck
|
||||||
|
description: Возвращает статус готовности. Доступен в корне (без префикса).
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Сервис готов
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: ready
|
||||||
|
|
||||||
|
/metrics:
|
||||||
|
get:
|
||||||
|
tags: [service]
|
||||||
|
summary: Prometheus-метрики
|
||||||
|
description: Метрики в формате Prometheus. Доступен в корне (без префикса).
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Метрики
|
||||||
|
content:
|
||||||
|
text/plain:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
# ----- Публичные маршруты (/api/v1) и внутренние (/internal/v1) идентичны,
|
||||||
|
# кроме webhook, который есть только на /internal/v1. Пути ниже указаны
|
||||||
|
# относительно префикса из блока servers. -----
|
||||||
|
|
||||||
|
/cross-sections:
|
||||||
|
post:
|
||||||
|
tags: [cross-sections]
|
||||||
|
summary: Создать разрез
|
||||||
|
description: Создаёт cross-section вместе с его данными (`data.raw_data`).
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CreateCrossSectionRequest"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Созданный разрез
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CrossSection"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
get:
|
||||||
|
tags: [cross-sections]
|
||||||
|
summary: Список разрезов по instance_id
|
||||||
|
parameters:
|
||||||
|
- name: instance_id
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: UUID инстанса (чертежа)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Массив разрезов (пустой, если ничего не найдено)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/CrossSection"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
/cross-sections/{cs_id}:
|
||||||
|
delete:
|
||||||
|
tags: [cross-sections]
|
||||||
|
summary: Удалить разрез (soft-delete)
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/CrossSectionId"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: "#/components/responses/OK"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"404":
|
||||||
|
description: Разрез не найден (возможно, уже удалён)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
/cross-sections/{cs_id}/data:
|
||||||
|
get:
|
||||||
|
tags: [cross-sections]
|
||||||
|
summary: Данные разреза
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/CrossSectionId"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Данные разреза
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Data"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
/exports:
|
||||||
|
post:
|
||||||
|
tags: [exports]
|
||||||
|
summary: Создать экспорт разреза в DWG
|
||||||
|
description: |
|
||||||
|
Создаёт запись экспорта и запускает workflow экспорта в DWG.
|
||||||
|
В ответе `workflow_status` = `running`.
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/CreateExportRequest"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Созданный экспорт
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Export"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
get:
|
||||||
|
tags: [exports]
|
||||||
|
summary: Список экспортов по фильтрам
|
||||||
|
description: |
|
||||||
|
Хотя бы один из фильтров должен быть задан, иначе `400`.
|
||||||
|
Каждый параметр — список UUID/чисел через запятую.
|
||||||
|
parameters:
|
||||||
|
- name: cross_section_ids
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: UUID разрезов через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: export_ids
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: UUID экспортов через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: workflow_ids
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: UUID workflow через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: attachment_ids
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
description: ID вложений (целые) через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Массив экспортов (пустой, если ничего не найдено)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Export"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
/exports/{export_id}:
|
||||||
|
delete:
|
||||||
|
tags: [exports]
|
||||||
|
summary: Удалить экспорт
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/ExportId"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: "#/components/responses/OK"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
/exports/{export_id}/webhook:
|
||||||
|
post:
|
||||||
|
tags: [exports]
|
||||||
|
summary: Webhook завершения экспорта (только /internal/v1)
|
||||||
|
description: |
|
||||||
|
Вызывается воркером workflow по завершении экспорта. Переводит
|
||||||
|
экспорт в статус `done`. Доступен только по внутреннему префиксу
|
||||||
|
`/internal/v1`.
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/components/parameters/ExportId"
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
$ref: "#/components/responses/OK"
|
||||||
|
"400":
|
||||||
|
$ref: "#/components/responses/BadRequest"
|
||||||
|
"500":
|
||||||
|
$ref: "#/components/responses/StorageError"
|
||||||
|
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
CrossSectionId:
|
||||||
|
name: cs_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: UUID разреза
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
ExportId:
|
||||||
|
name: export_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: UUID экспорта
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
|
||||||
|
responses:
|
||||||
|
OK:
|
||||||
|
description: Успешно (тело — строка `"OK"`)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: OK
|
||||||
|
BadRequest:
|
||||||
|
description: Некорректный запрос
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
StorageError:
|
||||||
|
description: Внутренняя ошибка (ошибка хранилища)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/Error"
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
CrossSection:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deleted_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
instance_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
documents:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: "#/components/schemas/ConnectedDocument"
|
||||||
|
exports:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/Export"
|
||||||
|
|
||||||
|
ConnectedDocument:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
color:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Data:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cross_section_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
raw_data:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
CreateCrossSectionRequest:
|
||||||
|
type: object
|
||||||
|
description: Разрез плюс его данные. Наследует поля CrossSection.
|
||||||
|
allOf:
|
||||||
|
- $ref: "#/components/schemas/CrossSection"
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
$ref: "#/components/schemas/Data"
|
||||||
|
|
||||||
|
Author:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Export:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
author:
|
||||||
|
$ref: "#/components/schemas/Author"
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
deleted_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
nullable: true
|
||||||
|
cross_section_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
workflow_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
nullable: true
|
||||||
|
workflow_status:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
enum: [done, running, error]
|
||||||
|
file_type:
|
||||||
|
type: string
|
||||||
|
enum: [dwg]
|
||||||
|
attachment_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
|
||||||
|
CreateExportRequest:
|
||||||
|
type: object
|
||||||
|
required: [cross_section_id, author, file_type, company_id]
|
||||||
|
properties:
|
||||||
|
cross_section_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
author:
|
||||||
|
$ref: "#/components/schemas/Author"
|
||||||
|
file_type:
|
||||||
|
type: string
|
||||||
|
enum: [dwg]
|
||||||
|
company_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
description: Ответ об ошибке (gotools/httperror).
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
54
apps/eav/.env.example
Normal file
54
apps/eav/.env.example
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
# Django
|
||||||
|
DJANGO_SETTINGS_MODULE=config.settings.production
|
||||||
|
DJANGO_DEBUG=False
|
||||||
|
DJANGO_SECRET_KEY='v628rpgi^!!57jq9y7y3^by04c1bc@#6%0_a(ekxfmyat8gxew'
|
||||||
|
|
||||||
|
# App
|
||||||
|
SERVICE_NAME=eav
|
||||||
|
VERSION=1.0.0
|
||||||
|
|
||||||
|
# Database (PostgreSQL) — читается только в config.settings.production
|
||||||
|
DJANGO_POSTGRES_HOST=127.0.0.1
|
||||||
|
DJANGO_POSTGRES_PORT=6432
|
||||||
|
DJANGO_POSTGRES_DATABASE=eav_db
|
||||||
|
DJANGO_POSTGRES_USER=sarex
|
||||||
|
DJANGO_POSTGRES_PASSWORD=password
|
||||||
|
|
||||||
|
# Auth / JWT (RS512) — обязательны в config.settings.production
|
||||||
|
SIMPLE_JWT_ISSUER=django
|
||||||
|
# Replace newlines with \n
|
||||||
|
JWT_PRIVATE_KEY=''
|
||||||
|
JWT_PUBLIC_KEY=''
|
||||||
|
|
||||||
|
# S3 (Yandex Object Storage, бото3)
|
||||||
|
YC_S3_ACCESS_KEY_ID=
|
||||||
|
YC_S3_SECRET_ACCESS_KEY=
|
||||||
|
YC_S3_BUCKET_NAME=eav
|
||||||
|
YC_S3_ENDPOINT_URL=https://storage.yandexcloud.net
|
||||||
|
|
||||||
|
# Kafka
|
||||||
|
KAFKA_ENABLED=True
|
||||||
|
KAFKA_HOST=
|
||||||
|
KAFKA_USERNAME=platform
|
||||||
|
KAFKA_PASSWORD=
|
||||||
|
KAFKA_SSL_CAFILE=/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
|
||||||
|
SASL_MECHANISM=SCRAM-SHA-512
|
||||||
|
SECURITY_PROTOCOL=SASL_SSL
|
||||||
|
ASSETS_TOPIC=assets_broadcast_test
|
||||||
|
|
||||||
|
# Kafka topics (события EAV)
|
||||||
|
KAFKA_TOPIC_ATTRIBUTE_CREATED=eav.attribute.created.v1
|
||||||
|
KAFKA_TOPIC_ATTRIBUTE_UPDATED=eav.attribute.updated.v1
|
||||||
|
KAFKA_TOPIC_ATTRIBUTE_DELETED=eav.attribute.deleted.v1
|
||||||
|
KAFKA_TOPIC_VALUE_OPTION_CREATED=eav.value_option.created.v1
|
||||||
|
KAFKA_TOPIC_VALUE_OPTION_DELETED=eav.value_option.deleted.v1
|
||||||
|
|
||||||
|
# OpenTelemetry (трейсинг включается только если USE_OTEL задана)
|
||||||
|
USE_OTEL=False
|
||||||
|
SERVICE_NAME=eav.eav-backend
|
||||||
|
TRACER_ENDPOINT=localhost:4375
|
||||||
|
USE_INSECURE=False
|
||||||
|
ENVIRONMENT=prod
|
||||||
|
MODULE=eav
|
||||||
|
TEAM=platform_team
|
||||||
|
COMPONENT=backend
|
||||||
186
apps/eav/CONFIGURATION.md
Normal file
186
apps/eav/CONFIGURATION.md
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
# Конфигурация проекта eav-python
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис — это Django-приложение (**Django 4.1 + Django REST Framework**), запускаемое как WSGI (`config.wsgi`) через **uWSGI** (порт `8000`, см. `compose/eav-backend/uwsgi.ini`). Настройки читаются из переменных окружения в `src/config/settings/base.py` и `src/config/settings/production.py`. Разбор выполняется частично через библиотеку [`django-environ`](https://django-environ.readthedocs.io/) (объект `env = environ.Env()`), частично напрямую через `os.getenv`.
|
||||||
|
|
||||||
|
Особенности разбора:
|
||||||
|
|
||||||
|
- **префикса/делимитера у секций нет** — каждая настройка задаётся плоской переменной окружения (напр. `DJANGO_POSTGRES_HOST`, `KAFKA_HOST`, `YC_S3_BUCKET_NAME`);
|
||||||
|
- **`.env` не загружается автоматически** — в коде нет вызова `environ.Env.read_env()` / `load_dotenv`, хотя `python-dotenv` присутствует в зависимостях. Переменные нужно экспортировать в окружение самому (напр. `set -a && . ./.env && set +a`) либо задавать через `--env`/манифесты k8s. Файл `.env` при этом в `.gitignore`;
|
||||||
|
- **выбор набора настроек** задаётся `DJANGO_SETTINGS_MODULE`: `config.settings.production` (боевой набор с БД, CORS, JWT), `config.settings.test` (только `base`), либо `config.settings.local` (по умолчанию в `manage.py`, в репозитории отсутствует, `.gitignore`);
|
||||||
|
- **часть переменных читается только в `production.py`** — БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`); в `base.py`/`test.py` их нет.
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (manage.py / uWSGI) | Переменные окружения процесса (`.env` нужно экспортировать вручную) |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yml`: блок `environment` сервиса `backend` + образ `postgres` (timescaledb-postgis) |
|
||||||
|
| Kubernetes (Helm, репозиторий приложения) | `.helm/values-<env>.yaml`: блоки `backend.deployment.envs` (обычные значения) и `backend.deployment.secrets` (из k8s-секретов через `secretKeyRef`); шаблон `templates/server.yaml`, роутинг — `templates/mesh-config.yaml` (Istio VirtualService) |
|
||||||
|
| Kubernetes (infra, Flux/Kustomize) | `infra/iac/apps/eav/base/backend-deployment.yaml`: секреты инъектируются Vault-агентом (`vault.hashicorp.com/agent-inject-*`) и экспортируются в окружение в `args`; настройки `production.py` монтируются из `django-configmap` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci`, переменные пайплайна в `workflow.rules` |
|
||||||
|
|
||||||
|
Способы запуска процессов:
|
||||||
|
|
||||||
|
| Процесс | Точка входа | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| HTTP API | `compose/eav-backend/entrypoint.sh` → `uwsgi --ini uwsgi.ini` (`config.wsgi`, порт 8000) | REST API |
|
||||||
|
| Миграции | `entrypoint.sh` → `python3 manage.py migrate` (выполняется перед стартом uWSGI) | Миграции БД |
|
||||||
|
| Kafka-продюсер | `config/kafka.py` (инициализируется при импорте, если `KAFKA_ENABLED`) | Публикация событий EAV в топики |
|
||||||
|
|
||||||
|
Порядок запуска в контейнере (`entrypoint.sh`): сначала `manage.py migrate`, затем `uwsgi` (оба под `opentelemetry-instrument`).
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Дефолт `—` означает, что значения по умолчанию в коде нет.
|
||||||
|
|
||||||
|
### Django / приложение
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DJANGO_SETTINGS_MODULE` | string | `config.settings.local` (в `manage.py`); в контейнере — `config.settings.production` | Какой набор настроек Django загружать |
|
||||||
|
| `DJANGO_DEBUG` | bool | `False` | Режим отладки Django (в `production.py` жёстко `False`) |
|
||||||
|
| `DJANGO_SECRET_KEY` | string | (захардкоженный дефолт) | Секретный ключ Django. В проде обязателен свой |
|
||||||
|
| `SERVICE_NAME` | string | `eav` | Имя сервиса (в `base.py`); в OTEL-секции дефолт `eav.eav-backend` |
|
||||||
|
| `VERSION` | string | `1.0.0` | Версия приложения |
|
||||||
|
|
||||||
|
### Database — PostgreSQL (только `config.settings.production`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DJANGO_POSTGRES_HOST` | string | — | Хост PostgreSQL |
|
||||||
|
| `DJANGO_POSTGRES_PORT` | int | `6432` | Порт PostgreSQL (в infra-манифесте — `5432`) |
|
||||||
|
| `DJANGO_POSTGRES_DATABASE` | string | — | Имя базы данных |
|
||||||
|
| `DJANGO_POSTGRES_USER` | string | — | Пользователь БД |
|
||||||
|
| `DJANGO_POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||||
|
|
||||||
|
> Engine — `django.db.backends.postgresql`. В `docker-compose.yml` поднимается `timescale/timescaledb-postgis` (проекту нужны расширения PostGIS/ltree).
|
||||||
|
|
||||||
|
### Auth / JWT (только `config.settings.production`)
|
||||||
|
|
||||||
|
Используются два механизма аутентификации (`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`): `ZitadelJWTAuthentication` (заголовок `Identity`) и `rest_framework_simplejwt` (RS512).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `SIMPLE_JWT_ISSUER` | string | `django` | Значение claim `iss` (проверяется при верификации токена) |
|
||||||
|
| `JWT_PRIVATE_KEY` | string (PEM) | — | Приватный RSA-ключ (подпись). Экранированные `\n` заменяются на переводы строк. Обязателен |
|
||||||
|
| `JWT_PUBLIC_KEY` | string (PEM) | — | Публичный RSA-ключ (проверка). Экранированные `\n` заменяются на переводы строк. Обязателен |
|
||||||
|
|
||||||
|
> `SIMPLE_JWT`: алгоритм `RS512`, `ACCESS_TOKEN_LIFETIME` 5 мин, `REFRESH_TOKEN_LIFETIME` 1 день, тип заголовка `Bearer`, claim пользователя — `user_id`.
|
||||||
|
|
||||||
|
### S3 — Yandex Object Storage (`base.py`, boto3/django-storages)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `YC_S3_ACCESS_KEY_ID` | string | `None` | Access key |
|
||||||
|
| `YC_S3_SECRET_ACCESS_KEY` | string | `None` | Secret key |
|
||||||
|
| `YC_S3_BUCKET_NAME` | string | `None` | Бакет по умолчанию |
|
||||||
|
| `YC_S3_ENDPOINT_URL` | string | `None` | Эндпоинт S3 |
|
||||||
|
|
||||||
|
> `DEFAULT_FILE_STORAGE`/`STATICFILES_STORAGE` — `storages.backends.s3boto3.S3Boto3Storage`, `AWS_DEFAULT_ACL=public-read`.
|
||||||
|
|
||||||
|
### Kafka (`base.py`, `config/kafka.py`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `KAFKA_ENABLED` | bool | `True` | Включить реального продюсера (иначе `MockProducer` — события не отправляются) |
|
||||||
|
| `KAFKA_HOST` | string | `""` | Адрес брокера (`bootstrap_servers`) |
|
||||||
|
| `KAFKA_USERNAME` | string | `platform` | Пользователь SASL |
|
||||||
|
| `KAFKA_PASSWORD` | string | `""` | Пароль SASL |
|
||||||
|
| `KAFKA_SSL_CAFILE` | string | `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt` | CA-сертификат для TLS |
|
||||||
|
| `SASL_MECHANISM` | string | `SCRAM-SHA-512` | Механизм SASL |
|
||||||
|
| `SECURITY_PROTOCOL` | string | `SASL_SSL` | Протокол безопасности Kafka |
|
||||||
|
| `ASSETS_TOPIC` | string | `assets_broadcast_test` | Топик рассылки по ассетам |
|
||||||
|
|
||||||
|
Топики событий EAV:
|
||||||
|
|
||||||
|
| Переменная | Значение по умолчанию |
|
||||||
|
| --- | --- |
|
||||||
|
| `KAFKA_TOPIC_ATTRIBUTE_CREATED` | `eav.attribute.created.v1` |
|
||||||
|
| `KAFKA_TOPIC_ATTRIBUTE_UPDATED` | `eav.attribute.updated.v1` |
|
||||||
|
| `KAFKA_TOPIC_ATTRIBUTE_DELETED` | `eav.attribute.deleted.v1` |
|
||||||
|
| `KAFKA_TOPIC_VALUE_OPTION_CREATED` | `eav.value_option.created.v1` |
|
||||||
|
| `KAFKA_TOPIC_VALUE_OPTION_DELETED` | `eav.value_option.deleted.v1` |
|
||||||
|
|
||||||
|
### OpenTelemetry (`base.py`)
|
||||||
|
|
||||||
|
Блок трейсинга активируется, только если задана переменная `USE_OTEL` (проверяется через `os.getenv('USE_OTEL', False)` — истинно при любом непустом значении). Используется `django-otel-tools`; при включении в начало `MIDDLEWARE` добавляется `OtelMiddleware`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `USE_OTEL` | bool/string | `False` | Включить трейсинг и OTEL-логгер |
|
||||||
|
| `SERVICE_NAME` | string | `eav.eav-backend` | Имя сервиса в трейсах |
|
||||||
|
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
|
||||||
|
| `USE_INSECURE` | bool/string | `False` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
| `ENVIRONMENT` | string | `prod` | Атрибут ресурса `environment` |
|
||||||
|
| `MODULE` | string | `eav` | Атрибут ресурса `module` |
|
||||||
|
| `TEAM` | string | `platform_team` | Атрибут ресурса `team` |
|
||||||
|
| `COMPONENT` | string | `backend` | Атрибут ресурса `component` |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)
|
||||||
|
|
||||||
|
Обычные значения задаются в блоке `backend.deployment.envs` для каждого окружения (`stage`/`preprod`/`production`): `DJANGO_SETTINGS_MODULE`, `USE_OTEL`, `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE`, `ENVIRONMENT`, `KAFKA_HOST`, `ASSETS_TOPIC` (различаются адресами коллектора/брокера и именами топиков).
|
||||||
|
|
||||||
|
Значения из секретов (блок `secrets`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DJANGO_POSTGRES_HOST` | `yc-pg-secret` | `host` |
|
||||||
|
| `DJANGO_POSTGRES_DATABASE` | `yc-pg-secret` | `database` |
|
||||||
|
| `DJANGO_POSTGRES_PORT` | `yc-pg-secret` | `port` (только preprod) |
|
||||||
|
| `DJANGO_POSTGRES_USER` | `yc-pg-secret` | `user` |
|
||||||
|
| `DJANGO_POSTGRES_PASSWORD` | `yc-pg-secret` | `password` |
|
||||||
|
| `DJANGO_CLICKHOUSE_HOST` | `yc-ch-secret` | `host` |
|
||||||
|
| `DJANGO_CLICKHOUSE_DATABASE` | `yc-ch-secret` | `database` |
|
||||||
|
| `DJANGO_CLICKHOUSE_USER` | `yc-ch-secret` | `user` |
|
||||||
|
| `DJANGO_CLICKHOUSE_PASSWORD` | `yc-ch-secret` | `password` |
|
||||||
|
| `YC_S3_ACCESS_KEY_ID` | `yc-s3-secret` | `key_id` |
|
||||||
|
| `YC_S3_SECRET_ACCESS_KEY` | `yc-s3-secret` | `access_key` |
|
||||||
|
| `YC_S3_BUCKET_NAME` | `yc-s3-secret` | `storage_bucket_name` |
|
||||||
|
| `YC_S3_ENDPOINT_URL` | `yc-s3-secret` | `endpoint_url` |
|
||||||
|
| `JWT_PRIVATE_KEY` | `jwt-secret` | `private_key` |
|
||||||
|
| `JWT_PUBLIC_KEY` | `jwt-secret` | `public_key` |
|
||||||
|
| `KAFKA_USERNAME` | `kafka-secret` / `yc-kafka-secret` | `username` |
|
||||||
|
| `KAFKA_PASSWORD` | `kafka-secret` / `yc-kafka-secret` | `password` |
|
||||||
|
| `KAFKA_HOST` | `yc-kafka-secret` | `host` (prod/stage) |
|
||||||
|
|
||||||
|
Помимо env, чарт монтирует CA-сертификаты: PostgreSQL (`yc-pg-certificate` → `~/.postgresql/root.crt`) и Yandex Internal Root CA (`yc-ch-certificate` → `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt`, тот же путь, что в `KAFKA_SSL_CAFILE`), а также конфиг clickhouse-client.
|
||||||
|
|
||||||
|
## Переменные в infra-манифесте (Flux/Kustomize, `infra/iac/apps/eav`)
|
||||||
|
|
||||||
|
В отличие от Helm-чарта приложения, боевой деплой Sarex использует Vault-инъекцию (`base/backend-deployment.yaml`). Секреты рендерятся Vault-агентом в файлы `/vault/secrets/*` и экспортируются в окружение в `args` контейнера перед запуском `entrypoint.sh`:
|
||||||
|
|
||||||
|
| Переменная(ые) | Источник (Vault path) |
|
||||||
|
| --- | --- |
|
||||||
|
| `DJANGO_POSTGRES_HOST/PORT/DATABASE/USER/PASSWORD` | `secrets/data/postgresql/apps/eav` |
|
||||||
|
| `YC_S3_ENDPOINT_URL/BUCKET_NAME/ACCESS_KEY_ID/SECRET_ACCESS_KEY` | `secrets/data/minio/apps/eav` |
|
||||||
|
| `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` | `secrets/data/vault/common/rsa_keys` |
|
||||||
|
|
||||||
|
Прямо в `env` деплоймента задаются `KAFKA_ENABLED=False`, `ASSETS_TOPIC=sarex`, `DJANGO_SETTINGS_MODULE=config.settings.production`. Файл `production.py` монтируется из `django-configmap` (переопределяет `production.py` из образа; в нём `DEBUG=True`, `ALLOWED_HOSTS=['*']`, свои CORS/CSRF-домены и имена cookie `eav-sessionid`/`eav-csrftoken`).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- Приложение **не загружает `.env` автоматически** (нет `read_env`/`load_dotenv`). `python-dotenv` установлен, но не используется в настройках — переменные нужно экспортировать в окружение самому.
|
||||||
|
- Переменные БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`) читаются **только** в `config.settings.production`. При `test`/`base` их отсутствие не мешает старту, но БД по умолчанию не сконфигурирована.
|
||||||
|
- `JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY` в `production.py` читаются через `env.str(...)` **без дефолта** — их отсутствие приводит к ошибке старта. В infra-варианте (`django-configmap`) используется `get_env_variable` с тем же требованием.
|
||||||
|
- `DJANGO_CLICKHOUSE_*` присутствуют в Helm-секретах, но **кодом приложения не читаются** (в текущих настройках ClickHouse не используется) — это подготовка/наследие инфраструктуры.
|
||||||
|
- `KAFKA_ENABLED`: при ложном значении используется `MockProducer` — события EAV в Kafka не публикуются (так сделано в infra-деплое: `KAFKA_ENABLED=False`). Значение разбирается `django-environ` как bool.
|
||||||
|
- Флаги OTEL (`USE_OTEL`, `USE_INSECURE`) читаются через `os.getenv(..., False)` и трактуются как истинные при **любой непустой строке**, включая `"False"`. Чтобы отключить — переменную нужно не задавать вовсе.
|
||||||
|
- `SERVICE_NAME` определяется дважды: как имя приложения (`base.py`, дефолт `eav`) и как имя сервиса в OTEL (дефолт `eav.eav-backend`) — фактически одна и та же переменная окружения.
|
||||||
|
- В `docker-compose.yml` захардкожен пароль БД (`zealot096`) — только для локального окружения.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска (`config.settings.production`)
|
||||||
|
|
||||||
|
- `DJANGO_SETTINGS_MODULE=config.settings.production`
|
||||||
|
- `DJANGO_POSTGRES_HOST`, `DJANGO_POSTGRES_PORT`, `DJANGO_POSTGRES_DATABASE`, `DJANGO_POSTGRES_USER`, `DJANGO_POSTGRES_PASSWORD`
|
||||||
|
- `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY` (обязательны; можно тестовую RSA-пару)
|
||||||
|
- `KAFKA_ENABLED=False` (чтобы не поднимать брокер) либо `KAFKA_HOST`/`KAFKA_USERNAME`/`KAFKA_PASSWORD`
|
||||||
|
- при работе с файлами: `YC_S3_ACCESS_KEY_ID`, `YC_S3_SECRET_ACCESS_KEY`, `YC_S3_BUCKET_NAME`, `YC_S3_ENDPOINT_URL`
|
||||||
|
- `USE_OTEL` — не задавать (иначе включится трейсинг)
|
||||||
|
|
||||||
|
Готовые значения-примеры для всех переменных приведены в `.env.example`.
|
||||||
1303
apps/eav/openapi.yaml
Normal file
1303
apps/eav/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
187
apps/flows/.env.example
Normal file
187
apps/flows/.env.example
Normal 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
293
apps/flows/CONFIGURATION.md
Normal 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
142
apps/flows/ENDPOINTS.md
Normal 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
1736
apps/flows/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
75
apps/iam/.env.example
Normal file
75
apps/iam/.env.example
Normal 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
174
apps/iam/CONFIGURATION.md
Normal 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
207
apps/iam/ENDPOINTS.md
Normal 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
1170
apps/iam/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
65
apps/inspections/.env.example
Normal file
65
apps/inspections/.env.example
Normal 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
|
||||||
216
apps/inspections/CONFIGURATION.md
Normal file
216
apps/inspections/CONFIGURATION.md
Normal 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` локально
|
||||||
115
apps/inspections/ENDPOINTS.md
Normal file
115
apps/inspections/ENDPOINTS.md
Normal 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(...)`.
|
||||||
1243
apps/inspections/openapi.yaml
Normal file
1243
apps/inspections/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
101
apps/issues/.env.example
Normal file
101
apps/issues/.env.example
Normal 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=
|
||||||
245
apps/issues/CONFIGURATION.md
Normal file
245
apps/issues/CONFIGURATION.md
Normal 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
138
apps/issues/ENDPOINTS.md
Normal 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
3278
apps/issues/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
43
apps/mapper/.env.example
Normal file
43
apps/mapper/.env.example
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Mapper (flows mapper) — пример переменных окружения
|
||||||
|
# Все переменные читаются классами pydantic-settings в src/app/config.py.
|
||||||
|
# У каждого класса свой env_prefix; вложенного делимитера ("__") НЕТ.
|
||||||
|
# Значения ниже — дефолты из кода (stage-хосты). Приложение НЕ загружает .env
|
||||||
|
# автоматически (env_file не задан) — переменные нужно экспортировать в окружение.
|
||||||
|
|
||||||
|
# App (класс Settings, без префикса)
|
||||||
|
API_PREFIX=/api/v1
|
||||||
|
|
||||||
|
# Logger (префикс LOG_)
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_FORMAT='[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s'
|
||||||
|
|
||||||
|
# Redis (префикс REDIS_) — кеш ответов внешних сервисов
|
||||||
|
REDIS_USE=True
|
||||||
|
REDIS_HOST=localhost
|
||||||
|
REDIS_PORT=6379
|
||||||
|
REDIS_DB=0
|
||||||
|
REDIS_EXPIRE_DAYS=1
|
||||||
|
|
||||||
|
# Documentation service (префикс DOCUMENTATION_)
|
||||||
|
DOCUMENTATION_HOST=https://stage-api.sarex.io/documentations/api/v1
|
||||||
|
DOCUMENTATION_TIMEOUT=30
|
||||||
|
DOCUMENTATION_RETRIES=3
|
||||||
|
|
||||||
|
# Flow service (префикс FLOW_)
|
||||||
|
FLOW_HOST=https://stage-api.sarex.io/flows/api/v1
|
||||||
|
FLOW_TIMEOUT=30
|
||||||
|
FLOW_RETRIES=3
|
||||||
|
|
||||||
|
# Django / sarex-backend (префикс DJANGO_)
|
||||||
|
DJANGO_HOST=https://stage.sarex.io/api
|
||||||
|
DJANGO_TIMEOUT=30
|
||||||
|
DJANGO_RETRIES=3
|
||||||
|
|
||||||
|
# Note service (префикс NOTE_)
|
||||||
|
NOTE_HOST=https://stage-api.sarex.io/notes/api/v1
|
||||||
|
NOTE_TIMEOUT=30
|
||||||
|
NOTE_RETRIES=3
|
||||||
|
|
||||||
|
# ВНИМАНИЕ: переменная TIMEOUT (без префикса), которая задаётся в Helm/Kustomize
|
||||||
|
# как "120", НИ ОДНИМ классом настроек не читается. Реальный таймаут HTTP-клиентов
|
||||||
|
# берётся из <PREFIX>_TIMEOUT (по умолчанию 30). См. CONFIGURATION.md.
|
||||||
193
apps/mapper/CONFIGURATION.md
Normal file
193
apps/mapper/CONFIGURATION.md
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
# Конфигурация проекта mapper (flows mapper)
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса `mapper` — mini-HTTP-сервиса, который объединяет (мапит) данные сервисов документации (`documentations`) и процессов (`flows`), а также заметок (`notes`) и Django-бэкенда Sarex.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/app/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/).
|
||||||
|
|
||||||
|
В отличие от многих сервисов, здесь **нет единого корневого префикса и нет вложенного делимитера** (`env_nested_delimiter`). Вместо этого каждая секция описана отдельным классом `BaseSettings` со своим `env_prefix` (задаётся во вложенном классе `Config`):
|
||||||
|
|
||||||
|
| Класс | `env_prefix` | Секция |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `Settings` | — (без префикса) | Корневые настройки (`API_PREFIX`) |
|
||||||
|
| `LoggerSettings` | `LOG_` | Логирование |
|
||||||
|
| `RedisSettings` | `REDIS_` | Кеш Redis |
|
||||||
|
| `DocumentationSettings` | `DOCUMENTATION_` | HTTP-клиент сервиса документации |
|
||||||
|
| `FlowSettings` | `FLOW_` | HTTP-клиент сервиса процессов |
|
||||||
|
| `DjangoSettings` | `DJANGO_` | HTTP-клиент Django-бэкенда |
|
||||||
|
| `NoteSettings` | `NOTE_` | HTTP-клиент сервиса заметок |
|
||||||
|
|
||||||
|
`DocumentationSettings`, `FlowSettings`, `DjangoSettings` и `NoteSettings` наследуются от общего класса `AsyncSessionManager` (поля `host`, `timeout`, `retries`), поэтому у каждого из них одинаковый набор из трёх переменных: `<PREFIX>_HOST`, `<PREFIX>_TIMEOUT`, `<PREFIX>_RETRIES`.
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет. Приложение **не загружает `.env` автоматически** (в `config.py` не задан `env_file`, зависимости `python-dotenv` нет) — переменные нужно экспортировать в окружение самому, напр. `set -a && . ./.env && set +a`, либо пробрасывать через контейнер/оркестратор.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (uvicorn/gunicorn) | Переменные окружения процесса. Redis поднимается через `docker-compose.yaml` (только сервис `redis`) |
|
||||||
|
| Контейнер | `Dockerfile` → `entrypoint.sh`: `gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app.main:app` |
|
||||||
|
| Kubernetes (Helm, из репозитория сервиса) | `.helm/values.yaml`, блок `universal-chart.services.backend.envs`; деплой из `.gitlab-ci.yml` |
|
||||||
|
| Kubernetes (GitOps, инфра-репозиторий) | `iac/apps/mapper/*`: Kustomize-база `base/deployment.yaml` (env + секреты Vault) и Flux `HelmRelease` в overlay'ах `brusnika-*` |
|
||||||
|
|
||||||
|
Точки входа:
|
||||||
|
|
||||||
|
| Команда | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `uvicorn main:app` / `python app/main.py` | Локальный запуск (в `main.py` порт `8002`, host `0.0.0.0`) |
|
||||||
|
| `gunicorn -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 app.main:app` | Прод-запуск (`entrypoint.sh`, порт `8000`) |
|
||||||
|
|
||||||
|
Стек: Python 3.10 (`python:3.10-slim-buster`), FastAPI, httpx (асинхронные клиенты), Redis (кеш), PyJWT (разбор токенов).
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Дефолт `—` означает, что значение обязательно (иначе ошибка старта). Все дефолты ниже соответствуют коду `config.py`.
|
||||||
|
|
||||||
|
### App (класс `Settings`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `API_PREFIX` | string | `/api/v1` | Префикс маршрутов API |
|
||||||
|
|
||||||
|
### Logger (`LOG_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | `INFO` | Уровень логирования (имя уровня `logging`; при неизвестном значении используется `INFO`) |
|
||||||
|
| `LOG_FORMAT` | string | `[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s` | Формат строки лога |
|
||||||
|
|
||||||
|
### Redis (`REDIS_*`)
|
||||||
|
|
||||||
|
Кеширует JSON-ответы внешних сервисов (по ключу `"{user_id}_{url}"`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `REDIS_USE` | bool | `True` | Включить кеш. При `True` на старте выполняется `PING` (падение при недоступном Redis) |
|
||||||
|
| `REDIS_HOST` | string | `localhost` | Хост Redis |
|
||||||
|
| `REDIS_PORT` | int | `6379` | Порт Redis |
|
||||||
|
| `REDIS_DB` | int | `0` | Номер базы Redis |
|
||||||
|
| `REDIS_EXPIRE_DAYS` | int | `1` | TTL записей кеша в днях (в секундах — `expire_days * 24 * 3600`) |
|
||||||
|
|
||||||
|
### HTTP-клиенты внешних сервисов
|
||||||
|
|
||||||
|
Все четыре клиента наследуют `AsyncSessionManager` (`host`, `timeout`, `retries`). Клиент httpx создаётся с `verify=False` (проверка TLS-сертификата отключена) и транспортом с числом ретраев `retries`. Токены пробрасываются заголовками `Authorization` (всегда) и `Identity` (в режиме Zitadel).
|
||||||
|
|
||||||
|
| Секция / префикс | Назначение | Дефолт `HOST` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DOCUMENTATION_*` | Сервис документации (диски, документы, бандлы) | `https://stage-api.sarex.io/documentations/api/v1` |
|
||||||
|
| `FLOW_*` | Сервис процессов (flows, review-данные) | `https://stage-api.sarex.io/flows/api/v1` |
|
||||||
|
| `DJANGO_*` | Django-бэкенд Sarex (target-links) | `https://stage.sarex.io/api` |
|
||||||
|
| `NOTE_*` | Сервис заметок (notes) | `https://stage-api.sarex.io/notes/api/v1` |
|
||||||
|
|
||||||
|
Для каждого — три переменные (пример для `FLOW`):
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `FLOW_HOST` | string | см. выше | Базовый URL сервиса |
|
||||||
|
| `FLOW_TIMEOUT` | int | `30` | Таймаут запроса (сек) |
|
||||||
|
| `FLOW_RETRIES` | int | `3` | Число повторов транспорта httpx |
|
||||||
|
|
||||||
|
## Аутентификация
|
||||||
|
|
||||||
|
Аутентификация выполняется в `src/app/dependensies.py` (`get_user_data`) на основе заголовков запроса и **без проверки подписи токена** (`jwt.decode(..., options={"verify_signature": False})`). Публичный ключ не используется, отдельных переменных для ключа нет.
|
||||||
|
|
||||||
|
| Условие | Режим | Как извлекается `user_id` |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Есть заголовки `Authorization` и `Identity` | `zitadel` | Из payload `Identity`-токена, поле `urn:zitadel:iam:user:metadata.user_id` (base64) |
|
||||||
|
| Есть только `Authorization` | `sarex` | Из payload основного токена, поле `user_id` |
|
||||||
|
| Заголовков нет | — | `401 Unauthorized` |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры и сборки
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в сборке/запуске.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `SERVICE_NAME` | `.gitlab-ci.yml` | Имя сервиса (`mapper`), используется как `CHART_NAME` |
|
||||||
|
| `DOCKERFILE_PATH` | `.gitlab-ci.yml` | Путь к Dockerfile |
|
||||||
|
| `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` | Аргументы сборки / источник триггера (`app`) |
|
||||||
|
| `IMAGE_NAME`, `CI_COMMIT_SHA`, `CI_PROJECT_URL`, `CI_JOB_URL`, `CI_PROJECT_NAMESPACE` | `.gitlab-ci.yml` (`HELM_SET_ARGS`) | Прокидываются в universal-chart (образ, commit, ссылки, owner) |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта репозитория сервиса (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Чарт зависит от `universal-chart` (`oci://cr.yandex/.../charts`, версия `0.1.7`). Переменные приложения задаются в блоке `services.backend.envs` с разбивкой по окружениям (`_default`/`stage`/`preprod`/`production`).
|
||||||
|
|
||||||
|
| Переменная | `stage` | `preprod` | `production` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DOCUMENTATION_HOST` | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.preprod.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` |
|
||||||
|
| `FLOW_HOST` | `https://stage-api.sarex.io/flows/api/v1` | `https://api.preprod.sarex.io/flows/api/v1` | `https://api.sarex.io/flows/api/v1` |
|
||||||
|
| `DJANGO_HOST` | `https://stage.sarex.io/api` | `https://preprod.sarex.io/api` | `https://lk.sarex.io/api` |
|
||||||
|
| `NOTE_HOST` | `https://stage-api.sarex.io/notes/api/v1` | `https://api.preprod.sarex.io/notes/api/v1` | `https://api.sarex.io/notes/api/v1` |
|
||||||
|
| `REDIS_USE` | `0` | `0` | `0` |
|
||||||
|
| `TIMEOUT` | `120` | `120` | `120` |
|
||||||
|
|
||||||
|
Прочие параметры чарта (не переменные приложения): `deployment.*` (имя, порт `8000`, `replicaCount` 1/1/3/3, ресурсы, `probes.liveness/readiness` — **отключены**), `image.name` (`cr.yandex/.../mapper`), `service.*` (ClusterIP, порт `8000`), `imagePullSecrets` (`dockerhub`), `labels.monitoring=prometheus`.
|
||||||
|
|
||||||
|
## Переменные из инфра-репозитория (`iac/apps/mapper`)
|
||||||
|
|
||||||
|
GitOps-деплой через Kustomize + Flux, namespace `mapper`. Здесь же лежит настоящий документ.
|
||||||
|
|
||||||
|
Структура:
|
||||||
|
|
||||||
|
| Путь | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `base/` | Базовый Kustomize (`namespace`, `serviceaccount` `mapper-vault`, `deployment`, `service`) |
|
||||||
|
| `yc-k8s-test/` | Overlay поверх `base` (патч `replicas: 1`) |
|
||||||
|
| `brusnika-stage/` | Flux `HelmRelease` (universal-chart), хосты `test.sarex.brusnika.tech`, `imagePullSecrets: dockerhub` |
|
||||||
|
| `brusnika-prod/` | Flux `HelmRelease` (universal-chart), хосты `cde.brusnika.ru`, `imagePullSecrets: regcred` |
|
||||||
|
|
||||||
|
Обычные env в `base/deployment.yaml` (production-хосты Sarex):
|
||||||
|
|
||||||
|
| Переменная | Значение |
|
||||||
|
| --- | --- |
|
||||||
|
| `DOCUMENTATION_HOST` | `https://api.sarex.io/documentations/api/v1` |
|
||||||
|
| `FLOW_HOST` | `https://api.sarex.io/flows/api/v1` |
|
||||||
|
| `DJANGO_HOST` | `https://lk.sarex.io/api` |
|
||||||
|
| `NOTE_HOST` | `https://api.sarex.io/notes/api/v1` |
|
||||||
|
| `REDIS_USE` | `0` |
|
||||||
|
| `TIMEOUT` | `120` |
|
||||||
|
|
||||||
|
Секреты монтируются через **HashiCorp Vault Agent** (аннотации `vault.hashicorp.com/*`, роль `mapper`) как файлы в `/vault/secrets/*`, которые перед стартом экспортируются в окружение (`set -a && . /vault/secrets/... && set +a`):
|
||||||
|
|
||||||
|
| Файл секрета | Источник (Vault path) | Экспортируемые переменные |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `mapper-django-auth` | `secrets/data/vault/common/django_auth` | `MAPPER_DJANGO_TOKEN` |
|
||||||
|
| `mapper-db` | `secrets/data/postgresql/apps/mapper` | `MAPPER_DB_USER`, `MAPPER_DB_PASSWORD`, `MAPPER_DB_HOST`, `MAPPER_DB_PORT`, `MAPPER_DB_NAME` |
|
||||||
|
| `mapper-rabbitmq` | `secrets/data/rabbitmq/apps/mapper` | `MAPPER_RABBITMQ_VHOST`, `MAPPER_RABBITMQ_USERNAME`, `MAPPER_RABBITMQ_PASSWORD`, `MAPPER_RABBITMQ_HOST`, `MAPPER_RABBITMQ_PORT` |
|
||||||
|
| `mapper-s3` | `secrets/data/minio/apps/mapper` | `MAPPER_S3_ENDPOINT`, `MAPPER_S3_REGION`, `MAPPER_S3_BUCKET`, `MAPPER_S3_ACCESS_KEY_ID`, `MAPPER_S3_SECRET_ACCESS_KEY` |
|
||||||
|
| `mapper-kafka` | `secrets/data/kafka/apps/mapper` | `MAPPER_KAFKA_BOOTSTRAP_SERVERS`, `MAPPER_KAFKA_SECURITY_PROTOCOL`, `MAPPER_KAFKA_SASL_MECHANISM`, `MAPPER_KAFKA_USERNAME`, `MAPPER_KAFKA_PASSWORD` |
|
||||||
|
|
||||||
|
## Переменные в CI (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`) и переключает окружение по ветке/тегу:
|
||||||
|
|
||||||
|
| Условие | STAND | Namespace | CHART_VERSION |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `platform` | `0.0.1-stage` |
|
||||||
|
| ветка `master` | `preprod` | `mapper-preprod` | `0.0.1-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `mapper-prod` | `0.0.1-prod` |
|
||||||
|
| merge request | — (сборка образа отключена) | — | — |
|
||||||
|
|
||||||
|
Стадия `test`: job `linter` (`flake8 src/app`, `max-line-length=120`) и `typechecker` (`mypy src/app` с `types-redis`; `disallow_untyped_defs=True`).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **`TIMEOUT` не читается приложением.** В Helm/Kustomize задаётся `TIMEOUT=120`, но клиенты читают `DOCUMENTATION_TIMEOUT`/`FLOW_TIMEOUT`/`DJANGO_TIMEOUT`/`NOTE_TIMEOUT` (каждый со своим префиксом). Без префикса переменная игнорируется — реальный таймаут остаётся `30`. Чтобы поднять таймаут, задавайте `<PREFIX>_TIMEOUT`.
|
||||||
|
- **Секреты Vault не используются кодом.** `MAPPER_DB_*`, `MAPPER_RABBITMQ_*`, `MAPPER_S3_*`, `MAPPER_KAFKA_*`, `MAPPER_DJANGO_TOKEN` монтируются и экспортируются в окружение (`base/deployment.yaml`), но текущая версия приложения ни PostgreSQL, ни RabbitMQ, ни S3, ни Kafka, ни `MAPPER_DJANGO_TOKEN` **не читает** (в `config.py` таких настроек нет). Похоже, инфраструктура заготовлена наперёд либо унаследована из шаблона.
|
||||||
|
- **Кеш отключён во всех окружениях (`REDIS_USE=0`).** При этом в `get_response` (`utils.py`) при не-200 ответе апстрима и выключенном кеше возвращается `None`, а роутер отдаёт `400 Bad Request`. То есть при выключенном Redis запасного кеша нет.
|
||||||
|
- **Подпись JWT не проверяется** (`verify_signature=False`) ни в режиме `zitadel`, ни в `sarex`. Доверие к токену — на сетевом слое (Istio/ingress). Публичный ключ не настраивается.
|
||||||
|
- **TLS-проверка апстримов отключена** (`httpx.AsyncClient(verify=False)`) для всех четырёх клиентов.
|
||||||
|
- **Нет healthcheck-эндпоинта.** Пробы `liveness`/`readiness` в чарте выключены — это согласовано.
|
||||||
|
- **Порты различаются:** локально `main.py` слушает `8002`, в контейнере gunicorn — `8000` (проброшен в k8s Service).
|
||||||
|
- **`docker-compose.yaml`** поднимает только Redis (redis-stack-server); само приложение в compose не описано.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Поднять Redis (`docker compose up redis`) либо задать `REDIS_USE=False`, затем `uvicorn main:app` из `src`. Минимально стоит задать (у остальных есть рабочие дефолты для stage):
|
||||||
|
|
||||||
|
- `REDIS_USE` (`False`, если Redis не поднят) и при необходимости `REDIS_HOST`/`REDIS_PORT`
|
||||||
|
- при работе против нестандартных стендов — `DOCUMENTATION_HOST`, `FLOW_HOST`, `DJANGO_HOST`, `NOTE_HOST`
|
||||||
|
- `LOG_LEVEL` (по желанию)
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example`.
|
||||||
95
apps/mapper/ENDPOINTS.md
Normal file
95
apps/mapper/ENDPOINTS.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
# Эндпоинты сервиса mapper
|
||||||
|
|
||||||
|
Документ описывает HTTP-интерфейс сервиса `mapper` (flows mapper): собственные эндпоинты, которые сервис публикует, и внешние эндпоинты сервисов Sarex, к которым он обращается для сборки ответа.
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
`mapper` — асинхронный FastAPI-прокси-агрегатор. Каждый входящий запрос:
|
||||||
|
|
||||||
|
1. проходит аутентификацию (`app/dependensies.py:get_user_data`) — из заголовков `Authorization` и опционального `Identity` извлекается `user_id` (подпись JWT не проверяется);
|
||||||
|
2. создаёт httpx-клиенты к нужным внешним сервисам (`app/config.py`, базовые хосты — из `*_HOST`, `verify=False`, заголовки авторизации пробрасываются);
|
||||||
|
3. параллельно-последовательно запрашивает 2 внешних сервиса через `ServiceManager.get_response()` (`app/utils.py`);
|
||||||
|
4. при `REDIS_USE=True` кеширует успешные (200) ответы в Redis по ключу `"{user_id}_{url}"`, а при ошибке апстрима возвращает данные из кеша;
|
||||||
|
5. объединяет ответы (`modify_pdm_data` / `modify_notes_data`) и отдаёт результат.
|
||||||
|
|
||||||
|
Если любой из двух апстримов вернул `None` (ошибка и нет кеша) — роутер отвечает `400 Bad Request`.
|
||||||
|
|
||||||
|
## Собственные эндпоинты (что публикует mapper)
|
||||||
|
|
||||||
|
Базовый префикс — `API_PREFIX` (по умолчанию `/api/v1`). Оба эндпоинта требуют заголовок `Authorization` (и `Identity` для режима Zitadel).
|
||||||
|
|
||||||
|
| Метод | Путь | Назначение | Ответ |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| GET | `/api/v1/disks/{disk}/documents/` | Документы диска, обогащённые review-данными из сервиса процессов | `object` (`{"documents": [...]}`) |
|
||||||
|
| GET | `/api/v1/notes/{service}/{entity}/{instance_id}/` | Заметки сущности, обогащённые target-links из Django-бэкенда | `array` (список заметок) |
|
||||||
|
|
||||||
|
### `GET /api/v1/disks/{disk}/documents/`
|
||||||
|
|
||||||
|
Параметры пути: `disk` (string).
|
||||||
|
|
||||||
|
Логика (`routers.py:get_documents`):
|
||||||
|
|
||||||
|
- запрос к **documentations**: `GET /disks/{disk}/documents` → берётся поле `documents`;
|
||||||
|
- запрос к **flows**: `GET /documents/?full=true`;
|
||||||
|
- `modify_pdm_data` матчит по `document_id`/`bundle_id` и добавляет `review_data` в соответствующие бандлы документов.
|
||||||
|
|
||||||
|
### `GET /api/v1/notes/{service}/{entity}/{instance_id}/`
|
||||||
|
|
||||||
|
Параметры пути: `service`, `entity`, `instance_id` (string). Дополнительно **все query-параметры запроса пробрасываются** в сервис заметок (к ним добавляется `full=true`).
|
||||||
|
|
||||||
|
Логика (`routers.py:get_notes`):
|
||||||
|
|
||||||
|
- запрос к **notes**: `GET /notes/{service}/{entity}/{instance_id}/?full=true&<проброшенные query>`;
|
||||||
|
- запрос к **Django**: `GET /core/target-links/` (полный путь — `{DJANGO_HOST}/core/target-links/`);
|
||||||
|
- `modify_notes_data` заменяет id-ссылки в поле `links` каждой заметки на объекты target-links.
|
||||||
|
|
||||||
|
Полное описание схем — в `openapi.yaml`.
|
||||||
|
|
||||||
|
## Внешние сервисы и базовые хосты по окружениям
|
||||||
|
|
||||||
|
Базовые хосты берутся из `*_HOST` (`app/config.py`). Итоговый URL = `<HOST>` + путь ниже. Значения по окружениям — из `.helm/values.yaml` (деплой из репозитория сервиса) и overlay'ов инфра-репозитория.
|
||||||
|
|
||||||
|
| Сервис | Переменная | Дефолт в коде (stage) | preprod | production | brusnika-stage | brusnika-prod |
|
||||||
|
| --- | --- | --- | --- | --- | --- | --- |
|
||||||
|
| documentations | `DOCUMENTATION_HOST` | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.preprod.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` | `https://test.sarex.brusnika.tech/documentations/api/v1` | `https://cde.brusnika.ru/documentations/api/v1` |
|
||||||
|
| flows | `FLOW_HOST` | `https://stage-api.sarex.io/flows/api/v1` | `https://api.preprod.sarex.io/flows/api/v1` | `https://api.sarex.io/flows/api/v1` | `https://test.sarex.brusnika.tech/flows/api/v1` | `https://cde.brusnika.ru/flows/api/v1` |
|
||||||
|
| django (sarex-backend) | `DJANGO_HOST` | `https://stage.sarex.io/api` | `https://preprod.sarex.io/api` | `https://lk.sarex.io/api` | `https://test.sarex.brusnika.tech/api` | `https://cde.brusnika.ru/api` |
|
||||||
|
| notes | `NOTE_HOST` | `https://stage-api.sarex.io/notes/api/v1` | `https://api.preprod.sarex.io/notes/api/v1` | `https://api.sarex.io/notes/api/v1` | `https://test.sarex.brusnika.tech/notes/api/v1` | `https://cde.brusnika.ru/notes/api/v1` |
|
||||||
|
|
||||||
|
## Эндпоинты внешних сервисов (что вызывает mapper)
|
||||||
|
|
||||||
|
### `documentations`
|
||||||
|
|
||||||
|
| Метод | Путь | Параметры | Назначение | Где используется |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| GET | `/disks/{disk}/documents` | `disk` (path) | Документы диска (поле `documents` в ответе) | `get_documents` |
|
||||||
|
|
||||||
|
### `flows`
|
||||||
|
|
||||||
|
| Метод | Путь | Параметры | Назначение | Где используется |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| GET | `/documents/` | `full=true` (query) | Документы процессов с review-данными | `get_documents` |
|
||||||
|
|
||||||
|
### `notes`
|
||||||
|
|
||||||
|
| Метод | Путь | Параметры | Назначение | Где используется |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| GET | `/notes/{service}/{entity}/{instance_id}/` | `service`, `entity`, `instance_id` (path); `full=true` + проброшенные query | Заметки сущности | `get_notes` |
|
||||||
|
|
||||||
|
### `django` (sarex-backend)
|
||||||
|
|
||||||
|
| Метод | Путь | Параметры | Назначение | Где используется |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| GET | `/core/target-links/` | — | Связи (target-links); из ответа берётся `results`, если ответ — объект | `get_notes` |
|
||||||
|
|
||||||
|
## Заголовки и аутентификация
|
||||||
|
|
||||||
|
- `Authorization: <token>` — обязателен для обоих эндпоинтов; пробрасывается во все внешние запросы как есть.
|
||||||
|
- `Identity: <token>` — опционален; при наличии включается режим Zitadel, заголовок также пробрасывается во внешние запросы.
|
||||||
|
- Ответы при ошибках: `401 Unauthorized` (нет `Authorization`), `400 Bad Request` (ошибка апстрима без кеша), `422 Unprocessable Entity` (ошибка валидации параметров пути, стандартный ответ FastAPI).
|
||||||
|
|
||||||
|
## Замечания
|
||||||
|
|
||||||
|
- В коде клиент к сервису заметок называется `NoteSettings`/`note`, к Django — `DjangoSettings`/`django`. Пути `/documents/` (flows) и `/notes/.../` (notes) содержат завершающий слэш — важно для совпадения с маршрутами апстрима.
|
||||||
|
- Кеш ключуется по `user_id` + URL, поэтому проброшенные query-параметры в `get_notes` не входят в ключ кеша (URL берётся без query). При включённом Redis это стоит учитывать.
|
||||||
|
- Healthcheck-эндпоинта у сервиса нет.
|
||||||
230
apps/mapper/openapi.yaml
Normal file
230
apps/mapper/openapi.yaml
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Mapper Service API
|
||||||
|
version: "1.0.0"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **mapper** (flows mapper) — mini-HTTP-сервис, который
|
||||||
|
объединяет (мапит) данные нескольких сервисов Sarex: документы дисков из
|
||||||
|
сервиса документации (`documentations`) обогащаются review-данными из
|
||||||
|
сервиса процессов (`flows`); заметки из сервиса `notes` обогащаются
|
||||||
|
связями (target-links) из Django-бэкенда.
|
||||||
|
|
||||||
|
Сервис написан на Python (**FastAPI**), приложение создаётся в
|
||||||
|
`src/app/main.py` (`app = FastAPI()`), маршруты — в `src/app/routers.py`
|
||||||
|
с префиксом `API_PREFIX` (по умолчанию `/api/v1`).
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Оба эндпоинта требуют заголовок `Authorization`. Опциональный заголовок
|
||||||
|
`Identity` включает режим Zitadel. Подпись JWT **не проверяется**
|
||||||
|
(`verify_signature=False`, `src/app/dependensies.py`) — доверие к токену
|
||||||
|
обеспечивается сетевым слоем (Istio/ingress). Из токена извлекается
|
||||||
|
`user_id`, который используется как часть ключа кеша Redis.
|
||||||
|
|
||||||
|
### Агрегация и кеш
|
||||||
|
Каждый запрос обращается к двум внешним сервисам через `ServiceManager`
|
||||||
|
(`src/app/utils.py`). При `REDIS_USE=True` успешные (200) ответы апстрима
|
||||||
|
кешируются в Redis (ключ `"{user_id}_{url}"`, TTL `REDIS_EXPIRE_DAYS`
|
||||||
|
суток), а при ошибке апстрима отдаётся кеш. Если хотя бы один апстрим
|
||||||
|
вернул ошибку и кеша нет — сервис отвечает `400 Bad Request`.
|
||||||
|
|
||||||
|
Схемы ответов заданы как свободные JSON-структуры (`object`/`array`),
|
||||||
|
так как сервис проксирует и объединяет ответы внешних сервисов без
|
||||||
|
фиксированной модели.
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://api.sarex.io/mapper
|
||||||
|
description: production (Sarex)
|
||||||
|
- url: https://stage-api.sarex.io/mapper
|
||||||
|
description: stage (Sarex)
|
||||||
|
- url: https://cde.brusnika.ru/mapper
|
||||||
|
description: production (Brusnika)
|
||||||
|
- url: https://test.sarex.brusnika.tech/mapper
|
||||||
|
description: stage (Brusnika)
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: documents
|
||||||
|
description: Документы дисков, обогащённые review-данными процессов
|
||||||
|
- name: notes
|
||||||
|
description: Заметки, обогащённые связями (target-links)
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/v1/disks/{disk}/documents/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- documents
|
||||||
|
summary: Документы диска с review-данными
|
||||||
|
operationId: get_documents
|
||||||
|
description: |
|
||||||
|
Возвращает документы диска из сервиса документации, обогащённые
|
||||||
|
review-данными из сервиса процессов. Внутри выполняются запросы
|
||||||
|
`GET {DOCUMENTATION_HOST}/disks/{disk}/documents` и
|
||||||
|
`GET {FLOW_HOST}/documents/?full=true`, после чего review-данные
|
||||||
|
добавляются в поле `review_data` соответствующих бандлов документов
|
||||||
|
(`modify_pdm_data`).
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: disk
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Идентификатор диска
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: Authorization
|
||||||
|
in: header
|
||||||
|
required: true
|
||||||
|
description: Токен доступа (пробрасывается во внешние сервисы)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: Identity
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
description: Identity-токен (включает режим Zitadel)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Успешный ответ
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/DocumentsResponse"
|
||||||
|
"400":
|
||||||
|
description: Один из внешних сервисов недоступен и данных в кеше нет
|
||||||
|
"401":
|
||||||
|
description: Отсутствует заголовок Authorization
|
||||||
|
"422":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
|
||||||
|
/api/v1/notes/{service}/{entity}/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- notes
|
||||||
|
summary: Заметки сущности со связями (target-links)
|
||||||
|
operationId: get_notes
|
||||||
|
description: |
|
||||||
|
Возвращает заметки сущности из сервиса `notes`, у которых поле `links`
|
||||||
|
заменено на объекты связей (target-links) из Django-бэкенда. Внутри
|
||||||
|
выполняются запросы
|
||||||
|
`GET {NOTE_HOST}/notes/{service}/{entity}/{instance_id}/?full=true`
|
||||||
|
(с проброской всех query-параметров запроса) и
|
||||||
|
`GET {DJANGO_HOST}/core/target-links/`, после чего выполняется
|
||||||
|
объединение (`modify_notes_data`).
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
parameters:
|
||||||
|
- name: service
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Логическое имя сервиса-владельца сущности
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: entity
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Тип сущности
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: instance_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Идентификатор экземпляра сущности
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: Authorization
|
||||||
|
in: header
|
||||||
|
required: true
|
||||||
|
description: Токен доступа (пробрасывается во внешние сервисы)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- name: Identity
|
||||||
|
in: header
|
||||||
|
required: false
|
||||||
|
description: Identity-токен (включает режим Zitadel)
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: |
|
||||||
|
Успешный ответ — список заметок. Все дополнительные query-параметры
|
||||||
|
запроса проксируются в сервис заметок.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/NotesResponse"
|
||||||
|
"400":
|
||||||
|
description: Один из внешних сервисов недоступен и данных в кеше нет
|
||||||
|
"401":
|
||||||
|
description: Отсутствует заголовок Authorization
|
||||||
|
"422":
|
||||||
|
$ref: "#/components/responses/ValidationError"
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: |
|
||||||
|
Токен передаётся заголовком `Authorization`. Подпись не проверяется
|
||||||
|
приложением. Для режима Zitadel дополнительно передаётся заголовок
|
||||||
|
`Identity`.
|
||||||
|
|
||||||
|
responses:
|
||||||
|
ValidationError:
|
||||||
|
description: Ошибка валидации параметров запроса
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/HTTPValidationError"
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
DocumentsResponse:
|
||||||
|
type: object
|
||||||
|
description: |
|
||||||
|
Ответ агрегатора документов. Структура повторяет ответ сервиса
|
||||||
|
документации, где в бандлы добавлено поле `review_data` с данными
|
||||||
|
из сервиса процессов.
|
||||||
|
properties:
|
||||||
|
documents:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
NotesResponse:
|
||||||
|
type: array
|
||||||
|
description: |
|
||||||
|
Список заметок сервиса `notes`, где поле `links` каждой заметки
|
||||||
|
заменено на объекты связей (target-links).
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
|
||||||
|
ValidationErrorItem:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
loc:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- loc
|
||||||
|
- msg
|
||||||
|
- type
|
||||||
|
|
||||||
|
HTTPValidationError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
detail:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ValidationErrorItem"
|
||||||
50
apps/measurements/.env.example
Normal file
50
apps/measurements/.env.example
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# measurements — пример переменных окружения (HTTP-сервис на FastAPI)
|
||||||
|
#
|
||||||
|
# Скопируйте нужные строки в config.env / .env сервиса.
|
||||||
|
# Переменные, помеченные (обяз.), обязательны — без них процесс не стартует.
|
||||||
|
# Конфигурация читается через pydantic-settings (src/measurements/config.py).
|
||||||
|
# bool принимает 1/0, true/false, yes/no.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- S3 / MinIO (обяз.) -----------------------------------------------------
|
||||||
|
# Единственная обязательная переменная. JSON-строка с доступами к S3.
|
||||||
|
# Разбирается в S3CredentialsSettings.from_env(); если не задана —
|
||||||
|
# ValueError и процесс не стартует.
|
||||||
|
# Поля: host, login, password (обяз.), verify (bool, по умолч. false),
|
||||||
|
# buckets (список; если пуст — читается через list_buckets()).
|
||||||
|
S3_JSON_SETTINGS='{"host":"https://s3.example.com","login":"login","password":"password","verify":false,"buckets":["measurements"]}'
|
||||||
|
|
||||||
|
# --- Логирование (префикс LOG_) ---------------------------------------------
|
||||||
|
LOG_LEVEL=INFO # уровень логирования (по умолчанию INFO)
|
||||||
|
# LOG_FORMAT='{"timestamp": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}' # формат JSON-лога
|
||||||
|
|
||||||
|
# --- Приложение (ApplicationSettings, без префикса) -------------------------
|
||||||
|
# AUTH=0 # включить CustomAuthenticationMiddleware (по умолч. false)
|
||||||
|
# SHOW_UI=0 # показывать Swagger/redoc (по умолч. false — docs отключены)
|
||||||
|
# USE_SENTRY=0 # инициализировать Sentry (по умолч. false)
|
||||||
|
# DEBUG=0 # флаг отладки (по умолч. false)
|
||||||
|
# CLASSIC_MODE=1 # классический режим расчётов (по умолч. true)
|
||||||
|
# BLOCK_SIZE=256 # размер блока обработки растра (по умолч. 256)
|
||||||
|
# BLOCK_SIZE_FACTOR=10 # множитель площади блока (по умолч. 10)
|
||||||
|
# CPU_NUMBER=10 # число используемых CPU (по умолч. 10)
|
||||||
|
|
||||||
|
# --- Django / ЛК (префикс DJANGO_; читается при AUTH=1) ----------------------
|
||||||
|
# DJANGO_USE=1 # использовать интеграцию с Django (по умолч. true)
|
||||||
|
DJANGO_HOST=https://lk.sarex.io # базовый URL Django/ЛК (по умолч. https://lk.sarex.io)
|
||||||
|
# DJANGO_TIMEOUT=10 # таймаут HTTP-запросов к Django, сек (по умолч. 10)
|
||||||
|
|
||||||
|
# --- Sentry (префикс SENTRY_; читается при USE_SENTRY=1) ---------------------
|
||||||
|
# SENTRY_DSN= # DSN проекта Sentry (по умолч. пусто)
|
||||||
|
# SENTRY_ENVIRONMENT=production # окружение (по умолч. production)
|
||||||
|
# SENTRY_TRACES_SAMPLE_RATE=1.0 # доля трейсов (по умолч. 1.0)
|
||||||
|
# SENTRY_SEND_DEFAULT_PII=1 # отправлять PII (по умолч. true)
|
||||||
|
|
||||||
|
# --- Трейсинг OpenTelemetry (префикс TRACING_; читается при TRACING_USE=1) ---
|
||||||
|
TRACING_USE=0 # включить OTEL-трейсинг и otel-логгер (по умолч. false)
|
||||||
|
# TRACING_HOST=localhost:4317 # адрес OTLP-коллектора (по умолч. localhost:4317)
|
||||||
|
# TRACING_SERVICE_NAME=measurements # имя сервиса в трейсах (по умолч. measurements)
|
||||||
|
# TRACING_INSECURE=0 # подключение без TLS (по умолч. false)
|
||||||
|
|
||||||
|
# --- Задаётся в манифестах, кодом приложения НЕ читается ---------------------
|
||||||
|
# S3_JSON_FILE=/opt/cred_s3.json # присутствует в .helm/values.yaml, но код читает только S3_JSON_SETTINGS
|
||||||
150
apps/measurements/CONFIGURATION.md
Normal file
150
apps/measurements/CONFIGURATION.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Конфигурация measurements
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса репозитория `measurements`:
|
||||||
|
|
||||||
|
- **measurements** — HTTP-сервис на FastAPI (`src/measurements`), запускается через gunicorn/uvicorn (`entrypoint.sh`, `measurements.main:app`). Считает измерения по растрам (GeoTIFF), читая их напрямую из S3/MinIO через GDAL (`vsis3`). Отдельного воркера у сервиса нет.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/measurements/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/): классы `LoggerSettings`, `SentrySettings`, `DjangoSettings`, `ApplicationSettings`, `TraceSettings`, `S3CredentialsSettings`, `Store`. Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Каждый класс задаёт свой префикс через `class Config: env_prefix` (`LOG_`, `SENTRY_`, `DJANGO_`, `TRACING_`, `S3_`); у `ApplicationSettings` префикса нет — её поля читаются по имени напрямую (`AUTH`, `SHOW_UI`, `USE_SENTRY` и т.п.). Почти все переменные имеют значения по умолчанию, поэтому обязательна фактически одна — **`S3_JSON_SETTINGS`**: её отсутствие приводит к `ValueError` в `S3CredentialsSettings.from_env()` и процесс не стартует.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yaml` — образ `measurements`, проброс порта `8000:8000`, инлайн `environment: S3_JSON_SETTINGS`. Сервис запускается `entrypoint.sh` (gunicorn, 4 воркера, uvicorn worker, таймаут 240) |
|
||||||
|
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml` (universal-chart, dependency `oci://…/charts`): блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов). Per-env значения через ключи `_default`/`stage`/`preprod`/`production` |
|
||||||
|
| Kubernetes — этот infra-репозиторий (`iac/apps/measurements`) | `base/` — kustomize-манифесты с инъекцией секрета S3 через **HashiCorp Vault** (annotations `vault.hashicorp.com/*`), обычные переменные заданы инлайн в `env:`. Оверлеи: `yc-k8s-test` (base + патч реплик), `brusnika-stage`/`brusnika-prod` (Flux `HelmRelease` на `universal-chart`, блок `secretEnvs`) |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml` подключает шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`); в `workflow.rules` задаются переменные пайплайна (`STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `SERVICE_NAME`, `DOCKERFILE_PATH`) |
|
||||||
|
|
||||||
|
**Миграции БД.** Отсутствуют. Сервис не хранит собственное состояние в реляционной БД (`psycopg2` присутствует в зависимостях, но код измерений работает с растрами из S3). Шага миграций в `entrypoint.sh` нет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## measurements (`measurements`)
|
||||||
|
|
||||||
|
Переменные читаются набором классов `*Settings` в `config.py`, инстанцируемых на уровне модуля: `settings = ApplicationSettings()`, `store = Store()`, `logger = LoggerSettings().logger`, `tracing_settings = TraceSettings()`.
|
||||||
|
|
||||||
|
### S3 / MinIO (обязательно)
|
||||||
|
|
||||||
|
Класс `S3CredentialsSettings`. Единственный обязательный источник конфигурации — переменная `S3_JSON_SETTINGS` (JSON-строка). Валидатор `from_env` (`model_validator(mode='before')`) читает её из окружения и при отсутствии выбрасывает `ValueError`. Доступы к S3 используются как boto3-клиентом (список бакетов), так и GDAL (`AWS_S3_ENDPOINT`/`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, драйвер `vsis3`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `S3_JSON_SETTINGS` | string (JSON) | да | — | JSON с доступами к S3. Поля: `host`, `login`, `password` (обяз.); `verify` (bool, по умолч. `false`); `buckets` (список; если пуст — бакеты запрашиваются через `list_buckets()`). Пример: `{"host":"https://s3…","login":"…","password":"…","verify":false,"buckets":["measurements"]}` |
|
||||||
|
|
||||||
|
> В `host` поддерживаются схемы `http://`/`https://`: при `http://` GDAL переключается на `AWS_HTTPS=NO`, отключает `GDAL_DISABLE_READDIR_ON_OPEN` и `AWS_VIRTUAL_HOSTING`.
|
||||||
|
|
||||||
|
### Логирование (префикс `LOG_`)
|
||||||
|
|
||||||
|
Класс `LoggerSettings`. Настраивает JSON-логгер (`python-json-logger`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | нет | `INFO` | Уровень логирования (`INFO`/`DEBUG`/…); неизвестное значение → `INFO` |
|
||||||
|
| `LOG_FORMAT` | string | нет | JSON-шаблон | Формат строки лога для `JsonFormatter` |
|
||||||
|
|
||||||
|
### Приложение (`ApplicationSettings`, без префикса)
|
||||||
|
|
||||||
|
Поля читаются по имени напрямую (регистронезависимо). Управляют поведением сервиса и подключением middleware в `main.py`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `AUTH` | bool | нет | `false` | Подключить `CustomAuthenticationMiddleware` (проверка JWT `authorization`/`identity`) |
|
||||||
|
| `SHOW_UI` | bool | нет | `false` | Включить Swagger/redoc; при `false` `docs_url`/`redoc_url` отключены |
|
||||||
|
| `USE_SENTRY` | bool | нет | `false` | Инициализировать Sentry SDK и `SentryAsgiMiddleware` |
|
||||||
|
| `DEBUG` | bool | нет | `false` | Флаг отладки |
|
||||||
|
| `CLASSIC_MODE` | bool | нет | `true` | Классический режим расчётов |
|
||||||
|
| `BLOCK_SIZE` | int | нет | `256` | Размер блока обработки растра; участвует в `area_factor` |
|
||||||
|
| `BLOCK_SIZE_FACTOR` | int | нет | `10` | Множитель площади блока (`area_factor = BLOCK_SIZE_FACTOR × BLOCK_SIZE²`) |
|
||||||
|
| `CPU_NUMBER` | int | нет | `10` | Число используемых CPU |
|
||||||
|
|
||||||
|
> `DEBUG`, `CLASSIC_MODE`, `BLOCK_SIZE`, `BLOCK_SIZE_FACTOR`, `CPU_NUMBER` задаются в конфиге, но в текущих обработчиках напрямую не считываются (в `main.py` используются только `SHOW_UI`, `USE_SENTRY`, `AUTH`). Оставлены как настраиваемые параметры.
|
||||||
|
|
||||||
|
### Django / ЛК (префикс `DJANGO_`)
|
||||||
|
|
||||||
|
Класс `DjangoSettings`. Используется `CustomAuthenticationMiddleware`/`DjangoUserMiddleware` при включённой авторизации (`AUTH=1`) для запросов к ЛК.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `DJANGO_USE` | bool | нет | `true` | Использовать интеграцию с Django |
|
||||||
|
| `DJANGO_HOST` | string | нет | `https://lk.sarex.io` | Базовый URL Django/ЛК |
|
||||||
|
| `DJANGO_TIMEOUT` | int | нет | `10` | Таймаут HTTP-запросов к Django, сек |
|
||||||
|
|
||||||
|
### Sentry (префикс `SENTRY_`)
|
||||||
|
|
||||||
|
Класс `SentrySettings`. Значения передаются в `sentry_sdk.init(**settings.sentry.kwargs)` только при `USE_SENTRY=1`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `SENTRY_DSN` | string | нет | `""` | DSN проекта Sentry |
|
||||||
|
| `SENTRY_ENVIRONMENT` | string | нет | `production` | Имя окружения в Sentry |
|
||||||
|
| `SENTRY_TRACES_SAMPLE_RATE` | float | нет | `1.0` | Доля трейсов |
|
||||||
|
| `SENTRY_SEND_DEFAULT_PII` | bool | нет | `true` | Отправлять PII |
|
||||||
|
|
||||||
|
### Трейсинг (OpenTelemetry, префикс `TRACING_`)
|
||||||
|
|
||||||
|
Класс `TraceSettings`. Активируется при `TRACING_USE=1` (`fastapi-otel-tools`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `TRACING_USE` | bool | нет | `false` | Включает OTEL-трейсинг, otel-логгер и `OtelMiddleware` |
|
||||||
|
| `TRACING_HOST` | string | нет | `localhost:4317` | Адрес OTLP-коллектора |
|
||||||
|
| `TRACING_SERVICE_NAME` | string | нет | `measurements` | Имя сервиса в трейсах |
|
||||||
|
| `TRACING_INSECURE` | bool | нет | `false` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
|
||||||
|
> Тип `bool` в pydantic принимает `1`/`0`, `true`/`false`, `yes`/`no`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Инфраструктурные и вспомогательные переменные
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `S3_JSON_FILE` | `.helm/values.yaml` (`envs`) | Путь к файлу с доступами S3 (`/opt/cred_s3.json`). **Кодом не читается** — приложение использует только `S3_JSON_SETTINGS` |
|
||||||
|
| `SERVICE_NAME`, `DOCKERFILE_PATH`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` (`variables`) | Параметры сборки образа |
|
||||||
|
| `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` | `.gitlab-ci.yml` (`workflow.rules`) | Параметры пайплайна `universal-pipeline` (деплой чарта per-env: `stage`/`preprod`/`production`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Деплой из этого репозитория (`iac/apps/measurements`)
|
||||||
|
|
||||||
|
В `base/` используется **kustomize** (не собственный Helm-чарт сервиса). Доступ к S3/MinIO инъектируется агентом **Vault** и подгружается в окружение процесса до старта.
|
||||||
|
|
||||||
|
### `base/`
|
||||||
|
|
||||||
|
- `deployment.yaml` — единственный Deployment `measurements` (namespace `measurements`). Аннотации Vault (`agent-inject`, `role: measurements`) формируют шаблон секрета `measurements-s3` из `secrets/data/minio/apps/measurements`, собирая `S3_JSON_SETTINGS='{"host":…,"login":…,"password":…,"verify":false,"buckets":["measurements"]}'`. Контейнер запускается командой `set -a; . /vault/secrets/measurements-s3; set +a; exec /opt/entrypoint.sh`. Инлайн задан только `TRACING_USE=false`. Порт `8000` (`http`), `serviceAccountName: measurements-vault`, `imagePullSecrets: regcred`, ресурсы `cpu 25m` / `memory 128Mi`.
|
||||||
|
- `service.yaml` — `Service` `measurements-svc` (ClusterIP, порт `8000` → `8000`).
|
||||||
|
- `namespace.yaml` — namespace `measurements` с `istio-injection: enabled`.
|
||||||
|
- `serviceaccount.yaml` — SA `measurements-vault`.
|
||||||
|
- `kustomization.yaml` собирает `namespace`, `serviceaccount`, `deployment`, `service`.
|
||||||
|
|
||||||
|
### Оверлеи
|
||||||
|
|
||||||
|
- **`yc-k8s-test`** — `../base` + патч `replicas.yaml` (реплики Deployment `measurements` = 1).
|
||||||
|
- **`brusnika-stage`** / **`brusnika-prod`** — Flux `HelmRelease` `measurements` на `universal-chart` `0.1.7` (source `yc-oci-charts`). Секрет `S3_JSON_SETTINGS` берётся из k8s-секрета `s3-json-settings` (`secretEnvs`). `replicaCount`: `stage 1`, `preprod 3`, `production 3`; `imagePullSecrets: regcred`; `labels.monitoring: prometheus`; сервис `measurements-service` (ClusterIP, `8000`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **`S3_JSON_SETTINGS` — единственная жёстко обязательная переменная.** Локальный `docker-compose.yaml` задаёт её значением-заглушкой (`{"host":"host","login":"login","password":"password"}`) — для реальной работы значение нужно заменить.
|
||||||
|
- **`S3_JSON_FILE` в `.helm/values.yaml` кодом не читается** — приложение использует только `S3_JSON_SETTINGS` (в этом infra-репозитории она и инъектируется Vault). Расхождение способов передачи доступов между собственным чартом и infra-репо.
|
||||||
|
- **Опечатка в `middleware.py`:** в `DjangoUserMiddleware` используется `settings.django.self.timeout` вместо `settings.django.timeout` — лишний `.self` приведёт к `AttributeError`. Сам `DjangoUserMiddleware` в `main.py` не подключается (подключается `CustomAuthenticationMiddleware`).
|
||||||
|
- **Отсутствует поле `jwt_public_key`:** `CustomAuthenticationMiddleware` при отсутствии заголовка `identity` вызывает `jwt.decode(key=settings.jwt_public_key, …)`, но такого поля в `ApplicationSettings` нет — при `AUTH=1` и запросе без `identity` это приведёт к `AttributeError`. Если планируется проверка подписи, следует добавить переменную (напр. `JWT_PUBLIC_KEY`) в конфиг.
|
||||||
|
- **`brusnika-stage`/`brusnika-prod`: `image.name` указывает на `documentations` (`…/documentations:prod_5904312b`), а не на `measurements`** — вероятно скопировано из другого сервиса; для measurements образ должен указывать на `…/measurements`.
|
||||||
|
- **Проверки liveness/readiness отключены** во всех манифестах (`probes.*.enabled: false`); HTTP-эндпоинта healthcheck у сервиса нет.
|
||||||
|
- **Probes/порт в brusnika-оверлеях:** `deployment.port` задан `8080`, тогда как контейнер (`entrypoint.sh` → gunicorn) слушает `8000`, и `service.port`/`targetPort` = `8000`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
- `S3_JSON_SETTINGS` (обязателен) — реальные доступы к S3/MinIO с бакетом(ами) растров.
|
||||||
|
- при необходимости: `LOG_LEVEL`, `TRACING_USE` (+ `TRACING_*`), `USE_SENTRY` (+ `SENTRY_*`), `AUTH` (+ `DJANGO_*`).
|
||||||
|
|
||||||
|
Сервис слушает `0.0.0.0:8000` (gunicorn, 4 воркера uvicorn). См. пример значений в `.env.example`.
|
||||||
927
apps/measurements/openapi.json
Normal file
927
apps/measurements/openapi.json
Normal file
@ -0,0 +1,927 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "measurements",
|
||||||
|
"description": "HTTP-сервис измерений по GeoTIFF-растрам (DEM/thermal), читаемым напрямую из S3/MinIO через GDAL.\n\nВсе эндпоинты — POST под префиксом `/api`. `path` в теле запроса указывается в формате `<bucket>:<путь/к/файлу.tif>`; система координат задаётся строкой `proj` (proj4). Пустой `proj` означает, что точки уже в системе координат растра.\n\nАутентификация (JWT в заголовках `authorization`/`identity`) включается переменной `AUTH=1`.",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/point": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Height/temperature at a single point",
|
||||||
|
"description": "Возвращает высоту (h) и/или температуру (t) в одной точке по DEM/термо-растру из S3.",
|
||||||
|
"operationId": "point_api_point_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffPoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Point Api Point Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/points": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Height/temperature at multiple points",
|
||||||
|
"description": "Батч-версия /point: массив высот/температур для списка точек.",
|
||||||
|
"operationId": "points_api_points_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffPoints"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Response Points Api Points Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/profile": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Elevation profile along a polyline",
|
||||||
|
"description": "Профиль высот вдоль ломаной; между вершинами добавляются промежуточные точки.",
|
||||||
|
"operationId": "profile_api_profile_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffProfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Response Profile Api Profile Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/volume": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Volume inside a polygon",
|
||||||
|
"description": "Объём внутри полигона. mode=cv2 (по умолчанию) или pillow (DEM-режим, требует > 2 точек).",
|
||||||
|
"operationId": "volume_api_volume_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffVolume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Response Volume Api Volume Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/multiple": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Volume difference between two DEMs",
|
||||||
|
"description": "Разница объёмов между master- и slave-растром в пределах полигона.",
|
||||||
|
"operationId": "multiple_api_multiple_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffMultiple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Multiple Api Multiple Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/transform": {
|
||||||
|
"post": {
|
||||||
|
"summary": "GeoTIFF affine transform",
|
||||||
|
"description": "Возвращает 6 коэффициентов GDAL GeoTransform растра.",
|
||||||
|
"operationId": "transform_api_transform_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 6,
|
||||||
|
"minItems": 6,
|
||||||
|
"title": "Response Transform Api Transform Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/info": {
|
||||||
|
"post": {
|
||||||
|
"summary": "GeoTIFF metadata",
|
||||||
|
"description": "Метаданные растра (размер, проекция, geotransform и т.п.).",
|
||||||
|
"operationId": "info_api_info_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Info Api Info Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/statistics": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Raster min/max statistics",
|
||||||
|
"description": "Пара (min, max) значений растра.",
|
||||||
|
"operationId": "statistics_api_statistics_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2,
|
||||||
|
"title": "Response Statistics Api Statistics Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/bounds": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Tile bounds by zoom range",
|
||||||
|
"description": "Границы тайлов (XYZ) для диапазона zoom_from..zoom_to.",
|
||||||
|
"operationId": "bounds_api_bounds_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"items": {
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Bounds Api Bounds Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ValidationError"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Detail"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "HTTPValidationError"
|
||||||
|
},
|
||||||
|
"Tiff": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"zoom_to": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Zoom To",
|
||||||
|
"default": 21
|
||||||
|
},
|
||||||
|
"zoom_from": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Zoom From",
|
||||||
|
"default": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"title": "Tiff"
|
||||||
|
},
|
||||||
|
"TiffMultiple": {
|
||||||
|
"properties": {
|
||||||
|
"master_path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Master Path"
|
||||||
|
},
|
||||||
|
"slave_path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Slave Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"master_path",
|
||||||
|
"slave_path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffMultiple",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"master_path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
37.34743572357015,
|
||||||
|
55.68881158675471
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.347128864435845,
|
||||||
|
55.6888704629146
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.34719182945381,
|
||||||
|
55.68896771656465
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.34732057806979,
|
||||||
|
55.688942063559054
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"slave_path": "geotiff_dems/NTG030521_DEM.tif"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffPoint": {
|
||||||
|
"properties": {
|
||||||
|
"altitude_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Altitude Path"
|
||||||
|
},
|
||||||
|
"temperature_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Temperature Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"point": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2,
|
||||||
|
"title": "Point"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"point"
|
||||||
|
],
|
||||||
|
"title": "TiffPoint",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"point": [
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"point": [
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
],
|
||||||
|
"proj": "",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"point": [
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
],
|
||||||
|
"proj": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffPoints": {
|
||||||
|
"properties": {
|
||||||
|
"altitude_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Altitude Path"
|
||||||
|
},
|
||||||
|
"temperature_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Temperature Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffPoints",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95563056285039,
|
||||||
|
57.966613723012216
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
1494221.1549999786,
|
||||||
|
516495.86600015266
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffProfile": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffProfile",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/BLG_080122_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
3334617.713961677,
|
||||||
|
590691.7743950449
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3334977.6348458366,
|
||||||
|
590688.296080064
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffVolume": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Level"
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/VolumeMode"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "cv2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffVolume",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95563056285039,
|
||||||
|
57.966613723012216
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "pillow",
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
1494221.1549999786,
|
||||||
|
516495.86600015266
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Location"
|
||||||
|
},
|
||||||
|
"msg": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Message"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Error Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"loc",
|
||||||
|
"msg",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"title": "ValidationError"
|
||||||
|
},
|
||||||
|
"VolumeMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"cv2",
|
||||||
|
"pillow"
|
||||||
|
],
|
||||||
|
"title": "VolumeMode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
565
apps/measurements/openapi.yaml
Normal file
565
apps/measurements/openapi.yaml
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: measurements
|
||||||
|
description: 'HTTP-сервис измерений по GeoTIFF-растрам (DEM/thermal), читаемым напрямую из S3/MinIO
|
||||||
|
через GDAL.
|
||||||
|
|
||||||
|
|
||||||
|
Все эндпоинты — POST под префиксом `/api`. `path` в теле запроса указывается в формате `<bucket>:<путь/к/файлу.tif>`;
|
||||||
|
система координат задаётся строкой `proj` (proj4). Пустой `proj` означает, что точки уже в системе
|
||||||
|
координат растра.
|
||||||
|
|
||||||
|
|
||||||
|
Аутентификация (JWT в заголовках `authorization`/`identity`) включается переменной `AUTH=1`.'
|
||||||
|
version: 0.0.1
|
||||||
|
paths:
|
||||||
|
/api/point:
|
||||||
|
post:
|
||||||
|
summary: Height/temperature at a single point
|
||||||
|
description: Возвращает высоту (h) и/или температуру (t) в одной точке по DEM/термо-растру из S3.
|
||||||
|
operationId: point_api_point_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffPoint'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
title: Response Point Api Point Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/points:
|
||||||
|
post:
|
||||||
|
summary: Height/temperature at multiple points
|
||||||
|
description: 'Батч-версия /point: массив высот/температур для списка точек.'
|
||||||
|
operationId: points_api_points_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffPoints'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
title: Response Points Api Points Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/profile:
|
||||||
|
post:
|
||||||
|
summary: Elevation profile along a polyline
|
||||||
|
description: Профиль высот вдоль ломаной; между вершинами добавляются промежуточные точки.
|
||||||
|
operationId: profile_api_profile_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffProfile'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
title: Response Profile Api Profile Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/volume:
|
||||||
|
post:
|
||||||
|
summary: Volume inside a polygon
|
||||||
|
description: Объём внутри полигона. mode=cv2 (по умолчанию) или pillow (DEM-режим, требует > 2 точек).
|
||||||
|
operationId: volume_api_volume_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffVolume'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
- type: 'null'
|
||||||
|
title: Response Volume Api Volume Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/multiple:
|
||||||
|
post:
|
||||||
|
summary: Volume difference between two DEMs
|
||||||
|
description: Разница объёмов между master- и slave-растром в пределах полигона.
|
||||||
|
operationId: multiple_api_multiple_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffMultiple'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
title: Response Multiple Api Multiple Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/transform:
|
||||||
|
post:
|
||||||
|
summary: GeoTIFF affine transform
|
||||||
|
description: Возвращает 6 коэффициентов GDAL GeoTransform растра.
|
||||||
|
operationId: transform_api_transform_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 6
|
||||||
|
minItems: 6
|
||||||
|
title: Response Transform Api Transform Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/info:
|
||||||
|
post:
|
||||||
|
summary: GeoTIFF metadata
|
||||||
|
description: Метаданные растра (размер, проекция, geotransform и т.п.).
|
||||||
|
operationId: info_api_info_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
title: Response Info Api Info Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/statistics:
|
||||||
|
post:
|
||||||
|
summary: Raster min/max statistics
|
||||||
|
description: Пара (min, max) значений растра.
|
||||||
|
operationId: statistics_api_statistics_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
title: Response Statistics Api Statistics Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/bounds:
|
||||||
|
post:
|
||||||
|
summary: Tile bounds by zoom range
|
||||||
|
description: Границы тайлов (XYZ) для диапазона zoom_from..zoom_to.
|
||||||
|
operationId: bounds_api_bounds_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
title: Response Bounds Api Bounds Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
HTTPValidationError:
|
||||||
|
properties:
|
||||||
|
detail:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
type: array
|
||||||
|
title: Detail
|
||||||
|
type: object
|
||||||
|
title: HTTPValidationError
|
||||||
|
Tiff:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
zoom_to:
|
||||||
|
type: integer
|
||||||
|
title: Zoom To
|
||||||
|
default: 21
|
||||||
|
zoom_from:
|
||||||
|
type: integer
|
||||||
|
title: Zoom From
|
||||||
|
default: 14
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
title: Tiff
|
||||||
|
TiffMultiple:
|
||||||
|
properties:
|
||||||
|
master_path:
|
||||||
|
type: string
|
||||||
|
title: Master Path
|
||||||
|
slave_path:
|
||||||
|
type: string
|
||||||
|
title: Slave Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- master_path
|
||||||
|
- slave_path
|
||||||
|
- points
|
||||||
|
title: TiffMultiple
|
||||||
|
example:
|
||||||
|
- master_path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 37.34743572357015
|
||||||
|
- 55.68881158675471
|
||||||
|
- - 37.347128864435845
|
||||||
|
- 55.6888704629146
|
||||||
|
- - 37.34719182945381
|
||||||
|
- 55.68896771656465
|
||||||
|
- - 37.34732057806979
|
||||||
|
- 55.688942063559054
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
slave_path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
TiffPoint:
|
||||||
|
properties:
|
||||||
|
altitude_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Altitude Path
|
||||||
|
temperature_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Temperature Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
point:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
title: Point
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- point
|
||||||
|
title: TiffPoint
|
||||||
|
example:
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
point:
|
||||||
|
- 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- point:
|
||||||
|
- 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
point:
|
||||||
|
- 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
TiffPoints:
|
||||||
|
properties:
|
||||||
|
altitude_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Altitude Path
|
||||||
|
temperature_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Temperature Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- points
|
||||||
|
title: TiffPoints
|
||||||
|
example:
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
- - 59.95563056285039
|
||||||
|
- 57.966613723012216
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- points:
|
||||||
|
- - 1494221.1549999786
|
||||||
|
- 516495.86600015266
|
||||||
|
- - 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
TiffProfile:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- points
|
||||||
|
title: TiffProfile
|
||||||
|
example:
|
||||||
|
- path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
- path: geotiff_dems/BLG_080122_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 3334617.713961677
|
||||||
|
- 590691.7743950449
|
||||||
|
- - 3334977.6348458366
|
||||||
|
- 590688.296080064
|
||||||
|
TiffVolume:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
level:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
title: Level
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
mode:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/VolumeMode'
|
||||||
|
default: cv2
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- points
|
||||||
|
title: TiffVolume
|
||||||
|
example:
|
||||||
|
- path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
- - 59.95563056285039
|
||||||
|
- 57.966613723012216
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
- mode: pillow
|
||||||
|
path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 1494221.1549999786
|
||||||
|
- 516495.86600015266
|
||||||
|
- - 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
ValidationError:
|
||||||
|
properties:
|
||||||
|
loc:
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
|
type: array
|
||||||
|
title: Location
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
title: Message
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
title: Error Type
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- loc
|
||||||
|
- msg
|
||||||
|
- type
|
||||||
|
title: ValidationError
|
||||||
|
VolumeMode:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- cv2
|
||||||
|
- pillow
|
||||||
|
title: VolumeMode
|
||||||
87
apps/message-hub/.env.example
Normal file
87
apps/message-hub/.env.example
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# Message Hub — пример конфигурации (.env)
|
||||||
|
# Версия: 0.1.0
|
||||||
|
# Скопируйте в .env и заполните значения.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# --- Приложение (префикс SETTINGS_) ---
|
||||||
|
SETTINGS_DEBUG=False
|
||||||
|
# Соответствие логических топиков реальным именам топиков Kafka.
|
||||||
|
# Допустимые ключи: planning, assets, issues
|
||||||
|
SETTINGS_TOPICS={"planning": "planning", "assets": "assets", "issues": "issues"}
|
||||||
|
# Проверять SSL-сертификаты у S3 и всех HTTP-клиентов внешних сервисов (1/0)
|
||||||
|
SETTINGS_VERIFY_SSL=1
|
||||||
|
SETTINGS_RETRY_DELAY=3
|
||||||
|
SETTINGS_MAX_RETRIES=3
|
||||||
|
SETTINGS_REQUEST_RETRIES=2
|
||||||
|
SETTINGS_REQUEST_DELAY=2
|
||||||
|
SETTINGS_MESSAGE_SKIP_AGE=300
|
||||||
|
SETTINGS_CACHE_EXPIRATION=120
|
||||||
|
SETTINGS_SENDER=noreply@sarex.io
|
||||||
|
# SETTINGS_WORKER_TIMEOUT=30
|
||||||
|
|
||||||
|
# --- Логирование (префикс LOG_) ---
|
||||||
|
# LOG_LEVEL=INFO
|
||||||
|
# LOG_FORMAT=[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s
|
||||||
|
|
||||||
|
# --- База данных PostgreSQL (префикс DB_) ---
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=5433
|
||||||
|
DB_DATABASE=sarex_db
|
||||||
|
DB_USERNAME=sarex
|
||||||
|
DB_PASSWORD=sarex
|
||||||
|
# DB_DIALECT=postgresql+psycopg
|
||||||
|
|
||||||
|
# --- Kafka (префикс KAFKA_) ---
|
||||||
|
KAFKA_HOST=
|
||||||
|
KAFKA_PORT=
|
||||||
|
KAFKA_USERNAME=
|
||||||
|
KAFKA_PASSWORD=
|
||||||
|
# PLAINTEXT | SSL | SASL_PLAINTEXT | SASL_SSL
|
||||||
|
KAFKA_SECURITY_PROTOCOL=
|
||||||
|
# PLAINTEXT | SCRAM-SHA-512
|
||||||
|
KAFKA_SASL_MECHANISM=
|
||||||
|
KAFKA_SSL_CAFILE=
|
||||||
|
|
||||||
|
# --- Redis / кеш (префикс CACHE_) ---
|
||||||
|
CACHE_HOST=localhost
|
||||||
|
CACHE_PORT=6378
|
||||||
|
CACHE_PASSWORD=
|
||||||
|
CACHE_SSL=0
|
||||||
|
# CACHE_SSL_CA_CERTS=/opt/ssl/ca.pem
|
||||||
|
|
||||||
|
# --- S3 (Yandex Object Storage, префикс S3_) ---
|
||||||
|
S3_HOST=http://localhost:9000
|
||||||
|
S3_LOGIN=minioadmin
|
||||||
|
S3_PASSWORD=minioadmin
|
||||||
|
S3_BUCKET=mybucket
|
||||||
|
|
||||||
|
# --- Внешние HTTP-сервисы (HOST / TIMEOUT на каждый префикс) ---
|
||||||
|
# Sarex backend
|
||||||
|
SAREX_HOST=http://localhost:8001
|
||||||
|
SAREX_TIMEOUT=60
|
||||||
|
# PM backend
|
||||||
|
PM_HOST=http://localhost:8001
|
||||||
|
PM_TIMEOUT=60
|
||||||
|
# Issues backend
|
||||||
|
ISSUES_HOST=http://localhost:8001
|
||||||
|
ISSUES_TIMEOUT=60
|
||||||
|
# BI backend
|
||||||
|
BI_HOST=http://localhost:8001
|
||||||
|
BI_TIMEOUT=60
|
||||||
|
# EAV service
|
||||||
|
EAV_HOST=http://localhost:8001
|
||||||
|
EAV_TIMEOUT=60
|
||||||
|
# HTML -> PDF converter (export-project)
|
||||||
|
PDF_CONVERTER_HOST=http://localhost:8001
|
||||||
|
PDF_CONVERTER_TIMEOUT=60
|
||||||
|
# Mailer service
|
||||||
|
MAILER_HOST=http://localhost:8001
|
||||||
|
MAILER_PREFIX=/api/v1
|
||||||
|
MAILER_TIMEOUT=60
|
||||||
|
|
||||||
|
# --- Инфраструктурные переменные (gunicorn / контейнер) ---
|
||||||
|
# Не читаются классом Settings, используются entrypoint.sh и docker-compose
|
||||||
|
# PYTHONPATH=src
|
||||||
|
# WORKERS=2
|
||||||
|
# WORKER_TIMEOUT=30
|
||||||
208
apps/message-hub/CONFIGURATION.md
Normal file
208
apps/message-hub/CONFIGURATION.md
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
# Конфигурация проекта message-hub
|
||||||
|
# Версия: 0.1.0
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **через переменные окружения**. Разбор выполняется в `src/config/` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/). Настройки разбиты на несколько классов, каждый со своим префиксом:
|
||||||
|
|
||||||
|
- `Settings` (`src/config/__init__.py`) — общий класс приложения, префикс `SETTINGS_`;
|
||||||
|
- `DBSettings` (`src/config/db.py`) — префикс `DB_`;
|
||||||
|
- `KafkaSettings` (`src/config/kafka.py`) — префикс `KAFKA_`;
|
||||||
|
- `RedisSettings` (`src/config/redis.py`) — префикс `CACHE_`;
|
||||||
|
- `ServiceConfig` и наследники (`src/config/sarex.py`) — префиксы `SAREX_`, `PM_`, `ISSUES_`, `BI_`, `EAV_`, `PDF_CONVERTER_`;
|
||||||
|
- `S3Settings` (`src/config/s3.py`) — префикс `S3_`;
|
||||||
|
- `MailerSettings` (`src/config/mailer.py`) — префикс `MAILER_`;
|
||||||
|
- `LoggerSettings` (`src/config/logger.py`) — префикс `LOG_`.
|
||||||
|
|
||||||
|
Особенности разбора:
|
||||||
|
|
||||||
|
- у каждого класса задан `env_file='.env'` и `extra='ignore'` — при наличии файла `.env` в рабочей директории он загружается автоматически, лишние переменные игнорируются;
|
||||||
|
- вложенных секций через разделитель нет — каждая группа настроек читается отдельным классом по своему префиксу;
|
||||||
|
- поле `VERIFY_SSL` в `S3Settings` и во всех `ServiceConfig` объявлено с `alias='SETTINGS_VERIFY_SSL'` — то есть единая переменная `SETTINGS_VERIFY_SSL` управляет проверкой TLS-сертификатов сразу для S3 и всех HTTP-клиентов внешних сервисов;
|
||||||
|
- у большинства полей есть значения по умолчанию, поэтому формально сервис стартует и без `.env`, но с дефолтными (локальными) адресами БД, Kafka, Redis и сервисов.
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (бинарник) | Файл `.env` в рабочей директории (загружается `pydantic-settings`) и/или переменные окружения процесса |
|
||||||
|
| Локально (контейнеры) | `docker-compose.yaml`: блок `environment` для сервиса `message-hub` (`PYTHONPATH`, `KAFKA_HOST`, `KAFKA_PORT`) |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блок `universal-chart.services.message-hub.envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов); базовый чарт — `universal-chart` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) и build-args |
|
||||||
|
|
||||||
|
Запуск процесса (`docker/entrypoint.sh`): единый ASGI-процесс поднимается через `gunicorn` с воркерами `uvicorn.workers.UvicornWorker` на `0.0.0.0:8000`:
|
||||||
|
|
||||||
|
```
|
||||||
|
gunicorn -w $WORKERS -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000 --timeout $WORKER_TIMEOUT --access-logfile - main:app
|
||||||
|
```
|
||||||
|
|
||||||
|
Приложение `main:app` (`src/main.py`) объединяет в одном ASGI-приложении: FastStream-брокер Kafka (потребители сообщений), HTTP-роуты health-проверок и Socket.IO-сервер (`AsyncServer` поверх `AsyncRedisManager`). Отдельных точек входа для воркеров/крон-задач нет — `pyproject.toml` не содержит `[project.scripts]`.
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
### App (`SETTINGS_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `SETTINGS_TOPICS` | dict (JSON) | `{}` | Соответствие логических топиков (`planning`/`assets`/`issues`) реальным именам топиков Kafka. Валидатор запрещает ключи вне набора `assets`/`planning`/`issues` |
|
||||||
|
| `SETTINGS_DEBUG` | bool | `False` | Режим отладки |
|
||||||
|
| `SETTINGS_VERIFY_SSL` | bool | `True` | Проверка TLS-сертификатов для S3 и всех HTTP-клиентов внешних сервисов (общий флаг через alias) |
|
||||||
|
| `SETTINGS_WORKER_TIMEOUT` | int | `30` | Таймаут воркера (поле `WORKER_TIMEOUT` класса `Settings`) |
|
||||||
|
| `SETTINGS_RETRY_DELAY` | int | `3` | Стартовая задержка (сек) между повторами обработки сообщения Kafka; удваивается на каждой попытке |
|
||||||
|
| `SETTINGS_MAX_RETRIES` | int | `3` | Число попыток обработки сообщения Kafka перед `ack` |
|
||||||
|
| `SETTINGS_REQUEST_RETRIES` | int | `2` | Число повторов HTTP-запросов к внешним сервисам |
|
||||||
|
| `SETTINGS_REQUEST_DELAY` | int | `2` | Задержка (сек) между повторами HTTP-запросов |
|
||||||
|
| `SETTINGS_MESSAGE_SKIP_AGE` | int | `300` | Возраст сообщения (сек), старше которого оно пропускается |
|
||||||
|
| `SETTINGS_CACHE_EXPIRATION` | int | `120` | TTL (сек) ключей присутствия пользователей в Redis (WebSocket) |
|
||||||
|
| `SETTINGS_SENDER` | string | `noreply@sarex.io` | Адрес отправителя по умолчанию |
|
||||||
|
|
||||||
|
### Логирование (`LOG_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | `INFO` | Уровень логирования (стандартные уровни `logging`) |
|
||||||
|
| `LOG_FORMAT` | string | `[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s` | Формат строк лога |
|
||||||
|
|
||||||
|
### Database (`DB_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DB_HOST` | string | `''` | Хост PostgreSQL |
|
||||||
|
| `DB_PORT` | int | `5432` | Порт PostgreSQL |
|
||||||
|
| `DB_DATABASE` | string | `''` | Имя базы данных |
|
||||||
|
| `DB_USERNAME` | string | `''` | Пользователь БД |
|
||||||
|
| `DB_PASSWORD` | string | `''` | Пароль пользователя БД |
|
||||||
|
| `DB_DIALECT` | string | `postgresql+psycopg` | Диалект/драйвер SQLAlchemy. Итоговый DSN собирается в `db.url` |
|
||||||
|
|
||||||
|
### Kafka (`KAFKA_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `KAFKA_HOST` | string | `localhost` | Хост брокера |
|
||||||
|
| `KAFKA_PORT` | int | `9092` | Порт брокера |
|
||||||
|
| `KAFKA_USERNAME` | string \| null | `None` | Логин SASL |
|
||||||
|
| `KAFKA_PASSWORD` | string \| null | `None` | Пароль SASL |
|
||||||
|
| `KAFKA_SECURITY_PROTOCOL` | string | `PLAINTEXT` | Протокол безопасности: `PLAINTEXT`/`SSL`/`SASL_PLAINTEXT`/`SASL_SSL`. При `SSL`/`SASL_SSL` используется SSL-контекст |
|
||||||
|
| `KAFKA_SASL_MECHANISM` | string \| null | `None` | Механизм SASL. Обрабатываются `PLAINTEXT` и `SCRAM-SHA-512` |
|
||||||
|
| `KAFKA_SSL_CAFILE` | string \| null | `None` | Путь к CA-сертификату для SSL-контекста |
|
||||||
|
|
||||||
|
### Redis / кеш (`CACHE_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `CACHE_HOST` | string | `localhost` | Хост Redis |
|
||||||
|
| `CACHE_PORT` | int | `6378` | Порт Redis |
|
||||||
|
| `CACHE_PASSWORD` | string \| null | `None` | Пароль Redis |
|
||||||
|
| `CACHE_SSL` | bool | `False` | Подключение по TLS (`rediss://`) |
|
||||||
|
| `CACHE_SSL_CA_CERTS` | string \| null | `None` | Путь к CA-сертификату Redis |
|
||||||
|
|
||||||
|
> Redis используется как менеджер состояния Socket.IO (`AsyncRedisManager`) и как хранилище присутствия пользователей в проектах.
|
||||||
|
|
||||||
|
### S3 (`S3_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `S3_HOST` | string | `https://storage.yandexcloud.net` | Endpoint S3 (`endpoint_url`) |
|
||||||
|
| `S3_LOGIN` | string | `''` | Access key |
|
||||||
|
| `S3_PASSWORD` | string | `''` | Secret key |
|
||||||
|
| `S3_BUCKET` | string | `sarex-media-storage` | Бакет по умолчанию |
|
||||||
|
| `SETTINGS_VERIFY_SSL` | bool | `True` | Проверять TLS-сертификат (общий флаг, см. App) |
|
||||||
|
|
||||||
|
### HTTP-клиенты внешних сервисов
|
||||||
|
|
||||||
|
Все клиенты наследуют общий класс `ServiceConfig` с полями `HOST`, `TIMEOUT` и общим флагом `VERIFY_SSL` (через alias `SETTINGS_VERIFY_SSL`). Значения по умолчанию: `HOST=http://localhost:8001`, `TIMEOUT=60`.
|
||||||
|
|
||||||
|
| Секция / префикс | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `SAREX_*` | Sarex backend (получение токенов клиентов и пр.) |
|
||||||
|
| `PM_*` | PM backend (синхронизация задач, автопланирование) |
|
||||||
|
| `ISSUES_*` | Сервис issues (типы задач, модели статусов) |
|
||||||
|
| `BI_*` | BI backend (синхронизация значений аналитики) |
|
||||||
|
| `EAV_*` | EAV-сервис (ассеты и атрибуты) |
|
||||||
|
| `PDF_CONVERTER_*` | Конвертер HTML → PDF (export-project) |
|
||||||
|
|
||||||
|
Для каждого — две переменные, напр. для PM:
|
||||||
|
|
||||||
|
| Переменная | Тип | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PM_HOST` | string | Базовый URL сервиса |
|
||||||
|
| `PM_TIMEOUT` | int | Таймаут запроса (сек) |
|
||||||
|
|
||||||
|
### Mailer (`MAILER_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `MAILER_HOST` | string | `http://localhost:8001` | Базовый URL сервиса рассылок |
|
||||||
|
| `MAILER_PREFIX` | string | `/api/v1` | Префикс маршрутов сервиса рассылок |
|
||||||
|
| `MAILER_TIMEOUT` | int | `60` | Таймаут запроса (сек) |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры, сборки и запуска
|
||||||
|
|
||||||
|
Не читаются классами настроек приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PYTHONPATH` | `docker-compose.yaml`, Helm `envs` | Каталог исходников (`src`) |
|
||||||
|
| `WORKERS` | `docker/entrypoint.sh`, Helm `envs` | Число воркеров gunicorn (по умолчанию `2`) |
|
||||||
|
| `WORKER_TIMEOUT` | `docker/entrypoint.sh`, Helm `envs` | Таймаут воркера gunicorn (`--timeout`) |
|
||||||
|
| `CI_COMMIT_SHORT_SHA` | `docker/Dockerfile` (build-arg через `BUILD_ARGS`) | Идентификатор сборки |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Сервис деплоится через зависимость `universal-chart`. Обычные значения задаются в блоке `universal-chart.services.message-hub.envs` для окружений `stage`/`preprod`/`production` (различаются адресами БД, Kafka, Redis, сервисов, именами топиков, числом реплик и таймаутами).
|
||||||
|
|
||||||
|
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Переменная | Секрет (`_default` / `production`) | Ключ (`secretKey`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `KAFKA_USERNAME` | `message-hub-kafka-secret` / `kafka-secret` | `username` |
|
||||||
|
| `KAFKA_PASSWORD` | `message-hub-kafka-secret` / `kafka-secret` | `password` |
|
||||||
|
| `DB_USERNAME` | `pm-postgresql-secret` / `postgres-pm-secret` | `user` |
|
||||||
|
| `DB_PASSWORD` | `pm-postgresql-secret` / `postgres-pm-secret` | `password` |
|
||||||
|
| `CACHE_PASSWORD` | `cache-secret-pm` / `cache-secret` | `password` |
|
||||||
|
| `S3_LOGIN` | `planning-s3-secret` / `s3-secret` | `username` |
|
||||||
|
| `S3_PASSWORD` | `planning-s3-secret` / `s3-secret` | `password` |
|
||||||
|
| `S3_BUCKET` | `planning-s3-secret` / `s3-secret` | `bucket` |
|
||||||
|
| `S3_HOST` | `planning-s3-secret` / `s3-secret` | `host` |
|
||||||
|
|
||||||
|
Помимо env, чарт монтирует CA-сертификат из секрета `kafka-secret` (ключ `ssl_cafile`) как файл `/opt/ssl/ca.pem` (том `kafka-ca-volume`, `readOnly`) — на него указывают `KAFKA_SSL_CAFILE` и `CACHE_SSL_CA_CERTS` в конфигурациях окружений.
|
||||||
|
|
||||||
|
Health-пробы (`.helm/values.yaml`): liveness `GET /health/live`, readiness `GET /health/ready`, порт `8000`.
|
||||||
|
|
||||||
|
## Переменные в 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` | `planning` | `0.0.1-stage` |
|
||||||
|
| ветка `master` | `preprod` | `message-hub-preprod` | `0.0.1-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `message-hub-prod` | `0.0.1-prod` |
|
||||||
|
|
||||||
|
Ключевые переменные пайплайна: `SERVICE_NAME=message-hub`, `DOCKERFILE_PATH=./docker/Dockerfile`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE=app`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` (`--set universal-chart...`), флаг `ENABLE_BUILD_IMAGE`. Отдельные job'ы `linter` (`ruff check` / `ruff format --check`) и `typechecker` (`mypy src`).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- Единая переменная `SETTINGS_VERIFY_SSL` управляет проверкой TLS сразу для S3 и всех шести HTTP-клиентов (alias у поля `VERIFY_SSL`). Отдельно на клиент это не настраивается.
|
||||||
|
- Поле `WORKER_TIMEOUT` есть и в классе `Settings` (читается как `SETTINGS_WORKER_TIMEOUT`, дефолт `30`), и как самостоятельная переменная `WORKER_TIMEOUT` для gunicorn (`entrypoint.sh`, Helm). Это разные переменные — не перепутайте.
|
||||||
|
- `SETTINGS_TOPICS` валидируется: допустимы только ключи `assets`, `planning`, `issues`. Прочие ключи вызывают ошибку старта. Если ключ отсутствует, соответствующий потребитель подписывается на пустое имя топика.
|
||||||
|
- Файл `.env.example` в репозитории сервиса не содержит части переменных (сервисы `PM_/ISSUES_/BI_/EAV_/PDF_CONVERTER_`, `MAILER_`, `LOG_`, ряд `SETTINGS_*`) — при реальном запуске задавайте их явно (полный перечень — в данном документе и в `.env.example` рядом).
|
||||||
|
- У большинства полей есть дефолты (локальные адреса), поэтому при пустом окружении сервис поднимется, но будет ходить на `localhost` — для рабочих окружений значения задаются через Helm.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Kafka поднимается через `docker-compose.yaml` (сервисы `kafka`, `kafka-ui`); PostgreSQL и Redis — внешние. Минимально стоит задать (с учётом префиксов):
|
||||||
|
|
||||||
|
- `SETTINGS_TOPICS` — карта логических топиков в реальные;
|
||||||
|
- `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`;
|
||||||
|
- `KAFKA_HOST`, `KAFKA_PORT` (для docker-compose — `kafka:9092`);
|
||||||
|
- `CACHE_HOST`, `CACHE_PORT` (+ `CACHE_PASSWORD`/`CACHE_SSL` при необходимости);
|
||||||
|
- `S3_HOST`, `S3_LOGIN`, `S3_PASSWORD`, `S3_BUCKET`;
|
||||||
|
- `HOST` для внешних сервисов, которые реально используются (`SAREX_`, `PM_`, `ISSUES_`, `BI_`, `EAV_`, `PDF_CONVERTER_`, `MAILER_`);
|
||||||
|
- `SETTINGS_VERIFY_SSL` (`0` локально, если сертификаты самоподписанные).
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example` рядом с этим документом.
|
||||||
94
apps/message-hub/ENDPOINTS.md
Normal file
94
apps/message-hub/ENDPOINTS.md
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# Интерфейсы сервиса message-hub
|
||||||
|
# Версия: 0.1.0
|
||||||
|
|
||||||
|
Документ описывает интерфейсную поверхность сервиса: HTTP-эндпоинты, WebSocket (Socket.IO), потребляемые топики Kafka и исходящие HTTP-запросы к внешним сервисам.
|
||||||
|
|
||||||
|
> В отличие от классических backend-сервисов, у `message-hub` нет публичного REST API и, соответственно, нет OpenAPI-схемы (health-роуты объявлены с `include_in_schema=False`). Основные интерфейсы — это Kafka-потребители и Socket.IO. Поэтому файла `openapi.yaml` для сервиса нет.
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Единое ASGI-приложение (`main:app`, `src/main.py`) объединяет три поверхности:
|
||||||
|
|
||||||
|
- **HTTP** — health-проверки (`src/health.py`), обслуживаются FastStream ASGI;
|
||||||
|
- **WebSocket** — Socket.IO-сервер (`socketio.AsyncServer` + `AsyncRedisManager`), пространство имён `/project` (`src/ws/namespaces.py`);
|
||||||
|
- **Kafka** — потребители сообщений (`src/consumers/*.py`) на базе FastStream `KafkaRouter`.
|
||||||
|
|
||||||
|
Исходящие вызовы к внешним сервисам выполняются через `httpx.AsyncClient` (`src/config/sarex.py`, `mailer.py`), базовый хост берётся из соответствующего `*_HOST` (см. `CONFIGURATION.md`).
|
||||||
|
|
||||||
|
## HTTP-эндпоинты (входящие)
|
||||||
|
|
||||||
|
| Метод | Путь | Ответ | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| GET | `/health/live` | `204 No Content` | Liveness-проба (всегда 204, если процесс жив) |
|
||||||
|
| GET | `/health/ready` | `204` / `500` | Readiness-проба: проверяет доступность Kafka (`broker.ping`) и Redis (`ping`); `500`, если хотя бы один недоступен |
|
||||||
|
|
||||||
|
Слушает `0.0.0.0:8000` (gunicorn + UvicornWorker). В Helm пробы настроены на `/health/live` и `/health/ready`, порт `8000`.
|
||||||
|
|
||||||
|
## WebSocket (Socket.IO)
|
||||||
|
|
||||||
|
Пространство имён: **`/project`** (`ProjectNamespace`). Менеджер состояния — Redis (`AsyncRedisManager`), CORS — `*`.
|
||||||
|
|
||||||
|
Параметры подключения (query string при `connect`): `project_id` (int), `user_id` (int). Клиент помещается в комнату `project:{project_id}`.
|
||||||
|
|
||||||
|
| Направление | Событие | Данные | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| client → server | `connect` | query: `project_id`, `user_id` | Подключение; вход в комнату проекта, регистрация присутствия в Redis |
|
||||||
|
| client → server | `heartbeat` | — | Продление TTL присутствия пользователя в проекте |
|
||||||
|
| client → server | `disconnect` | — | Отключение; выход из комнаты, снятие присутствия |
|
||||||
|
| server → client | `connected_users` | `list[int]` (user_id) | Актуальный список пользователей, подключённых к проекту (рассылается в комнату `project:{project_id}` при connect/disconnect) |
|
||||||
|
|
||||||
|
Ключи присутствия в Redis (TTL = `SETTINGS_CACHE_EXPIRATION`): `project:{project_id}:{user_id}`, `user_sids:{project_id}:{user_id}:{sid}`.
|
||||||
|
|
||||||
|
## Kafka-потребители (входящие сообщения)
|
||||||
|
|
||||||
|
Реальные имена топиков задаются переменной `SETTINGS_TOPICS` (маппинг логических имён `planning`/`assets`/`issues` в имена топиков). Формат сообщения — `MessageSchema` (`src/schemas/message.py`): поля `schema_version`, `model`, `sender`, `type`, `body`, `timestamp`, `xtraceId`, `user_id`, `tenants`, `tags`.
|
||||||
|
|
||||||
|
| Топик (логич.) | `group_id` | Offset reset | Обработчик | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `assets` | `assets_consumer` | earliest | `update_attributes_with_assets` | Обновление атрибутов по ассетам |
|
||||||
|
| `planning` | `planning` | earliest | диспетчер по `type` (см. ниже) | Обработка событий планирования |
|
||||||
|
| `issues` | `project_entity` | earliest | `create_or_update_entity` | Создание/обновление сущности проекта из issue |
|
||||||
|
| `issues` | `analytic_values` | latest | `proceed_entity_value` | Обработка значений аналитики по сущности |
|
||||||
|
|
||||||
|
Диспетчеризация топика `planning` по полю `type` (`src/consumers/planning.py`):
|
||||||
|
|
||||||
|
| `type` сообщения | Обработчик | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `auto_scheduling` | `handle_auto_scheduling` | Автопланирование |
|
||||||
|
| `get_converted_file` | `handle_file_export` | Экспорт/конвертация файла |
|
||||||
|
| `system_log` | `handle_system_log` | Системный журнал изменений |
|
||||||
|
| `email_notifications` | `handle_email_notification` | Email-уведомления |
|
||||||
|
| `sync_entity_to_project` | `handle_project_entity_sync` | Синхронизация сущности в проект |
|
||||||
|
| `sync_detailed_tasks_attributes` | `handle_task_attributes_sync` | Синхронизация атрибутов детальных задач |
|
||||||
|
| `sync_tasks` | `handle_task_sync` | Синхронизация задач |
|
||||||
|
| `detailed_tasks_analytics` | `handle_task_analytics` | Аналитика по детальным задачам |
|
||||||
|
| `update_project` | — | Пропускается (в списке `PLANNING_SKIP_TYPES`) |
|
||||||
|
|
||||||
|
Обработка обёрнута в `retry_handler` (`src/infrastructure/kafka/retry.py`): до `SETTINGS_MAX_RETRIES` попыток с экспоненциальной задержкой (старт `SETTINGS_RETRY_DELAY`), ручной `ack` после успеха либо исчерпания попыток.
|
||||||
|
|
||||||
|
## Исходящие HTTP-запросы к внешним сервисам
|
||||||
|
|
||||||
|
Базовый хост каждого сервиса — из соответствующего `*_HOST` (см. `CONFIGURATION.md`). Итоговый URL = `<HOST>` + путь из таблицы.
|
||||||
|
|
||||||
|
| Сервис (`config`) | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `bi` (`BI_HOST`) | POST | `/internal/values/sync_value/` | Синхронизация значений аналитики |
|
||||||
|
| `pm` (`PM_HOST`) | POST | `/internal/pm/detailed_tasks/` | Синхронизация атрибутов детальных задач |
|
||||||
|
| `pm` (`PM_HOST`) | POST | `/internal/pm/{endpoint}/` | Автопланирование (endpoint из тела сообщения) |
|
||||||
|
| `pm` (`PM_HOST`) | POST | `/internal/pm/sync_tasks/` | Синхронизация задач |
|
||||||
|
| `pdf` (`PDF_CONVERTER_HOST`) | POST | `/convert_to_pdf/` | Конвертация HTML → PDF |
|
||||||
|
| `mailer` (`MAILER_HOST`) | POST | `{MAILER_PREFIX}/emails/bulk` | Массовая отправка email |
|
||||||
|
| `issues` (`ISSUES_HOST`) | GET | `/api/issue-types/?company_id={id}` | Список типов issue компании |
|
||||||
|
| `issues` (`ISSUES_HOST`) | GET | `/api/companies/{company_id}/status-model/v2/?issue_type_id={id}` | Модель статусов по типу issue |
|
||||||
|
| `eav` (`EAV_HOST`) | POST | `/api/v4/assets/search/` | Поиск ассетов по идентификаторам |
|
||||||
|
| `eav` (`EAV_HOST`) | GET | `/api/v4/attribute/` | Список атрибутов |
|
||||||
|
| `sarex` (`SAREX_HOST`) | GET | `internal/client/token/{user_id}/` | Получение токена клиента |
|
||||||
|
|
||||||
|
## Внешние зависимости (инфраструктура)
|
||||||
|
|
||||||
|
| Зависимость | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| Kafka | Источник сообщений (топики `planning`/`assets`/`issues`) |
|
||||||
|
| PostgreSQL | Хранилище данных (SQLAlchemy + psycopg) |
|
||||||
|
| Redis | Менеджер состояния Socket.IO и хранилище присутствия пользователей |
|
||||||
|
| S3 (Yandex Object Storage) | Файловое хранилище |
|
||||||
56
apps/notes/.env.example
Normal file
56
apps/notes/.env.example
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# App
|
||||||
|
BASE_HOST=https://stage-api.sarex.io/notes
|
||||||
|
API_PREFIX=/api/v1
|
||||||
|
# DEBUG влияет и на PostgresSettings (при true хост БД -> localhost:6432), и на Settings.debug
|
||||||
|
DEBUG=false
|
||||||
|
REGISTRY=cr.yandex/crp3ccidau046kdj8g9q/
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
|
||||||
|
# Database (префикс PG_)
|
||||||
|
PG_LOGIN=notes
|
||||||
|
PG_PASSWORD=notes
|
||||||
|
PG_DB=notes_db
|
||||||
|
PG_HOST=127.0.0.1
|
||||||
|
PG_PORT=5432
|
||||||
|
# В коде подключение к БД всегда идёт с sslmode=verify-full (см. замечания в CONFIGURATION.md)
|
||||||
|
PG_SSL_MODE=verify-full
|
||||||
|
|
||||||
|
# Django (sarex-backend, префикс DJANGO_)
|
||||||
|
# DJANGO_USE=false — отключает проверку токена через Django (локальная разработка, тестовый пользователь)
|
||||||
|
DJANGO_USE=false
|
||||||
|
DJANGO_HOST=https://stage.sarex.io
|
||||||
|
DJANGO_TIMEOUT=10
|
||||||
|
DJANGO_TOKEN=token
|
||||||
|
|
||||||
|
# Documentations (префикс DOCUMENTATIONS_)
|
||||||
|
DOCUMENTATIONS_HOST=https://stage-api.sarex.io/documentations/api/v1
|
||||||
|
|
||||||
|
# Workflows (префикс WORKFLOW_)
|
||||||
|
WORKFLOW_HOST=https://stage-api.sarex.io/workflows/api/v1
|
||||||
|
WORKFLOW_TAG=dev
|
||||||
|
WORKFLOW_TIMEOUT=30
|
||||||
|
|
||||||
|
# Attachments (префикс ATTACHMENT_)
|
||||||
|
ATTACHMENT_HOST=http://attachments-service.attachments-stage.svc.cluster.local:80/api/v1
|
||||||
|
ATTACHMENT_TIMEOUT=30
|
||||||
|
|
||||||
|
# Внешние сервисы (top-level Settings)
|
||||||
|
FAAS_SERVICE=https://stage-api.sarex.io/lambdas
|
||||||
|
WORKSPACE_URL=https://stage-api.sarex.io/workspaces/api/v1
|
||||||
|
RESOURCE_URL=https://stage-api.sarex.io/resources/api/v1
|
||||||
|
# Включает вычисление resource_id по workspace/target при создании заметки
|
||||||
|
SYNC_RESOURCE_ID=false
|
||||||
|
|
||||||
|
# ND-сервис (проксирование НД, префиксов нет)
|
||||||
|
ENABLE_ND=false
|
||||||
|
ND_JWT_ENABLE=false
|
||||||
|
ND_JWT_SECRET=
|
||||||
|
ND_JWT_ALGORITHM=HS256
|
||||||
|
ND_ACCESS_TOKEN_EXPIRE_DAYS=30
|
||||||
|
|
||||||
|
# Logger (префикс LOG_)
|
||||||
|
# LOG_FORMAT='[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s'
|
||||||
|
|
||||||
|
# Инфраструктура / запуск (не читаются кодом приложения)
|
||||||
|
# Таймаут воркеров gunicorn (entrypoint.sh)
|
||||||
|
TIMEOUT=120
|
||||||
184
apps/notes/CONFIGURATION.md
Normal file
184
apps/notes/CONFIGURATION.md
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# Конфигурация проекта notes-backend
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/app/config.py` через библиотеку [`pydantic`](https://docs.pydantic.dev/) (`pydantic.BaseSettings`, pydantic v1).
|
||||||
|
|
||||||
|
Конфигурация разбита на несколько классов настроек, каждый со своим префиксом (`Config.env_prefix`):
|
||||||
|
|
||||||
|
- `PostgresSettings` — префикс `PG_`;
|
||||||
|
- `DjangoSettings` — префикс `DJANGO_`;
|
||||||
|
- `Documentations` — префикс `DOCUMENTATIONS_`;
|
||||||
|
- `WorkflowSettings` — префикс `WORKFLOW_`;
|
||||||
|
- `AttachmentSettings` — префикс `ATTACHMENT_`;
|
||||||
|
- `LoggerSettings` — префикс `LOG_`;
|
||||||
|
- корневой `Settings` — **без префикса** (поля читаются по имени в верхнем регистре, напр. `BASE_HOST`, `FAAS_SERVICE`).
|
||||||
|
|
||||||
|
Каждый вложенный класс настроек инстанцируется отдельно и читает свои переменные из окружения по своему префиксу. Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (uvicorn/gunicorn) | Переменные окружения процесса. Приложение **не загружает `.env` автоматически** (в `config.py` нет `env_file`/`python-dotenv`) — переменные нужно экспортировать самому |
|
||||||
|
| Локально (контейнеры) | `docker-compose.yml`: блок `environment` для сервиса `notes` (`PG_HOST`, `PG_DB`, `PG_LOGIN`, `PG_PASSWORD`, `DJANGO_USE`, `TIMEOUT`) |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) чарта `universal-chart` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) — выбор окружения по ветке/тегу |
|
||||||
|
|
||||||
|
Порядок запуска в контейнере (`entrypoint.sh`): сначала выполняются миграции (`alembic upgrade head`), затем стартует gunicorn с воркерами `uvicorn.workers.UvicornWorker` на `0.0.0.0:8000` с таймаутом `$TIMEOUT`.
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Дефолт `—` означает, что явного значения по умолчанию нет (пустая строка/обязательно задать для реального окружения).
|
||||||
|
|
||||||
|
### App / корневой `Settings` (без префикса)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `BASE_HOST` | string | `https://lk.sarex.io` | Внешний базовый URL сервиса |
|
||||||
|
| `API_PREFIX` | string | `/api/v1` | Префикс публичного API |
|
||||||
|
| `DEBUG` | bool | `False` | Режим отладки. Также влияет на `PostgresSettings` (см. ниже) и включает `ProfilingSqlQueryMiddleware` |
|
||||||
|
| `FAAS_SERVICE` | string | `https://stage-api.sarex.io/lambdas` | URL сервиса лямбд/FaaS |
|
||||||
|
| `WORKSPACE_URL` | string | `https://stage-api.sarex.io/workspaces/api/v1` | URL сервиса рабочих областей (для `SYNC_RESOURCE_ID`) |
|
||||||
|
| `RESOURCE_URL` | string | `https://stage-api.sarex.io/resources/api/v1` | URL сервиса ресурсов (для `SYNC_RESOURCE_ID`) |
|
||||||
|
| `SYNC_RESOURCE_ID` | bool | `False` | При `True` `resource_id` заметки вычисляется по workspace → target → resource |
|
||||||
|
| `REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q/` | Реестр образов (используется вспомогательно) |
|
||||||
|
| `ENABLE_ND` | bool | `False` | Подключить роутер `nd_service` (`/api/v1/nd/*`) |
|
||||||
|
|
||||||
|
### ND-сервис (без префикса)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `ND_JWT_ENABLE` | bool | `False` | Включить проверку JWT (`JWTBearer`) на части эндпоинтов НД |
|
||||||
|
| `ND_JWT_SECRET` | string | `""` | Секрет для подписи/проверки JWT |
|
||||||
|
| `ND_JWT_ALGORITHM` | string | `HS256` | Алгоритм JWT |
|
||||||
|
| `ND_ACCESS_TOKEN_EXPIRE_DAYS` | int | `30` | Срок жизни токена НД (дни) |
|
||||||
|
|
||||||
|
### Database (`PG_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `PG_LOGIN` | string | `""` | Пользователь PostgreSQL |
|
||||||
|
| `PG_PASSWORD` | string | `""` | Пароль пользователя |
|
||||||
|
| `PG_DB` | string | `""` | Имя базы данных |
|
||||||
|
| `PG_HOST` | string | `""` | Хост PostgreSQL |
|
||||||
|
| `PG_PORT` | string | `5432` | Порт PostgreSQL |
|
||||||
|
| `PG_SSL_MODE` | string | `disable` | Поле `ssl_mode` настроек (см. замечание ниже — фактически подключение всегда `verify-full`) |
|
||||||
|
| `DEBUG` | bool | `False` | Через `Field(env='DEBUG')`. При `True` хост БД принудительно `localhost:6432` (pgbouncer) |
|
||||||
|
|
||||||
|
> Итоговый DSN собирается в `PostgresSettings.url` как `postgresql://<login>:<password>@<host>:<port>/<db>`.
|
||||||
|
|
||||||
|
### Django / sarex-backend (`DJANGO_*`)
|
||||||
|
|
||||||
|
Клиент к основному backend (Django). Используется middleware `DjangoUserMiddleware` для аутентификации пользователя (запрос `/client/settings/`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DJANGO_USE` | bool | `True` | При `False` аутентификация через Django отключается, используется тестовый пользователь (`is_admin=True`) |
|
||||||
|
| `DJANGO_HOST` | string | `http://localhost:8000` | Базовый хост Django (к нему добавляется `/api`) |
|
||||||
|
| `DJANGO_TIMEOUT` | int | `10` | Таймаут HTTP-клиента (сек) |
|
||||||
|
| `DJANGO_TOKEN` | string | `token` | Токен для служебных (sync) запросов |
|
||||||
|
|
||||||
|
### Documentations (`DOCUMENTATIONS_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DOCUMENTATIONS_HOST` | string | `https://stage-api.sarex.io/documentations/api/v1` | URL сервиса документации |
|
||||||
|
|
||||||
|
### Workflows (`WORKFLOW_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `WORKFLOW_HOST` | string | `https://stage-api.sarex.io/workflows/api/v1` | URL сервиса обработки процессов |
|
||||||
|
| `WORKFLOW_TAG` | string | `dev` | Тег/канал workflow (`dev`/`stable`) |
|
||||||
|
| `WORKFLOW_TIMEOUT` | int | `30` | Таймаут HTTP-клиента (сек) |
|
||||||
|
|
||||||
|
### Attachments (`ATTACHMENT_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `ATTACHMENT_HOST` | string | `http://attachments-service.attachments-stage.svc.cluster.local:80/api/v1` | URL сервиса вложений |
|
||||||
|
| `ATTACHMENT_TIMEOUT` | int | `30` | Таймаут HTTP-клиента (сек) |
|
||||||
|
|
||||||
|
### Logger (`LOG_*`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | `INFO` | Уровень логирования (`DEBUG`/`INFO`/…); при неизвестном значении используется `INFO` |
|
||||||
|
| `LOG_FORMAT` | string | `[%(asctime)s] [%(levelname)s] [%(filename)s]: %(message)s` | Формат сообщений лога |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры, сборки и вспомогательных утилит
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `TIMEOUT` | `docker-compose.yml`, `.helm/values.yaml`, `entrypoint.sh` | Таймаут воркеров gunicorn (`--timeout`) |
|
||||||
|
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` | `docker-compose.yml` (сервис `database`) | Параметры локального контейнера Postgres |
|
||||||
|
| `PGADMIN_DEFAULT_EMAIL` / `PGADMIN_DEFAULT_PASSWORD` | `docker-compose.yml` (сервис `pgadmin`) | Учётные данные pgAdmin для локальной разработки |
|
||||||
|
| `NPM_NEXUS_TOKEN` | (для фронтенда) | Токен приватного npm-реестра — здесь не используется |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Чарт — `universal-chart` (зависимость в `.helm/Chart.yaml`). Обычные значения задаются в блоке `services.main.envs` и различаются по окружениям (`_default`/`stage`/`preprod`/`production`):
|
||||||
|
|
||||||
|
| Переменная | `_default` (stage) | `preprod` | `production` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `PG_SSL_MODE` | `verify-full` | — | — |
|
||||||
|
| `PG_PORT` | `6432` | — | — |
|
||||||
|
| `DJANGO_HOST` | `https://stage.sarex.io` | `https://lk.preprod.sarex.io` | `https://lk.sarex.io` |
|
||||||
|
| `BASE_HOST` | `https://stage-api.sarex.io/notes` | `https://api.preprod.sarex.io/notes` | `https://api.sarex.io/notes` |
|
||||||
|
| `TIMEOUT` | `120` | — | — |
|
||||||
|
| `FAAS_SERVICE` | `https://stage-api.sarex.io/lambdas` | `https://api.preprod.sarex.io/lambdas` | `https://api.sarex.io/lambdas` |
|
||||||
|
| `WORKSPACE_URL` | `https://stage-api.sarex.io/workspaces/api/v1` | `https://api.preprod.sarex.io/workspaces/api/v1` | `https://api.sarex.io/workspaces/api/v1` |
|
||||||
|
| `WORKFLOW_HOST` | `https://stage-api.sarex.io/workflows/api/v1` | `https://api.preprod.sarex.io/workflows/api/v1` | `https://api.sarex.io/workflows/api/v1` |
|
||||||
|
| `WORKFLOW_TAG` | `dev` | `stable` | `stable` |
|
||||||
|
| `RESOURCE_URL` | `https://stage-api.sarex.io/resources/api/v1` | `https://api.preprod.sarex.io/resources/api/v1` | `https://api.sarex.io/resources/api/v1` |
|
||||||
|
| `SYNC_RESOURCE_ID` | `0` | — | — |
|
||||||
|
| `ENABLE_ND` | `1` | `0` | `0` |
|
||||||
|
| `ATTACHMENT_HOST` | `…attachments-stage…` | `…attachments-preprod…` | `…attachments-prod…` |
|
||||||
|
|
||||||
|
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Переменная | Секрет (`secretName`, stage) | Ключ (`secretKey`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `PG_DB` | `notes-postgresql-secret` | `database` |
|
||||||
|
| `PG_LOGIN` | `notes-postgresql-secret` | `username` |
|
||||||
|
| `PG_PASSWORD` | `notes-postgresql-secret` | `password` |
|
||||||
|
| `PG_HOST` | `notes-postgresql-secret` | `host` |
|
||||||
|
| `DJANGO_TOKEN` | `django-secret` | `token` |
|
||||||
|
|
||||||
|
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, порт `8000`, реплики, ресурсы, probes отключены), `image.*`, `service.*` (`notes-backend-service`, в production — `backend-service`), `imagePullSecrets` (`dockerhub`), `ingress.enabled: false`.
|
||||||
|
|
||||||
|
## Переменные в CI (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||||
|
|
||||||
|
| Условие | STAND | NAMESPACE | CHART_VERSION |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `aero` | `0.0.1-stage` |
|
||||||
|
| ветка `master` | `preprod` | `notes-preprod` | `0.0.1-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `notes-prod` | `0.0.1-prod` |
|
||||||
|
|
||||||
|
Ключевые переменные: `SERVICE_NAME=notes-backend`, `DOCKERFILE_PATH`, `RELEASE_NAME`, `CHART_NAME`, `HELM_SET_ARGS` (проброс образа/окружения в `universal-chart`). Джобы `linter` (flake8), `typechecker` (mypy), `rest-api` (docker-compose + newman/postman) выполняются на MR/ветках/тегах.
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **SSL к БД всегда `verify-full`.** Поле `PG_SSL_MODE` (по умолчанию `disable`) в код подключения не попадает: и `PostgresSettings.create_session`, и `DBSessionMiddleware` жёстко передают `connect_args={'sslmode': "verify-full"}`. CA-сертификат монтируется из образа: `Dockerfile` копирует `yandex_pg.pem` → `/root/.postgresql/root.crt`.
|
||||||
|
- **`DEBUG` — общая переменная.** Она читается и корневым `Settings.debug`, и `PostgresSettings.debug` (`Field(env='DEBUG')`). При `DEBUG=true` хост БД принудительно становится `localhost:6432`, а также включается `ProfilingSqlQueryMiddleware`.
|
||||||
|
- **Приложение не загружает `.env` автоматически** — переменные нужно экспортировать в окружение (или задавать через `--env`/compose/helm).
|
||||||
|
- **Аутентификация.** При `DJANGO_USE=true` каждый публичный запрос (кроме путей с `/nd`) проверяется через Django `/client/settings/` по заголовку `Authorization` (опционально `Identity` для Zitadel). При `DJANGO_USE=false` подставляется тестовый администратор — использовать только локально.
|
||||||
|
- **Роутер НД включается флагом `ENABLE_ND`.** На stage он включён (`1`), на preprod/production выключен (`0`).
|
||||||
|
- Значение `SYNC_RESOURCE_ID` требует доступности `WORKSPACE_URL` и `RESOURCE_URL`; клиент к ним создаётся с `verify=False`.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Postgres и pgAdmin поднимаются через `docker-compose up -d database pgadmin`. Минимально необходимо задать:
|
||||||
|
|
||||||
|
- `PG_LOGIN`, `PG_PASSWORD`, `PG_DB`, `PG_HOST`, `PG_PORT`
|
||||||
|
- `DJANGO_USE=false` (чтобы не требовать реальный Django-токен)
|
||||||
|
- при `ENABLE_ND=true` — `ND_JWT_*` при необходимости проверки токена
|
||||||
|
|
||||||
|
Остальные значения имеют рабочие дефолты (см. `.env.example`).
|
||||||
76
apps/notes/ENDPOINTS.md
Normal file
76
apps/notes/ENDPOINTS.md
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует notes-frontend
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `notes-frontend`, remote-имя `srx_notes`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Все запросы собраны в объекте `notesApi` в `module/api/endpoints.ts`. Каждый метод вызывает `httpService` (`module/api/http-service.ts`, обёртка над `@sarex-team/sdk-js`) одним из методов `getRequest` / `postRequest` / `putRequest` / `deleteRequest`, передавая:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (относительно базового хоста сервиса);
|
||||||
|
- `data` — тело запроса (для POST/PUT).
|
||||||
|
|
||||||
|
Базовый хост подставляется `httpService` из `module/api/hosts.ts` в зависимости от `BUILD_ENV` (`local`/`stage`/`preprod`/`prod`). `BUILD_ENV` задаётся через webpack `DefinePlugin` на этапе сборки (`build.config.js`). Итоговый URL = `<базовый хост сервиса>` + `url`. Удалённый модуль `documentations` (Module Federation) подключается отдельно через `module/api/modules-hosts.ts`.
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `module/api/hosts.ts`.
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | `stage` | `prod` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `notes` | Бэкенд заметок (notes-backend) | `https://stage-api.sarex.io/notes` | `https://api.sarex.io/notes` |
|
||||||
|
| `sarexApi` | Gateway/API Sarex (`/eav`, `/notes`) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||||
|
| `documentations` | Сервис документации (бандлы) | `https://stage-api.sarex.io/documentations/` | `https://api.sarex.io/documentations/` |
|
||||||
|
| `sarex` | Основной backend Sarex (`/api/core`) | `""` (относительные пути) | `""` |
|
||||||
|
| `workspaces` | Сервис рабочих областей | `https://stage-workspaces.sarex.io` | `https://workspaces.sarex.io` |
|
||||||
|
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
|
||||||
|
|
||||||
|
> Также определены окружения `local` и `preprod`. В `local` `sarex` указывает на `https://stage.sarex.io`, в остальных — пустая строка (относительные пути). Сервисы `workspaces` и `zitadel` объявлены в хостах, но напрямую из `endpoints.ts` не вызываются. Удалённый модуль `documentations` описан в `module/api/modules-hosts.ts` (`…/documentations/static/module/remoteEntry.js`).
|
||||||
|
|
||||||
|
## Эндпоинты по сервисам
|
||||||
|
|
||||||
|
### `notes` — Бэкенд заметок (notes-backend)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `createNote` | POST | `/api/v1/notes/` | Создать заметку |
|
||||||
|
| `getNote` | GET | `/api/v1/notes/{id}/` | Заметка по id |
|
||||||
|
| `updateNote` | PUT | `/api/v1/notes/{id}/` | Обновить заметку |
|
||||||
|
| `deleteNote` | DELETE | `/api/v1/notes/{id}/` | Удалить заметку |
|
||||||
|
| `getNoteAttachments` | GET | `/api/v1/notes/{noteId}/attachments/` | Вложения заметки |
|
||||||
|
| `createAttachmentsToNote` | POST | `/api/v1/notes/{noteId}/attachments/` | Загрузить вложения к заметке |
|
||||||
|
| `deleteAttachmentsFromNote` | DELETE | `/api/v1/attachments/{attachmentId}/` | Удалить вложение |
|
||||||
|
| `generateDocument` | POST | `/api/v1/notes/{noteId}/generate_document/` | Сгенерировать документ по заметке |
|
||||||
|
| `postScreen` | POST | `/api/v1/nd/bound-note/{noteId}/` | Привязать скриншот/файл к заметке (НД) |
|
||||||
|
|
||||||
|
### `sarexApi` — Gateway/API Sarex
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getAttributes` | GET | `/eav/api/v0/attribute/` | Атрибуты (EAV) |
|
||||||
|
| `createLinkNote` | POST | `/notes/api/v1/links/` | Привязать ссылку к заметке (через gateway) |
|
||||||
|
|
||||||
|
### `documentations` — Сервис документации
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getBundle` | GET | `/api/v1/bundles/{id}` | Бандл по id |
|
||||||
|
|
||||||
|
### `sarex` — Основной backend Sarex (`/api/core`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getCompanies` | GET | `/api/core/companies/` | Список компаний |
|
||||||
|
| `createLink` | POST | `/api/core/target-links/` | Создать ссылку у target |
|
||||||
|
| `updateLink` | PUT | `/api/core/target-links/{id}/` | Обновить ссылку |
|
||||||
|
| `deleteLink` | DELETE | `/api/core/target-links/{id}/` | Удалить ссылку |
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
Централизованного модуля обработки ошибок (аналога `errors.ts`) нет. Ответы `httpService` (`@sarex-team/sdk-js` поверх axios) обрабатываются в местах вызова — в MobX-сторах (`module/Notes/stores/notes.ts`, `sendScreen.ts`) через `try/catch`.
|
||||||
|
|
||||||
|
## Замечания
|
||||||
|
|
||||||
|
- Путь `postScreen` (`/api/v1/nd/bound-note/{noteId}/`) не совпадает с фактическим маршрутом бэкенда `/api/v1/nd/nd_proxy/{instance_id}/bound/` — при интеграции стоит свериться с актуальным API notes-backend.
|
||||||
|
- Часть создания/обновления ссылок идёт через сервис `sarex` (`/api/core/target-links/`), а привязка ссылки к заметке — через `sarexApi` (`/notes/api/v1/links/`).
|
||||||
|
- В `endpoints.ts` присутствует закомментированный устаревший вариант `updateLink` (декларативный стиль `service/method/path/body`) — актуальна функция-обёртка над `httpService`.
|
||||||
737
apps/notes/openapi.yaml
Normal file
737
apps/notes/openapi.yaml
Normal file
@ -0,0 +1,737 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: Notes Service API
|
||||||
|
version: "0.0.1"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **notes-backend** (`aero/notes-backend`) — управление
|
||||||
|
заметками к сущностям (workspace), их ссылками, документами и вложениями,
|
||||||
|
а также генерацией документов через workflow.
|
||||||
|
|
||||||
|
Сервис написан на Python (**FastAPI**, pydantic v1). Приложение собирается
|
||||||
|
фабрикой `get_app` в `src/app/main.py`. Публичный роутинг подключается с
|
||||||
|
префиксом `/api/v1` (`src/app/routers/__init__.py`) и включает группы
|
||||||
|
`notes`, `links`, `documents`, `attachments`.
|
||||||
|
|
||||||
|
Опционально (при `ENABLE_ND=true`) подключается роутер `nd_service` с
|
||||||
|
префиксом `/api/v1/nd` (`src/app/nd_service/router.py`).
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Аутентификация выполняется middleware `DjangoUserMiddleware`
|
||||||
|
(`src/app/middleware.py`). При `DJANGO_USE=true` каждый запрос (кроме путей,
|
||||||
|
содержащих `/nd`) должен содержать заголовок `Authorization` — токен
|
||||||
|
проверяется обращением к Django `/client/settings/`. Опционально
|
||||||
|
передаётся заголовок `Identity` (режим Zitadel). Если заголовок
|
||||||
|
`Authorization` отсутствует — возвращается `401`.
|
||||||
|
|
||||||
|
При `DJANGO_USE=false` middleware подставляет тестового пользователя-
|
||||||
|
администратора (использовать только локально).
|
||||||
|
|
||||||
|
Дополнительно, права проверяются зависимостью `PermissionManager`:
|
||||||
|
для не-админов метод сопоставляется с правом (`base.can_add_note` для POST,
|
||||||
|
`base.can_view_note` для GET, `base.can_change_note` для PUT/PATCH,
|
||||||
|
`base.can_delete_note` для DELETE); при отсутствии права — `403`.
|
||||||
|
|
||||||
|
Часть эндпоинтов НД (`/api/v1/nd/nd_proxy/*` на запись) защищена
|
||||||
|
JWT (`JWTBearer`, включается флагом `ND_JWT_ENABLE`).
|
||||||
|
|
||||||
|
### Замечания (расхождения кода)
|
||||||
|
- Ошибки валидации тела/параметров (Pydantic) отдаются FastAPI в
|
||||||
|
стандартном формате `422`.
|
||||||
|
- Подключение к БД всегда идёт с `sslmode=verify-full` независимо от
|
||||||
|
значения `PG_SSL_MODE`.
|
||||||
|
- Многие пути завершаются слэшем (`/api/v1/notes/{id}/`).
|
||||||
|
|
||||||
|
contact:
|
||||||
|
name: notes-backend
|
||||||
|
url: https://gitlab/aero/notes-backend
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://api.sarex.io/notes
|
||||||
|
description: Production (ingress, BASE_HOST)
|
||||||
|
- url: https://api.preprod.sarex.io/notes
|
||||||
|
description: Preprod (ingress, BASE_HOST)
|
||||||
|
- url: https://stage-api.sarex.io/notes
|
||||||
|
description: Stage (ingress, BASE_HOST)
|
||||||
|
- url: http://notes-backend-service:8000
|
||||||
|
description: Внутрикластерный адрес (ClusterIP)
|
||||||
|
- url: http://localhost:8000
|
||||||
|
description: Локальный запуск (gunicorn/docker, порт 8000)
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: notes
|
||||||
|
description: Заметки — создание, просмотр, обновление, поиск, документы и вложения
|
||||||
|
- name: links
|
||||||
|
description: Ссылки, привязанные к заметкам
|
||||||
|
- name: documents
|
||||||
|
description: Документы, привязанные к заметкам
|
||||||
|
- name: attachments
|
||||||
|
description: Вложения заметок
|
||||||
|
- name: nd_service
|
||||||
|
description: Проксирование НД (подключается при ENABLE_ND=true)
|
||||||
|
|
||||||
|
security:
|
||||||
|
- bearerAuth: []
|
||||||
|
|
||||||
|
paths:
|
||||||
|
/api/v1/notes/:
|
||||||
|
post:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Создать заметку
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/BaseNote'
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Создано
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
'400':
|
||||||
|
description: Некорректный company_id
|
||||||
|
get:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Список заметок
|
||||||
|
parameters:
|
||||||
|
- { name: search, in: query, schema: { type: string } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer, default: 1000, minimum: 0 } }
|
||||||
|
- { name: offset, in: query, schema: { type: integer, default: 0, minimum: 0 } }
|
||||||
|
- { name: created_from, in: query, schema: { type: string, format: date-time } }
|
||||||
|
- { name: created_to, in: query, schema: { type: string, format: date-time } }
|
||||||
|
- { name: time_start, in: query, schema: { type: string, format: date-time } }
|
||||||
|
- { name: time_end, in: query, schema: { type: string, format: date-time } }
|
||||||
|
- { name: author, in: query, schema: { type: string } }
|
||||||
|
- { name: description, in: query, schema: { type: boolean } }
|
||||||
|
- { name: document, in: query, schema: { type: boolean } }
|
||||||
|
- { name: resource_id, in: query, description: "Список UUID через запятую", schema: { type: string } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Note'
|
||||||
|
|
||||||
|
/api/v1/notes/{service}/{entity}/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Заметки по сервису/сущности/инстансу
|
||||||
|
parameters:
|
||||||
|
- { name: service, in: path, required: true, schema: { $ref: '#/components/schemas/Service' } }
|
||||||
|
- { name: entity, in: path, required: true, schema: { $ref: '#/components/schemas/Entity' } }
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: string } }
|
||||||
|
- { name: full, in: query, description: "Вернуть расширенные заметки (ExtendedNote)", schema: { type: boolean, default: false } }
|
||||||
|
- { name: search, in: query, schema: { type: string } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer, default: 1000 } }
|
||||||
|
- { name: offset, in: query, schema: { type: integer, default: 0 } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
oneOf:
|
||||||
|
- $ref: '#/components/schemas/Note'
|
||||||
|
- $ref: '#/components/schemas/ExtendedNote'
|
||||||
|
|
||||||
|
/api/v1/notes/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Заметка по id
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Note' }
|
||||||
|
'404':
|
||||||
|
description: Не найдено
|
||||||
|
put:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Обновить заметку
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/BaseNote' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Note' }
|
||||||
|
'400': { description: Некорректный company_id }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
delete:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Удалить заметку
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Удалено (возвращает удалённый объект)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Note' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/notes/{instance_id}/documents/:
|
||||||
|
post:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Добавить документы к заметке
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/BaseDocument' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Создано
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/Document' }
|
||||||
|
'404': { description: Заметка не найдена }
|
||||||
|
|
||||||
|
/api/v1/notes/{instance_id}/attachments/:
|
||||||
|
post:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Загрузить файлы-вложения к заметке
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
files:
|
||||||
|
type: array
|
||||||
|
items: { type: string, format: binary }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: { type: string }
|
||||||
|
link: { type: string }
|
||||||
|
id: { type: integer }
|
||||||
|
attachment_type: { type: string }
|
||||||
|
'404': { description: Заметка не найдена }
|
||||||
|
get:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Вложения заметки
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { type: array, items: { type: object } }
|
||||||
|
'404': { description: Заметка не найдена }
|
||||||
|
|
||||||
|
/api/v1/notes/{instance_id}/bound_attachments/:
|
||||||
|
post:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Привязать существующее вложение к заметке
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/BaseAttachment' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Создано
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Attachment' }
|
||||||
|
'404': { description: Заметка не найдена }
|
||||||
|
|
||||||
|
/api/v1/notes/{instance_id}/generate_document/:
|
||||||
|
post:
|
||||||
|
tags: [notes]
|
||||||
|
summary: Сгенерировать документ по заметке (через workflow)
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/DocumentCreation' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/WFResponse' }
|
||||||
|
'404': { description: Заметка не найдена }
|
||||||
|
|
||||||
|
/api/v1/links/:
|
||||||
|
post:
|
||||||
|
tags: [links]
|
||||||
|
summary: Создать ссылку
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/BaseLink' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Создано
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Link' }
|
||||||
|
'400': { description: Некорректный note_id }
|
||||||
|
get:
|
||||||
|
tags: [links]
|
||||||
|
summary: Список ссылок
|
||||||
|
parameters:
|
||||||
|
- { name: note, in: query, description: "Фильтр по note_id", schema: { type: integer } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer, default: 1000 } }
|
||||||
|
- { name: offset, in: query, schema: { type: integer, default: 0 } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { type: array, items: { $ref: '#/components/schemas/Link' } }
|
||||||
|
|
||||||
|
/api/v1/links/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [links]
|
||||||
|
summary: Ссылка по id
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Link' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
put:
|
||||||
|
tags: [links]
|
||||||
|
summary: Обновить ссылку
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/BaseLink' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Link' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
delete:
|
||||||
|
tags: [links]
|
||||||
|
summary: Удалить ссылку
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Удалено
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Link' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/documents/{instance_id}/:
|
||||||
|
delete:
|
||||||
|
tags: [documents]
|
||||||
|
summary: Удалить документ заметки
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Удалено
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Document' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/attachments/{instance_id}/:
|
||||||
|
delete:
|
||||||
|
tags: [attachments]
|
||||||
|
summary: Удалить вложение
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, description: "attachment_id", schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Удалено
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Attachment' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/nd/nd_proxy/:
|
||||||
|
post:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Создать НД-прокси
|
||||||
|
security: [{ bearerAuth: [] }]
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxyCreate' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxySchema' }
|
||||||
|
'400': { description: Нарушение целостности }
|
||||||
|
get:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Список НД-прокси
|
||||||
|
security: [{ bearerAuth: [] }]
|
||||||
|
parameters:
|
||||||
|
- { name: is_bound, in: query, schema: { type: boolean } }
|
||||||
|
- { name: limit, in: query, schema: { type: integer, default: 1000 } }
|
||||||
|
- { name: offset, in: query, schema: { type: integer, default: 0 } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { type: array, items: { $ref: '#/components/schemas/NDProxySchema' } }
|
||||||
|
|
||||||
|
/api/v1/nd/nd_proxy/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: НД-прокси по коду
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, description: "nd_code", schema: { type: string } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxySchema' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
put:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Обновить НД-прокси
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: string } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxyCreate' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxySchema' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
patch:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Частично обновить НД-прокси
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: string } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxyUpdate' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxySchema' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
delete:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Удалить НД-прокси
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: string } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Удалено
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDProxySchema' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/nd/nd_proxy/{instance_id}/bound/:
|
||||||
|
post:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Привязать заметку и файл к НД-прокси
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, description: "nd_code", schema: { type: string } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
multipart/form-data:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
note_id: { type: integer }
|
||||||
|
name: { type: string }
|
||||||
|
file: { type: string, format: binary }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/nd/notes/{instance_id}/:
|
||||||
|
get:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Заметка НД с изображением
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { type: object }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
patch:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Обновить время заметки (НД)
|
||||||
|
parameters:
|
||||||
|
- { name: instance_id, in: path, required: true, schema: { type: integer } }
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NDUpdateTimeNote' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Note' }
|
||||||
|
'404': { description: Не найдено }
|
||||||
|
|
||||||
|
/api/v1/nd/users/:
|
||||||
|
post:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Проверить существование пользователя по username
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/User' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
exist: { type: boolean }
|
||||||
|
'400': { description: Ошибка запроса к Django }
|
||||||
|
|
||||||
|
/api/v1/nd/token/:
|
||||||
|
get:
|
||||||
|
tags: [nd_service]
|
||||||
|
summary: Сгенерировать JWT для НД-сервиса
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
token: { type: string }
|
||||||
|
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
description: |
|
||||||
|
Заголовок `Authorization` (проверяется через Django `/client/settings/`).
|
||||||
|
Опционально заголовок `Identity` для режима Zitadel. Эндпоинты `/api/v1/nd/*`
|
||||||
|
на запись используют собственный JWT (`JWTBearer`, флаг ND_JWT_ENABLE).
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
Service:
|
||||||
|
type: string
|
||||||
|
enum: [workspace]
|
||||||
|
Entity:
|
||||||
|
type: string
|
||||||
|
enum: [workspace]
|
||||||
|
|
||||||
|
BaseNote:
|
||||||
|
type: object
|
||||||
|
required: [name, service, entity, instance_id, company_id]
|
||||||
|
properties:
|
||||||
|
name: { type: string }
|
||||||
|
body: { type: string, nullable: true }
|
||||||
|
time_start: { type: string, format: date-time, nullable: true }
|
||||||
|
time_end: { type: string, format: date-time, nullable: true }
|
||||||
|
service: { $ref: '#/components/schemas/Service' }
|
||||||
|
entity: { $ref: '#/components/schemas/Entity' }
|
||||||
|
instance_id: { type: string }
|
||||||
|
meta_data: { type: object, nullable: true }
|
||||||
|
height_base_plane: { type: number, format: float, nullable: true }
|
||||||
|
height_geom_prmtv: { type: number, format: float, nullable: true }
|
||||||
|
created_by: { type: integer, nullable: true, minimum: 1 }
|
||||||
|
company_id: { type: integer, minimum: 1 }
|
||||||
|
resource_id: { type: string, format: uuid, nullable: true }
|
||||||
|
|
||||||
|
Note:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseNote'
|
||||||
|
- type: object
|
||||||
|
required: [id, created_at, updated_at]
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
created_at: { type: string, format: date-time }
|
||||||
|
updated_at: { type: string, format: date-time }
|
||||||
|
|
||||||
|
ExtendedNote:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/Note'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
links:
|
||||||
|
type: array
|
||||||
|
items: { type: integer }
|
||||||
|
documents:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/Document' }
|
||||||
|
|
||||||
|
DocumentCreation:
|
||||||
|
type: object
|
||||||
|
required: [path, values, file_name, template]
|
||||||
|
properties:
|
||||||
|
path: { type: string }
|
||||||
|
values: { type: object, additionalProperties: true }
|
||||||
|
file_name: { type: string }
|
||||||
|
template: { type: string }
|
||||||
|
|
||||||
|
WFResponse:
|
||||||
|
type: object
|
||||||
|
required: [context]
|
||||||
|
properties:
|
||||||
|
context: { type: object }
|
||||||
|
|
||||||
|
BaseLink:
|
||||||
|
type: object
|
||||||
|
required: [link_id, note_id]
|
||||||
|
properties:
|
||||||
|
link_id: { type: integer, minimum: 1 }
|
||||||
|
note_id: { type: integer, minimum: 1 }
|
||||||
|
|
||||||
|
Link:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseLink'
|
||||||
|
- type: object
|
||||||
|
required: [id]
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
|
||||||
|
BaseDocument:
|
||||||
|
type: object
|
||||||
|
required: [document_id, bundle_id]
|
||||||
|
properties:
|
||||||
|
document_id: { type: integer }
|
||||||
|
bundle_id: { type: string }
|
||||||
|
note_id: { type: integer, nullable: true }
|
||||||
|
|
||||||
|
Document:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseDocument'
|
||||||
|
- type: object
|
||||||
|
required: [id]
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
|
||||||
|
BaseAttachment:
|
||||||
|
type: object
|
||||||
|
required: [attachment_id, object_name, attachment_type, is_state]
|
||||||
|
properties:
|
||||||
|
attachment_id: { type: integer, minimum: 1 }
|
||||||
|
note_id: { type: integer, nullable: true, minimum: 1 }
|
||||||
|
object_name: { type: string }
|
||||||
|
attachment_type: { type: string }
|
||||||
|
is_state: { type: boolean }
|
||||||
|
|
||||||
|
Attachment:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/BaseAttachment'
|
||||||
|
- type: object
|
||||||
|
required: [id]
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
|
||||||
|
NDProxyCreate:
|
||||||
|
type: object
|
||||||
|
required: [nd_code, creator, is_locked]
|
||||||
|
properties:
|
||||||
|
nd_code: { type: string }
|
||||||
|
work_description: { type: string, nullable: true }
|
||||||
|
equipment_description: { type: string, nullable: true }
|
||||||
|
description: { type: string, nullable: true }
|
||||||
|
creator: { type: string }
|
||||||
|
is_locked: { type: boolean }
|
||||||
|
note_id: { type: integer, nullable: true }
|
||||||
|
|
||||||
|
NDProxyUpdate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
nd_code: { type: string, nullable: true }
|
||||||
|
work_description: { type: string, nullable: true }
|
||||||
|
equipment_description: { type: string, nullable: true }
|
||||||
|
description: { type: string, nullable: true }
|
||||||
|
creator: { type: string, nullable: true }
|
||||||
|
is_locked: { type: boolean, nullable: true }
|
||||||
|
note_id: { type: integer, nullable: true }
|
||||||
|
|
||||||
|
NDProxySchema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/NDProxyCreate'
|
||||||
|
- type: object
|
||||||
|
required: [id]
|
||||||
|
properties:
|
||||||
|
id: { type: integer }
|
||||||
|
|
||||||
|
NDUpdateTimeNote:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
time_start: { type: string, format: date-time, nullable: true }
|
||||||
|
time_end: { type: string, format: date-time, nullable: true }
|
||||||
|
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
required: [username]
|
||||||
|
properties:
|
||||||
|
username: { type: string }
|
||||||
134
apps/pm/.env.example
Normal file
134
apps/pm/.env.example
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
# Server
|
||||||
|
SERVER_HOST="https://lk.sarex.io"
|
||||||
|
SERVER_API_HOST="https://api.sarex.io"
|
||||||
|
SERVER_DEBUG=false
|
||||||
|
SERVER_ENABLE_SILK=false
|
||||||
|
SERVER_ALLOWED_HOSTS=["*"]
|
||||||
|
SERVER_SECRET_KEY="secret"
|
||||||
|
SERVER_USE_OTEL=false
|
||||||
|
SERVER_VERIFY_SSL=true
|
||||||
|
SERVER_LOG_LEVEL=INFO
|
||||||
|
SERVER_ENABLE_SYNC_RESOURCES=false
|
||||||
|
# SERVER_MEDIA_ROOT=sarex/media
|
||||||
|
SERVER_DELETED_TASK_MAX_AGE_DAYS=30
|
||||||
|
SERVER_EXPIRED_TASK_NOTIFICATION_HOUR=9
|
||||||
|
SERVER_EXPIRED_TASK_NOTIFICATION_MAX_DAYS=7
|
||||||
|
SERVER_SEND_UPDATED_PROJECTS_INFO_INTERVAL=5
|
||||||
|
|
||||||
|
# Auth
|
||||||
|
AUTH_ALGORITHM=RS512
|
||||||
|
# Replace newlines with \n
|
||||||
|
AUTH_PUBLIC_KEY=''
|
||||||
|
AUTH_PUBLIC_TOKEN_URL="https://lk.sarex.io/api/token/public/"
|
||||||
|
|
||||||
|
# Database (PostgreSQL)
|
||||||
|
DB_ENGINE="django.db.backends.postgresql"
|
||||||
|
DB_HOST="localhost"
|
||||||
|
DB_PORT=5432
|
||||||
|
DB_DATABASE="sarex_db"
|
||||||
|
DB_USERNAME="sarex"
|
||||||
|
DB_PASSWORD="sarex"
|
||||||
|
|
||||||
|
# S3
|
||||||
|
S3_HOST="https://storage.yandexcloud.net"
|
||||||
|
S3_LOGIN=""
|
||||||
|
S3_PASSWORD=""
|
||||||
|
S3_BUCKET="sarex-media-storage"
|
||||||
|
S3_VERIFY="true"
|
||||||
|
|
||||||
|
# Cache (Redis)
|
||||||
|
CACHE_ENABLE=0
|
||||||
|
CACHE_EXPIRATION=300
|
||||||
|
CACHE_HOST="localhost"
|
||||||
|
CACHE_PORT=6379
|
||||||
|
CACHE_PASSWORD=None
|
||||||
|
CACHE_SSL=0
|
||||||
|
CACHE_SSL_CA_CERTS=None
|
||||||
|
CACHE_QUEUE=default
|
||||||
|
|
||||||
|
# ClickHouse
|
||||||
|
CLICKHOUSE_ENABLE=0
|
||||||
|
CLICKHOUSE_HOST="localhost"
|
||||||
|
CLICKHOUSE_PORT=9000
|
||||||
|
CLICKHOUSE_USER=""
|
||||||
|
CLICKHOUSE_PASSWORD=""
|
||||||
|
CLICKHOUSE_DATABASE="values_db"
|
||||||
|
CLICKHOUSE_TABLE="values"
|
||||||
|
CLICKHOUSE_SECURE=0
|
||||||
|
CLICKHOUSE_VERIFY=0
|
||||||
|
CLICKHOUSE_CERT=""
|
||||||
|
|
||||||
|
# Kafka
|
||||||
|
KAFKA_ENABLE=0
|
||||||
|
KAFKA_BOOTSTRAP_SERVERS=["localhost:9092"]
|
||||||
|
KAFKA_SECURITY_PROTOCOL=""
|
||||||
|
KAFKA_SASL_MECHANISM=""
|
||||||
|
KAFKA_SASL_PLAIN_USERNAME="user"
|
||||||
|
KAFKA_SASL_PLAIN_PASSWORD="password"
|
||||||
|
KAFKA_SSL_CAFILE=""
|
||||||
|
KAFKA_TOPICS={"planning": "message-hub-stage"}
|
||||||
|
|
||||||
|
# Celery — RabbitMQ (broker)
|
||||||
|
CELERY_RABBITMQ_HOST='localhost'
|
||||||
|
CELERY_RABBITMQ_PORT=5672
|
||||||
|
CELERY_RABBITMQ_USER='rabbit'
|
||||||
|
CELERY_RABBITMQ_PASSWORD='rabbit'
|
||||||
|
CELERY_RABBITMQ_VHOST="pm"
|
||||||
|
|
||||||
|
# Celery — Redis (result backend)
|
||||||
|
CELERY_REDIS_HOST='redis-service.sarex-stage.svc.cluster.local'
|
||||||
|
CELERY_REDIS_PORT=6379
|
||||||
|
CELERY_REDIS_DATABASE=0
|
||||||
|
# CELERY_REDIS_PASSWORD=
|
||||||
|
CELERY_REDIS_SSL=false
|
||||||
|
# CELERY_REDIS_SSL_CA_CERTS=
|
||||||
|
CELERY_REDIS_SSL_CERT_REQS=required
|
||||||
|
|
||||||
|
# Users service
|
||||||
|
USERS_HOST=https://lk.sarex.io
|
||||||
|
USERS_API_PREFIX=/api/core
|
||||||
|
USERS_INTERNAL_HOST=http://backend-service.sarex-stage.svc.cluster.local:8000
|
||||||
|
USERS_INTERNAL_PREFIX=/internal
|
||||||
|
USERS_TIMEOUT=10
|
||||||
|
USERS_ENABLE=true
|
||||||
|
|
||||||
|
# Resources service (IAM/resources)
|
||||||
|
RESOURCES_INTERNAL_HOST=http://sarex-resources-service.resources-stage
|
||||||
|
RESOURCES_INTERNAL_PREFIX=/api/v1
|
||||||
|
RESOURCES_TIMEOUT=10
|
||||||
|
RESOURCES_ENABLE=true
|
||||||
|
|
||||||
|
# EAV service
|
||||||
|
EAV_HOST=http://eav-service.eav-stage
|
||||||
|
EAV_API_PREFIX=/api/v0
|
||||||
|
EAV_API_PREFIX_V1=/api/v1
|
||||||
|
EAV_TIMEOUT=10
|
||||||
|
EAV_ENABLE=true
|
||||||
|
|
||||||
|
# Gateway service
|
||||||
|
# GATEWAY_HOST=https://api.sarex.io
|
||||||
|
GATEWAY_API_PREFIX=/gateway/api/v1
|
||||||
|
GATEWAY_TIMEOUT=10
|
||||||
|
GATEWAY_ENABLE=true
|
||||||
|
|
||||||
|
# Documentation service
|
||||||
|
# DOCUMENTATION_HOST=https://api.sarex.io
|
||||||
|
DOCUMENTATION_API_PREFIX=/documentations/api/v1
|
||||||
|
DOCUMENTATION_TIMEOUT=10
|
||||||
|
DOCUMENTATION_ENABLE=true
|
||||||
|
|
||||||
|
# Tracing (OpenTelemetry) — used when SERVER_USE_OTEL=true
|
||||||
|
TRACING_SERVICE_NAME=pm-backend.pm-pord
|
||||||
|
TRACING_ENDPOINT=localhost:4317
|
||||||
|
TRACING_INSECURE=false
|
||||||
|
TRACING_ENVIRONMENT=prod
|
||||||
|
TRACING_MODULE=planning
|
||||||
|
TRACING_TEAM=team_planning
|
||||||
|
TRACING_COMPONENT=backend
|
||||||
|
|
||||||
|
# Sentry
|
||||||
|
SENTRY_USE=true
|
||||||
|
SENTRY_HOST=''
|
||||||
|
SENTRY_ENVIRONMENT=""
|
||||||
|
SENTRY_TRACES_SAMPLE_RATE=1.0
|
||||||
|
SENTRY_PROFILES_SAMPLE_RATE=0.1
|
||||||
280
apps/pm/CONFIGURATION.md
Normal file
280
apps/pm/CONFIGURATION.md
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# Конфигурация проекта pm-backend
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис — это Django-приложение (Django 5.1 + Django REST Framework), запускаемое как ASGI (`config.asgi_root:application`) через gunicorn с воркерами `uvicorn.workers.UvicornWorker`. Настройки читаются из переменных окружения через набор классов [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/), объявленных в `config/settings/base.py` и `config/settings/deps/*`.
|
||||||
|
|
||||||
|
Особенности разбора:
|
||||||
|
|
||||||
|
- **у каждой секции свой префикс** (`env_prefix`), напр. `SERVER_`, `DB_`, `S3_`, `CACHE_`, `CLICKHOUSE_`, `KAFKA_`, `CELERY_RABBITMQ_`, `CELERY_REDIS_`, `AUTH_`, `GATEWAY_`, `EAV_`, `DOCUMENTATION_`, `USERS_`, `RESOURCES_`, `TRACING_`, `SENTRY_`;
|
||||||
|
- **вложенного делимитера нет** — каждая настройка задаётся плоской переменной вида `<PREFIX><FIELD>`, напр. `DB_HOST`, `CELERY_RABBITMQ_VHOST`;
|
||||||
|
- **`extra='ignore'`** — все классы игнорируют посторонние переменные, поэтому один общий `.env` без ошибок разбирается всеми секциями;
|
||||||
|
- **`env_file='.env'`** — в отличие от эталонного сервиса, здесь `.env` **загружается автоматически** каждым классом настроек (у `SentrySettings` — `.env.base` и `.env`). Значения из реального окружения процесса имеют приоритет над файлом.
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет. `DJANGO_SETTINGS_MODULE` по умолчанию — `config.settings.base` (см. `manage.py`).
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (manage.py / gunicorn) | Переменные окружения процесса + файл `.env` в корне репозитория (загружается pydantic-settings) |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yaml` поднимает зависимости (postgres, redis, rabbit, clickhouse, minio, pgadmin); переменные приложения задаются через окружение/`.env` |
|
||||||
|
| Kubernetes (Helm) | `.helm/values.yaml`: блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) для сервисов `api` и `celery`; чарт-зависимость `universal-chart` |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) и job-ы `linter`/`typechecker`/`linter_src` |
|
||||||
|
|
||||||
|
Способы запуска процессов:
|
||||||
|
|
||||||
|
| Процесс | Точка входа | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| HTTP API | `docker/entrypoint.sh` → `gunicorn config.asgi_root:application` (uvicorn worker, порт 8000) | REST API |
|
||||||
|
| Celery worker/beat | `celery -A config worker -B -Q pm …` (см. `.helm/values.yaml`, сервис `celery`) | Фоновые задачи и периодические (beat) |
|
||||||
|
| `manage.py migrate` | `manage.py` | Миграции БД |
|
||||||
|
| `manage.py clean_db` / `import_data` | `manage.py` | Служебные команды (см. `README.md`) |
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Ниже перечислены все секции настроек с их префиксами. Дефолт `—` означает отсутствие значения по умолчанию в коде.
|
||||||
|
|
||||||
|
### Server (`SERVER_*`)
|
||||||
|
|
||||||
|
Класс `ServerSettings` (`config/settings/base.py`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `SERVER_HOST` | string | `https://lk.sarex.io` | Внешний базовый URL (личный кабинет); из него формируется `SERVER_MEDIA_HOST` |
|
||||||
|
| `SERVER_API_HOST` | string | `https://api.sarex.io` | Базовый URL API-шлюза (используется внешними клиентами по умолчанию) |
|
||||||
|
| `SERVER_MEDIA_ROOT` | string | `sarex/media` | Каталог медиафайлов |
|
||||||
|
| `SERVER_DEBUG` | bool | `False` | Django DEBUG. При `True` также включает `FAKE_CELERY` и обход аутентификации в `JWTAuthentication` |
|
||||||
|
| `SERVER_ENABLE_SILK` | bool | `False` | Подключить профайлер django-silk (только при `DEBUG`) |
|
||||||
|
| `SERVER_ALLOWED_HOSTS` | list[str] (JSON) | `["*"]` | Django `ALLOWED_HOSTS` |
|
||||||
|
| `SERVER_SECRET_KEY` | string | `secret` | Django `SECRET_KEY` |
|
||||||
|
| `SERVER_USE_OTEL` | bool | `False` | Включить OpenTelemetry-трейсинг и OTel-логгер (см. секцию `TRACING_*`) |
|
||||||
|
| `SERVER_VERIFY_SSL` | bool | `True` | Проверять TLS-сертификаты при обращении к внешним сервисам |
|
||||||
|
| `SERVER_LOG_LEVEL` | enum | `INFO` | `DEBUG`/`INFO`/`WARNING`/`CRITICAL`/`FATAL` |
|
||||||
|
| `SERVER_ENABLE_SYNC_RESOURCES` | bool | `False` | Включить синхронизацию ресурсов |
|
||||||
|
| `SERVER_DELETED_TASK_MAX_AGE_DAYS` | int | `30` | Срок хранения удалённых задач (дней) |
|
||||||
|
| `SERVER_EXPIRED_TASK_NOTIFICATION_HOUR` | int | `9` | Час отправки уведомлений о просроченных задачах |
|
||||||
|
| `SERVER_EXPIRED_TASK_NOTIFICATION_MAX_DAYS` | int | `7` | Горизонт уведомлений о просрочке (дней) |
|
||||||
|
| `SERVER_SEND_UPDATED_PROJECTS_INFO_INTERVAL` | int | `5` | Интервал отправки информации об обновлённых проектах |
|
||||||
|
|
||||||
|
### Auth (`AUTH_*`)
|
||||||
|
|
||||||
|
Класс `AUTHSettings` (`config/settings/deps/auth.py`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `AUTH_ALGORITHM` | string | `RS512` | Алгоритм проверки подписи JWT |
|
||||||
|
| `AUTH_PUBLIC_KEY` | string | `''` | Публичный RSA-ключ для проверки JWT в режиме sarex-backend |
|
||||||
|
| `AUTH_PUBLIC_TOKEN_URL` | string | `https://lk.sarex.io/api/token/public/` | URL получения публичного ключа/токена |
|
||||||
|
|
||||||
|
Аутентификация DRF (`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`) — по очереди `ZitadelJWTAuthentication`, затем `JWTAuthentication`; доступ по умолчанию `IsAuthenticated`. Zitadel-режим требует одновременно заголовки `Authorization` и `Identity`.
|
||||||
|
|
||||||
|
### Database (`DB_*`)
|
||||||
|
|
||||||
|
Класс `DBSettings` (`config/settings/deps/db.py`). PostgreSQL.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DB_ENGINE` | string | `django.db.backends.postgresql` | Движок Django ORM |
|
||||||
|
| `DB_HOST` | string | `localhost` | Хост PostgreSQL |
|
||||||
|
| `DB_PORT` | int | `5432` | Порт PostgreSQL |
|
||||||
|
| `DB_DATABASE` | string | `sarex_db` | Имя базы данных |
|
||||||
|
| `DB_USERNAME` | string | `sarex` | Пользователь БД |
|
||||||
|
| `DB_PASSWORD` | string | `sarex` | Пароль пользователя БД |
|
||||||
|
|
||||||
|
### S3 (`S3_*`)
|
||||||
|
|
||||||
|
Класс `S3Settings` (`config/settings/deps/s3.py`). Хранилище через django-storages (boto3), по умолчанию Yandex Object Storage.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `S3_HOST` | string | `https://storage.yandexcloud.net` | Endpoint S3 |
|
||||||
|
| `S3_LOGIN` | string | `''` | Access key |
|
||||||
|
| `S3_PASSWORD` | string | `''` | Secret key |
|
||||||
|
| `S3_BUCKET` | string | `sarex-media-storage` | Бакет по умолчанию |
|
||||||
|
| `S3_VERIFY` | bool | `True` | Проверять TLS-сертификат |
|
||||||
|
|
||||||
|
### Cache (`CACHE_*`)
|
||||||
|
|
||||||
|
Класс `CacheSettings` (`config/settings/deps/cache.py`). Redis-кеш, включается отдельно.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `CACHE_ENABLE` | bool | `False` | Включить кеш (иначе `CACHE_CLIENT=None`) |
|
||||||
|
| `CACHE_EXPIRATION` | int | `300` | TTL записей (сек) |
|
||||||
|
| `CACHE_HOST` | string | `localhost` | Хост Redis |
|
||||||
|
| `CACHE_PORT` | int | `6379` | Порт Redis |
|
||||||
|
| `CACHE_PASSWORD` | string \| null | `None` | Пароль |
|
||||||
|
| `CACHE_SSL` | bool | `False` | Подключение по TLS |
|
||||||
|
| `CACHE_SSL_CA_CERTS` | string \| null | `None` | Путь к CA-сертификату |
|
||||||
|
| `CACHE_QUEUE` | string | `default` | Имя очереди кеша |
|
||||||
|
|
||||||
|
### ClickHouse (`CLICKHOUSE_*`)
|
||||||
|
|
||||||
|
Класс `ClickHouseSettings` (`config/settings/deps/click_house.py`). Хранилище значений, включается отдельно.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `CLICKHOUSE_ENABLE` | bool | `False` | Включить ClickHouse |
|
||||||
|
| `CLICKHOUSE_HOST` | string | `rc1d-….mdb.yandexcloud.net` | Хост |
|
||||||
|
| `CLICKHOUSE_PORT` | int | `9000` | Порт |
|
||||||
|
| `CLICKHOUSE_USER` | string | `''` | Пользователь |
|
||||||
|
| `CLICKHOUSE_PASSWORD` | string | `''` | Пароль |
|
||||||
|
| `CLICKHOUSE_DATABASE` | string | `values_db` | База данных |
|
||||||
|
| `CLICKHOUSE_TABLE` | string | `values` | Таблица |
|
||||||
|
| `CLICKHOUSE_SECURE` | bool | `False` | Защищённое подключение |
|
||||||
|
| `CLICKHOUSE_VERIFY` | bool | `False` | Проверять сертификат |
|
||||||
|
| `CLICKHOUSE_CERT` | string | `''` | Путь к CA-сертификату |
|
||||||
|
|
||||||
|
### Kafka (`KAFKA_*`)
|
||||||
|
|
||||||
|
Класс `KafkaSettings` (`config/settings/deps/kafka.py`). Продюсер сообщений, включается отдельно.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `KAFKA_ENABLE` | bool | `False` | Включить продюсер (иначе `get_producer()` вернёт `None`) |
|
||||||
|
| `KAFKA_BOOTSTRAP_SERVERS` | list[str] (JSON) | `["localhost:9092"]` | Список брокеров |
|
||||||
|
| `KAFKA_SECURITY_PROTOCOL` | string | `''` | Протокол безопасности |
|
||||||
|
| `KAFKA_SASL_MECHANISM` | string | `''` | SASL-механизм |
|
||||||
|
| `KAFKA_SASL_PLAIN_USERNAME` | string | `user` | SASL-логин |
|
||||||
|
| `KAFKA_SASL_PLAIN_PASSWORD` | string | `password` | SASL-пароль |
|
||||||
|
| `KAFKA_SSL_CAFILE` | string | `''` | Путь к CA-сертификату |
|
||||||
|
| `KAFKA_TOPICS` | dict (JSON) | `{}` | Карта топиков, напр. `{"planning": "message-hub-stage"}` |
|
||||||
|
|
||||||
|
### Celery — RabbitMQ (`CELERY_RABBITMQ_*`)
|
||||||
|
|
||||||
|
Класс `CeleryRabbitMQ` (`config/settings/deps/celery.py`). Брокер задач; из полей собирается `BROKER_URL` (`amqp://…?heartbeat=30`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `CELERY_RABBITMQ_HOST` | string | `localhost` | Хост |
|
||||||
|
| `CELERY_RABBITMQ_PORT` | int | `5672` | Порт |
|
||||||
|
| `CELERY_RABBITMQ_USER` | string | `rabbit` | Пользователь |
|
||||||
|
| `CELERY_RABBITMQ_PASSWORD` | string | `rabbit` | Пароль |
|
||||||
|
| `CELERY_RABBITMQ_VHOST` | string | `api` | Виртуальный хост (в `.env`/helm — `pm`) |
|
||||||
|
|
||||||
|
### Celery — Redis (`CELERY_REDIS_*`)
|
||||||
|
|
||||||
|
Класс `CeleryRedis` (`config/settings/deps/celery.py`). Result backend; при `SSL=true` используется `rediss://` и `ssl_cert_reqs`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `CELERY_REDIS_HOST` | string | `redis` | Хост |
|
||||||
|
| `CELERY_REDIS_PORT` | int | `6379` | Порт |
|
||||||
|
| `CELERY_REDIS_DATABASE` | int | `0` | Номер БД Redis |
|
||||||
|
| `CELERY_REDIS_PASSWORD` | string \| null | `None` | Пароль (используется при SSL) |
|
||||||
|
| `CELERY_REDIS_SSL` | bool | `False` | Подключение по TLS (`rediss://`) |
|
||||||
|
| `CELERY_REDIS_SSL_CA_CERTS` | string \| null | `None` | Путь к CA-сертификату |
|
||||||
|
| `CELERY_REDIS_SSL_CERT_REQS` | string \| null | `required` | Требования к сертификату |
|
||||||
|
|
||||||
|
### HTTP-клиенты внешних сервисов
|
||||||
|
|
||||||
|
Общий базовый класс `BaseApiServiceMixin` (`config/settings/base.py`): поля `host` (по умолчанию `SERVER_API_HOST`), `api_prefix`, `internal_host`, `internal_prefix`, `timeout` (`10`), `enable` (`True`). Наследники задают собственные префиксы и дефолтные значения prefix.
|
||||||
|
|
||||||
|
| Секция / префикс | Класс | Назначение | Особенности |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `GATEWAY_*` | `GateWaySetttings` | API-шлюз | `api_prefix=/gateway/api/v1` |
|
||||||
|
| `EAV_*` | `EAVSettings` | Сервис атрибутов (EAV) | `api_prefix=/eav/api/v0`, доп. `EAV_API_PREFIX_V1=/eav/api/v1` |
|
||||||
|
| `DOCUMENTATION_*` | `DocumentationSettings` | Сервис документаций | `api_prefix=/documentations/api/v1` |
|
||||||
|
| `USERS_*` | `UsersSettings` | Сервис пользователей (core) | `host=SERVER_HOST`, `api_prefix=/api/core`, `internal_host=http://localhost:8001`, `internal_prefix=/internal` |
|
||||||
|
| `RESOURCES_*` | `ResourceSettings` | Сервис ресурсов (IAM) | `internal_host=http://localhost:8001`, `internal_prefix=/api/v1` |
|
||||||
|
|
||||||
|
Для каждого клиента доступны переменные `<PREFIX>HOST`, `<PREFIX>API_PREFIX`, `<PREFIX>INTERNAL_HOST`, `<PREFIX>INTERNAL_PREFIX`, `<PREFIX>TIMEOUT`, `<PREFIX>ENABLE` (плюс `EAV_API_PREFIX_V1`).
|
||||||
|
|
||||||
|
### Tracing / OpenTelemetry (`TRACING_*`)
|
||||||
|
|
||||||
|
Класс `TracingConfig` (`config/settings/base.py`). Применяется только при `SERVER_USE_OTEL=true`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `TRACING_SERVICE_NAME` | string | `pm-backend.pm-pord` | Имя сервиса в трейсах |
|
||||||
|
| `TRACING_ENDPOINT` | string | `localhost:4317` | Адрес OTLP-коллектора |
|
||||||
|
| `TRACING_INSECURE` | bool | `False` | Небезопасное (без TLS) подключение |
|
||||||
|
| `TRACING_ENVIRONMENT` | string | `prod` | Окружение (атрибут трейса) |
|
||||||
|
| `TRACING_MODULE` | string | `planning` | Модуль (атрибут трейса) |
|
||||||
|
| `TRACING_TEAM` | string | `team_planning` | Команда (атрибут трейса) |
|
||||||
|
| `TRACING_COMPONENT` | string | `backend` | Компонент (атрибут трейса) |
|
||||||
|
|
||||||
|
### Sentry (`SENTRY_*`)
|
||||||
|
|
||||||
|
Класс `SentrySettings` (`config/settings/deps/sentry.py`). Читает `.env.base` и `.env`. Инициализируется при `SENTRY_USE=true`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `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` | Доля профилей |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры, сборки и деплоя
|
||||||
|
|
||||||
|
Не читаются кодом приложения напрямую, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `GUNICORN_WORKERS` | `docker/entrypoint.sh` | Число воркеров gunicorn (по умолчанию `4`) |
|
||||||
|
| `TIMEOUT` | `docker/entrypoint.sh` | Таймаут воркера gunicorn (по умолчанию `60`) |
|
||||||
|
| `NEXUS_USERNAME`, `NEXUS_PASSWORD` | `docker/Dockerfile` (build-arg), `.gitlab-ci.yml` | Доступ к приватному индексу пакетов Nexus |
|
||||||
|
| `SETTINGS_BASE_HOST` | `.helm/values.yaml` (env) | Базовый хост окружения (`stage`/`preprod`/`lk`) |
|
||||||
|
|
||||||
|
Порядок запуска контейнера (`docker/entrypoint.sh`): миграции закомментированы, сразу стартует gunicorn с ASGI-приложением `config.asgi_root:application`.
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Чарт зависит от `universal-chart` (`oci://cr.yandex/crp3ccidau046kdj8g9q/charts`). Значения задаются для двух сервисов — `api` и `celery` — с ключами по окружениям `_default`/`stage`/`preprod`/`production`.
|
||||||
|
|
||||||
|
Обычные значения (блок `envs`) включают: `USERS_INTERNAL_HOST`, `CELERY_REDIS_HOST`, `RESOURCES_INTERNAL_HOST`, `EAV_HOST`, `EAV_API_PREFIX`, `EAV_API_PREFIX_V1`, `TRACING_ENDPOINT`, `TRACING_INSECURE`, `SERVER_ENABLE_SYNC_RESOURCES`, `SERVER_DELETED_TASK_MAX_AGE_DAYS`, `SERVER_EXPIRED_TASK_NOTIFICATION_HOUR`, `SETTINGS_BASE_HOST` (различаются адресами сервисов по окружениям).
|
||||||
|
|
||||||
|
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||||
|
|
||||||
|
| Секрет (`secretName`) | Переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| `ya-pg-secret-pm` | `DB_USERNAME`, `DB_PASSWORD`, `DB_DATABASE`, `DB_HOST`, `DB_PORT` |
|
||||||
|
| `ya-s3-secret-pm` | `S3_HOST`, `S3_LOGIN`, `S3_PASSWORD`, `S3_BUCKET` |
|
||||||
|
| `cache-secret-pm` | `CACHE_HOST`, `CACHE_PORT`, `CACHE_PASSWORD`, `CACHE_SSL`, `CACHE_SSL_CA_CERTS`, `CACHE_ENABLE` |
|
||||||
|
| `clickhouse-secret-pm` | `CLICKHOUSE_HOST`, `CLICKHOUSE_PORT`, `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD`, `CLICKHOUSE_DATABASE`, `CLICKHOUSE_TABLE`, `CLICKHOUSE_SECURE`, `CLICKHOUSE_VERIFY`, `CLICKHOUSE_CERT`, `CLICKHOUSE_ENABLE` |
|
||||||
|
| `ya-kafka-secret-pm` | `KAFKA_ENABLE`, `KAFKA_BOOTSTRAP_SERVERS`, `KAFKA_SECURITY_PROTOCOL`, `KAFKA_SASL_MECHANISM`, `KAFKA_SASL_PLAIN_USERNAME`, `KAFKA_SASL_PLAIN_PASSWORD`, `KAFKA_SSL_CAFILE`, `KAFKA_TOPICS` |
|
||||||
|
| `rabbit-secret-pm` | `CELERY_RABBITMQ_HOST`, `CELERY_RABBITMQ_PORT`, `CELERY_RABBITMQ_USER`, `CELERY_RABBITMQ_PASSWORD`, `CELERY_RABBITMQ_VHOST` |
|
||||||
|
| `server-secret-pm` | `AUTH_PUBLIC_TOKEN_URL`, `SERVER_HOST`, `SERVER_API_HOST`, `SERVER_DEBUG`, `SERVER_ALLOWED_HOSTS`, `SERVER_VERIFY_SSL`, `SERVER_LOG_LEVEL` |
|
||||||
|
|
||||||
|
Дополнительно чарт монтирует CA-сертификат ClickHouse (configMap `ch-cert`, ключ `CA.pem`) как файл `/root/clickhouse/RootCA.crt` и `tmp-volume` в `/tmp`. Прочие значения чарта (не переменные приложения): `deployment.*` (имя, реплики, ресурсы, probes на `/api/health/`), `image.*`, `service.*`, `affinity`, `owner`.
|
||||||
|
|
||||||
|
## Переменные в CI (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||||
|
|
||||||
|
| Условие | STAND | NAMESPACE | CHART_VERSION |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `planning` | `0.0.1-stage` |
|
||||||
|
| ветка `master` | `preprod` | `pm-preprod` | `0.0.1-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `pm-prod` | `0.0.1-prod` |
|
||||||
|
| merge request | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
|
||||||
|
|
||||||
|
Ключевые переменные пайплайна: `SERVICE_NAME=pm-backend`, `DOCKERFILE_PATH=docker/Dockerfile`, `RELEASE_NAME`, `CHART_NAME`, `K8S_HUSTLER_BRANCH`, `IMAGE_NAME`, `HELM_SET_ARGS` (`--set universal-chart.services.{api,celery}.image.name…` и метаданные коммита). Джобы стадии `test`: `linter` (flake8 по `sarex`), `typechecker` (mypy по `src`), `linter_src` (ruff check/format по `src`).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- При `SERVER_DEBUG=true` `JWTAuthentication` возвращает анонимного пользователя и **аутентификация обходится** — использовать только локально.
|
||||||
|
- Все секции читают `.env` автоматически (`env_file='.env'`), поэтому один общий `.env` в корне достаточен для локального запуска. `SentrySettings` дополнительно читает `.env.base`.
|
||||||
|
- Sentry инициализируется по умолчанию (`SENTRY_USE=true`), но при пустом `SENTRY_HOST` DSN не задан — задайте `SENTRY_USE=false` локально, чтобы отключить.
|
||||||
|
- `CELERY_RABBITMQ_VHOST` в коде по умолчанию `api`, тогда как в `.env.example`/helm используется `pm` — для корректной работы очереди значение должно совпадать с брокером.
|
||||||
|
- Переменные `CACHE_PASSWORD`/`CACHE_SSL_CA_CERTS`/`CELERY_REDIS_PASSWORD` допускают `None`; в `.env` для «пустого» значения используйте `None` или закомментируйте строку.
|
||||||
|
- Список-переменные (`SERVER_ALLOWED_HOSTS`, `KAFKA_BOOTSTRAP_SERVERS`) и dict (`KAFKA_TOPICS`) задаются в формате JSON.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Зависимости (postgres, redis, rabbit, clickhouse, minio) поднимаются через `docker-compose up`. Минимально необходимо задать:
|
||||||
|
|
||||||
|
- `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`
|
||||||
|
- `S3_HOST`, `S3_BUCKET`, `S3_LOGIN`, `S3_PASSWORD`, `S3_VERIFY`
|
||||||
|
- `CELERY_RABBITMQ_*` (host/port/user/password/vhost) и `CELERY_REDIS_HOST`/`CELERY_REDIS_PORT`
|
||||||
|
- `SERVER_DEBUG=true` (локально), `SERVER_ALLOWED_HOSTS`, `SERVER_LOG_LEVEL`
|
||||||
|
- `AUTH_PUBLIC_KEY` (можно пустой при `SERVER_DEBUG=true`)
|
||||||
|
- адреса внешних сервисов при необходимости: `USERS_INTERNAL_HOST`, `RESOURCES_INTERNAL_HOST`, `EAV_HOST`
|
||||||
|
- `SENTRY_USE=false`, `SERVER_USE_OTEL=false` — чтобы не подключать Sentry/OTel локально
|
||||||
|
- опциональные подсистемы по флагам: `CACHE_ENABLE`, `CLICKHOUSE_ENABLE`, `KAFKA_ENABLE` (`0` по умолчанию)
|
||||||
|
|
||||||
|
Готовые значения-примеры приведены в `.env.example`.
|
||||||
229
apps/pm/ENDPOINTS.md
Normal file
229
apps/pm/ENDPOINTS.md
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует pm-frontend
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `pm-frontend`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Все REST-запросы идут через единый `httpService` (`src/services/api/http-service.ts`, поверх `@sarex-team/sdk-js` + axios). API-модули объявлены декларативно в `src/store/api/*` и `src/services/api/*` и вызывают `httpService.<method>Request({ service, url, data?, axiosConfig?, errorMessage? })`, где:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (обычно уже включает свой префикс, напр. `/api/pm/msp/...`);
|
||||||
|
- `data` — тело запроса; `errorMessage` — сообщение при ошибке.
|
||||||
|
|
||||||
|
Базовый хост подставляется SDK-функцией `resolveHost(service)` по значению `hosts[ENDPOINT].hosts[service]` из `src/services/api/hosts.ts`. Итоговый URL = `<базовый хост сервиса>` + `url`. Прямых вызовов `axios.*`/`fetch()` в `src/` нет.
|
||||||
|
|
||||||
|
Выбор окружения — сборочная переменная `process.env.ENDPOINT` (инъектируется webpack через `DefinePlugin`). Допустимые значения: `local`, `stage`, `prod`, `preprod`, `contour`; значение по умолчанию — `prod`. Для `local`/`stage` dev-сервер webpack (`configWebpack/buildDevServer.ts`) проксирует относительные префиксы (`/sarex-backend`, `/pm`, `/sarex-eav-v1`, `/sarex-gateway`, `/sarex-api` …) на stage-бэкенды.
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `src/services/api/hosts.ts`. Для сервиса `sarex` в удалённых окружениях хост пустой (`""`) — запросы идут относительно текущего origin (маршрутизируются ingress/gateway перед SPA).
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | `stage` | `prod` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `sarex` | Монолит / PM REST (`/api/pm/...`, `/api/core/...`) | `""` (same-origin) | `""` (same-origin) |
|
||||||
|
| `pm` | PM-микросервис (`/api/v1/...`: интегрированные задачи, комментарии) | `https://stage-api.sarex.io/pm` | `https://api.sarex.io/pm` |
|
||||||
|
| `sarexApi` | API-шлюз для flows (reviews, документы, процессы) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||||
|
| `eavV1` | Сервис атрибутов EAV (`/api/v2`, `/api/v4`) | `https://stage-api.sarex.io/eav` | `https://api.sarex.io/eav` |
|
||||||
|
| `gateway` | Шлюз ресурсов (`/api/v1/resources`) | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||||
|
| `notifications` | Лямбда уведомлений | `https://stage-api.sarex.io/lambdas/notification` | `https://api.sarex.io/lambdas/notification` |
|
||||||
|
| `bimv2` | BIM v2 | `https://stage-api.sarex.io/bimv2` | `https://api.sarex.io/bimv2` |
|
||||||
|
| `bim` | BIM v1 | `https://stage-api.sarex.io/bim` | `https://api.sarex.io/bim` |
|
||||||
|
| `analyticsV2` | Аналитика v2 | `https://stage-api.sarex.io/analytics-v2` | `https://api.sarex.io/analytics-v2` |
|
||||||
|
| `workspaces` | Сервис рабочих областей | `https://stage-api.sarex.io/workspaces` | `https://api.sarex.io/workspaces` |
|
||||||
|
| `documentations` | Сервис документаций | `https://stage-api.sarex.io/documentations` | `https://api.sarex.io/documentations` |
|
||||||
|
| `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` | Сервис замечаний | `https://stage-api.sarex.io/remarks` | `https://api.sarex.io/remarks` |
|
||||||
|
| `projects` | Сервис проектов | `https://stage-api.sarex.io/projects` | `https://api.sarex.io/projects` |
|
||||||
|
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
|
||||||
|
| `google` | Временное хранилище (GCS) | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` |
|
||||||
|
|
||||||
|
> Также определены окружения `local` (относительные прокси-префиксы) и `preprod`/`contour`. Реально используются в коде только `sarex`, `pm`, `sarexApi`, `eavV1`, `gateway`; остальные сервисы объявлены в hosts, но REST-вызовов к ним в этом модуле нет. Кроме REST есть WebSocket (`src/store/stores/gantt/ganttWebsocket.ts`): `io(`${url}/project`, { path: "/message-hub/socket.io" })`, где `url` — `https://stage-api.sarex.io` (stage) / `https://api.sarex.io` (prod).
|
||||||
|
|
||||||
|
## Эндпоинты по модулям
|
||||||
|
|
||||||
|
### `store/api/api.ts` — ProjectsAPI (сервис `sarex`, если не указано иное)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getFolderById` | GET | `/api/pm/msp/folders/{folderId}` | Папка по id |
|
||||||
|
| `getProjectsAndFolders` | GET | `/api/pm/msp/projects/?{query}` | Список проектов и папок |
|
||||||
|
| `getProjectTemplates` | GET | `/api/pm/msp/projects/?{query}` | Список шаблонов проектов |
|
||||||
|
| `getAllProjects` | GET | `/api/pm/msp/projects/` | Все проекты |
|
||||||
|
| `getAllProjectsWithNotFolders` | GET | `/api/pm/msp/projects/?schema=tiny&is_folder=false&company_id={companyId}` | Проекты (без папок) по компании |
|
||||||
|
| `getProject` | GET | `/api/pm/msp/projects/{projectId}/?extend=true` | Проект (расширенный) |
|
||||||
|
| `getKeyMilestones` | GET | `/api/pm/msp/projects/{projectId}/key_milestones/` | Ключевые вехи проекта |
|
||||||
|
| `getResourcePlanning` | GET | `/api/pm/msp/resources/resource_planning/?projects={projects}&start={start}&end={end}&resource_type=human&scale={scale}` | Ресурсное планирование |
|
||||||
|
| `changeResourceForTask` | PATCH | `/api/pm/msp/resources-tasks/assign/` | Назначить ресурс на задачу |
|
||||||
|
| `getIntegratedProjects` | GET | `/api/v1/projects/{projectId}/integrated-tasks/` (сервис `pm`) | Интегрированные задачи проекта |
|
||||||
|
| `createProject` | POST | `/api/pm/msp/projects/` | Создать проект |
|
||||||
|
| `importProject` / `importProjectV2` | POST | `/api/pm/msp/projects/import/` | Импорт проекта |
|
||||||
|
| `updateProjectV2` | PATCH | `/api/pm/msp/projects/{projectId}/` | Обновить проект |
|
||||||
|
| `updateFolders` | GET | `/api/pm/msp/projects/?{idsQuery}` | Папки по id |
|
||||||
|
| `deleteProject` | DELETE | `/api/pm/msp/projects/{id}` | Удалить проект |
|
||||||
|
| `createProjectTemplate` | POST | `/api/pm/msp/projects/{projectId}/create_template/` | Создать шаблон из проекта |
|
||||||
|
| `getProjectStates` | GET | `/api/pm/msp/projects/{id}/states/` | Базовые планы проекта |
|
||||||
|
| `createProjectState` | POST | `/api/pm/msp/project-states/` | Создать базовый план |
|
||||||
|
| `updateProjectState` | PUT | `/api/pm/msp/project-states/{id}/` | Обновить базовый план |
|
||||||
|
| `deleteProjectState` | DELETE | `/api/pm/msp/project-states/{id}/` | Удалить базовый план |
|
||||||
|
| `patchProjectStateDifferenceData` | PATCH | `/api/pm/msp/project-states/{stateId}/edit_state/` | Изменить данные базового плана |
|
||||||
|
| `getProjectState` | GET | `/api/pm/msp/project-states/{id}/` | Базовый план по id |
|
||||||
|
| `getProjectStateData` | GET | `/api/pm/msp/project-states/{id}/data/` | Данные базового плана |
|
||||||
|
| `addTasksToBasicPlan` | POST | `/api/pm/msp/project-states/{planId}/data/` | Добавить задачи в базовый план |
|
||||||
|
| `getStatusImport` | GET | `/api/pm/msp/external-task-info/?task_id={uuid}` | Статус фоновой задачи импорта |
|
||||||
|
| `getTasks` | GET | `/api/pm/msp/projects/{id}/tasks/` | Задачи проекта |
|
||||||
|
| `taskIndex` | PATCH | `/api/pm/msp/tasks/task_index/` | Переиндексация задач |
|
||||||
|
| `bulkCreateTasks` | POST | `/api/pm/msp/projects/{project}/create_tasks/` | Массовое создание задач |
|
||||||
|
| `bulkUpdateTasks` | PATCH | `/api/pm/msp/projects/{project}/update_tasks/` | Массовое обновление задач |
|
||||||
|
| `bulkDeleteTasks` | DELETE | `/api/pm/msp/projects/{project}/delete_tasks/` | Массовое удаление задач |
|
||||||
|
| `copyPasteTasks` | POST | `/api/pm/msp/tasks/copy/` | Копирование задач |
|
||||||
|
| `getTaskDescription` | GET | `/api/pm/msp/tasks/{taskId}/descriptions/` | Описание задачи |
|
||||||
|
| `updateTaskDescription` | PATCH | `/api/pm/msp/tasks/{taskId}/descriptions/` | Обновить описание задачи |
|
||||||
|
| `createComment` | POST | `/api/v1/comments/` (сервис `pm`) | Создать комментарий к задаче |
|
||||||
|
| `getActualValues` | GET | `/api/pm/msp/values/?task={task}` | Фактические значения по задаче |
|
||||||
|
| `createActualValue` | POST | `/api/pm/msp/values/` | Создать фактическое значение |
|
||||||
|
| `updateActualValue` | PUT | `/api/pm/msp/values/{id}/` | Обновить фактическое значение |
|
||||||
|
| `deleteActualValues` | DELETE | `/api/pm/msp/values/{id}/` | Удалить фактическое значение |
|
||||||
|
| `getAllGanttLinks` | GET | `/api/pm/msp/task-relations/{params}` | Связи задач (Ганта) |
|
||||||
|
| `createGanttLinks` | POST | `/api/pm/msp/task-relations/` | Создать связи задач |
|
||||||
|
| `updateGanttLink` | PUT | `/api/pm/msp/task-relations/{id}/` | Обновить связь задач |
|
||||||
|
| `bulkDeleteRelation` | DELETE | `/api/pm/msp/task-relations/bulk_delete/` | Массовое удаление связей |
|
||||||
|
| `getResourcesTable` | GET | `/api/pm/msp/resources/?{query}` | Таблица ресурсов |
|
||||||
|
| `updateResources` | PUT | `/api/pm/msp/resources/{id}/` | Обновить ресурс |
|
||||||
|
| `deleteResource` | DELETE | `/api/pm/msp/resources/{id}/` | Удалить ресурс |
|
||||||
|
| `createVisualProfile` | POST | `/api/pm/msp/visual-profiles/` | Создать визуальный профиль |
|
||||||
|
| `editVisualProfile` | PATCH | `/api/pm/msp/visual-profiles/{id}` | Изменить визуальный профиль |
|
||||||
|
| `getVisualProfiles` | GET | `/api/pm/msp/projects/{projectId}/profiles/` | Визуальные профили проекта |
|
||||||
|
| `getMyTasks` | GET | `/api/pm/msp/tasks/?responsible=true&executors=true&{query}` | Мои задачи |
|
||||||
|
| `copyProject` | POST | `/api/pm/msp/projects/{projectId}/copy_project/` | Копировать проект |
|
||||||
|
| `createProjectDocument` | POST | `/api/pm/msp/projects/{projectId}/ksg_docs_sync/` | Синхронизация КСГ-документов |
|
||||||
|
| `getUsersByCompanyId` | GET | `/api/core/v2/users/?company={companyId}&{query}` | Пользователи компании |
|
||||||
|
| `getDepartmentsV2` | GET | `/api/core/admin/departments/?company={companyId}` | Отделы компании |
|
||||||
|
| `getPositionsV2` | GET | `/api/core/admin/positions/?company={companyId}` | Должности компании |
|
||||||
|
| `getDocumentStatus` | GET | `/flows/api/v1/documents/?full=true&document_ids={ids}` (сервис `sarexApi`) | Статус документов |
|
||||||
|
| `exportTasksPDF` | POST | `/api/pm/msp/projects/{id}/export_project_to_pdf/` | Экспорт проекта в PDF (+опрос `external-task-info`) |
|
||||||
|
| `projectExport` | POST | `/api/pm/msp/projects/{id}/project_export/` | Экспорт проекта (xlsx/xml) |
|
||||||
|
|
||||||
|
### `store/api/attributes-api-v2.ts` — AttributesApiV2
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getProjectAttributes` | GET | `/api/pm/msp/project-attribute/?project={projectId}` (сервис `sarex`) | Атрибуты проекта |
|
||||||
|
| `updateProjectAttribute` | PUT | `/api/pm/msp/project-attribute/{id}/` (сервис `sarex`) | Обновить атрибут проекта |
|
||||||
|
| `deleteProjectAttribute` | DELETE | `/api/pm/msp/project-attribute/{id}/` (сервис `sarex`) | Удалить атрибут проекта |
|
||||||
|
| `addAttributesToProject` | POST | `/api/pm/msp/project-attribute/` (сервис `sarex`) | Привязать атрибуты к проекту |
|
||||||
|
| `getTimeMarkersData` | GET | `/api/pm/msp/projects/{projectID}/time_markers_data/?attributes={ids}&with_hierarchy={flag}` (сервис `sarex`) | Данные временных маркеров |
|
||||||
|
| `getAttributesList` | GET | `/api/v4/attribute/?model_name=gantt-task&company_id={companyId}` (сервис `eavV1`) | Список атрибутов (EAV) |
|
||||||
|
| `getAssetsByAttribute` | GET | `/api/v4/assets/?path_contains={assetsId}` (сервис `eavV1`) | Ассеты по атрибуту |
|
||||||
|
| `getAttributesByAssetsParent` | GET | `/api/v4/assets/?tenant_id={companyId}&depth=0` (сервис `eavV1`) | Корневые ассеты компании |
|
||||||
|
| `createNewAttribute` | POST | `/api/v2/attribute/` (сервис `eavV1`) | Создать атрибут (EAV) |
|
||||||
|
| `updateAttribute` | PATCH | `/api/v2/attribute/{id}/` (сервис `eavV1`) | Обновить атрибут (EAV) |
|
||||||
|
|
||||||
|
### `store/api/calculate-api.ts` — CalculatesAPI (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `postFormula` | POST | `/api/pm/msp/projects/{id}/formula/` | Пересчёт по формуле |
|
||||||
|
|
||||||
|
### `store/api/calendarsApi.ts` — CalendarsApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getAllCalendars` | GET | `/api/pm/msp/calendars/` | Все календари |
|
||||||
|
| `getCalendarById` | GET | `/api/pm/msp/calendars/{id}/` | Календарь по id |
|
||||||
|
| `createCalendar` | POST | `/api/pm/msp/calendars/` | Создать календарь |
|
||||||
|
| `editCalendar` | PATCH | `/api/pm/msp/calendars/{id}/` | Изменить календарь |
|
||||||
|
| `deleteCalendar` | DELETE | `/api/pm/msp/calendars/{id}/` | Удалить календарь |
|
||||||
|
|
||||||
|
### `store/api/issuesApi.ts` — IssuesApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getIssueTypes` | GET | `/api/pm/msp/entity-relations/?project_id={projectId}` | Типы связей/проблем проекта |
|
||||||
|
| `patchIssueType` | PATCH | `/api/pm/msp/entity-relations/{issueTypeId}/` | Обновить тип связи |
|
||||||
|
| `getIssueData` | GET | `/api/pm/msp/projects/{projectId}/issues_data/` | Данные проблем проекта |
|
||||||
|
|
||||||
|
### `store/api/ksgStatesApi.ts` — KsgStatesApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getProjestStates` | GET | `/api/pm/msp/project-settings/?{query}` | Настройки/состояния КСГ |
|
||||||
|
| `createProjectState` | POST | `/api/pm/msp/project-settings/` | Создать состояние КСГ |
|
||||||
|
| `editProjectState` | PATCH | `/api/pm/msp/project-settings/{id}/` | Изменить состояние КСГ |
|
||||||
|
| `deleteProjectAttribute` | DELETE | `/api/pm/msp/project-settings/{id}/` | Удалить состояние КСГ |
|
||||||
|
| `checkApplyState` | POST | `/api/pm/msp/project-settings/{id}/apply/` | Применить состояние КСГ |
|
||||||
|
|
||||||
|
### `store/api/permissionsApi.ts` — PermissionsApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getPermissions` | GET | `/api/pm/msp/projects/{projectId}/permissions/` | Права проекта |
|
||||||
|
| `getTaskPermissions` | GET | `/api/pm/msp/tasks/{taskId}/permissions/` | Права задачи |
|
||||||
|
| `getAllTaskPermissions` | GET | `/api/pm/msp/projects/{projectId}/all_permissions/` | Все права задач проекта |
|
||||||
|
| `savePermission` | POST | `/api/pm/msp/projects/{projectId}/permissions/` | Сохранить права проекта |
|
||||||
|
| `saveTaskPermission` | POST | `/api/pm/msp/tasks/{taskId}/permissions/` | Сохранить права задачи |
|
||||||
|
|
||||||
|
### `store/api/relationApi.ts` — RelationsApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getProjectRelations` | GET | `/api/pm/msp/projects/{projectId}/relations/` | Связи/интеграции проекта |
|
||||||
|
| `postProjectIntegrations` | POST | `/api/pm/msp/projects/{id}/bulk_integration/` | Массовое создание интеграций |
|
||||||
|
| `updateProjectIntegration` | PATCH | `/api/pm/msp/project-relations/{id}/` | Обновить интеграцию |
|
||||||
|
| `deleteProjectIntegration` | DELETE | `/api/pm/msp/project-relations/{id}/` | Удалить интеграцию |
|
||||||
|
|
||||||
|
### `store/api/reviewsApi.ts` — ReviewAPI (сервис `sarexApi`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `createReview` | POST | `/flows/api/v1/reviews/` | Создать ревью/согласование |
|
||||||
|
| `getProcesses` | GET | `/flows/api/v1/flows/?{query}` | Процессы/потоки согласования |
|
||||||
|
|
||||||
|
### `store/api/systemLogApi.ts` — SystemLogApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getNewSystemLogs` | GET | `/api/pm/msp/projects/{projectId}/system-logs/?{query}` | Системные логи проекта |
|
||||||
|
| `getDetailsSystemLog` | GET | `/api/pm/msp/projects/{projectId}/system-log-detail/?log_id={logId}` | Детали записи лога |
|
||||||
|
| `rollBack` | POST | `/api/pm/msp/projects/{projectId}/rollback-to-record/` | Откат к записи лога |
|
||||||
|
|
||||||
|
### `store/api/taskDetailingApi.ts` — TaskDetailingApi (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getRole` | GET | `/api/pm/msp/projects/{projectId}/rule/` | Правило детализации проекта |
|
||||||
|
| `createRule` | POST | `/api/pm/msp/projects/{projectId}/rule/` | Создать правило детализации |
|
||||||
|
| `getDetailTasks` | GET | `/api/pm/msp/detailed-tasks/?task={taskId}` | Детализированные задачи |
|
||||||
|
| `patchDetailTasks` | PATCH | `/api/pm/msp/detailed-tasks/{detailingTaskId}/` | Обновить детализированную задачу |
|
||||||
|
|
||||||
|
### `store/api/workspaceApi.ts` — WorkspaceAPI (сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getResourcesByTaskId` | GET | `/api/pm/msp/resources-tasks/?tasks={task}` | Ресурсы по задаче |
|
||||||
|
| `getResourcesByIds` | GET | `/api/pm/msp/resources/?ids={resourcesIds}` | Ресурсы по id |
|
||||||
|
| `loadElementsByResourceIds` | GET | `/api/pm/msp/resources-elements/?resources={ids}` | Элементы ресурсов |
|
||||||
|
| `connectResourcesTasks` | POST | `/api/pm/msp/resources-tasks/` | Привязать ресурс к задаче |
|
||||||
|
| `editResourcesTasks` | PATCH | `/api/pm/msp/resources-tasks/bulk_update/` | Массово изменить связи ресурс-задача |
|
||||||
|
| `deleteResource` | DELETE | `/api/pm/msp/resources-tasks/bulk_delete/` | Массово удалить связи ресурс-задача |
|
||||||
|
|
||||||
|
### `services/api/fetch/gateway.ts` — GatewayAPI (сервис `gateway`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchResources` | GET | `/api/v1/resources` | Ресурсы шлюза (доступы/фичи) |
|
||||||
|
|
||||||
|
### Gantt-репозитории (`src/pages/TasksNew/GanttWorkspace/repositories/*`, сервис `sarex`)
|
||||||
|
|
||||||
|
| Ключ | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `WorkspaceSelectedKSGProjectRepository` | GET | `/api/pm/msp/projects/?bundle_id={bundleID}&schema=tiny` | Проекты выбранного бандла КСГ |
|
||||||
|
| `TaskResourceConnectionBaseRepository` | GET | `/api/pm/msp/projects/{id}/profiles/` | Профили ресурсов проекта |
|
||||||
|
| `TaskResourcesConnectionsRepository` | GET | `/api/pm/msp/resources-tasks/?projects={project}` | Связи ресурс-задача по проекту |
|
||||||
|
| `TasksRepository` (список) | GET | `/api/pm/msp/tasks/?project={project}&{query}` | Задачи проекта |
|
||||||
|
| `TasksRepository` (одна) | GET | `/api/pm/msp/tasks/{id}/` | Одна задача |
|
||||||
|
| `uploadFileToServer` | POST (multipart) | `/api/pm/msp/descriptions/upload_file/` | Загрузка файла/изображения в описание |
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
Централизованного middleware (RTK Query `createApi`/`fetchBaseQuery` не используется) нет — API-модули оборачивают axios-based `httpService`. Типичные паттерны: большинство вызовов `.then(r => r.data)` и пробрасывают ошибку выше; часть — `try/catch` с `isAxiosError(error)`, где `403` даёт «Нет доступа»/«Доступ запрещён», а прочие ошибки — общее сообщение (напр. «Не удалось сохранить данные, попробуйте ещё раз»), нередко показываемое через `createToast(...)` и повторно выбрасываемое как `new Error(...)`. Некоторые читают `error.response.data.detail`. `getStatusImport` при ошибке возвращает `{ request_failed: true }`; `exportTasksPDF` опрашивает `external-task-info` каждые 2 с до `is_ready`. Часть вызовов передаёт в SDK опцию `errorMessage` (напр. «Некорректные данные»).
|
||||||
1768
apps/pm/openapi.yaml
Normal file
1768
apps/pm/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
21
apps/prescriptions/.env.example
Normal file
21
apps/prescriptions/.env.example
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# prescriptions-frontend — переменные СБОРКИ и CI
|
||||||
|
#
|
||||||
|
# ВАЖНО: у фронтенда нет рантайм-.env. Собранный бандл — статика, которую
|
||||||
|
# раздаёт nginx. Все переменные ниже используются на этапе СБОРКИ образа
|
||||||
|
# (Dockerfile ARG / --build-arg в .gitlab-ci.yml) и локального запуска.
|
||||||
|
# Значение BUILD_ENV валидируется в env.js и внедряется в бандл через
|
||||||
|
# webpack DefinePlugin. Подробности — в CONFIGURATION.md.
|
||||||
|
|
||||||
|
# Build (обязательно). Одно из: local | stage | preprod | prod | contour
|
||||||
|
BUILD_ENV=stage
|
||||||
|
|
||||||
|
# Флаг сборки под Storybook: "true" | "false"
|
||||||
|
STORYBOOK=false
|
||||||
|
|
||||||
|
# Токен приватного npm-реестра nexus.infra.sarex.io (см. .npmrc)
|
||||||
|
NPM_TOKEN=
|
||||||
|
|
||||||
|
# --- Cypress e2e ---
|
||||||
|
# Задаются в cypress.env.json (пример — cypress.env-example.json), не в .env:
|
||||||
|
# SRX_LOGIN=
|
||||||
|
# SRX_PASSWORD=
|
||||||
127
apps/prescriptions/CONFIGURATION.md
Normal file
127
apps/prescriptions/CONFIGURATION.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Конфигурация проекта prescriptions-frontend
|
||||||
|
|
||||||
|
Документ описывает способы конфигурирования микрофронтенда `prescriptions-frontend` (Module Federation `srx_prescriptions`, экспонирует `./PrescriptionsPage`), его переменные сборки/CI и параметры деплоя.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
В отличие от backend-сервисов, у фронтенда **нет рантайм-конфигурации через `.env`**: собранный бандл — статические файлы, которые раздаёт nginx. Всё поведение задаётся **на этапе сборки** одной переменной — `BUILD_ENV`.
|
||||||
|
|
||||||
|
- `env.js` читает `process.env.BUILD_ENV` и валидирует его против списка `local`/`stage`/`prod`/`contour`/`preprod` (иначе — ошибка сборки `set BUILD_ENV one of ...`);
|
||||||
|
- `webpack.config.js` через `DefinePlugin` внедряет глобальные константы `BUILD_ENV` и `STORYBOOK` в бандл;
|
||||||
|
- `build.config.js` по `BUILD_ENV` выбирает режим сборки (`mode`/`devtool`);
|
||||||
|
- `module/api/hosts.ts` и `module/api/module-hosts.ts` содержат карты хостов по окружениям; SDK (`@sarex-team/sdk-js`) на рантайме выбирает нужный хост по глобальной константе `BUILD_ENV` (по умолчанию `prod`).
|
||||||
|
|
||||||
|
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
### Значения `BUILD_ENV`
|
||||||
|
|
||||||
|
| `BUILD_ENV` | `mode` | `devtool` | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `local` | `development` | `eval-source-map` | Локальная разработка (dev-server, storybook), `httpService` → `original` |
|
||||||
|
| `stage` | `development` | `eval-source-map` | Стенд stage |
|
||||||
|
| `preprod` | `production` | `source-map` | Предпрод |
|
||||||
|
| `prod` | `production` | `source-map` | Прод |
|
||||||
|
| `contour` | `production` | `source-map` | Изолированный контур (относительные пути хостов) |
|
||||||
|
|
||||||
|
## Переменные сборки и CI
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `BUILD_ENV` | `env.js`, `webpack.config.js`, `Dockerfile` (ARG), `.gitlab-ci.yml` (`--build-arg`) | Целевое окружение сборки. Обязательна |
|
||||||
|
| `NPM_TOKEN` | `.npmrc`, `Dockerfile` (ARG), `.gitlab-ci.yml` (`--build-arg`) | Токен доступа к приватному npm-реестру `nexus.infra.sarex.io` |
|
||||||
|
| `STORYBOOK` | `webpack.config.js` (`DefinePlugin`), `module/pages/index.tsx` | Флаг сборки под Storybook (`"true"`/`"false"`) |
|
||||||
|
|
||||||
|
Cypress-тесты берут учётные данные из `cypress.env.json` (пример — `cypress.env-example.json`): `SRX_LOGIN`, `SRX_PASSWORD`.
|
||||||
|
|
||||||
|
Версия Node для разработки — `v20.16.0` (`.nvmrc`).
|
||||||
|
|
||||||
|
## Хосты по окружениям
|
||||||
|
|
||||||
|
Базовые API-хосты подставляются из `module/api/hosts.ts` по `BUILD_ENV` (детальная разбивка по сервисам — в `ENDPOINTS.md`):
|
||||||
|
|
||||||
|
| Окружение | Базовый API | Пример (`documentations`) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `local` / `stage` | `https://stage-api.sarex.io` | `https://stage-api.sarex.io/documentations/api/v1` |
|
||||||
|
| `preprod` | `https://api.preprod.sarex.io` | `https://api.preprod.sarex.io/documentations/api/v1` |
|
||||||
|
| `prod` | `https://api.sarex.io` | `https://api.sarex.io/documentations/api/v1` |
|
||||||
|
| `contour` | относительные пути | `/documentations/api/v1` |
|
||||||
|
|
||||||
|
Удалённый модуль (Module Federation) `documentations` подключается по `module/api/module-hosts.ts` (`remoteEntry.js`).
|
||||||
|
|
||||||
|
## Запуск и скрипты (`package.json`)
|
||||||
|
|
||||||
|
| Команда | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `npm run serve-module` | Dev-server (`webpack.dev.js`, порт `9001`, https, proxy `/api`, `/admin` на `appUrl`), `BUILD_ENV=local` |
|
||||||
|
| `npm run storybook` | Storybook (порт `9000`, https), `BUILD_ENV=local` |
|
||||||
|
| `npm run start` / `test:dev` | Параллельный запуск serve-module + storybook (+ cypress в `test:dev`) |
|
||||||
|
| `npm run build-module` | Продакшн-сборка модуля (`webpack --config webpack.config.js`) |
|
||||||
|
| `npm run build-storybook` | Сборка статики Storybook |
|
||||||
|
| `npm run cypress:open` | Запуск e2e-тестов Cypress |
|
||||||
|
| `npm run lint` / `lint:ts` | Prettier / проверка типов `tsc --noEmit` |
|
||||||
|
|
||||||
|
## Сборка образа (`Dockerfile`)
|
||||||
|
|
||||||
|
Двухстадийная сборка:
|
||||||
|
|
||||||
|
1. `node:15` — установка зависимостей (`npm i --legacy-peer-deps` с `NPM_TOKEN`), `npm run lint`, `BUILD_ENV=$BUILD_ENV npm run build-module` → `/app/dist`;
|
||||||
|
2. `nginx:mainline-alpine-otel` — копирование `dist` в `/dist` и конфига `nginx/nginx.conf`.
|
||||||
|
|
||||||
|
Build-args: `BUILD_ENV`, `NPM_TOKEN`.
|
||||||
|
|
||||||
|
nginx (`nginx/nginx.conf`) раздаёт статику из `/dist`, отдаёт `/ping` → `{"result": "ok"}` (healthcheck) и запрещает кеширование `/module/remoteEntry.js` (`Cache-Control: no-store`).
|
||||||
|
|
||||||
|
## CI/CD (`.gitlab-ci.yml`)
|
||||||
|
|
||||||
|
Пайплайн подключает общие шаблоны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml`, ref `apps-business`). `SERVICE_NAME=prescriptions-frontend`. Окружение переключается по ветке/тегу (`workflow.rules`):
|
||||||
|
|
||||||
|
| Условие | STAND | NAMESPACE | BUILD_ENV | CHART_VERSION |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| ветка `stage` | `stage` | `proc` | `stage` | `1.0.0-stage` |
|
||||||
|
| ветка `master` | `preprod` | `prescriptions-preprod` | `preprod` | `1.0.0-preprod` |
|
||||||
|
| тег (`CI_COMMIT_TAG`) | `production` | `prescriptions-prod` | `prod` | `1.0.0-prod` |
|
||||||
|
| merge request | — | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
|
||||||
|
|
||||||
|
Деплой параметризуется через `HELM_SET_ARGS` (образ, `global.env`, `commitSha`, `gitlabUri`, `gitlabJobUrl`, `owner`).
|
||||||
|
|
||||||
|
## Helm-чарт проекта (`.helm`)
|
||||||
|
|
||||||
|
`Chart.yaml`: зависимость `universal-chart` (`oci://cr.yandex/.../charts`, версия `0.1.7`).
|
||||||
|
|
||||||
|
`values.yaml` (`universal-chart.services.frontend`):
|
||||||
|
|
||||||
|
- `deployment.name = prescription-frontend`, `replicaCount = 1`, `port = 80`, `revisionHistoryLimit = 10` (prod — `15`);
|
||||||
|
- `image.name = cr.yandex/crp3ccidau046kdj8g9q/prescription-frontend`, `pullPolicy = IfNotPresent`;
|
||||||
|
- `service.name = prescriptions-frontend-service`, `type = ClusterIP`, `port/targetPort = 80`;
|
||||||
|
- `imagePullSecrets = dockerhub`;
|
||||||
|
- probes (`liveness`/`readiness`) на `/ping:80` заданы, но **выключены** (`enabled: false`);
|
||||||
|
- `envs: []`, `secretEnvs: []` — переменных окружения контейнеру не передаётся;
|
||||||
|
- ресурсы: `requests` `memory 100Mi`, `cpu 100m`.
|
||||||
|
|
||||||
|
## Инфраструктура (`iac/apps/prescriptions`)
|
||||||
|
|
||||||
|
Разворачивается через Kustomize. Состав каталога:
|
||||||
|
|
||||||
|
| Путь | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `base/namespace.yaml` | Namespace `prescriptions` с `istio-injection: enabled` |
|
||||||
|
| `base/deployment.yaml` | Deployment `frontend`, образ `cr.yandex/.../prescriptions-frontend:production_...`, порт `80`, `requests` `cpu 25m`/`memory 100Mi`, `imagePullSecrets: regcred` |
|
||||||
|
| `base/service.yaml` | Service `frontend-service`, `ClusterIP`, порт `80` |
|
||||||
|
| `base/kustomization.yaml` | Сборка base (namespace + deployment + service) |
|
||||||
|
| `yc-k8s-test/` | Оверлей поверх `../base` (патч `replicas.yaml` закомментирован) |
|
||||||
|
| `brusnika-prod/`, `brusnika-stage/` | Оверлеи (см. замечание ниже) |
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **Оверлеи `brusnika-prod` / `brusnika-stage`** сейчас содержат `HelmRelease` бэкенда `measurements` (namespace `measurements`, образ `documentations`), не относящийся к prescriptions — похоже на копипаст-заготовку, которую нужно заменить на конфигурацию prescriptions-frontend либо удалить.
|
||||||
|
- **Два способа описания деплоя**: helm-чарт в репозитории фронтенда (`.helm`, `universal-chart`) и Kustomize-манифесты в инфраструктуре (`iac/apps/prescriptions`) описывают один и тот же сервис по-разному — стоит зафиксировать единый источник истины.
|
||||||
|
- **Несогласованные имена**: `SERVICE_NAME=prescriptions-frontend` (мн. ч.), а `deployment.name`/`image.name` в `.helm` — `prescription-frontend` (ед. ч.). В инфра-манифестах deployment называется просто `frontend`.
|
||||||
|
- **Версии Node расходятся**: разработка — `v20.16.0` (`.nvmrc`), сборка образа — `node:15` (`Dockerfile`).
|
||||||
|
- **Мёртвая конфигурация**: экспорт `hosts` в `networking.config.js` (внедрение `__<service>_host`) и `module/Env` (`IEnv`, `process.env as IEnv`) в коде модуля не используются; `dotenv-webpack` присутствует в devDependencies, но не подключён в `webpack.config.js`. `networking.config.js` реально используется только в `webpack.dev.js` (прокси dev-сервера).
|
||||||
|
|
||||||
|
## Минимальный набор для сборки
|
||||||
|
|
||||||
|
- `BUILD_ENV` — одно из `local`/`stage`/`preprod`/`prod`/`contour` (обязательно);
|
||||||
|
- `NPM_TOKEN` — для установки приватных пакетов `@sarex-team/*` из nexus;
|
||||||
|
- (опционально) `STORYBOOK=true` — при сборке/запуске Storybook;
|
||||||
|
- (для e2e) `SRX_LOGIN`, `SRX_PASSWORD` в `cypress.env.json`.
|
||||||
166
apps/prescriptions/ENDPOINTS.md
Normal file
166
apps/prescriptions/ENDPOINTS.md
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
# Эндпоинты, с которыми взаимодействует prescriptions-frontend
|
||||||
|
|
||||||
|
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `prescriptions-frontend`, Module Federation `srx_prescriptions`, экспонирует `./PrescriptionsPage`).
|
||||||
|
|
||||||
|
## Как устроено взаимодействие
|
||||||
|
|
||||||
|
Запросы выполняются через единый `httpService` (`module/api/http-service.ts`), созданный фабрикой `createHttpService` из [`@sarex-team/sdk-js`](https://www.npmjs.com/) поверх `axios`. Каждый вызов задаётся объектом с полями:
|
||||||
|
|
||||||
|
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||||
|
- `url` — путь запроса (дописывается к базовому хосту сервиса);
|
||||||
|
- `data` — тело запроса (для `POST`/`PUT`/`PATCH`);
|
||||||
|
- `queryKey`, `axiosConfig` (в т.ч. `responseType: "blob"` для файлов), `params` — опции кеширования/повторов и параметры запроса.
|
||||||
|
|
||||||
|
Метод HTTP определяется вызываемой функцией `httpService`: `getRequest`/`postRequest`/`putRequest`/`patchRequest`/`deleteRequest`.
|
||||||
|
|
||||||
|
Базовый хост подставляется SDK по паре (`BUILD_ENV`, `service`) из реестра `module/api/hosts.ts`. Значение `BUILD_ENV` задаётся на этапе сборки (`webpack.config.js` → `DefinePlugin`, глобальная константа `BUILD_ENV`), по умолчанию — `prod` (`http-service.ts`: `BUILD_ENV ?? "prod"`). В окружении `local` тип сервиса переключается на `original` (`setTypeOfHttpService("original")`).
|
||||||
|
|
||||||
|
Итоговый URL = `<базовый хост сервиса>` + `url`.
|
||||||
|
|
||||||
|
Определения вызовов сосредоточены в `module/api/*` (`index.ts`, `contractsApi.ts`, `resourcesApi.ts`, `templatesApi.ts`, `marks.ts`) и частично в сторах (`module/store/stores/resources.ts`, `module/store/stores/users.ts`).
|
||||||
|
|
||||||
|
## Базовые хосты по сервисам и окружениям
|
||||||
|
|
||||||
|
Значения из `module/api/hosts.ts`. Помимо `stage`/`prod` определены окружения `local`, `preprod` и `contour` (в `contour` — относительные пути для изолированного контура).
|
||||||
|
|
||||||
|
| Сервис (`service`) | Назначение | `stage` | `prod` |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `prescriptions` | Предписания (поверх issues) | `https://stage-api.sarex.io/issues/api/prescriptions` | `https://api.sarex.io/issues/api/prescriptions` |
|
||||||
|
| `issues` | Сервис замечаний/issues | `https://stage-api.sarex.io/issues/api` | `https://api.sarex.io/issues/api` |
|
||||||
|
| `documentations` | Сервис документации (документы, бандлы, диски) | `https://stage-api.sarex.io/documentations/api/v1` | `https://api.sarex.io/documentations/api/v1` |
|
||||||
|
| `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` |
|
||||||
|
| `sarex` | Основной backend (core/client) | `https://stage.sarex.io` | `https://lk.sarex.io` |
|
||||||
|
| `sarexApi` | API Sarex (contracts) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||||
|
| `eav_api_v0` | Сервис атрибутов (EAV) | `https://stage-api.sarex.io/eav/api/v0` | `https://api.sarex.io/eav/api/v0` |
|
||||||
|
| `orchestrator` | Оркестратор процессов (маркировка, подпись) | `https://stage-api.sarex.io/orchestrator` | `https://api.sarex.io/orchestrator/api` |
|
||||||
|
| `files` | Сервис файлов (скачивание) | `https://stage-api.sarex.io/files/api/v1` | `https://api.sarex.io/files/api/v1` |
|
||||||
|
| `lambdas` | Лямбды (экспорт reviews) | `https://stage-api.sarex.io/lambdas` | `https://api.sarex.io/lambdas` |
|
||||||
|
| `checklists` | Сервис чек-листов | `https://stage-api.sarex.io/checklists/api/v1` | `https://api.sarex.io/checklists/api/v1` |
|
||||||
|
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
|
||||||
|
|
||||||
|
> Подключаемый удалённый модуль (Module Federation) описан отдельно в `module/api/module-hosts.ts`: `documentations` → `remoteEntry.js` (stage: `https://stage-modules.sarex.io/documentations/static/module/remoteEntry.js`, prod: `https://modules.sarex.io/documentations/static/module/remoteEntry.js`). Хост выбирается функцией `getModuleHost(moduleName)` по `BUILD_ENV`.
|
||||||
|
|
||||||
|
## Эндпоинты по сервисам
|
||||||
|
|
||||||
|
### `prescriptions` — Предписания
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getPrescriptions` | GET | `/?{query}` | Список предписаний (фильтры/поиск, сериализация в `serializePrescriptionParams`) |
|
||||||
|
| `createPrescription` | POST | `/prescription/` | Создать предписание ⚠ вызывается с `service: "prescription"` (см. замечания) |
|
||||||
|
| `getPrescriptionById` | GET | `/{id}/` | Предписание по id |
|
||||||
|
| `editPrescription` | PATCH | `/{id}/` | Редактировать предписание |
|
||||||
|
| `deletePrescription` | DELETE | `/{id}/` | Удалить предписание |
|
||||||
|
| `exportPrescriptionById` | GET | `/{id}/export/?file_format={docx\|pdf}` | Экспорт предписания в docx/pdf |
|
||||||
|
| `getStatusCount` | GET | `/status-count/?{query}` | Счётчики по статусам |
|
||||||
|
| `getHistoryByCompanyId` | GET | `/history/?company_id={id}` | История предписаний компании |
|
||||||
|
| `getHistoryByPrescriptionId` | GET | `/{id}/history/` | История конкретного предписания |
|
||||||
|
|
||||||
|
### `issues` — Замечания / статусы
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getStatusModels` | GET | `/prescription-status-models/?company_id={id}` | Модели статусов предписаний |
|
||||||
|
| `getCompanyStatuses` | GET | `/prescription-statuses/?company_id={id}` | Статусы предписаний компании |
|
||||||
|
| `getIssues` | GET | `/issues/?{params}` | Список замечаний |
|
||||||
|
| `getCustomStatuses` | GET | `/companies/{companyId}/status-model/v2/` | Кастомная модель статусов компании |
|
||||||
|
|
||||||
|
### `documentations` — Сервис документации
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getDocument` | GET | `/documents/{id}` | Документ по id |
|
||||||
|
| `getDisks` | GET | `/disks` | Список дисков (используется в `DocumentAPI` и `TemplatesApi`) |
|
||||||
|
| `mark` | PUT | `/bundles/{bundleId}/marks` | Добавить штампы/QR/подписи в бандл |
|
||||||
|
| `sign` | POST | `/bundles/{bundleId}/sign` | Подписать бандл |
|
||||||
|
| `downloadFile` | GET | `/bundles/{bundleId}/{key}/download` | Скачать файл бандла (`responseType: blob`) |
|
||||||
|
|
||||||
|
### `gateway_api_v1` — Gateway API v1
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getResources` (store) | GET | `/resources/?company_id={id}` | Список ресурсов компании |
|
||||||
|
| `fetchParentDocumentByResourceId` | GET | `/resources-rpc/parent-document-by-resource-id/{resourceId}/` | Родительский документ по resource id |
|
||||||
|
| `fetchDocumentsBundleVersions` | POST | `/documents/bundle_versions` | Версии бандлов документов |
|
||||||
|
| `getDocumentAncestors` | POST | `/documents/ancestors` | Предки документов |
|
||||||
|
| `getTemplates` | GET | `/disks/{diskId}/flat_documents/?type={type}` | Шаблоны диска (плоский список) |
|
||||||
|
|
||||||
|
### `gateway_api_v2` — Gateway API v2
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getUsersByResourceId` | GET | `/users/?{query}` | Пользователи по фильтру ресурса |
|
||||||
|
| `getResourceFullInfo` | GET | `/resources/{resourceId}/` | Полная информация о ресурсе |
|
||||||
|
|
||||||
|
### `sarex` — Основной backend (core/client)
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getUsersByCompanyId` | GET | `/api/core/users/?company={id}&{query}` | Пользователи компании |
|
||||||
|
| `getDepartments` | GET | `/api/core/admin/departments/?company={id}` | Отделы компании |
|
||||||
|
| `getDepartmentsV2` | GET | `/api/core/admin/departments/?company={id}&{query}` | Отделы компании (с доп. query) |
|
||||||
|
| `getPositions` | GET | `/api/core/admin/positions/?company={id}` | Должности компании |
|
||||||
|
| `getPositionsV2` | GET | `/api/core/admin/positions/?company={id}&{query}` | Должности компании (с доп. query) |
|
||||||
|
| `getSettings` (store) | GET | `/api/client/settings/` | Клиентские настройки |
|
||||||
|
|
||||||
|
### `sarexApi` — API Sarex (contracts)
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getContracts` | GET | `/contracts/api/v0/contracts/?tenant_id={companyId}` | Договоры компании |
|
||||||
|
| `getContractsByContractorId` | GET | `/contracts/api/v0/contracts/?tenant_id={companyId}&contractor_id={id}` | Договоры по контрагенту |
|
||||||
|
|
||||||
|
### `eav_api_v0` — Сервис атрибутов (EAV)
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `getAttributes` | GET | `/attribute/?company_id={id}` | Атрибуты компании |
|
||||||
|
|
||||||
|
### `orchestrator` — Оркестратор процессов
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `createMarkFlow` | POST | `/process` | Запустить процесс маркировки |
|
||||||
|
| `getMarkFlow` | GET | `/process/{id}` | Процесс по id |
|
||||||
|
| `startSign` | POST | `/sign` | Запустить подписание |
|
||||||
|
|
||||||
|
### `files` — Сервис файлов
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `downloadDocuments` | POST | `/documents/` | Скачать документы по `bundle_ids` (`responseType: blob`) |
|
||||||
|
|
||||||
|
### `lambdas` — Лямбды (экспорт)
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `fetchExportReviewsByResourceIDs` | GET | `/export-reviews/{params}` | Экспорт reviews (xlsx) |
|
||||||
|
| `fetchExportReview` | GET | `/export-reviews/{reviewId}/report/` | Отчёт по review (pdf) |
|
||||||
|
|
||||||
|
### `flows` — Процессы (⚠ сервис не задан в hosts.ts)
|
||||||
|
|
||||||
|
| Функция | Метод | Путь | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `changeCopyPaths` | PATCH | `/documents/change-copy-paths/` | Изменить пути копий документов |
|
||||||
|
|
||||||
|
## Обработка ошибок
|
||||||
|
|
||||||
|
Отдельного модуля-маппера ошибок (`errors.ts`) в проекте нет — обработка распределена:
|
||||||
|
|
||||||
|
- часть обёрток (`ContractsApi`, `ResourcesApi`, `TemplatesApi`) при ошибке пробрасывают `throw new Error(error)`;
|
||||||
|
- часть функций (`fetchParentDocumentByResourceId`, `fetchExportReview*`) гасят ошибку через `console.error` и не пробрасывают её;
|
||||||
|
- тип ответа об ошибке — `ErrorResponse` (`module/api/types.ts`): читается `response.data.detail`;
|
||||||
|
- статусы запроса в сторах: `RequestStatus` — `init`/`loading`/`success`/`fetching`/`error`/`permissionError`.
|
||||||
|
|
||||||
|
## Права доступа (`module/api/permissions.ts`)
|
||||||
|
|
||||||
|
Модуль оперирует правами `core.*`: `can_view_prescription`, `can_add_prescription`, `can_edit_prescription`, `can_delete_prescription`, `can_view_all_prescriptions`, `can_admin_prescription`. Группы (`FG_PERMISSIONS`): `ADMIN` (все права), `AUTHOR` (просмотр + создание), `RESPONSIBLE` и `VIEW_ALL` (просмотр).
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **`prescription` (единственное число)** — `createPrescription` вызывается с `service: "prescription"`, но такого ключа в `module/api/hosts.ts` нет (есть только `prescriptions`). Базовый хост не резолвится корректно — вероятно опечатка, следует использовать `prescriptions`.
|
||||||
|
- **`flows`** — `changeCopyPaths` использует `service: "flows"`, который также не задан в `hosts.ts`. Ключ нужно добавить в реестр либо исправить.
|
||||||
|
- **`contour`** — в окружении `contour` не определён сервис `sarexApi`, поэтому `getContracts`/`getContractsByContractorId` в этом контуре работать не будут.
|
||||||
|
- **`orchestrator`** — в `prod` базовый URL с суффиксом `/api` (`.../orchestrator/api`), а в `stage`/`local`/`preprod` — без него. Пути эндпоинтов (`/process`, `/sign`) следует проверять с учётом этого различия.
|
||||||
|
- **`checklists` и `zitadel`** заданы в `hosts.ts`, но напрямую через `httpService` в модуле не вызываются (`zitadel` — IdP, используется SDK для авторизации; `checklists` в текущем коде модуля не используется).
|
||||||
246
apps/processing/workflows-api.CONFIGURATION.md
Normal file
246
apps/processing/workflows-api.CONFIGURATION.md
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
# Конфигурация проекта workflows-api
|
||||||
|
|
||||||
|
`workflows-api` — HTTP-сервис (Go 1.24, фреймворк [Fiber v2](https://github.com/gofiber/fiber)) для работы с workflow: создание, чтение, перезапуск задач, отмена запусков, приоритизация. Хранилище — PostgreSQL. Трейсинг — OpenTelemetry (через внешнюю библиотеку `gitlab.sarex.io/infra/golang-fiber-otel-tools`).
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Конфигурация читается **только из переменных окружения**. Используется библиотека [`github.com/ilyakaznacheev/cleanenv`](https://github.com/ilyakaznacheev/cleanenv) (`cleanenv.ReadEnv`). Файлы конфигурации (`.yaml`, `.json`) не читаются — вызывается именно `ReadEnv`, а не `ReadConfig`.
|
||||||
|
|
||||||
|
- **Префикс** у переменных отсутствует — используются «плоские» имена (`POSTGRES_ADDRESS`, `HTTP_HOST` и т. п.).
|
||||||
|
- **Вложенность** структуры `Config` описывается через встроенные (embedded) структуры (`App`, `Log`, `HTTP`, `pgxconnection.Postgres`, `TRACER`, `Execution`), но на имена переменных это не влияет — теги `env` заданы плоско.
|
||||||
|
- Значения по умолчанию задаются тегом `env-default`.
|
||||||
|
- Булевы значения cleanenv принимает как `true/false`, а также `1/0` (в Helm используется числовая форма).
|
||||||
|
|
||||||
|
Точка сборки конфигурации — `config/config.go`, функция `config.New()`. Отдельно, для запуска миграций, вторая структура `pkg/postgres/gopg.Postgres` читается своим вызовом `cleanenv.ReadEnv` в `gopg.GetPgConnectionWithoutConfig()` — **у неё те же имена переменных, но частично другие значения по умолчанию** (см. «Замечания»).
|
||||||
|
|
||||||
|
### Способы запуска
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Бинарь `httpserver` (production, `Dockerfile` `ENTRYPOINT ["/httpserver", "migrate"]`) | Переменные окружения контейнера (в k8s — из Helm-чарта: блоки `envs` и `secretEnvs`) |
|
||||||
|
| Бинарь `migrations` (отдельный ранер миграций) | Переменные окружения контейнера |
|
||||||
|
| Локальный запуск через `air` (`.air.toml`, hot-reload, сборка `./cmd/httpserver/main.go`) | Переменные окружения оболочки / `.env` (подхватываются вручную), значения по умолчанию из кода |
|
||||||
|
| `docker-compose up` (`docker-compose.yaml`) | `environment:` в compose + значения по умолчанию из кода |
|
||||||
|
|
||||||
|
### Точки входа и вспомогательные скрипты
|
||||||
|
|
||||||
|
| Файл / скрипт | Назначение |
|
||||||
|
| --- | --- |
|
||||||
|
| `cmd/httpserver/main.go` | Основная точка входа. Если передан хотя бы один аргумент (например `migrate`) — сначала выполняет миграции (`go-pg-migrations`), затем поднимает Fiber-сервер |
|
||||||
|
| `cmd/migrations/main.go` | Отдельный бинарь только для миграций (без запуска сервера) |
|
||||||
|
| `Dockerfile` | Multi-stage сборка: собирает `httpserver` и `migrations`, `ENTRYPOINT ["/httpserver", "migrate"]` |
|
||||||
|
| `entrypoint.sh` | Скрипт-обёртка (`/go/bin/migrations migrate` → `/go/bin/httpserver`). **Не используется** Dockerfile и ссылается на несуществующие пути бинарей — устаревший артефакт (см. «Замечания») |
|
||||||
|
| `.air.toml` | Конфиг hot-reload `air` для локальной разработки |
|
||||||
|
| `docker-compose.yaml` | Локальный стенд: PostgreSQL 14-alpine + сборка API. Блок `migrations` закомментирован |
|
||||||
|
| `Makefile` | Юнит-тесты, генерация моков, поднятие/сборка контейнера БД (`.docker/postgres`) |
|
||||||
|
| `.docker/postgres/Dockerfile` | Образ локальной БД для `make container-run-deps` |
|
||||||
|
|
||||||
|
### Порядок старта контейнера (production)
|
||||||
|
|
||||||
|
1. Контейнер стартует с `ENTRYPOINT ["/httpserver", "migrate"]`.
|
||||||
|
2. `httpserver` видит аргумент `migrate` (`len(os.Args) > 1`) → открывает подключение к БД через `go-pg` (`gopg.GetPgConnectionWithoutConfig`, читает env заново) и прогоняет миграции из `cmd/migrations/migrationfiles`.
|
||||||
|
3. После миграций поднимается Fiber-приложение (`server.New` → `server.Run`), подключается пул `pgx` (`pgxconnection.GetPgConnection`), при `TRACER_USE=true` инициализируется трейсер/otel-логгер.
|
||||||
|
4. Сервер слушает адрес из `HTTP_HOST`. Health-check — `GET /ping`.
|
||||||
|
|
||||||
|
## Переменные приложения
|
||||||
|
|
||||||
|
Ниже — переменные, которые **реально читает код** (`config/config.go` + `pkg/postgres/pgxconnection/postgres.go`).
|
||||||
|
|
||||||
|
### App (`config/config.go`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `APP_NAME` | string | `workflows-api` | Имя приложения (`App.Name`) |
|
||||||
|
| `APP_VERSION` | string | `v1` | Версия приложения (`App.Version`) |
|
||||||
|
|
||||||
|
### Log
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | `info` | Уровень логирования (`logging.NewLogger`) |
|
||||||
|
|
||||||
|
### HTTP
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `HTTP_HOST` | string | `0.0.0.0:8000` | Адрес и порт прослушивания Fiber-сервера |
|
||||||
|
| `PUBLIC_KEY` | string | — (пусто) | PEM-публичный ключ (PKIX) для проверки JWT Sarex. **Обязателен**: при пустом значении `auth.New` вызывает `panic` на старте |
|
||||||
|
| `HTTP_BODY_LIMIT` | int | `268435456` (256 MiB) | Максимальный размер тела запроса (`fiber.Config.BodyLimit`) |
|
||||||
|
| `HTTP_READ_BUFFER_SIZE` | int | `98304` (96 KiB) | Размер буфера чтения (`fiber.Config.ReadBufferSize`) |
|
||||||
|
|
||||||
|
### Database (`pkg/postgres/pgxconnection`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `POSTGRES_ADDRESS` | string | `localhost` | Хост PostgreSQL |
|
||||||
|
| `POSTGRES_DB` | string | `processing_db` | Имя базы данных |
|
||||||
|
| `POSTGRES_USER` | string | `sarex` | Пользователь БД |
|
||||||
|
| `POSTGRES_PASSWORD` | string | `sarex` | Пароль БД |
|
||||||
|
| `POSTGRES_PORT` | string | `5432` | Порт PostgreSQL |
|
||||||
|
| `POSTGRES_POOL_SIZE` | int | `3` | Размер пула (используется только для логирования; фактический размер пула pgx задаётся строкой подключения) |
|
||||||
|
| `ENABLE_SQL_QUERY` | bool | `true` | Флаг логирования SQL. **Читается в конфиг, но нигде не используется** (см. «Замечания») |
|
||||||
|
| `YC-PG-CERTIFICATE` | string | — (пусто) | CA-сертификат (PEM) для TLS-подключения к БД. Непустое значение включает TLS |
|
||||||
|
| `POSTGRES_SSL_USE` | bool | `false` | Включение TLS-подключения к БД |
|
||||||
|
|
||||||
|
### Tracer (`config.TRACER`, OpenTelemetry)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `TRACER_USE` | bool | `false` | Включить трейсинг/otel-логгер и otelfiber-middleware |
|
||||||
|
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора (gRPC) |
|
||||||
|
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
| `SERVICE_NAME` | string | `wf-test-db` | Имя сервиса в трейсах |
|
||||||
|
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя otel-логгера |
|
||||||
|
|
||||||
|
### Execution (лимиты ресурсов задач, `config.Execution`)
|
||||||
|
|
||||||
|
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `MAX_CPU_REQUESTS` | string | `25` | Максимально допустимый CPU-request в конфиге execution задачи (валидация через `k8s.io/apimachinery/resource`) |
|
||||||
|
| `MAX_MEMORY_REQUESTS` | string | `300Gi` | Максимально допустимый memory-request в конфиге execution задачи |
|
||||||
|
|
||||||
|
## Переменные инфраструктуры, сборки и вспомогательных утилит
|
||||||
|
|
||||||
|
Переменные `Makefile` (значения по умолчанию, переопределяются через `make VAR=...`):
|
||||||
|
|
||||||
|
| Переменная | Значение по умолчанию | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `OCI` | `docker` | Контейнерный движок (`docker`/`podman`) |
|
||||||
|
| `WF_API_CONTAINER__NETWORK_NAME` | `wf-api-network` | Имя bridge-сети для локальных контейнеров |
|
||||||
|
| `WF_API_DATABASE_IMAGE__TAG` | `wf-api-database` | Тег образа локальной БД |
|
||||||
|
| `WF_API_DATABASE_CONTAINER__NAME` | `wf-api-database-c` | Имя контейнера БД |
|
||||||
|
| `WF_API_DATABASE__VOLUME_NAME` | `wf-api-data` | Имя тома данных БД |
|
||||||
|
| `POSTGRES_USER` / `POSTGRES_PASSWORD` / `POSTGRES_DB` / `POSTGRES_PORT` | берутся из окружения | Прокидываются в контейнер БД при `container-run-database` |
|
||||||
|
|
||||||
|
Переменные сборки образа (`Dockerfile`):
|
||||||
|
|
||||||
|
| Переменная | Значение | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `CGO_ENABLED` | `0` | Статическая сборка Go |
|
||||||
|
| `GOOS` | `linux` | Целевая ОС |
|
||||||
|
| `GOARCH` | `amd64` | Целевая архитектура |
|
||||||
|
|
||||||
|
Переменные `.docker/postgres/Dockerfile` и `docker-compose.yaml` (локальный стенд):
|
||||||
|
|
||||||
|
| Переменная | Значение | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POSTGRES_DB` | `processing_db` | БД локального PostgreSQL |
|
||||||
|
| `POSTGRES_USER` | `processing` | Пользователь локального PostgreSQL |
|
||||||
|
| `POSTGRES_PASSWORD` | `processing` | Пароль локального PostgreSQL |
|
||||||
|
| `POSTGRES_ADDRESS` | `database` (в compose для сервиса `api`) | Хост БД внутри сети compose |
|
||||||
|
| `POSTGRES_SSL_USE` | `false` | Отключение TLS локально |
|
||||||
|
|
||||||
|
## Переменные из Helm-чарта
|
||||||
|
|
||||||
|
Деплой выполняется зависимостью-чартом `universal-chart` (`.helm/Chart.yaml`, версия `0.1.7`), значения — в `.helm/values.yaml`. Значения даются по стендам через ключи `_default / stage / preprod / production`.
|
||||||
|
|
||||||
|
### Обычные переменные (`services.workflows-api.envs`)
|
||||||
|
|
||||||
|
| Переменная | Значение (`_default`) | Значения по стендам / примечание |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POD_NAME` | `$(K8S_POD_NAME)` | Имя пода. **Кодом не читается** |
|
||||||
|
| `POSTGRES_POOL_SIZE` | `3` | Размер пула (логирование) |
|
||||||
|
| `HTTP_HOST` | `0.0.0.0:8080` | Адрес прослушивания в k8s (порт 8080) |
|
||||||
|
| `S3_SERVICE_ACCOUNT` | `/etc/sarex/yc-s3/yc-s3-service-account.json` | **Кодом не читается** |
|
||||||
|
| `DJANGO_HOST` | `https://stage.sarex.io` | stage: `stage.sarex.io`, preprod: `preprod.sarex.io`, production: `lk.sarex.io`. **Кодом не читается** |
|
||||||
|
| `OBSERVABILITY_TRACE_COLLECTOR_ENDPOINT` | `opentelemetry-collector.observability.svc.cluster.local:4318` | Одинаково на всех стендах. **Кодом не читается** (легаси-пакет `observavility` не подключён) |
|
||||||
|
| `ENABLE_SQL_QUERY` | `0` | Читается в конфиг, но не используется |
|
||||||
|
| `POSTGRES_SSL_USE` | `1` | preprod: `true`, остальные `1` |
|
||||||
|
| `ENABLE_OBSERVABILITY` | `1` | stage `1`, preprod `0`, production `1`. **Кодом не читается** |
|
||||||
|
| `TRACER_USE` | `1` | Включает трейсинг |
|
||||||
|
| `TRACER_HOST` | `signoz-otel-collector.signoz.svc.cluster.local:4317` | preprod/production: `signoz-otel-collector-external.signoz.svc.cluster.local:4317` |
|
||||||
|
| `SERVICE_NAME` | `workflows-api.processing-stage` | stage: `workflows-api.platform`, preprod: `workflows-api.processing-preprod`, production: `workflows-api.processing-prod` |
|
||||||
|
| `TRACER_USE_INSECURE` | `1` | Небезопасное подключение к коллектору |
|
||||||
|
| `TRACER_LOGGER_NAME` | `tracer_logger` | Имя otel-логгера |
|
||||||
|
| `MAX_CPU_REQUESTS` | `25` | Лимит CPU-request задач |
|
||||||
|
| `MAX_MEMORY_REQUESTS` | `300Gi` | Лимит memory-request задач |
|
||||||
|
|
||||||
|
### Секретные переменные (`services.workflows-api.secretEnvs`)
|
||||||
|
|
||||||
|
| Переменная | Секрет (secret_name) | Ключ (secret_key) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `POSTGRES_ADDRESS` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `host` |
|
||||||
|
| `POSTGRES_PORT` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `port` |
|
||||||
|
| `POSTGRES_DB` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `database` |
|
||||||
|
| `POSTGRES_USER` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `_default`/`stage`: `user`; `preprod`/`production`: `username` |
|
||||||
|
| `POSTGRES_PASSWORD` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `ya-pg-secret` | `password` |
|
||||||
|
| `PUBLIC_KEY` | `_default`/`stage`: `jwt-secret`; `preprod`/`production`: `public-key` | `_default`/`stage`: `public_key`; `preprod`/`production`: `key` |
|
||||||
|
| `YC-PG-CERTIFICATE` | `_default`/`stage`: `processing-postgresql-secret`; `preprod`/`production`: `yc-pg-certificate` | `_default`/`stage`: `ca.crt`; `preprod`/`production`: `certificate` |
|
||||||
|
|
||||||
|
### Прочие параметры чарта
|
||||||
|
|
||||||
|
- **Порт деплоймента**: `8080`; сервис `ClusterIP`, `targetPort: 8080`, `port: 80` (stage: `8000`).
|
||||||
|
- **Имя сервиса**: `workflows-service` (stage: `workflows-api-service`).
|
||||||
|
- **Реплики**: `_default`/`stage` — 1, preprod/production — 2.
|
||||||
|
- **Ресурсы пода**: requests `_default` 100Mi / 100m, preprod/production 200Mi / 200m.
|
||||||
|
- **Пробы**: liveness и readiness — `httpGet /ping` на порту 8080.
|
||||||
|
- **serviceAccount**: `workflows-api-sa`.
|
||||||
|
- **imagePullSecrets**: `dockerhub`. **Образ**: `cr.yandex/crp3ccidau046kdj8g9q/workflows-api`.
|
||||||
|
|
||||||
|
## Переменные в CI
|
||||||
|
|
||||||
|
`.gitlab-ci.yml` подключает общие пайплайны `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml@apps-business`). Глобальные переменные:
|
||||||
|
|
||||||
|
| Переменная | Значение | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `SERVICE_NAME` | `workflows-api` | Имя сервиса в пайплайне |
|
||||||
|
| `DOCKERFILE_PATH` | `Dockerfile` | Путь к Dockerfile |
|
||||||
|
| `BUILD_ARGS` | `--build-arg CI_COMMIT_SHORT_SHA=${CI_COMMIT_SHORT_SHA}` | Аргументы сборки образа |
|
||||||
|
| `CI_TRIGGER_SOURCE` | `app` | Источник триггера |
|
||||||
|
|
||||||
|
Маппинг ветка/тег → стенд и namespace (`workflow.rules`):
|
||||||
|
|
||||||
|
| Условие | STAND | NAMESPACE | CHART_VERSION | K8S_HUSTLER_BRANCH | env (universal-chart.global.env) |
|
||||||
|
| --- | --- | --- | --- | --- | --- |
|
||||||
|
| `CI_COMMIT_BRANCH == "stage"` | `stage` | `platform` | `0.0.1-stage` | `universal-chart-stage` | `stage` |
|
||||||
|
| `CI_COMMIT_BRANCH == "master"` | `preprod` | `processing-preprod` | `0.0.1-preprod` | `universal-chart-preprod` | `preprod` |
|
||||||
|
| `CI_COMMIT_TAG` (любой тег) | `production` | `processing-prod` | `0.0.1-prod` | `universal-chart-production` | `production` |
|
||||||
|
| `CI_PIPELINE_SOURCE == "merge_request_event"` | — | — | — | — | сборка образа выключена (`ENABLE_BUILD_IMAGE=false`) |
|
||||||
|
|
||||||
|
Во всех деплой-правилах через `HELM_SET_ARGS` пробрасываются `IMAGE_NAME`, `commitSha=${CI_COMMIT_SHA}`, `gitlabUri`, `gitlabJobUrl`, `owner`. `RELEASE_NAME`/`CHART_NAME` — `workflows-api`.
|
||||||
|
|
||||||
|
Джоба `unittest` (`stage: test`, образ `golang:1.24`, `make unit-tests`) запускается на любых ветках/тегах и MR, `allow_failure: true`.
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
1. **Две разные структуры конфигурации БД с разными дефолтами.** Сервер использует `pkg/postgres/pgxconnection.Postgres` (дефолты `POSTGRES_USER=sarex`, `POSTGRES_PASSWORD=sarex`), а миграции — `pkg/postgres/gopg.Postgres` (дефолты `processing`/`processing`). При запуске без явно заданных переменных сервер и миграции подключались бы под разными кредами. В production это не проявляется, т. к. все переменные приходят из секретов.
|
||||||
|
2. **`ENABLE_SQL_QUERY` не используется.** Поле читается в обе структуры (`EnableSQLQuery`), но нигде в коде не применяется — флаг «мёртвый».
|
||||||
|
3. **`OBSERVABILITY_TRACE_COLLECTOR_ENDPOINT`, `ENABLE_OBSERVABILITY` не используются.** Пакет `pkg/observavility` (с `SetupOTelSDK`) нигде не импортируется — это легаси. Реальный трейсинг настраивается переменными `TRACER_*` через внешнюю библиотеку `golang-fiber-otel-tools`.
|
||||||
|
4. **`POD_NAME`, `S3_SERVICE_ACCOUNT`, `DJANGO_HOST` из Helm кодом не читаются** — либо задел на будущее, либо устаревшие переменные.
|
||||||
|
5. **`entrypoint.sh` устарел и не используется.** Он ссылается на `/go/bin/migrations` и `/go/bin/httpserver`, тогда как в образе бинари лежат в `/httpserver` и `/migrations`, а `ENTRYPOINT` задан в `Dockerfile` напрямую (`/httpserver migrate`).
|
||||||
|
6. **Опечатка в mount-пути internal-middleware.** В `server.go` middleware, выставляющий `is_internal=true`, монтируется как `app.Use("./internal", ...)` (с ведущей точкой) вместо `"/internal"`. Из-за этого для маршрутов группы `/internal` флаг `is_internal` может не выставляться; в контроллерах `nil`-значение трактуется как «внутренний/доверенный запрос» (проверки принадлежности к компании пропускаются). Логически поведение сохраняется, но путь выглядит как баг.
|
||||||
|
7. **`PUBLIC_KEY` обязателен.** При пустом значении `auth.New` делает `panic("failed to parse PEM block ...")` — сервис не стартует. Дефолта нет.
|
||||||
|
8. **Расхождение по порту.** Дефолт кода `HTTP_HOST=0.0.0.0:8000`, docker-compose — `8000`, а в k8s (Helm) — `8080`. Локально сервис слушает 8000, в кластере — 8080.
|
||||||
|
9. **`BUILD_ARGS` передаёт `CI_COMMIT_SHORT_SHA`, но `Dockerfile` не объявляет соответствующий `ARG`** — build-arg игнорируется.
|
||||||
|
10. **`POSTGRES_SSL_USE` в Helm задаётся то как `1`, то как `true`** (preprod). cleanenv корректно парсит обе формы, но единообразия нет.
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
Для запуска сервера локально (например, БД поднята через `make container-run-deps` или `docker-compose`) достаточно:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# БД
|
||||||
|
POSTGRES_ADDRESS=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=processing_db
|
||||||
|
POSTGRES_USER=processing
|
||||||
|
POSTGRES_PASSWORD=processing
|
||||||
|
POSTGRES_SSL_USE=false
|
||||||
|
|
||||||
|
# HTTP
|
||||||
|
HTTP_HOST=0.0.0.0:8000
|
||||||
|
|
||||||
|
# Обязательно: PEM публичный ключ (PKIX) для проверки JWT
|
||||||
|
PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
|
||||||
|
|
||||||
|
# Трейсинг можно выключить
|
||||||
|
TRACER_USE=false
|
||||||
|
```
|
||||||
|
|
||||||
|
Минимальный сценарий:
|
||||||
|
1. Поднять PostgreSQL: `make container-run-deps` (или `docker-compose up database`).
|
||||||
|
2. Экспортировать переменные выше (особенно валидный `PUBLIC_KEY`, иначе `panic`).
|
||||||
|
3. Прогнать миграции и запустить сервер: `go run ./cmd/httpserver migrate` (аргумент `migrate` включает миграции), либо `air` для hot-reload.
|
||||||
|
4. Проверить: `GET http://localhost:8000/ping` → `{"status":"ready"}`.
|
||||||
|
|
||||||
|
> Через `docker-compose up` сервис поднимается на `:8000`, БД — `processing/processing/processing_db`, но `PUBLIC_KEY` в compose не задан — для полноценной работы API его нужно добавить.
|
||||||
68
apps/processing/workflows-api.env.example
Normal file
68
apps/processing/workflows-api.env.example
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# workflows-api — пример переменных окружения
|
||||||
|
# Конфигурация читается через cleanenv (github.com/ilyakaznacheev/cleanenv)
|
||||||
|
# из переменных окружения. Префикса нет. Значения по умолчанию — из кода.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# ----- App -----
|
||||||
|
APP_NAME=workflows-api
|
||||||
|
APP_VERSION=v1
|
||||||
|
|
||||||
|
# ----- Logging -----
|
||||||
|
# Уровень логирования: debug | info | warn | error
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# ----- HTTP -----
|
||||||
|
# Адрес и порт прослушивания (в k8s задаётся 0.0.0.0:8080)
|
||||||
|
HTTP_HOST=0.0.0.0:8000
|
||||||
|
# PEM публичный ключ (PKIX) для проверки JWT Sarex. ОБЯЗАТЕЛЕН:
|
||||||
|
# при пустом значении сервис падает с panic на старте.
|
||||||
|
PUBLIC_KEY=
|
||||||
|
# Максимальный размер тела запроса, байт (по умолчанию 256 MiB)
|
||||||
|
HTTP_BODY_LIMIT=268435456
|
||||||
|
# Размер буфера чтения, байт (по умолчанию 96 KiB)
|
||||||
|
HTTP_READ_BUFFER_SIZE=98304
|
||||||
|
|
||||||
|
# ----- Database (PostgreSQL) -----
|
||||||
|
POSTGRES_ADDRESS=localhost
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
POSTGRES_DB=processing_db
|
||||||
|
POSTGRES_USER=processing
|
||||||
|
POSTGRES_PASSWORD=processing
|
||||||
|
# Размер пула (используется только для лога)
|
||||||
|
POSTGRES_POOL_SIZE=3
|
||||||
|
# Включить TLS-подключение к БД
|
||||||
|
POSTGRES_SSL_USE=false
|
||||||
|
# CA-сертификат (PEM) для TLS. Непустое значение включает TLS автоматически.
|
||||||
|
YC-PG-CERTIFICATE=
|
||||||
|
# ВНИМАНИЕ: переменная читается в конфиг, но кодом НЕ используется (мёртвый флаг).
|
||||||
|
ENABLE_SQL_QUERY=true
|
||||||
|
|
||||||
|
# ----- Tracing (OpenTelemetry) -----
|
||||||
|
# Включает трейсинг, otel-логгер и otelfiber-middleware
|
||||||
|
TRACER_USE=false
|
||||||
|
# Адрес OTLP-коллектора (gRPC)
|
||||||
|
TRACER_HOST=localhost:4317
|
||||||
|
# Небезопасное (без TLS) подключение к коллектору
|
||||||
|
TRACER_USE_INSECURE=true
|
||||||
|
# Имя сервиса в трейсах
|
||||||
|
SERVICE_NAME=workflows-api
|
||||||
|
# Имя otel-логгера
|
||||||
|
TRACER_LOGGER_NAME=tracer_logger
|
||||||
|
|
||||||
|
# ----- Execution (лимиты ресурсов задач) -----
|
||||||
|
# Максимально допустимый CPU-request в execution-конфиге задачи
|
||||||
|
MAX_CPU_REQUESTS=25
|
||||||
|
# Максимально допустимый memory-request в execution-конфиге задачи
|
||||||
|
MAX_MEMORY_REQUESTS=300Gi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Переменные ниже присутствуют в .helm/values.yaml / старом .example.env,
|
||||||
|
# но кодом НЕ читаются (легаси). Оставлены для справки, включать не нужно:
|
||||||
|
# OBSERVABILITY_TRACE_COLLECTOR_ENDPOINT — пакет observavility не подключён
|
||||||
|
# ENABLE_OBSERVABILITY
|
||||||
|
# POD_NAME
|
||||||
|
# S3_SERVICE_ACCOUNT
|
||||||
|
# DJANGO_HOST
|
||||||
|
# ENABLE_SSL — опечатка старого .example.env; корректное имя POSTGRES_SSL_USE
|
||||||
|
# ============================================================================
|
||||||
755
apps/processing/workflows-api.openapi.yaml
Normal file
755
apps/processing/workflows-api.openapi.yaml
Normal file
@ -0,0 +1,755 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
info:
|
||||||
|
title: Workflows API
|
||||||
|
version: "1.0.0"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **workflows-api** для работы с workflow (пайплайнами задач):
|
||||||
|
создание, получение, перезапуск задач, отмена запусков, приоритизация и получение
|
||||||
|
позиции в очереди.
|
||||||
|
|
||||||
|
### Технологии
|
||||||
|
- Язык: Go 1.24, HTTP-фреймворк **Fiber v2**.
|
||||||
|
- JSON-сериализация: `bytedance/sonic`.
|
||||||
|
- Хранилище: PostgreSQL (пул `pgx/v5`).
|
||||||
|
- Трейсинг: OpenTelemetry (`otelfiber`), включается переменной `TRACER_USE`.
|
||||||
|
|
||||||
|
### Группы маршрутов
|
||||||
|
Все обработчики регистрируются дважды — в двух группах с одинаковым набором путей
|
||||||
|
(см. `internal/server/server.go` и `internal/controller/http/v1/workflows/routes.go`):
|
||||||
|
|
||||||
|
- **`/api/v1/...`** — публичная группа. На префикс `/api` навешен middleware аутентификации
|
||||||
|
(`auth.AuthMiddleware`): требуется валидный JWT. Для запросов выставляется `is_internal=false`
|
||||||
|
и проверяется принадлежность пользователя к компании.
|
||||||
|
- **`/internal/v1/...`** — внутренняя группа (сервис-сервис) без аутентификации; трактуется как
|
||||||
|
доверенная (`is_internal=true`), проверки принадлежности к компании пропускаются.
|
||||||
|
|
||||||
|
Отдельно, вне групп и без авторизации, доступен health-check **`GET /ping`**.
|
||||||
|
|
||||||
|
В этом документе пути описаны относительно базового префикса группы `/api/v1`
|
||||||
|
(см. `servers`). Те же пути доступны и под `/internal/v1`.
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Группа `/api` защищена middleware, который принимает один из двух токенов:
|
||||||
|
- **`Authorization: Bearer <JWT>`** — токен Sarex, подпись проверяется публичным ключом
|
||||||
|
из переменной `PUBLIC_KEY` (RS/PKIX). Из claims извлекаются `user_id`, `company_ids`,
|
||||||
|
`is_superuser`.
|
||||||
|
- **`Identity: Bearer <JWT>`** — токен Zitadel (проверяется без верификации подписи,
|
||||||
|
`ParseUnverified`); данные пользователя берутся из claim
|
||||||
|
`urn:zitadel:iam:user:metadata` (поля `id`, `company_ids`, `is_superuser`).
|
||||||
|
|
||||||
|
Если присутствует заголовок `Identity`, используется он; иначе — `Authorization`.
|
||||||
|
Часть операций (`prioritize`, `move_to_super_high_resources`) доступна только суперпользователю
|
||||||
|
(`is_superuser=true`).
|
||||||
|
|
||||||
|
### Пагинация
|
||||||
|
Метод `GET /workflows` поддерживает `limit` и `offset` (query-параметры, строки).
|
||||||
|
Ответ содержит `items`, а также `limit`, `offset`, `total`.
|
||||||
|
|
||||||
|
### Обработка ошибок
|
||||||
|
Ошибки возвращаются в JSON вида:
|
||||||
|
```json
|
||||||
|
{ "message": "human readable message", "error_code": "WF-0001" }
|
||||||
|
```
|
||||||
|
Коды (`internal/app_errors`): `WF-0000` (system, 500), `WF-0001` (not found, 404),
|
||||||
|
`WF-0002` (no auth, 401), `WF-0003` (no access), `WF-0004` (invalid, 400),
|
||||||
|
`WF-0005` (workflow config error / forbidden, 400/403).
|
||||||
|
HTTP-статус выбирается в `ErrorHandler` фреймворка по типу ошибки: 400 (ошибки парсинга/валидации/
|
||||||
|
конфигурации workflow), 401 (нет/некорректный токен), 403 (forbidden), 404 (не найдено), 500 (прочее).
|
||||||
|
|
||||||
|
### Замечания (расхождения кода и существующей схемы)
|
||||||
|
Документ приведён в соответствие с реальными маршрутами `routes.go`. Отличия от старого
|
||||||
|
`openapi_schema.yaml`:
|
||||||
|
- **Добавлены** отсутствовавшие маршруты: `POST /workflows/batch`, `GET /workflows/{workflow_id}/position`,
|
||||||
|
`POST /workflows/{workflow_id}/prioritize`, `POST /tasks/{task_id}/move_to_super_high_resources`.
|
||||||
|
- **Удалён** маршрут `GET /tasks-runs/{task_run_id}/logs` — в коде такого обработчика нет.
|
||||||
|
(Внимание: `workflows-frontend` этот эндпоинт вызывает — см. `workflows-frontend.ENDPOINTS.md`.)
|
||||||
|
- `POST /tasks-runs/{task_run_id}/cancel` фактически возвращает пустой объект `{}` (в коде
|
||||||
|
`c.JSON(&struct{}{})`), а не объект task_run.
|
||||||
|
- У задачи (`Task`) в модели есть поля `execution`, `services`, `layer`, `id`, `workflow_id`,
|
||||||
|
а у workflow — `document_id`, которые в старой схеме отсутствовали.
|
||||||
|
servers:
|
||||||
|
- url: 'http://localhost:8000/api/v1'
|
||||||
|
description: Локальный сервер (дефолт HTTP_HOST / docker-compose)
|
||||||
|
- url: 'http://workflows-service/api/v1'
|
||||||
|
description: Внутрикластерный сервис (preprod/production; stage — workflows-api-service:8000)
|
||||||
|
- url: 'https://stage-api.sarex.io/workflows/api/v1'
|
||||||
|
description: Публичный stage (через ingress, префикс /workflows)
|
||||||
|
- url: 'https://api.sarex.io/workflows/api/v1'
|
||||||
|
description: Публичный production (через ingress, префикс /workflows)
|
||||||
|
tags:
|
||||||
|
- name: workflows
|
||||||
|
description: Операции с workflow
|
||||||
|
- name: tasks
|
||||||
|
description: Операции с задачами workflow
|
||||||
|
- name: task-runs
|
||||||
|
description: Операции с запусками задач
|
||||||
|
- name: health
|
||||||
|
description: Проверка доступности сервиса
|
||||||
|
paths:
|
||||||
|
/ping:
|
||||||
|
get:
|
||||||
|
tags: [health]
|
||||||
|
summary: Health-check
|
||||||
|
description: >
|
||||||
|
Проверка готовности сервиса. Зарегистрирован вне групп `/api` и `/internal`,
|
||||||
|
без аутентификации (реальный путь — `/ping`, без префикса `/api/v1`).
|
||||||
|
operationId: Ping
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Сервис готов
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: string
|
||||||
|
example: ready
|
||||||
|
|
||||||
|
/companies/{company_id}/workflows:
|
||||||
|
post:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Создать workflow
|
||||||
|
description: >
|
||||||
|
Создаёт workflow для компании. Для группы `/api` пользователь должен принадлежать
|
||||||
|
компании `company_id`. Задачи не должны содержать поле `services` — используется
|
||||||
|
`service_requests`. Если у задачи не задан `backoff_limit`, он проставляется равным 5.
|
||||||
|
Поле `valid_until` должно быть в будущем.
|
||||||
|
operationId: CreateWorkflow
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: company_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Идентификатор компании
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 1
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/CreateWorkflowRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Созданный workflow
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Workflow'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows:
|
||||||
|
get:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Список workflow по компаниям
|
||||||
|
description: >
|
||||||
|
Возвращает список workflow для указанных компаний. Параметр `company_ids` —
|
||||||
|
обязательная строка с идентификаторами через запятую. Для группы `/api`
|
||||||
|
не-суперпользователю возвращаются только его компании.
|
||||||
|
operationId: ListByCompanyID
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: company_ids
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
description: Идентификаторы компаний через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "1,2,3"
|
||||||
|
- name: limit
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 100
|
||||||
|
- name: offset
|
||||||
|
in: query
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 0
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Список workflow
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/WorkflowsResponse'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows/batch:
|
||||||
|
post:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Получить workflow пачкой
|
||||||
|
description: Возвращает workflow по списку идентификаторов.
|
||||||
|
operationId: GetWorkflows
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/GetBatchWorkflowsRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Найденные workflow
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/WorkflowsBatchResponse'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows/{id}:
|
||||||
|
get:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Получить workflow по ID
|
||||||
|
operationId: GetWorkflow
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: UUID workflow
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Workflow
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Workflow'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows/{workflows_ids}/state:
|
||||||
|
get:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Состояния нескольких workflow
|
||||||
|
description: >
|
||||||
|
Возвращает отображение `workflow_id -> state` для списка workflow.
|
||||||
|
`workflows_ids` — UUID через запятую.
|
||||||
|
operationId: GetStates
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: workflows_ids
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: UUID workflow через запятую
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "3fa85f64-5717-4562-b3fc-2c963f66afa6,4fa85f64-5717-4562-b3fc-2c963f66afa6"
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Отображение id -> состояние
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/CompletenessState'
|
||||||
|
example: { "3fa85f64-5717-4562-b3fc-2c963f66afa6": "done" }
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows/{workflow_id}/position:
|
||||||
|
get:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Позиция workflow в очереди
|
||||||
|
description: Возвращает позицию workflow в очереди на выполнение (ограничение 100).
|
||||||
|
operationId: GetWorkflowQueuePosition
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: workflow_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Позиция в очереди
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
position:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/workflows/{workflow_id}/prioritize:
|
||||||
|
post:
|
||||||
|
tags: [workflows]
|
||||||
|
summary: Приоритизировать workflow
|
||||||
|
description: Повышает приоритет workflow. Доступно только суперпользователю.
|
||||||
|
operationId: PrioritizeWorkflow
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: workflow_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Успешно, тело отсутствует
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/tasks/{task_id}/restart:
|
||||||
|
post:
|
||||||
|
tags: [tasks]
|
||||||
|
summary: Перезапустить задачу
|
||||||
|
description: >
|
||||||
|
Перезапускает задачу, опционально переопределяя `inputs`, `outputs`, `parameters`,
|
||||||
|
`valid_until`. Возвращает обновлённый workflow.
|
||||||
|
operationId: RestartTask
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: task_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
requestBody:
|
||||||
|
required: false
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/RestartTaskRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Обновлённый workflow
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Workflow'
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/tasks/{task_id}/move_to_super_high_resources:
|
||||||
|
post:
|
||||||
|
tags: [tasks]
|
||||||
|
summary: Перевести задачу на super-high-resources
|
||||||
|
description: Перемещает задачу на пул ресурсов super-high-resources. Только суперпользователь.
|
||||||
|
operationId: MoveTaskToSuperHighResources
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: task_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Успешно, тело отсутствует
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
/tasks-runs/{task_run_id}/cancel:
|
||||||
|
post:
|
||||||
|
tags: [task-runs]
|
||||||
|
summary: Отменить запуск задачи
|
||||||
|
description: Отменяет запуск задачи (task run). Возвращает пустой объект.
|
||||||
|
operationId: CancelTaskRun
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/AuthorizationHeader'
|
||||||
|
- name: task_run_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Успешно (пустой объект)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/BadRequest'
|
||||||
|
'401':
|
||||||
|
$ref: '#/components/responses/Unauthorized'
|
||||||
|
'403':
|
||||||
|
$ref: '#/components/responses/Forbidden'
|
||||||
|
'404':
|
||||||
|
$ref: '#/components/responses/NotFound'
|
||||||
|
'500':
|
||||||
|
$ref: '#/components/responses/InternalError'
|
||||||
|
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
AuthorizationHeader:
|
||||||
|
name: Authorization
|
||||||
|
in: header
|
||||||
|
required: true
|
||||||
|
description: >
|
||||||
|
`Bearer <JWT>` — токен Sarex. Альтернативно можно передать заголовок
|
||||||
|
`Identity: Bearer <JWT>` (токен Zitadel). Не требуется для группы `/internal`.
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
example: "Bearer eyJhbGciOi..."
|
||||||
|
|
||||||
|
responses:
|
||||||
|
BadRequest:
|
||||||
|
description: Некорректный запрос (парсинг/валидация/конфигурация workflow)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AppError'
|
||||||
|
Unauthorized:
|
||||||
|
description: Не авторизован (нет/некорректный токен)
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AppError'
|
||||||
|
Forbidden:
|
||||||
|
description: Доступ запрещён
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AppError'
|
||||||
|
NotFound:
|
||||||
|
description: Ресурс не найден
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AppError'
|
||||||
|
InternalError:
|
||||||
|
description: Внутренняя ошибка
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/AppError'
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
AppError:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "not found workflow"
|
||||||
|
error_code:
|
||||||
|
type: string
|
||||||
|
description: "Код ошибки: WF-0000..WF-0005"
|
||||||
|
example: "WF-0001"
|
||||||
|
|
||||||
|
CompletenessState:
|
||||||
|
type: string
|
||||||
|
description: Состояние workflow
|
||||||
|
enum: [done, running, error]
|
||||||
|
|
||||||
|
ExecutionState:
|
||||||
|
type: string
|
||||||
|
description: Состояние запуска задачи (task run)
|
||||||
|
enum: [pending, done, canceling, canceled, idle, running, error, lost]
|
||||||
|
|
||||||
|
Description:
|
||||||
|
type: object
|
||||||
|
description: Описание входа/выхода задачи (источник/приёмник данных)
|
||||||
|
required: [type, path]
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
maxLength: 32
|
||||||
|
description: "Тип хранилища (валидация: google, local, s3, s3v2, srx-tmp, url, pdm)"
|
||||||
|
example: s3
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
maxLength: 512
|
||||||
|
|
||||||
|
ServicePublicDescription:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
kind:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Resources:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cpu_limits:
|
||||||
|
type: string
|
||||||
|
memory_limits:
|
||||||
|
type: string
|
||||||
|
cpu_requests:
|
||||||
|
type: string
|
||||||
|
memory_requests:
|
||||||
|
type: string
|
||||||
|
|
||||||
|
Execution:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
executor:
|
||||||
|
type: string
|
||||||
|
description: "Исполнитель задачи (допустимые: k8s, amqp)"
|
||||||
|
enum: [k8s, amqp]
|
||||||
|
resources:
|
||||||
|
$ref: '#/components/schemas/Resources'
|
||||||
|
|
||||||
|
Task:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
workflow_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
slug:
|
||||||
|
type: string
|
||||||
|
docker_image:
|
||||||
|
type: string
|
||||||
|
backoff_limit:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
description: "По умолчанию 5, если не задан"
|
||||||
|
inputs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/Description'
|
||||||
|
outputs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/Description'
|
||||||
|
parameters:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
service_requests:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
services:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ServicePublicDescription'
|
||||||
|
execution:
|
||||||
|
$ref: '#/components/schemas/Execution'
|
||||||
|
needs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
layer:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
TaskRun:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
task_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
workflow_id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
state:
|
||||||
|
$ref: '#/components/schemas/ExecutionState'
|
||||||
|
reason:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
progress:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
nullable: true
|
||||||
|
total_time:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
nullable: true
|
||||||
|
logs_paths:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
logs_storage:
|
||||||
|
type: string
|
||||||
|
nullable: true
|
||||||
|
logs_last_gathered:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
nullable: true
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
company_id:
|
||||||
|
type: integer
|
||||||
|
state:
|
||||||
|
$ref: '#/components/schemas/CompletenessState'
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
valid_until:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
document_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
tasks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Task'
|
||||||
|
task_runs:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/TaskRun'
|
||||||
|
|
||||||
|
CreateWorkflowRequest:
|
||||||
|
type: object
|
||||||
|
required: [name, valid_until]
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
valid_until:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
document_id:
|
||||||
|
type: integer
|
||||||
|
nullable: true
|
||||||
|
tasks:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Task'
|
||||||
|
|
||||||
|
RestartTaskRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
inputs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/Description'
|
||||||
|
outputs:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/Description'
|
||||||
|
parameters:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
valid_until:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
|
||||||
|
GetBatchWorkflowsRequest:
|
||||||
|
type: object
|
||||||
|
required: [workflows_ids]
|
||||||
|
properties:
|
||||||
|
workflows_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
|
||||||
|
WorkflowsBatchResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
workflows:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Workflow'
|
||||||
|
|
||||||
|
WorkflowsResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
items:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Workflow'
|
||||||
|
limit:
|
||||||
|
type: integer
|
||||||
|
offset:
|
||||||
|
type: integer
|
||||||
|
total:
|
||||||
|
type: integer
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user