Add example .env files and detailed configuration documentation for mapper, message-hub, notes, pm, and prescriptions services.
This commit is contained in:
parent
b18e31d5d6
commit
9cff5d6e39
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"
|
||||
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
|
||||
363
apps/processing/workflows-engine.CONFIGURATION.md
Normal file
363
apps/processing/workflows-engine.CONFIGURATION.md
Normal file
@ -0,0 +1,363 @@
|
||||
# Конфигурация проекта workflows-engine
|
||||
|
||||
`workflows-engine` (внутреннее имя образа — `kubernetes-engine`) — это фоновый сервис-оркестратор (демон без REST API), который вычитывает workflow/задачи из PostgreSQL и запускает их либо как Job'ы в Kubernetes, либо публикует в RabbitMQ (AMQP-исполнитель). Конфигурируется исключительно через переменные окружения.
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
- **Библиотека парсинга:** [`github.com/ilyakaznacheev/cleanenv`](https://github.com/ilyakaznacheev/cleanenv).
|
||||
- **Точка входа конфигурации:** `config/config.go`, функция `config.New()` вызывает `cleanenv.ReadEnv(cfg)` и заполняет структуру `Config` из переменных окружения процесса.
|
||||
- **Формат тегов:** каждое поле помечено тегом `env:"ИМЯ"`; значение по умолчанию задаётся тегом `env-default:"..."`. Префикса имён переменных нет.
|
||||
- **Составная структура `Config`** собирается из встроенных (embedded) структур:
|
||||
- `App`, `Log`, `Executor`, `Kubernetes`, `Resources`, `Storages`, `Pooling`, `Workflows`, `Security` — объявлены в `config/config.go`;
|
||||
- `pgxconnection.Postgres` — объявлена в `pkg/pgxconnection/postgres.go` (переменные `POSTGRES_*`);
|
||||
- `rabbitmq.RabbitMQ` — объявлена в `pkg/rabbitmq/config.go` (переменные `RABBITMQ_*`).
|
||||
- **Кастомный парсинг:** поле `WORKFLOW_PRIORITY` имеет тип `entity.WorkflowPriority` с методом `SetValue` (`internal/entity/workflow.go`); допустимые значения: `default`, `high`, `low` (регистр не важен).
|
||||
- **Важно:** часть переменных из `config.env` и Helm-чарта **не читается** структурой конфигурации. Они либо читаются напрямую через `os.Getenv(...)` в `pkg/kube_services/services.go` и **пробрасываются в env запускаемых Job-подов** (а не потребляются самим engine), либо не используются кодом вовсе (см. раздел «Замечания и потенциальные проблемы»).
|
||||
|
||||
### Режимы запуска / исполнители
|
||||
|
||||
Бинарь один (`cmd/engine`), «режим» определяется набором включённых исполнителей и приоритетом.
|
||||
|
||||
| Режим | Как включается | Назначение |
|
||||
| --- | --- | --- |
|
||||
| Kubernetes-исполнитель | `ENABLE_KUBERNETES_EXECUTOR=1` (по умолчанию `true`) | Запуск задач как `batchv1.Job` в кластере Kubernetes |
|
||||
| AMQP-исполнитель | `ENABLE_AMQP_EXECUTOR=1` (по умолчанию `false`) | Публикация задач в RabbitMQ и приём результатов |
|
||||
| backend (обычный приоритет) | Helm-деплой `backend`, `WORKFLOW_PRIORITY=low` | Обработка обычных workflow |
|
||||
| backend-high-priority | Helm-деплой `backend-high-priority`, `WORKFLOW_PRIORITY=high` | Обработка высокоприоритетных workflow |
|
||||
|
||||
Оба Helm-деплоя разворачивают один и тот же образ; отличаются только значением `WORKFLOW_PRIORITY` (и `MAX_WORKFLOWS_LIMIT`).
|
||||
|
||||
### Точки входа (entrypoints)
|
||||
|
||||
| Путь | Тип | Описание |
|
||||
| --- | --- | --- |
|
||||
| `cmd/engine/main.go` | main-пакет | Единственная точка входа. Поднимает pprof-сервер на `:8081`, читает конфиг, создаёт `engine.New(cfg, logger)` и вызывает `Run()` |
|
||||
| `internal/apps/engine/engine.go` | приложение | Основная логика: подключение к Postgres, инициализация исполнителей (K8s/AMQP), pooling-контроллер и оркестратор, graceful shutdown по SIGINT |
|
||||
|
||||
### Запуск контейнера
|
||||
|
||||
- **Dockerfile:** multi-stage сборка на `golang:1.20-buster`, финальный образ `FROM scratch`.
|
||||
- **CMD/ENTRYPOINT:** `ENTRYPOINT ["/engine"]` — запускается собранный бинарь напрямую, без shell-скрипта/entrypoint-обёртки.
|
||||
- Внутри процесса дополнительно поднимается HTTP-сервер `net/http/pprof` на порту `:8081` (профилирование). Полноценного REST API нет (см. раздел про `API_ADDRESS`).
|
||||
|
||||
## Переменные приложения
|
||||
|
||||
### Приложение и логирование (`App`, `Log`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `APP_NAME` | string | `kubernetes-engine` | Имя приложения (в Helm переопределяется на `kubernetes-engine`/`kubernetes-engine-high-priority`) |
|
||||
| `APP_VERSION` | string | `v1` | Версия приложения |
|
||||
| `LOG_LEVEL` | string | `info` | Уровень логирования |
|
||||
|
||||
### PostgreSQL (`pkg/pgxconnection`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `POSTGRES_ADDRESS` | string | `localhost` | Хост БД |
|
||||
| `POSTGRES_DB` | string | `processing_db` | Имя базы данных |
|
||||
| `POSTGRES_USER` | string | `user` | Пользователь БД |
|
||||
| `POSTGRES_PASSWORD` | string | `password` | Пароль БД |
|
||||
| `POSTGRES_PORT` | string | `5432` | Порт БД |
|
||||
| `POSTGRES_POOL_SIZE` | int | `20` | Размер пула соединений |
|
||||
| `ENABLE_SQL_QUERY` | bool | `true` | Логирование SQL-запросов |
|
||||
| `YC-PG-CERTIFICATE` | string | `""` | TLS-сертификат CA для подключения к БД (значение сертификата) |
|
||||
| `POSTGRES_SSL_USE` | bool | `false` | Использовать SSL при подключении к БД |
|
||||
|
||||
### RabbitMQ (`pkg/rabbitmq`) — используется только при `ENABLE_AMQP_EXECUTOR=1`
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `RABBITMQ_HOST` | string | — | Хост брокера |
|
||||
| `RABBITMQ_PORT` | string | — | Порт брокера |
|
||||
| `RABBITMQ_USER` | string | — | Пользователь |
|
||||
| `RABBITMQ_PASS` | string | — | Пароль |
|
||||
| `RABBITMQ_VHOST` | string | — | Виртуальный хост |
|
||||
| `RABBITMQ_USE_SSL` | bool | — | Использовать TLS |
|
||||
| `RABBITMQ_CERTIFICATE` | string | — | TLS-сертификат |
|
||||
| `RABBITMQ_CREATE_EXCHANGE` | string | — | Exchange для отправки задач на запуск |
|
||||
| `RABBITMQ_CANCEL_EXCHANGE` | string | — | Exchange для отмены задач |
|
||||
| `RABBITMQ_CREATE_ROUTING_KEY` | string | — | Routing key для запуска |
|
||||
| `RABBITMQ_CANCEL_TOPIC` | string | — | Topic/ключ отмены |
|
||||
| `RABBITMQ_COMPLETENESS_EXCHANGE` | string | — | Exchange для получения результатов выполнения |
|
||||
| `RABBITMQ_COMPLETENESS_TOPIC` | string | — | Topic результатов выполнения |
|
||||
|
||||
> Примечание: в `config.env` присутствуют также `RABBITMQ_CREATE_TOPIC` — переменной `RABBITMQ_CREATE_TOPIC` в коде нет (в структуре есть только `CreateRoutingKey`).
|
||||
|
||||
### Исполнители и воркеры (`Executor`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `COUNT_RUNNING_WORKERS` | int | — (0) | Кол-во воркеров запуска задач |
|
||||
| `COUNT_CANCELING_WORKERS` | int | — (0) | Кол-во воркеров отмены задач |
|
||||
| `COUNT_HANDLE_JOB_WORKERS` | int | — (0) | Кол-во воркеров обработки Job'ов |
|
||||
| `ENABLE_AMQP_EXECUTOR` | bool | `false` | Включить AMQP-исполнитель (RabbitMQ) |
|
||||
| `ENABLE_KUBERNETES_EXECUTOR` | bool | `true` | Включить Kubernetes-исполнитель |
|
||||
|
||||
### Kubernetes (`Kubernetes`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `JOBS_NAMESPACE` | string | — | Namespace, в котором создаются Job'ы задач |
|
||||
| `KUBE_CONFIG` | string | — | Путь к kubeconfig. Если **пусто** — используется in-cluster конфигурация; если задан — out-of-cluster |
|
||||
| `KUBE_CONTEXT` | string | — | Контекст kubeconfig (для out-of-cluster) |
|
||||
| `KUBE_ADDR` | string | — | Адрес API-сервера кластера (для out-of-cluster; используется с `InsecureSkipTLSVerify`) |
|
||||
|
||||
### Ресурсы и планирование подов (`Resources`)
|
||||
|
||||
Управляют NodeSelector/Tolerations/requests для запускаемых Job'ов в зависимости от `service_requests` задачи (см. `README.md`).
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `CPU_COUNT` | int | — | CPU requests для `high-resources`/`persistent` |
|
||||
| `MEMORY_GI` | int | — | Memory (Gi) requests для `high-resources`/`persistent` |
|
||||
| `CPU_COUNT_LOW_RESOURCES` | int | — | CPU для `low-resources` |
|
||||
| `MEMORY_GI_LOW_RESOURCES` | int | — | Memory (Gi) для `low-resources` |
|
||||
| `CPU_COUNT_HIGH_MEM` | int | — | CPU для `super-high-resources` |
|
||||
| `MEMORY_GI_HIGH_MEM` | int | — | Memory (Gi) для `super-high-resources` |
|
||||
| `ENABLE_TOLERATION` | bool | — | Включить набор сервисов ресурсов (toleration/nodeselector) |
|
||||
| `TOLERATION_KEY` | string | — | Ключ toleration/nodeselector для high/low ресурсов |
|
||||
| `TOLERATION_VALUE` | string | — | Значение toleration/nodeselector для high/low ресурсов |
|
||||
| `TOLERATION_KEY_HIGH_MEM` | string | — | Ключ toleration для `super-high-resources` |
|
||||
| `TOLERATION_VALUE_HIGH_MEM` | string | — | Значение toleration для `super-high-resources` |
|
||||
| `TOLERATION_KEY_PERSISTENT` | string | — | Ключ toleration для `persistent` |
|
||||
| `TOLERATION_VALUE_PERSISTENT` | string | — | Значение toleration для `persistent` |
|
||||
| `DJANGO_BASIC_AUTH` | string | — | Базовая авторизация Django (поле присутствует в конфиге, но не используется в бизнес-логике; в под задачи прокидывается хардкод-путь `DJANGO_BASIC_AUTH_PATH`) |
|
||||
| `DEFAULT_TOLERATION_KEY` | string | — | Ключ toleration по умолчанию для всех подов |
|
||||
| `DEFAULT_TOLERATION_VALUE` | string | — | Значение toleration по умолчанию |
|
||||
| `DEFAULT_NODE_SELECTOR_KEY` | string | — | Ключ NodeSelector по умолчанию |
|
||||
| `DEFAULT_NODE_SELECTOR_VALUE` | string | — | Значение NodeSelector по умолчанию |
|
||||
| `DEFAULT_IMAGE_PULL_POLICY` | string | `always` | ImagePullPolicy для подов задач |
|
||||
| `DEFAULT_CPU_REQUESTS` | string | `100m` | CPU requests по умолчанию |
|
||||
| `DEFAULT_MEMORY_REQUESTS` | string | `64Mi` | Memory requests по умолчанию |
|
||||
|
||||
### Хранилища и внешние сервисы (`Storages`)
|
||||
|
||||
Флаги `ENABLE_*` включают соответствующий «сервис» (набор env/секретов/volume), который добавляется в под задачи в зависимости от `service_requests`. URL'ы прокидываются в поды mesh/pdm-сервисов.
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `S3_SERVICE_ACCOUNT` | string | — | Путь к service account JSON для S3 (в коде для подов путь по факту хардкодится) |
|
||||
| `ENABLE_S3_STORAGE` | bool | — | Включить сервис `s3` |
|
||||
| `ENABLE_S3V2_STORAGE` | bool | — | Включить сервис `s3v2` |
|
||||
| `ENABLE_PDM_STORAGE` | bool | — | Включить сервис `pdm` |
|
||||
| `ENABLE_URL_STORAGE` | bool | — | Включить сервис `url` |
|
||||
| `ENABLE_LOCAL` | bool | — | Включить локальное хранилище |
|
||||
| `ENABLE_BIM_API_DB` | bool | — | Включить сервис `bim_api_db` |
|
||||
| `ENABLE_BIM_API_CH` | bool | — | Включить сервис `bim_api_ch` (ClickHouse) |
|
||||
| `ENABLE_BIM_API_V2_DB` | bool | — | Включить сервисы `bim_api_v2_db` (+ db_2/3/4) |
|
||||
| `ENABLE_PDM_API_DB` | bool | — | Включить сервис `pdm_api_db` |
|
||||
| `ENABLE_COMPARISONS_API_DB` | bool | — | Включить сервис `comparison-api-db` |
|
||||
| `ENABLE_ISSUE_API_DB` | bool | — | Включить сервис `issues-api-db` |
|
||||
| `ENABLE_RESOURCES_API` | bool | — | Включить сервис `resources-api` |
|
||||
| `ENABLE_MAIL_GUN` | bool | — | Включить сервис `mailgun` |
|
||||
| `ENABLE_SMTP` | bool | — | Включить сервис `smtp` |
|
||||
| `ENABLE_WORKSPACE_API_DB` | bool | — | Включить сервис `workspace_api_db` |
|
||||
| `ENABLE_CROSS_SECTION_API_DB` | bool | — | Включить сервис `cross-section-api-db` |
|
||||
| `BIM_API_DEBUG` | bool | — | Флаг debug для BIM API (также пробрасывается в под, см. ниже) |
|
||||
| `COMPARISONS_API_DEBUG` | string | — | Флаг debug для Comparisons API (также пробрасывается в под) |
|
||||
| `INTERNAL_PDM_URL` | string | — | Внутренний URL PDM |
|
||||
| `EXTERNAL_PDM_URL` | string | — | Внешний URL PDM |
|
||||
| `INTERNAL_FILESTREAM_URL` | string | — | Внутренний URL filestream |
|
||||
| `EXTERNAL_FILESTREAM_URL` | string | — | Внешний URL filestream |
|
||||
| `INTERNAL_REMARK_URL` | string | — | Внутренний URL remarks |
|
||||
| `EXTERNAL_REMARK_URL` | string | — | Внешний URL remarks |
|
||||
| `DJANGO_HOST` | string | — | URL Django-хоста (ЛК) |
|
||||
| `INTERNAL_WORKSPACE_URL` | string | — | Внутренний URL workspaces |
|
||||
| `EXTERNAL_WORKSPACE_URL` | string | — | Внешний URL workspaces |
|
||||
|
||||
### Пулинг (`Pooling`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `MAX_WORKFLOWS_LIMIT` | uint64 | — (0) | Лимит одновременно обрабатываемых workflow при выборке |
|
||||
| `MAX_RUNNING_JOBS` | int64 | `200` | Максимум одновременно выполняющихся Job'ов |
|
||||
|
||||
### Workflow (`Workflows`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DEFAULT_GET_SINCE_LAST_HOURS` | uint64 | `168` | Глубина выборки задач в часах (нужно для FIFO; 168 ч = 7 дней) |
|
||||
| `WORKFLOW_PRIORITY` | enum (`default`/`high`/`low`) | `default` | Приоритет обрабатываемых workflow (задаёт «роль» инстанса) |
|
||||
|
||||
### Безопасность (`Security`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `ENABLE_RUNASUSER` | bool | `false` | Запускать контейнеры задач под фиксированным UID |
|
||||
| `USER_RUNASUSER` | int64 | `1000` | UID для `runAsUser` |
|
||||
|
||||
### Переменные, читаемые через `os.Getenv` и пробрасываемые в под'ы задач
|
||||
|
||||
Эти переменные **не входят** в структуру `Config` и **не потребляются** самим engine. Они читаются напрямую в `pkg/kube_services/services.go` и добавляются как env в контейнер запускаемой задачи (Job), когда включён соответствующий `ENABLE_*`-флаг. Тип — всегда строка (передаётся «как есть»).
|
||||
|
||||
| Переменная | Куда прокидывается (условие) | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `BIM_API_DB` | сервис `bim_api_db` (при `ENABLE_BIM_API_DB`) | Путь к JSON-конфигу БД BIM API |
|
||||
| `BIM_API_DEBUG` | сервис `bim_api_db` | Флаг debug |
|
||||
| `BIM_API_CH` | сервис `bim_api_ch` (при `ENABLE_BIM_API_CH`) | Путь к JSON-конфигу ClickHouse |
|
||||
| `BIM_API_CH_DEBUG` | сервис `bim_api_ch` | Флаг debug |
|
||||
| `BIM_API_V2_DB` | сервис `bim_api_v2_db` (при `ENABLE_BIM_API_V2_DB`) — в под прокидывается под именем `BIM_API_DB` | Путь к JSON-конфигу БД BIM API v2 |
|
||||
| `BIM_API_V2_DEBUG` | сервис `bim_api_v2_db` | Флаг debug |
|
||||
| `BIM_API_V2_DB_2` | сервис `bim_api_v2_db_2` — в под как `BIM_API_DB_2` | Путь к JSON-конфигу 2-й БД BIM v2 |
|
||||
| `BIM_API_V2_DB_3` | сервис `bim_api_v2_db_3` — в под как `BIM_API_DB_3` | Путь к JSON-конфигу 3-й БД BIM v2 |
|
||||
| `BIM_API_V2_DB_4` | сервис `bim_api_v2_db_4` — в под как `BIM_API_DB_4` | Путь к JSON-конфигу 4-й БД BIM v2 |
|
||||
| `COMPARISONS_API_DB` | сервис `comparison-api-db` (при `ENABLE_COMPARISONS_API_DB`) | Путь к JSON-конфигу БД Comparisons |
|
||||
| `COMPARISONS_API_DEBUG` | сервис `comparison-api-db` | Флаг debug |
|
||||
| `ISSUE_API_DB` | сервис `issues-api-db` (при `ENABLE_ISSUE_API_DB`) | Путь к JSON-конфигу БД Issues |
|
||||
| `ISSUE_API_DEBUG` | сервис `issues-api-db` | Флаг debug |
|
||||
| `RESOURCES_API_INTERNAL_HOST` | сервис `resources-api` (при `ENABLE_RESOURCES_API`) | Внутренний хост Resources API |
|
||||
| `SMTP` | сервис `smtp` (при `ENABLE_SMTP`) — в под как `SMTP_CONFIG_PATH` | Путь к JSON-конфигу SMTP |
|
||||
| `PDM_API_DB` | сервис `pdm_api_db` (при `ENABLE_PDM_API_DB`) | Путь к JSON-конфигу БД PDM |
|
||||
| `PDM_API_DEBUG` | сервис `pdm_api_db` | Флаг debug |
|
||||
| `WORKSPACE_API_DB` | сервис `workspace_api_db` (при `ENABLE_WORKSPACE_API_DB`) | Путь к JSON-конфигу БД Workspace |
|
||||
| `WORKSPACE_API_DEBUG` | сервис `workspace_api_db` | Флаг debug |
|
||||
| `CROSS_SECTION_API_DB` | сервис `cross-section-api-db` (при `ENABLE_CROSS_SECTION_API_DB`) | Путь к JSON-конфигу БД Cross-section |
|
||||
| `CROSS_SECTION_API_DEBUG` | сервис `cross-section-api-db` | Флаг debug |
|
||||
| `WORKFLOWS_SENTRY_DSN` | сервис `Default` (всегда) | Sentry DSN для подов задач |
|
||||
| `WORKFLOWS_SENTRY_DEBUG` | сервис `Default` (всегда) | Флаг debug Sentry |
|
||||
| `ENVIRONMENT` | сервис `Default` (всегда) | Имя окружения для подов задач |
|
||||
|
||||
> Пути `BIM_API_V2_DB_2/3/4`, `MAILGUN`, `SMTP` и т.п. задаются в Helm-чарте (`.helm/values.yaml`) — в `config.env` присутствуют не все из них.
|
||||
|
||||
## Переменные инфраструктуры/сборки
|
||||
|
||||
| Переменная | Где | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `CGO_ENABLED=0`, `GOOS=linux`, `GOARCH=amd64` | Dockerfile (build stage) | Статическая сборка бинаря под Linux/amd64 |
|
||||
| `CI_COMMIT_SHORT_SHA` | `.gitlab-ci.yml` → build-arg | Прокидывается в сборку образа |
|
||||
| `SERVICE_NAME=workflows-engine` | `.gitlab-ci.yml` | Имя сервиса/чарта в CI |
|
||||
| `DOCKERFILE_PATH=Dockerfile` | `.gitlab-ci.yml` | Путь к Dockerfile |
|
||||
| `BUILD_ARGS` | `.gitlab-ci.yml` | Аргументы сборки образа |
|
||||
| `CI_TRIGGER_SOURCE=app` | `.gitlab-ci.yml` | Источник триггера пайплайна |
|
||||
| `IMAGE_NAME`, `CI_COMMIT_SHA`, `CI_PROJECT_URL`, `CI_JOB_URL`, `CI_PROJECT_NAMESPACE` | `.gitlab-ci.yml` → `HELM_SET_ARGS` | Метаданные деплоя, пробрасываются в universal-chart |
|
||||
|
||||
Сборка/деплой наследуются из внешних CI-шаблонов проекта `generic/common-ci` (`universal-pipeline.yaml`, `common-security-scan.yaml`). Юнит-тесты: стадия `test`, образ `golang:1.20`, команда `make unit-tests`.
|
||||
|
||||
## Переменные из Helm-чарта
|
||||
|
||||
Чарт (`.helm/values.yaml`) построен на `universal-chart`; секреты монтируются как env (`secretEnvs`) и как volume'ы. Ниже — маппинг секретных env (общий для `backend` и `backend-high-priority`). Имя секрета зависит от окружения (`_default` / `stage` / `preprod` / `production`).
|
||||
|
||||
| Переменная | Секрет (secret_name) | Ключ (secret_key) |
|
||||
| --- | --- | --- |
|
||||
| `POSTGRES_ADDRESS` | `ya-pg-secret` (stage: `processing-postgresql-secret`) | `host` |
|
||||
| `POSTGRES_PORT` | `ya-pg-secret` (stage: `processing-postgresql-secret`) | `port` |
|
||||
| `POSTGRES_DB` | `ya-pg-secret` (stage: `processing-postgresql-secret`) | `database` |
|
||||
| `POSTGRES_USER` | `ya-pg-secret` (stage: `processing-postgresql-secret`) | `username` |
|
||||
| `POSTGRES_PASSWORD` | `ya-pg-secret` (stage: `processing-postgresql-secret`) | `password` |
|
||||
| `RABBITMQ_USER` | `rabbitmq-secret` | `username` |
|
||||
| `RABBITMQ_PASS` | `rabbitmq-secret` | `password` |
|
||||
| `YC-PG-CERTIFICATE` | `yc-pg-certificate` (stage: `processing-postgresql-secret`) | `certificate` (stage: `ca.crt`, preprod/prod: `certificate`) |
|
||||
|
||||
Секреты, монтируемые как volume'ы (файлы JSON-конфигов внешних БД и т.п.):
|
||||
|
||||
| Volume / секрет | mountPath |
|
||||
| --- | --- |
|
||||
| `yc-s3` | `/etc/sarex/yc-s3` |
|
||||
| `bim-api-db` | `/etc/sarex` |
|
||||
| `bim-api-v2-db-2` | `/etc/sarex/second-bim-bd` |
|
||||
| `bim-api-v2-db-3` | `/etc/sarex/third-bim-bd` |
|
||||
| `bim-api-v2-db-4` | `/etc/sarex/fourth-bim-bd` |
|
||||
| `comparison-api-db` | `/etc/comparisons` |
|
||||
| `pdm-api-db` | `/etc/pdm` |
|
||||
| `ws-api-db` | `/etc/ws` |
|
||||
| `mailgun-secret` | `/etc/mailgun-secret` |
|
||||
| `smtp-secret` | `/etc/smtp-secret` |
|
||||
| `issues-api-db` | `/etc/issues` |
|
||||
| `cross-section-api-db` | `/etc/cross_section` |
|
||||
| `tmp-volume` (emptyDir) | `/tmp` |
|
||||
|
||||
Прочие значимые переменные окружения задаются в `.helm/values.yaml` секцией `envs` для каждого сервиса (значения зависят от окружения `_default/stage/preprod/production`) — в т.ч. пути к JSON-конфигам (`BIM_API_DB`, `PDM_API_DB`, …), URL'ы сервисов, `POD_NAME=$(K8S_POD_NAME)`, `WORKFLOW_PRIORITY` (`low` для `backend`, `high` для `backend-high-priority`). Также в чарте описан `meshConfig` (Istio) — включён только для production.
|
||||
|
||||
## Переменные в CI
|
||||
|
||||
Определяются в `workflow.rules` файла `.gitlab-ci.yml` по ветке/тегу:
|
||||
|
||||
| Триггер | STAND | NAMESPACE | universal-chart env | CHART_VERSION | K8S_HUSTLER_BRANCH |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| ветка `stage` | `stage` | `platform` | `stage` | `0.0.1-stage` | `universal-chart-stage` |
|
||||
| ветка `master` | `preprod` | `processing-preprod` | `preprod` | `0.0.1-preprod` | `universal-chart-preprod` |
|
||||
| тег (`CI_COMMIT_TAG`) | `production` | `processing-prod` | `production` | `0.0.1-prod` | `universal-chart-production` |
|
||||
| Merge Request | — (сборка образа выключена: `ENABLE_BUILD_IMAGE=false`) | — | — | — | — |
|
||||
|
||||
`RELEASE_NAME` во всех случаях — `workflows-engine`. Для каждого стенда деплоятся оба сервиса: `backend` и `backend-high-priority`.
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
1. **Нет HTTP REST API, но `API_ADDRESS` присутствует.** Переменная `API_ADDRESS=0.0.0.0:8080` есть в `config.env` и в Helm (`envs`), но **не читается ни одной строкой Go-кода**. Реальный HTTP-сервер — только `net/http/pprof` на `:8081` (`cmd/engine/main.go`). В Helm у сервисов `service.enabled: false`, liveness/readiness-пробы выключены. Переменную стоит либо удалить, либо реализовать сервер. По этой причине `openapi.yaml` для сервиса не создаётся.
|
||||
2. **Дубликаты в `config.env`:**
|
||||
- `ENABLE_BIM_API_V2_DB=1` указана дважды (строки 18 и 19);
|
||||
- `JOBS_NAMESPACE` задаётся дважды с разными значениями: `processing-stage` и `processing-testing` — побеждает последнее;
|
||||
- `POD_NAME` задаётся дважды: `fieldRef(v1:metadata.name)` и `workflows-backend-869584d795-b7p2b` — второе значение это «замороженное» имя конкретного пода, что явно ошибочно для шаблона.
|
||||
3. **Хардкод локальных путей разработчика в `config.env`:**
|
||||
- `S3_SERVICE_ACCOUNT=/Users/khannanov/sarex/yc-s3-service-account.json`;
|
||||
- `KUBE_CONFIG=/Users/khannanov/.kube/config`.
|
||||
Эти пути специфичны для машины конкретного разработчика и не должны попадать в общий конфиг (в Helm `S3_SERVICE_ACCOUNT` корректно указывает на `/etc/sarex/yc-s3/...`).
|
||||
4. **Переменные из `config.env`/Helm, которые не читаются кодом `engine` вообще** (кандидаты на удаление либо потребляются исключительно другими компонентами): `API_ADDRESS`, `CONTROL_PLANE_PERIOD`, `ENABLE_GOOGLE_STORAGE`, `GOOGLE_STORAGE_BUCKET`, `GOOGLE_STORAGE_PROJECT`, `ENABLE_SRX_TMP`, `MAX_TASKS_IN_PERIOD`, `ENABLE_PDM_DB`, `POD_NAME`. Ни `Getenv`, ни struct-тега для них нет.
|
||||
5. **`config.env` частично устарел относительно `config/config.go` и Helm.** Ряд переменных, реально используемых кодом/чартом, в `config.env` отсутствует (`WORKFLOW_PRIORITY`, `DEFAULT_TOLERATION_*`, `DEFAULT_NODE_SELECTOR_*`, `ENABLE_ISSUE_API_DB`, `ENABLE_RESOURCES_API`, `ENABLE_WORKSPACE_API_DB`, `ENABLE_CROSS_SECTION_API_DB`, `RESOURCES_API_INTERNAL_HOST`, `WORKFLOWS_SENTRY_DSN`, пути `*_API_DB` для issues/ws/cross_section и т.д.). Источником истины следует считать `config/config.go` + `.helm/values.yaml`, а не `config.env`.
|
||||
6. **Расхождение `env-default` и реальных значений.** У ряда флагов (`ENABLE_S3_STORAGE`, `ENABLE_TOLERATION`, `CPU_COUNT`, воркеры и др.) в коде нет `env-default`, поэтому при отсутствии переменной поле останется нулевым (`0`/`false`/`""`) — сервис молча стартует с «пустой» конфигурацией планирования. Значения обязательно должны задаваться через окружение (Helm).
|
||||
7. **`DJANGO_BASIC_AUTH` / `COMPARISONS_API_DEBUG` / `BIM_API_DEBUG`** объявлены в структуре конфига, но фактически в бизнес-логике engine не задействованы (debug-флаги повторно читаются через `os.Getenv` и прокидываются в поды; `DJANGO_BASIC_AUTH` не используется — в под задачи попадает хардкод-путь `DJANGO_BASIC_AUTH_PATH=/etc/sarex/django-auth.json`).
|
||||
8. **`RABBITMQ_CREATE_TOPIC`** присутствует в `config.env`, но в конфиге RabbitMQ такого поля нет (используется `RABBITMQ_CREATE_ROUTING_KEY`) — переменная не читается.
|
||||
|
||||
## Минимальный набор для локального запуска
|
||||
|
||||
Для локального старта достаточно поднять PostgreSQL и указать доступ к нему; исполнители и внешние интеграции можно выключить.
|
||||
|
||||
```env
|
||||
# Логирование
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# PostgreSQL (обязательно — engine сразу подключается к БД)
|
||||
POSTGRES_ADDRESS=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=processing_db
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_POOL_SIZE=5
|
||||
POSTGRES_SSL_USE=false
|
||||
ENABLE_SQL_QUERY=true
|
||||
|
||||
# Исполнители: выключаем AMQP; для Kubernetes нужен доступ к кластеру
|
||||
ENABLE_AMQP_EXECUTOR=0
|
||||
ENABLE_KUBERNETES_EXECUTOR=1
|
||||
KUBE_CONFIG=/path/to/your/.kube/config # если пусто — используется in-cluster
|
||||
KUBE_CONTEXT=your-context
|
||||
JOBS_NAMESPACE=processing-testing
|
||||
|
||||
# Воркеры
|
||||
COUNT_RUNNING_WORKERS=1
|
||||
COUNT_CANCELING_WORKERS=1
|
||||
COUNT_HANDLE_JOB_WORKERS=1
|
||||
|
||||
# Пулинг / выборка
|
||||
MAX_WORKFLOWS_LIMIT=10
|
||||
MAX_RUNNING_JOBS=10
|
||||
DEFAULT_GET_SINCE_LAST_HOURS=168
|
||||
WORKFLOW_PRIORITY=default
|
||||
|
||||
# Дефолты планирования подов (иначе будут пустыми)
|
||||
DEFAULT_IMAGE_PULL_POLICY=IfNotPresent
|
||||
DEFAULT_CPU_REQUESTS=100m
|
||||
DEFAULT_MEMORY_REQUESTS=64Mi
|
||||
|
||||
# Хранилища/внешние БД можно отключить для минимального запуска
|
||||
ENABLE_S3_STORAGE=0
|
||||
ENABLE_S3V2_STORAGE=0
|
||||
ENABLE_PDM_STORAGE=0
|
||||
ENABLE_URL_STORAGE=0
|
||||
ENABLE_LOCAL=0
|
||||
ENABLE_BIM_API_DB=0
|
||||
ENABLE_BIM_API_CH=0
|
||||
ENABLE_BIM_API_V2_DB=0
|
||||
ENABLE_PDM_API_DB=0
|
||||
ENABLE_COMPARISONS_API_DB=0
|
||||
ENABLE_ISSUE_API_DB=0
|
||||
ENABLE_RESOURCES_API=0
|
||||
ENABLE_MAIL_GUN=0
|
||||
ENABLE_SMTP=0
|
||||
ENABLE_WORKSPACE_API_DB=0
|
||||
ENABLE_CROSS_SECTION_API_DB=0
|
||||
ENABLE_TOLERATION=0
|
||||
```
|
||||
|
||||
Запуск: `go run ./cmd/engine`. Если Kubernetes-исполнитель не нужен вовсе — установите `ENABLE_KUBERNETES_EXECUTOR=0` (тогда `KUBE_*`/`JOBS_NAMESPACE` не требуются), но учтите, что без единого включённого исполнителя сервис не будет запускать задачи. Профилирование доступно на `http://localhost:8081/debug/pprof/`.
|
||||
185
apps/processing/workflows-engine.env.example
Normal file
185
apps/processing/workflows-engine.env.example
Normal file
@ -0,0 +1,185 @@
|
||||
# ============================================================================
|
||||
# workflows-engine (kubernetes-engine) — пример переменных окружения
|
||||
# Конфигурация читается через cleanenv (github.com/ilyakaznacheev/cleanenv)
|
||||
# из переменных окружения. Префикса нет. Значения по умолчанию — из кода.
|
||||
# Сервис — фоновый оркестратор без REST API (только pprof на :8081).
|
||||
# ============================================================================
|
||||
|
||||
# ----- App / Logging -----
|
||||
APP_NAME=kubernetes-engine
|
||||
APP_VERSION=v1
|
||||
# Уровень логирования: debug | info | warn | error
|
||||
LOG_LEVEL=info
|
||||
|
||||
# ----- Database (PostgreSQL) -----
|
||||
POSTGRES_ADDRESS=localhost
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB=processing_db
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_POOL_SIZE=20
|
||||
# Включить TLS-подключение к БД
|
||||
POSTGRES_SSL_USE=false
|
||||
# CA-сертификат (PEM) для TLS-подключения к БД (значение сертификата)
|
||||
YC-PG-CERTIFICATE=
|
||||
# Логирование SQL-запросов
|
||||
ENABLE_SQL_QUERY=true
|
||||
|
||||
# ----- Executors / Workers -----
|
||||
# Включить AMQP-исполнитель (RabbitMQ). Требует секцию RabbitMQ ниже.
|
||||
ENABLE_AMQP_EXECUTOR=0
|
||||
# Включить Kubernetes-исполнитель (запуск задач как Job'ов). По умолчанию true.
|
||||
ENABLE_KUBERNETES_EXECUTOR=1
|
||||
COUNT_RUNNING_WORKERS=1
|
||||
COUNT_CANCELING_WORKERS=1
|
||||
COUNT_HANDLE_JOB_WORKERS=1
|
||||
|
||||
# ----- Kubernetes -----
|
||||
# Namespace, в котором создаются Job'ы задач
|
||||
JOBS_NAMESPACE=processing-testing
|
||||
# Путь к kubeconfig. Если ПУСТО — используется in-cluster конфигурация.
|
||||
KUBE_CONFIG=
|
||||
# Контекст kubeconfig (для out-of-cluster)
|
||||
KUBE_CONTEXT=
|
||||
# Адрес API-сервера кластера (для out-of-cluster, с InsecureSkipTLSVerify)
|
||||
KUBE_ADDR=
|
||||
|
||||
# ----- RabbitMQ (только при ENABLE_AMQP_EXECUTOR=1) -----
|
||||
RABBITMQ_HOST=localhost
|
||||
RABBITMQ_PORT=5672
|
||||
RABBITMQ_USER=sarex
|
||||
RABBITMQ_PASS=sarex
|
||||
RABBITMQ_VHOST=/
|
||||
RABBITMQ_USE_SSL=false
|
||||
RABBITMQ_CERTIFICATE=
|
||||
RABBITMQ_CREATE_EXCHANGE=autodesk.inputMessage
|
||||
RABBITMQ_CANCEL_EXCHANGE=autodesk.cancelMessage
|
||||
RABBITMQ_CREATE_ROUTING_KEY=converting
|
||||
RABBITMQ_CANCEL_TOPIC=cancel
|
||||
RABBITMQ_COMPLETENESS_EXCHANGE=autodesk.outputMessage
|
||||
RABBITMQ_COMPLETENESS_TOPIC=output
|
||||
|
||||
# ----- Pooling / Workflows -----
|
||||
# Лимит одновременно обрабатываемых workflow при выборке
|
||||
MAX_WORKFLOWS_LIMIT=100
|
||||
# Максимум одновременно выполняющихся Job'ов
|
||||
MAX_RUNNING_JOBS=200
|
||||
# Глубина выборки задач в часах (нужно для FIFO; 168 ч = 7 дней)
|
||||
DEFAULT_GET_SINCE_LAST_HOURS=168
|
||||
# Приоритет обрабатываемых workflow: default | high | low (задаёт «роль» инстанса)
|
||||
WORKFLOW_PRIORITY=default
|
||||
|
||||
# ----- Планирование подов (NodeSelector / Tolerations / requests) -----
|
||||
# CPU/Memory для high-resources / persistent
|
||||
CPU_COUNT=5
|
||||
MEMORY_GI=20
|
||||
# CPU/Memory для low-resources
|
||||
CPU_COUNT_LOW_RESOURCES=2
|
||||
MEMORY_GI_LOW_RESOURCES=10
|
||||
# CPU/Memory для super-high-resources
|
||||
CPU_COUNT_HIGH_MEM=10
|
||||
MEMORY_GI_HIGH_MEM=40
|
||||
# Включить набор сервисов ресурсов (toleration/nodeselector)
|
||||
ENABLE_TOLERATION=1
|
||||
# Toleration/NodeSelector для high/low ресурсов
|
||||
TOLERATION_KEY=dedicated
|
||||
TOLERATION_VALUE=processing
|
||||
# Toleration для super-high-resources
|
||||
TOLERATION_KEY_HIGH_MEM=dedicated
|
||||
TOLERATION_VALUE_HIGH_MEM=high-mem
|
||||
# Toleration для persistent
|
||||
TOLERATION_KEY_PERSISTENT=
|
||||
TOLERATION_VALUE_PERSISTENT=
|
||||
# Значения по умолчанию для всех подов
|
||||
DEFAULT_TOLERATION_KEY=
|
||||
DEFAULT_TOLERATION_VALUE=
|
||||
DEFAULT_NODE_SELECTOR_KEY=
|
||||
DEFAULT_NODE_SELECTOR_VALUE=
|
||||
DEFAULT_IMAGE_PULL_POLICY=always
|
||||
DEFAULT_CPU_REQUESTS=100m
|
||||
DEFAULT_MEMORY_REQUESTS=64Mi
|
||||
|
||||
# ----- Хранилища и внешние сервисы (флаги ENABLE_*) -----
|
||||
# Путь к service account JSON для S3 (в проде задаётся Helm'ом,
|
||||
# напр. /etc/sarex/yc-s3/yc-s3-service-account.json)
|
||||
S3_SERVICE_ACCOUNT=
|
||||
ENABLE_S3_STORAGE=1
|
||||
ENABLE_S3V2_STORAGE=1
|
||||
ENABLE_PDM_STORAGE=1
|
||||
ENABLE_URL_STORAGE=1
|
||||
ENABLE_LOCAL=0
|
||||
ENABLE_BIM_API_DB=1
|
||||
ENABLE_BIM_API_CH=1
|
||||
ENABLE_BIM_API_V2_DB=1
|
||||
ENABLE_PDM_API_DB=1
|
||||
ENABLE_COMPARISONS_API_DB=1
|
||||
ENABLE_ISSUE_API_DB=0
|
||||
ENABLE_RESOURCES_API=0
|
||||
ENABLE_MAIL_GUN=1
|
||||
ENABLE_SMTP=0
|
||||
ENABLE_WORKSPACE_API_DB=0
|
||||
ENABLE_CROSS_SECTION_API_DB=0
|
||||
# Debug-флаги (также пробрасываются в под задачи)
|
||||
BIM_API_DEBUG=0
|
||||
COMPARISONS_API_DEBUG=0
|
||||
|
||||
# ----- URL'ы внешних сервисов (internal/external) -----
|
||||
INTERNAL_PDM_URL=http://api-service.documentations-stage
|
||||
EXTERNAL_PDM_URL=https://stage-api.sarex.io/documentations
|
||||
INTERNAL_FILESTREAM_URL=http://filestream-service.documentations-stage
|
||||
EXTERNAL_FILESTREAM_URL=https://stage-api.sarex.io/files
|
||||
INTERNAL_REMARK_URL=http://remarks-service.remarks-stage
|
||||
EXTERNAL_REMARK_URL=https://stage-api.sarex.io/remarks
|
||||
INTERNAL_WORKSPACE_URL=http://workspaces-service.workspaces-stage
|
||||
EXTERNAL_WORKSPACE_URL=https://stage-api.sarex.io/workspaces
|
||||
DJANGO_HOST=https://stage.sarex.io
|
||||
|
||||
# ----- Security -----
|
||||
# Запускать контейнеры задач под фиксированным UID
|
||||
ENABLE_RUNASUSER=false
|
||||
USER_RUNASUSER=1000
|
||||
|
||||
# ============================================================================
|
||||
# Переменные ниже читаются НЕ конфигом engine, а напрямую через os.Getenv
|
||||
# (pkg/kube_services/services.go) и пробрасываются в env запускаемых Job-подов
|
||||
# при включённом соответствующем ENABLE_*-флаге. Значения — пути к JSON-конфигам
|
||||
# внутри контейнера (в проде монтируются Helm'ом как секреты-volume).
|
||||
# ============================================================================
|
||||
BIM_API_DB=/etc/sarex/bim-api-db-stage.json
|
||||
BIM_API_CH=/etc/sarex/bim-api-ch.json
|
||||
BIM_API_CH_DEBUG=0
|
||||
BIM_API_V2_DB=/etc/sarex/bim-api-v2-db-stage.json
|
||||
BIM_API_V2_DEBUG=0
|
||||
BIM_API_V2_DB_2=/etc/sarex/second-bim-bd/db.json
|
||||
BIM_API_V2_DB_3=/etc/sarex/third-bim-bd/db.json
|
||||
BIM_API_V2_DB_4=/etc/sarex/fourth-bim-bd/db.json
|
||||
COMPARISONS_API_DB=/etc/comparisons/comparisons-db-stage.json
|
||||
ISSUE_API_DB=/etc/issues/issues-db.json
|
||||
ISSUE_API_DEBUG=0
|
||||
RESOURCES_API_INTERNAL_HOST=
|
||||
SMTP=/etc/smtp-secret/env.json
|
||||
PDM_API_DB=/etc/pdm/pdm-api-db-stage.json
|
||||
PDM_API_DEBUG=0
|
||||
WORKSPACE_API_DB=/etc/ws/ws-api-db.json
|
||||
WORKSPACE_API_DEBUG=0
|
||||
CROSS_SECTION_API_DB=/etc/cross_section/db.json
|
||||
CROSS_SECTION_API_DEBUG=0
|
||||
# Прокидываются в под всегда (сервис Default)
|
||||
WORKFLOWS_SENTRY_DSN=
|
||||
WORKFLOWS_SENTRY_DEBUG=0
|
||||
ENVIRONMENT=stage
|
||||
# Mailgun-конфиг (при ENABLE_MAIL_GUN)
|
||||
MAILGUN=/etc/mailgun-secret/env.json
|
||||
|
||||
# ============================================================================
|
||||
# Переменные ниже присутствуют в config.env / Helm, но кодом engine НЕ читаются
|
||||
# (легаси / задел). Оставлены для справки, включать не обязательно:
|
||||
# API_ADDRESS — REST API отсутствует, реальный HTTP только pprof на :8081
|
||||
# CONTROL_PLANE_PERIOD
|
||||
# ENABLE_GOOGLE_STORAGE, GOOGLE_STORAGE_BUCKET, GOOGLE_STORAGE_PROJECT
|
||||
# ENABLE_SRX_TMP
|
||||
# MAX_TASKS_IN_PERIOD
|
||||
# ENABLE_PDM_DB
|
||||
# POD_NAME
|
||||
# RABBITMQ_CREATE_TOPIC — в конфиге нет такого поля (см. RABBITMQ_CREATE_ROUTING_KEY)
|
||||
# ============================================================================
|
||||
48
apps/processing/workflows-frontend.ENDPOINTS.md
Normal file
48
apps/processing/workflows-frontend.ENDPOINTS.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Эндпоинты, с которыми взаимодействует workflows-frontend
|
||||
|
||||
Микрофронтенд `workflows-frontend` обращается к внешним HTTP-сервисам через тонкий слой API, построенный поверх SDK `@sarex-team/sdk-js`. В отличие от `transmittal-frontend`, здесь нет реестра-объекта `endpoints` с полями `service/method/path/body/cache`. Вместо этого каждый вызов оформлен отдельной функцией-обёрткой (`fetch*`), которая напрямую вызывает `httpService.getRequest` / `httpService.postRequest` с указанием сервиса, URL и параметров. Все явные запросы фронтенда идут в один сервис — `workflows`. Хосты `sarex` и `zitadel` объявлены в конфигурации, но используются самим SDK (аутентификация через `AuthProvider`), а не прикладным кодом модуля.
|
||||
|
||||
## Как устроено взаимодействие
|
||||
|
||||
- **Слой API-функций** — `module/shared/api/fetch/workflows.api.ts`. Здесь объявлены все обёртки над HTTP-запросами. Каждая функция принимает типизированные аргументы (`id`, `taskId`, `taskRunId`, `workflowId`, `ids`, пагинация, фильтры) и возвращает промис от `httpService`. Ключевые поля запроса:
|
||||
- `service` — логическое имя сервиса (везде `"workflows"`), по которому SDK выбирает базовый хост;
|
||||
- `url` — путь эндпоинта, собирается через шаблонные строки с подстановкой параметров;
|
||||
- `data` — тело POST-запроса (например, `updates` при рестарте задачи);
|
||||
- `axiosConfig.params` — query-параметры (`limit`, `offset`, `company_ids`).
|
||||
- **Единая точка запроса** — `httpService`, создаётся в `module/shared/api/http-service.ts` через `createHttpService({ axiosConfig, config, buildEnv, hosts })` из `@sarex-team/sdk-js`. Методы `getRequest`/`postRequest` этого сервиса — единственный способ выполнить внешний вызов. Под капотом SDK использует axios.
|
||||
- **Выбор окружения и хоста** — окружение берётся из глобальной константы сборки `__BUILD_ENV__` (`const endpoint = (__BUILD_ENV__ as TypeEnvironment) || "prod"`). Для `local` дополнительно включается режим `setTypeOfHttpService("original")`. SDK по имени `service` и текущему `buildEnv` находит базовый хост в объекте `hosts` (`module/shared/api/hosts.ts`) и подставляет его перед `url`.
|
||||
- **Реэкспорт** — `module/shared/api/index.ts` реэкспортирует весь набор функций как `workflowsApi` и сам `httpService`.
|
||||
- **Использование** — вызовы происходят из MobX-стора (`module/workflows/store/*.ts`: `workflows.ts`, `workflow.ts`, `task.ts`, `taskRun.ts`, `watchList.ts`).
|
||||
- **Обработка ошибок** — отдельного файла `errors.ts` в проекте нет; маппинг и перехват ошибок HTTP выполняется внутри SDK `@sarex-team/sdk-js` и обрабатывается в сторах через try/catch с отображением состояний ошибки (`module/components/States/ErrorState.tsx`). Аутентификация и редиректы на страницу логина реализованы в `module/Auth/AuthProvider.tsx` через компонент `AuthProvider` из SDK.
|
||||
|
||||
## Базовые хосты по сервисам и окружениям
|
||||
|
||||
Источник: `module/shared/api/hosts.ts`. В коде определены окружения `local`, `stage`, `prod`, `preprod` и `cps` (закрытый контур Газпрома). Прикладной код обращается только к сервису `workflows`; хосты `sarex` и `zitadel` используются SDK для основного бэкенда и системы аутентификации (Zitadel).
|
||||
|
||||
| Сервис (service) | Назначение | local | stage | prod | preprod | cps (контур Газпром) |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| workflows | API оркестрации воркфлоу и задач | `/sarex-workflows` | `https://stage-api.sarex.io/workflows` | `https://api.sarex.io/workflows` | `https://api.preprod.sarex.io/workflows` | `https://api.aeromonitoring.codm.gazprom.loc/workflows/` |
|
||||
| sarex | Основной бэкенд Sarex (через SDK) | `/sarex-backend` | `/` | `/` | `/` | `https://aeromonitoring.codm.gazprom.loc/` |
|
||||
| zitadel | Сервис аутентификации Zitadel (через SDK) | `/zitadel` | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` | `https://login.preprod.sarex.io` | — (не задан) |
|
||||
|
||||
Примечание: в окружении `local` пути относительные (проксируются через dev-сервер), в `stage/prod/preprod` — абсолютные URL. Для контура `cps` сервис `zitadel` не объявлен.
|
||||
|
||||
## Эндпоинты по сервисам
|
||||
|
||||
### workflows — оркестрация воркфлоу и задач
|
||||
|
||||
Все функции определены в `module/shared/api/fetch/workflows.api.ts`, `service: "workflows"`.
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| fetchGetWorkflows | GET | `/api/v1/workflows?limit={limit}&offset={offset}&company_ids={companyIds}` | Список воркфлоу с пагинацией и фильтром по компаниям (`company_ids` — id через запятую) |
|
||||
| fetchGetWorkflowById | GET | `/api/v1/workflows/{id}` | Получить один воркфлоу по идентификатору |
|
||||
| fetchGetWorkflowsStates | GET | `/api/v1/workflows/{ids}/state` | Получить состояния набора воркфлоу (`ids` — идентификаторы через запятую), используется для отслеживаемого списка |
|
||||
| fetchGetWorkflowPosition | GET | `/api/v1/workflows/{workflowId}/position` | Позиция воркфлоу в очереди обработки |
|
||||
| fetchPrioritizeWorkflow | POST | `/api/v1/workflows/{workflowId}/prioritize` | Повысить приоритет воркфлоу |
|
||||
| fetchGetLogs | GET | `/api/v1/tasks-runs/{taskRunId}/logs` | Получить логи конкретного запуска задачи |
|
||||
| fetchCancelTaskRun | POST | `/api/v1/tasks-runs/{taskRunId}/cancel` | Отменить запуск задачи |
|
||||
| fetchRestartTask | POST | `/api/v1/tasks/{taskId}/restart` | Перезапустить задачу; тело запроса — объект `updates` (изменённые параметры/объекты/сервис-реквесты) |
|
||||
| fetchMoveTaskToSuperHighResources | POST | `/api/v1/tasks/{taskId}/move_to_super_high_resources` | Перевести задачу на пул сверхвысоких ресурсов |
|
||||
|
||||
> Примечание: фронтенд вызывает `GET /api/v1/tasks-runs/{taskRunId}/logs` (`fetchGetLogs`), однако в текущем коде `workflows-api` соответствующий обработчик отсутствует (см. `workflows-api.openapi.yaml`, раздел «Замечания»). Это расхождение стоит проверить: либо эндпоинт реализуется другим сервисом/ingress, либо один из репозиториев устарел.
|
||||
133
apps/projects/CONFIGURATION.md
Normal file
133
apps/projects/CONFIGURATION.md
Normal file
@ -0,0 +1,133 @@
|
||||
# Конфигурация проекта projects-frontend
|
||||
|
||||
Документ описывает, как конфигурируется микрофронтенд `projects-frontend`: переменные сборки, способы запуска, параметры Docker/nginx, Helm-чарта и CI.
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
`projects-frontend` — это клиентский микрофронтенд (React 17 + MobX, сборка Webpack 5, Module Federation). У приложения **нет runtime-конфигурации и файла `.env`**: всё поведение, зависящее от окружения, определяется **на этапе сборки** одной переменной `BUILD_ENV`.
|
||||
|
||||
Разбор выполняется в `configWebpack/config/build.config.ts` (`extractBuildOptions`): значение `process.env.BUILD_ENV` (одно из `local` / `stage` / `prod` / `preprod` / `contour`, по умолчанию `prod`) определяет режим сборки (`development`/`production`) и «endpoint». Полученный `endPoint` через `DefinePlugin` (`configWebpack/buildPlugins.ts`) подставляется в бандл как глобальная константа `__ENDPOINT__`:
|
||||
|
||||
```js
|
||||
new DefinePlugin({ __ENDPOINT__: JSON.stringify(endPoint) })
|
||||
```
|
||||
|
||||
Константа `__ENDPOINT__` используется в рантайме бандла для выбора:
|
||||
|
||||
- карты хостов API — `src/shared/api/http-service.ts` (`const endpoint = (__ENDPOINT__ as TypeEnvironment) || "prod"`) поверх `src/shared/api/hosts.ts`;
|
||||
- URL удалённого модуля timeline — `src/widgets/remote-timeline/ui/timelineProxy.tsx`;
|
||||
- локального провайдера разработки — `src/app/bootstrap.tsx` (`__ENDPOINT__ === "local" ? <LocalDevProvider /> : <div />`).
|
||||
|
||||
Отдельного конфиг-файла (yaml/env) у приложения нет.
|
||||
|
||||
## Переменные сборки и запуска
|
||||
|
||||
| Переменная | Где используется | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `BUILD_ENV` | `webpack.config.ts`, `configWebpack/config/build.config.ts`, `Dockerfile` (build-arg) | `prod` | Целевое окружение сборки: `local`/`stage`/`prod`/`preprod`/`contour`. Определяет режим (`development`/`production`), карту хостов и URL удалённых модулей |
|
||||
| `NPM_NEXUS_TOKEN` | `.npmrc`, `Dockerfile` (build-arg) | — | Токен доступа к приватному npm-реестру Nexus (`https://nexus.infra.sarex.io/repository/npm/`) для установки пакетов `@sarex-team/*` |
|
||||
| `PORT` | `webpack.config.ts` | `9001` | Порт dev-сервера Webpack |
|
||||
|
||||
Версия Node фиксирована в `.nvmrc` — `v16.0.0`. Приватный реестр и авторизация заданы в `.npmrc`:
|
||||
|
||||
```
|
||||
@sarex-team:registry=https://nexus.infra.sarex.io/repository/npm/
|
||||
//nexus.infra.sarex.io/repository/npm/:_authToken=${NPM_NEXUS_TOKEN}
|
||||
```
|
||||
|
||||
### npm-скрипты (`package.json`)
|
||||
|
||||
| Команда | Действие |
|
||||
| --- | --- |
|
||||
| `npm run dev` | Локальная разработка: `BUILD_ENV=local webpack serve --config webpack.config.ts` |
|
||||
| `npm run build-module` | Сборка модуля: `webpack --config webpack.config.ts` (окружение — из `BUILD_ENV`) |
|
||||
| `npm run build:start` | Раздача собранного бандла: `serve -s ./dist -l 9001` |
|
||||
| `npm run lint` | Форматирование Prettier: `npx prettier --write .` |
|
||||
|
||||
## Сборка Webpack и Module Federation
|
||||
|
||||
Точка входа — `src/app/index.ts` (`webpack.config.ts`). Для всех окружений, кроме `local`, добавляется `ModuleFederationPlugin`:
|
||||
|
||||
- `name`: `srx_projects`;
|
||||
- `filename`: `module/remoteEntry.js`;
|
||||
- `exposes`: `./ProjectsPage` → `./src/app/App.tsx`;
|
||||
- `shared` (singleton, `requiredVersion: false`): `react`, `react-dom`, `@material-ui/core`, `@sarex-team/sdk-js`.
|
||||
|
||||
Плагины (`configWebpack/buildPlugins.ts`): `HtmlWebpackPlugin`, `DefinePlugin`. В режиме `local` дополнительно `ProgressPlugin`, `ForkTsCheckerWebpackPlugin`, `ReactRefreshWebpackPlugin`; вне `local` — `MiniCssExtractPlugin` (хеши в именах файлов).
|
||||
|
||||
### Dev-сервер (`configWebpack/buildDevServer.ts`)
|
||||
|
||||
HTTPS, порт `9001`, `historyApiFallback`, `hot`. Прокси на stage-окружение с `pathRewrite`:
|
||||
|
||||
| Префикс | Target |
|
||||
| --- | --- |
|
||||
| `/sarex-backend` | `https://stage.sarex.io` |
|
||||
| `/sarex-gateway` | `https://stage-api.sarex.io/gateway` |
|
||||
| `/sarex-documentations` | `https://stage-api.sarex.io/documentations` |
|
||||
| `/sarex-api` | `https://stage-api.sarex.io` |
|
||||
|
||||
Заголовки CORS dev-сервера разрешают любой origin, методы `GET, POST, PUT, DELETE, PATCH, OPTIONS` и заголовки `X-Requested-With, content-type, Authorization`.
|
||||
|
||||
## Docker
|
||||
|
||||
Многоступенчатая сборка (`Dockerfile`):
|
||||
|
||||
1. **Стадия сборки** (`node:16`): установка зависимостей (`npm i` с `NPM_NEXUS_TOKEN`), `npm run lint`, `BUILD_ENV=$BUILD_ENV npm run build-module` → `dist`.
|
||||
2. **Стадия раздачи** (`nginx:1.19.6`): копирование `dist` в `/dist` и `nginx/nginx.conf` в `/etc/nginx/nginx.conf`.
|
||||
|
||||
Build-args: `BUILD_ENV`, `NPM_NEXUS_TOKEN`.
|
||||
|
||||
### nginx (`nginx/nginx.conf`)
|
||||
|
||||
Статика раздаётся с `root /dist` на порту `80`. Особенности:
|
||||
|
||||
- `location = /ping` → возвращает `200 {"result": "ok"}` (используется как liveness/readiness-проба);
|
||||
- `location = /module/remoteEntry.js` → `Cache-Control: no-store, no-cache, must-revalidate…`, `expires off` (точка входа Module Federation не кешируется);
|
||||
- `gzip on`, логи в `stdout`/`stderr`.
|
||||
|
||||
## Helm-чарт (`.helm`)
|
||||
|
||||
Чарт `projects-frontend` (`Chart.yaml`, `type: application`, `version: 0.1.0`, `appVersion: 1.16.0`) разворачивает статику как `Deployment` + `Service` (`templates/static.yaml`) и публикует её через Istio `VirtualService` (`templates/mesh-config.yaml`).
|
||||
|
||||
Значения задаются в `values-<env>.yaml` (блок `static`):
|
||||
|
||||
| Параметр | `stage` | `preprod` | `production` |
|
||||
| --- | --- | --- | --- |
|
||||
| `static.host` | `stage-modules.sarex.io` | `modules.preprod.sarex.io` | `modules.sarex.io` |
|
||||
| `static.replicas` | `1` | `2` | `2` |
|
||||
| `static.path` | `/projects/static/` | `/projects/static/` | `/projects/static/` |
|
||||
| `static.image` | `sarex/projects-frontend-static:latest` | то же | то же |
|
||||
| `static.port` / `static.service_port` | `80` / `80` | `80` / `80` | `80` / `80` |
|
||||
| `static.requests` | `memory: 100Mi`, `cpu: 100m` | то же | то же |
|
||||
| `imagePullSecrets` | `dockerhub` | `dockerhub` | `dockerhub` |
|
||||
|
||||
Общее для всех окружений: `static.name: projects-frontend-static`, `static.service_name: projects-frontend-static-service`, `static.version: stable`. Пробы `livenessProbe`/`readinessProbe` бьют в `/ping` (`templates/static.yaml`).
|
||||
|
||||
`VirtualService` (`templates/mesh-config.yaml`): хост `static.host`, шлюз `gateway/modules-gateway`, матч по префиксу `static.path` (`/projects/static/`) с `rewrite` на `/`. Политика CORS: `allowOrigins` по регулярке `(https://.*\.sarex\.io)|(https://localhost:.*)`, `allowMethods: [GET, POST, PUT, PATCH, HEAD, DELETE]`, `allowHeaders: [Authorization, Content-Type]`, `maxAge: 24h`.
|
||||
|
||||
## CI/CD (`.gitlab-ci.yml`)
|
||||
|
||||
Пайплайн подключает общие шаблоны из `generic/common-ci` (`universal-pipeline-*`, `common-security-scan`, `common-build`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||
|
||||
| Условие | STAND | Namespace | `BUILD_ENV` |
|
||||
| --- | --- | --- | --- |
|
||||
| ветка `master` | `preprod` | `projects-preprod` | `preprod` |
|
||||
| ветка `stage` | `stage` | `projects-stage` | `stage` |
|
||||
| тег (`CI_COMMIT_TAG`) | `prod` | `projects-prod` | `prod` |
|
||||
|
||||
Ключевые переменные пайплайна (общие для всех окружений): `RELEASE_NAME`/`CHART_NAME` = `projects-frontend`, `IMAGE_PATH` = `static.image`, `HELM_SET_ARGS` = `--set static.image=${IMAGE_NAME}`, `BUILD_ARGS` = `--build-arg BUILD_ENV=<env> --build-arg NPM_NEXUS_TOKEN=${NPM_NEXUS_TOKEN}`, `DOCKERFILE_PATH` = `Dockerfile`. Флаги этапов: `ENABLE_LINTER` (`false`), `ENABLE_BUILD_CHART`, `ENABLE_BUILD_IMAGE`, `ENABLE_STATE_UPDATE`, `ENABLE_DEPLOY` (`true`). `CHART_VERSION` задаётся как `0.0.1-<env>`. Стадии: `linter → test → unittest → prebuild-secscan → build → state-update → deploy`.
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
- **Нет runtime-конфигурации.** Всё, что зависит от окружения, «зашивается» в бандл на этапе сборки через `BUILD_ENV`/`__ENDPOINT__`. Пересборка под другое окружение обязательна — переопределить хосты в рантайме нельзя.
|
||||
- **Значение по умолчанию `BUILD_ENV=prod`.** Если переменная не задана при сборке, собирается production-вариант (`build.config.ts`, `webpack.config.ts`, `http-service.ts`). Для локальной разработки нужно явно `BUILD_ENV=local` (скрипт `npm run dev` это делает).
|
||||
- **Окружение `contour` неполно сконфигурировано.** Оно перечислено в `build.config.ts` и типах, но в `src/shared/api/hosts.ts` карта хостов для `contour` отсутствует, а URL удалённого модуля timeline для `contour` пуст (`timelineProxy.tsx`). Сборка с `BUILD_ENV=contour` не сможет разрешить хосты API.
|
||||
- **README не совпадает с `package.json`.** В `README.md` указаны `nvm use 16.0.0` и `npm run start:local`, однако скрипта `start:local` в `package.json` нет — локальный запуск выполняется командой `npm run dev`. `.nvmrc` фиксирует `v16.0.0`.
|
||||
- **Версии чарта расходятся.** В `Chart.yaml` — `version: 0.1.0` / `appVersion: 1.16.0`, тогда как в CI `CHART_VERSION` задаётся как `0.0.1-<env>`. Значения не синхронизированы.
|
||||
- **Приватный реестр.** Установка зависимостей требует валидный `NPM_NEXUS_TOKEN` (`.npmrc`); без него `npm i` (в т.ч. в Docker-сборке) завершится ошибкой авторизации.
|
||||
|
||||
## Минимальный набор для локального запуска
|
||||
|
||||
1. `nvm use` (Node `v16.0.0` из `.nvmrc`).
|
||||
2. Экспортировать `NPM_NEXUS_TOKEN` (доступ к Nexus) и выполнить `npm i`.
|
||||
3. `npm run dev` — соберёт с `BUILD_ENV=local` и поднимет HTTPS dev-сервер на `https://localhost:9001` (запросы к backend проксируются на stage через `/sarex-backend`, `/sarex-gateway`, `/sarex-documentations`, `/sarex-api`).
|
||||
94
apps/projects/ENDPOINTS.md
Normal file
94
apps/projects/ENDPOINTS.md
Normal file
@ -0,0 +1,94 @@
|
||||
# Эндпоинты, с которыми взаимодействует projects-frontend
|
||||
|
||||
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `projects-frontend`), а также удалённые модули, которые он подключает и экспортирует через Module Federation.
|
||||
|
||||
## Как устроено взаимодействие
|
||||
|
||||
Запросы описаны в слое `src/shared/api`. Функции запросов сгруппированы по доменам в каталоге `src/shared/api/fetch/*.api.ts` и реэкспортируются из `src/shared/api/index.ts` (`targetsApi`, `resourcesApi`, `projectsApi`, `userApi`, `companyApi`).
|
||||
|
||||
Каждый запрос выполняется через единый `httpService` (`src/shared/api/http-service.ts`), созданный фабрикой `createHttpService` из `@sarex-team/sdk-js` (поверх `axios`). У сервиса есть методы `getRequest` / `postRequest` / `putRequest` / `deleteRequest`, принимающие объект с полями:
|
||||
|
||||
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||
- `url` — путь запроса (относительно базового хоста сервиса);
|
||||
- `data` — тело запроса (для POST/PUT);
|
||||
- `axiosConfig.params` — query-параметры;
|
||||
- `isCSRF` — признак необходимости передать CSRF-токен (для сервиса `sarex`);
|
||||
- `cache`, `queryOptions`, `queryKey` — опции кеширования и ключи кеша.
|
||||
|
||||
Базовый хост подставляется по `service` из карты хостов `src/shared/api/hosts.ts` в зависимости от окружения сборки. Окружение задаётся значением `__ENDPOINT__`, которое подставляется на этапе сборки Webpack (`DefinePlugin`) из переменной `BUILD_ENV` и по умолчанию равно `prod` (`const endpoint = (__ENDPOINT__ as TypeEnvironment) || "prod"`).
|
||||
|
||||
## Базовые хосты по сервисам и окружениям
|
||||
|
||||
Значения из `src/shared/api/hosts.ts`. Итоговый URL = `<базовый хост сервиса>` + `url` эндпоинта.
|
||||
|
||||
| Сервис (`service`) | Назначение | `local` | `stage` | `prod` |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `sarex` | Основной backend (core, pm) | `/sarex-backend` (прокси dev-сервера → `https://stage.sarex.io`) | `/` (относительные пути, тот же origin) | `/` |
|
||||
| `gateway` | Gateway/API Sarex | `/sarex-gateway` (прокси → `https://stage-api.sarex.io/gateway`) | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||
| `documentations` | Сервис документации | `/sarex-documentations` (прокси → `https://stage-api.sarex.io/documentations`) | `https://stage-api.sarex.io/documentations` | `https://api.sarex.io/documentations` |
|
||||
| `sarexApi` | API Sarex (корень) | `/sarex-api` (прокси → `https://stage-api.sarex.io`) | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||
| `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` |
|
||||
| `eavV1` | EAV (атрибуты) | — | `https://stage-api.sarex.io/eav` | `https://api.sarex.io/eav` |
|
||||
| `notifications` | Сервис уведомлений (lambdas) | — | `https://stage-api.sarex.io/lambdas/notification` | `https://api.sarex.io/lambdas/notification` |
|
||||
| `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://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
|
||||
|
||||
> Также определено окружение `preprod` (`https://api.preprod.sarex.io/*`, `zitadel` → `https://login.preprod.sarex.io`). В `local` часть сервисов проксируется dev-сервером Webpack (`configWebpack/buildDevServer.ts`): `/sarex-backend`, `/sarex-gateway`, `/sarex-documentations`, `/sarex-api`. Остальные сервисы в `local` заданы пустой строкой (относительные пути). Окружение `contour` присутствует в конфигурации сборки (`build.config.ts`), но в `hosts.ts` для него карта хостов не задана.
|
||||
|
||||
## Эндпоинты по сервисам
|
||||
|
||||
Реально вызываются эндпоинты двух сервисов — `sarex` и `gateway`.
|
||||
|
||||
### `sarex` — Основной backend (core, pm)
|
||||
|
||||
Все запросы к `sarex` выполняются с `isCSRF: true`.
|
||||
|
||||
| Функция | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchTargetLinks` | GET | `/api/core/target-links/` | Список ссылок таргета (кешируется, `stateTime: 20`) |
|
||||
| `fetchCreateTargetLink` | POST | `/api/core/target-links/` | Создать ссылку (`name`, `link`, `target`, `type`) |
|
||||
| `fetchUpdateTargetLink` | PUT | `/api/core/target-links/{id}/` | Обновить ссылку |
|
||||
| `fetchDeleteTargetLink` | DELETE | `/api/core/target-links/{id}/` | Удалить ссылку |
|
||||
| `fetchMilestonesByProjectId` | GET | `/api/pm/msp/projects/{projectId}/key_milestones/` | Ключевые вехи проекта |
|
||||
| `fetchUsersByCompanyId` | GET | `/api/core/users/?companies={companyId}&limit=10000&id={usersIds}` | Пользователи компании по списку id |
|
||||
| `fetchCompanyById` | GET | `/api/core/companies/{companyId}/` | Компания по id (кешируется) |
|
||||
|
||||
### `gateway` — Gateway/API Sarex
|
||||
|
||||
| Функция | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchResources` | GET | `/api/v1/resources?company_id={companyId}` | Список ресурсов компании (кешируется, `stateTime: 20`) |
|
||||
| `fetchProjectsByCompanyId` | GET | `/api/v2/resources?company_id={companyId}&limit=10000` | Проекты компании (ресурсы v2, кешируется) |
|
||||
|
||||
## Удалённые модули (Module Federation)
|
||||
|
||||
Помимо HTTP-запросов, модуль взаимодействует с другими микрофронтендами через Webpack Module Federation.
|
||||
|
||||
### Подключаемый удалённый модуль
|
||||
|
||||
`src/widgets/remote-timeline` динамически загружает удалённый модуль `srx_pm`, экспонированный модуль `./Timeline` (`src/widgets/remote-timeline/ui/timelineProxy.tsx`, загрузка через `src/widgets/remote-timeline/lib/loader.ts`).
|
||||
|
||||
| Окружение | URL `remoteEntry.js` |
|
||||
| --- | --- |
|
||||
| `stage` | `https://stage-modules.sarex.io/pm/module/remoteEntry.js` |
|
||||
| `local` | `https://stage-modules.sarex.io/pm/module/remoteEntry.js` |
|
||||
| `prod` | `https://modules.sarex.io/pm/module/remoteEntry.js` |
|
||||
| `preprod` | `https://modules.sarex.io/pm/module/remoteEntry.js` |
|
||||
| `contour` | `""` (не задан) |
|
||||
|
||||
### Экспортируемый модуль
|
||||
|
||||
Сам `projects-frontend` при сборке (для всех окружений, кроме `local`) публикует себя как удалённый модуль `srx_projects` (`webpack.config.ts`, `ModuleFederationPlugin`):
|
||||
|
||||
- `filename`: `module/remoteEntry.js`;
|
||||
- `exposes`: `./ProjectsPage` → `./src/app/App.tsx`;
|
||||
- `shared` (singleton): `react`, `react-dom`, `@material-ui/core`, `@sarex-team/sdk-js`.
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
Отдельного модуля маппинга ошибок в `projects-frontend` нет (в отличие от некоторых других фронтендов) — обработка ответов и ошибок делегирована `httpService` из `@sarex-team/sdk-js`. Заголовки CORS для запросов в режиме разработки задаются dev-сервером Webpack, а в кластере — политикой CORS Istio `VirtualService` (`.helm/templates/mesh-config.yaml`): разрешённые источники по регулярке `(https://.*\.sarex\.io)|(https://localhost:.*)`, методы `GET, POST, PUT, PATCH, HEAD, DELETE`, заголовки `Authorization`, `Content-Type`.
|
||||
Loading…
Reference in New Issue
Block a user