Add example .env files and configuration documentation for measurements and subscriptions services.
This commit is contained in:
parent
1b5f5a1f67
commit
5f7fe11748
50
apps/measurements/.env.example
Normal file
50
apps/measurements/.env.example
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# measurements — пример переменных окружения (HTTP-сервис на FastAPI)
|
||||||
|
#
|
||||||
|
# Скопируйте нужные строки в config.env / .env сервиса.
|
||||||
|
# Переменные, помеченные (обяз.), обязательны — без них процесс не стартует.
|
||||||
|
# Конфигурация читается через pydantic-settings (src/measurements/config.py).
|
||||||
|
# bool принимает 1/0, true/false, yes/no.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- S3 / MinIO (обяз.) -----------------------------------------------------
|
||||||
|
# Единственная обязательная переменная. JSON-строка с доступами к S3.
|
||||||
|
# Разбирается в S3CredentialsSettings.from_env(); если не задана —
|
||||||
|
# ValueError и процесс не стартует.
|
||||||
|
# Поля: host, login, password (обяз.), verify (bool, по умолч. false),
|
||||||
|
# buckets (список; если пуст — читается через list_buckets()).
|
||||||
|
S3_JSON_SETTINGS='{"host":"https://s3.example.com","login":"login","password":"password","verify":false,"buckets":["measurements"]}'
|
||||||
|
|
||||||
|
# --- Логирование (префикс LOG_) ---------------------------------------------
|
||||||
|
LOG_LEVEL=INFO # уровень логирования (по умолчанию INFO)
|
||||||
|
# LOG_FORMAT='{"timestamp": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}' # формат JSON-лога
|
||||||
|
|
||||||
|
# --- Приложение (ApplicationSettings, без префикса) -------------------------
|
||||||
|
# AUTH=0 # включить CustomAuthenticationMiddleware (по умолч. false)
|
||||||
|
# SHOW_UI=0 # показывать Swagger/redoc (по умолч. false — docs отключены)
|
||||||
|
# USE_SENTRY=0 # инициализировать Sentry (по умолч. false)
|
||||||
|
# DEBUG=0 # флаг отладки (по умолч. false)
|
||||||
|
# CLASSIC_MODE=1 # классический режим расчётов (по умолч. true)
|
||||||
|
# BLOCK_SIZE=256 # размер блока обработки растра (по умолч. 256)
|
||||||
|
# BLOCK_SIZE_FACTOR=10 # множитель площади блока (по умолч. 10)
|
||||||
|
# CPU_NUMBER=10 # число используемых CPU (по умолч. 10)
|
||||||
|
|
||||||
|
# --- Django / ЛК (префикс DJANGO_; читается при AUTH=1) ----------------------
|
||||||
|
# DJANGO_USE=1 # использовать интеграцию с Django (по умолч. true)
|
||||||
|
DJANGO_HOST=https://lk.sarex.io # базовый URL Django/ЛК (по умолч. https://lk.sarex.io)
|
||||||
|
# DJANGO_TIMEOUT=10 # таймаут HTTP-запросов к Django, сек (по умолч. 10)
|
||||||
|
|
||||||
|
# --- Sentry (префикс SENTRY_; читается при USE_SENTRY=1) ---------------------
|
||||||
|
# SENTRY_DSN= # DSN проекта Sentry (по умолч. пусто)
|
||||||
|
# SENTRY_ENVIRONMENT=production # окружение (по умолч. production)
|
||||||
|
# SENTRY_TRACES_SAMPLE_RATE=1.0 # доля трейсов (по умолч. 1.0)
|
||||||
|
# SENTRY_SEND_DEFAULT_PII=1 # отправлять PII (по умолч. true)
|
||||||
|
|
||||||
|
# --- Трейсинг OpenTelemetry (префикс TRACING_; читается при TRACING_USE=1) ---
|
||||||
|
TRACING_USE=0 # включить OTEL-трейсинг и otel-логгер (по умолч. false)
|
||||||
|
# TRACING_HOST=localhost:4317 # адрес OTLP-коллектора (по умолч. localhost:4317)
|
||||||
|
# TRACING_SERVICE_NAME=measurements # имя сервиса в трейсах (по умолч. measurements)
|
||||||
|
# TRACING_INSECURE=0 # подключение без TLS (по умолч. false)
|
||||||
|
|
||||||
|
# --- Задаётся в манифестах, кодом приложения НЕ читается ---------------------
|
||||||
|
# S3_JSON_FILE=/opt/cred_s3.json # присутствует в .helm/values.yaml, но код читает только S3_JSON_SETTINGS
|
||||||
150
apps/measurements/CONFIGURATION.md
Normal file
150
apps/measurements/CONFIGURATION.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Конфигурация measurements
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса репозитория `measurements`:
|
||||||
|
|
||||||
|
- **measurements** — HTTP-сервис на FastAPI (`src/measurements`), запускается через gunicorn/uvicorn (`entrypoint.sh`, `measurements.main:app`). Считает измерения по растрам (GeoTIFF), читая их напрямую из S3/MinIO через GDAL (`vsis3`). Отдельного воркера у сервиса нет.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/measurements/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/): классы `LoggerSettings`, `SentrySettings`, `DjangoSettings`, `ApplicationSettings`, `TraceSettings`, `S3CredentialsSettings`, `Store`. Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
Каждый класс задаёт свой префикс через `class Config: env_prefix` (`LOG_`, `SENTRY_`, `DJANGO_`, `TRACING_`, `S3_`); у `ApplicationSettings` префикса нет — её поля читаются по имени напрямую (`AUTH`, `SHOW_UI`, `USE_SENTRY` и т.п.). Почти все переменные имеют значения по умолчанию, поэтому обязательна фактически одна — **`S3_JSON_SETTINGS`**: её отсутствие приводит к `ValueError` в `S3CredentialsSettings.from_env()` и процесс не стартует.
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yaml` — образ `measurements`, проброс порта `8000:8000`, инлайн `environment: S3_JSON_SETTINGS`. Сервис запускается `entrypoint.sh` (gunicorn, 4 воркера, uvicorn worker, таймаут 240) |
|
||||||
|
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml` (universal-chart, dependency `oci://…/charts`): блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов). Per-env значения через ключи `_default`/`stage`/`preprod`/`production` |
|
||||||
|
| Kubernetes — этот infra-репозиторий (`iac/apps/measurements`) | `base/` — kustomize-манифесты с инъекцией секрета S3 через **HashiCorp Vault** (annotations `vault.hashicorp.com/*`), обычные переменные заданы инлайн в `env:`. Оверлеи: `yc-k8s-test` (base + патч реплик), `brusnika-stage`/`brusnika-prod` (Flux `HelmRelease` на `universal-chart`, блок `secretEnvs`) |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml` подключает шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`); в `workflow.rules` задаются переменные пайплайна (`STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `SERVICE_NAME`, `DOCKERFILE_PATH`) |
|
||||||
|
|
||||||
|
**Миграции БД.** Отсутствуют. Сервис не хранит собственное состояние в реляционной БД (`psycopg2` присутствует в зависимостях, но код измерений работает с растрами из S3). Шага миграций в `entrypoint.sh` нет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## measurements (`measurements`)
|
||||||
|
|
||||||
|
Переменные читаются набором классов `*Settings` в `config.py`, инстанцируемых на уровне модуля: `settings = ApplicationSettings()`, `store = Store()`, `logger = LoggerSettings().logger`, `tracing_settings = TraceSettings()`.
|
||||||
|
|
||||||
|
### S3 / MinIO (обязательно)
|
||||||
|
|
||||||
|
Класс `S3CredentialsSettings`. Единственный обязательный источник конфигурации — переменная `S3_JSON_SETTINGS` (JSON-строка). Валидатор `from_env` (`model_validator(mode='before')`) читает её из окружения и при отсутствии выбрасывает `ValueError`. Доступы к S3 используются как boto3-клиентом (список бакетов), так и GDAL (`AWS_S3_ENDPOINT`/`AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY`, драйвер `vsis3`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `S3_JSON_SETTINGS` | string (JSON) | да | — | JSON с доступами к S3. Поля: `host`, `login`, `password` (обяз.); `verify` (bool, по умолч. `false`); `buckets` (список; если пуст — бакеты запрашиваются через `list_buckets()`). Пример: `{"host":"https://s3…","login":"…","password":"…","verify":false,"buckets":["measurements"]}` |
|
||||||
|
|
||||||
|
> В `host` поддерживаются схемы `http://`/`https://`: при `http://` GDAL переключается на `AWS_HTTPS=NO`, отключает `GDAL_DISABLE_READDIR_ON_OPEN` и `AWS_VIRTUAL_HOSTING`.
|
||||||
|
|
||||||
|
### Логирование (префикс `LOG_`)
|
||||||
|
|
||||||
|
Класс `LoggerSettings`. Настраивает JSON-логгер (`python-json-logger`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `LOG_LEVEL` | string | нет | `INFO` | Уровень логирования (`INFO`/`DEBUG`/…); неизвестное значение → `INFO` |
|
||||||
|
| `LOG_FORMAT` | string | нет | JSON-шаблон | Формат строки лога для `JsonFormatter` |
|
||||||
|
|
||||||
|
### Приложение (`ApplicationSettings`, без префикса)
|
||||||
|
|
||||||
|
Поля читаются по имени напрямую (регистронезависимо). Управляют поведением сервиса и подключением middleware в `main.py`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `AUTH` | bool | нет | `false` | Подключить `CustomAuthenticationMiddleware` (проверка JWT `authorization`/`identity`) |
|
||||||
|
| `SHOW_UI` | bool | нет | `false` | Включить Swagger/redoc; при `false` `docs_url`/`redoc_url` отключены |
|
||||||
|
| `USE_SENTRY` | bool | нет | `false` | Инициализировать Sentry SDK и `SentryAsgiMiddleware` |
|
||||||
|
| `DEBUG` | bool | нет | `false` | Флаг отладки |
|
||||||
|
| `CLASSIC_MODE` | bool | нет | `true` | Классический режим расчётов |
|
||||||
|
| `BLOCK_SIZE` | int | нет | `256` | Размер блока обработки растра; участвует в `area_factor` |
|
||||||
|
| `BLOCK_SIZE_FACTOR` | int | нет | `10` | Множитель площади блока (`area_factor = BLOCK_SIZE_FACTOR × BLOCK_SIZE²`) |
|
||||||
|
| `CPU_NUMBER` | int | нет | `10` | Число используемых CPU |
|
||||||
|
|
||||||
|
> `DEBUG`, `CLASSIC_MODE`, `BLOCK_SIZE`, `BLOCK_SIZE_FACTOR`, `CPU_NUMBER` задаются в конфиге, но в текущих обработчиках напрямую не считываются (в `main.py` используются только `SHOW_UI`, `USE_SENTRY`, `AUTH`). Оставлены как настраиваемые параметры.
|
||||||
|
|
||||||
|
### Django / ЛК (префикс `DJANGO_`)
|
||||||
|
|
||||||
|
Класс `DjangoSettings`. Используется `CustomAuthenticationMiddleware`/`DjangoUserMiddleware` при включённой авторизации (`AUTH=1`) для запросов к ЛК.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `DJANGO_USE` | bool | нет | `true` | Использовать интеграцию с Django |
|
||||||
|
| `DJANGO_HOST` | string | нет | `https://lk.sarex.io` | Базовый URL Django/ЛК |
|
||||||
|
| `DJANGO_TIMEOUT` | int | нет | `10` | Таймаут HTTP-запросов к Django, сек |
|
||||||
|
|
||||||
|
### Sentry (префикс `SENTRY_`)
|
||||||
|
|
||||||
|
Класс `SentrySettings`. Значения передаются в `sentry_sdk.init(**settings.sentry.kwargs)` только при `USE_SENTRY=1`.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `SENTRY_DSN` | string | нет | `""` | DSN проекта Sentry |
|
||||||
|
| `SENTRY_ENVIRONMENT` | string | нет | `production` | Имя окружения в Sentry |
|
||||||
|
| `SENTRY_TRACES_SAMPLE_RATE` | float | нет | `1.0` | Доля трейсов |
|
||||||
|
| `SENTRY_SEND_DEFAULT_PII` | bool | нет | `true` | Отправлять PII |
|
||||||
|
|
||||||
|
### Трейсинг (OpenTelemetry, префикс `TRACING_`)
|
||||||
|
|
||||||
|
Класс `TraceSettings`. Активируется при `TRACING_USE=1` (`fastapi-otel-tools`).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `TRACING_USE` | bool | нет | `false` | Включает OTEL-трейсинг, otel-логгер и `OtelMiddleware` |
|
||||||
|
| `TRACING_HOST` | string | нет | `localhost:4317` | Адрес OTLP-коллектора |
|
||||||
|
| `TRACING_SERVICE_NAME` | string | нет | `measurements` | Имя сервиса в трейсах |
|
||||||
|
| `TRACING_INSECURE` | bool | нет | `false` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
|
||||||
|
> Тип `bool` в pydantic принимает `1`/`0`, `true`/`false`, `yes`/`no`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Инфраструктурные и вспомогательные переменные
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `S3_JSON_FILE` | `.helm/values.yaml` (`envs`) | Путь к файлу с доступами S3 (`/opt/cred_s3.json`). **Кодом не читается** — приложение использует только `S3_JSON_SETTINGS` |
|
||||||
|
| `SERVICE_NAME`, `DOCKERFILE_PATH`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` (`variables`) | Параметры сборки образа |
|
||||||
|
| `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` | `.gitlab-ci.yml` (`workflow.rules`) | Параметры пайплайна `universal-pipeline` (деплой чарта per-env: `stage`/`preprod`/`production`) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Деплой из этого репозитория (`iac/apps/measurements`)
|
||||||
|
|
||||||
|
В `base/` используется **kustomize** (не собственный Helm-чарт сервиса). Доступ к S3/MinIO инъектируется агентом **Vault** и подгружается в окружение процесса до старта.
|
||||||
|
|
||||||
|
### `base/`
|
||||||
|
|
||||||
|
- `deployment.yaml` — единственный Deployment `measurements` (namespace `measurements`). Аннотации Vault (`agent-inject`, `role: measurements`) формируют шаблон секрета `measurements-s3` из `secrets/data/minio/apps/measurements`, собирая `S3_JSON_SETTINGS='{"host":…,"login":…,"password":…,"verify":false,"buckets":["measurements"]}'`. Контейнер запускается командой `set -a; . /vault/secrets/measurements-s3; set +a; exec /opt/entrypoint.sh`. Инлайн задан только `TRACING_USE=false`. Порт `8000` (`http`), `serviceAccountName: measurements-vault`, `imagePullSecrets: regcred`, ресурсы `cpu 25m` / `memory 128Mi`.
|
||||||
|
- `service.yaml` — `Service` `measurements-svc` (ClusterIP, порт `8000` → `8000`).
|
||||||
|
- `namespace.yaml` — namespace `measurements` с `istio-injection: enabled`.
|
||||||
|
- `serviceaccount.yaml` — SA `measurements-vault`.
|
||||||
|
- `kustomization.yaml` собирает `namespace`, `serviceaccount`, `deployment`, `service`.
|
||||||
|
|
||||||
|
### Оверлеи
|
||||||
|
|
||||||
|
- **`yc-k8s-test`** — `../base` + патч `replicas.yaml` (реплики Deployment `measurements` = 1).
|
||||||
|
- **`brusnika-stage`** / **`brusnika-prod`** — Flux `HelmRelease` `measurements` на `universal-chart` `0.1.7` (source `yc-oci-charts`). Секрет `S3_JSON_SETTINGS` берётся из k8s-секрета `s3-json-settings` (`secretEnvs`). `replicaCount`: `stage 1`, `preprod 3`, `production 3`; `imagePullSecrets: regcred`; `labels.monitoring: prometheus`; сервис `measurements-service` (ClusterIP, `8000`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **`S3_JSON_SETTINGS` — единственная жёстко обязательная переменная.** Локальный `docker-compose.yaml` задаёт её значением-заглушкой (`{"host":"host","login":"login","password":"password"}`) — для реальной работы значение нужно заменить.
|
||||||
|
- **`S3_JSON_FILE` в `.helm/values.yaml` кодом не читается** — приложение использует только `S3_JSON_SETTINGS` (в этом infra-репозитории она и инъектируется Vault). Расхождение способов передачи доступов между собственным чартом и infra-репо.
|
||||||
|
- **Опечатка в `middleware.py`:** в `DjangoUserMiddleware` используется `settings.django.self.timeout` вместо `settings.django.timeout` — лишний `.self` приведёт к `AttributeError`. Сам `DjangoUserMiddleware` в `main.py` не подключается (подключается `CustomAuthenticationMiddleware`).
|
||||||
|
- **Отсутствует поле `jwt_public_key`:** `CustomAuthenticationMiddleware` при отсутствии заголовка `identity` вызывает `jwt.decode(key=settings.jwt_public_key, …)`, но такого поля в `ApplicationSettings` нет — при `AUTH=1` и запросе без `identity` это приведёт к `AttributeError`. Если планируется проверка подписи, следует добавить переменную (напр. `JWT_PUBLIC_KEY`) в конфиг.
|
||||||
|
- **`brusnika-stage`/`brusnika-prod`: `image.name` указывает на `documentations` (`…/documentations:prod_5904312b`), а не на `measurements`** — вероятно скопировано из другого сервиса; для measurements образ должен указывать на `…/measurements`.
|
||||||
|
- **Проверки liveness/readiness отключены** во всех манифестах (`probes.*.enabled: false`); HTTP-эндпоинта healthcheck у сервиса нет.
|
||||||
|
- **Probes/порт в brusnika-оверлеях:** `deployment.port` задан `8080`, тогда как контейнер (`entrypoint.sh` → gunicorn) слушает `8000`, и `service.port`/`targetPort` = `8000`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
|
- `S3_JSON_SETTINGS` (обязателен) — реальные доступы к S3/MinIO с бакетом(ами) растров.
|
||||||
|
- при необходимости: `LOG_LEVEL`, `TRACING_USE` (+ `TRACING_*`), `USE_SENTRY` (+ `SENTRY_*`), `AUTH` (+ `DJANGO_*`).
|
||||||
|
|
||||||
|
Сервис слушает `0.0.0.0:8000` (gunicorn, 4 воркера uvicorn). См. пример значений в `.env.example`.
|
||||||
927
apps/measurements/openapi.json
Normal file
927
apps/measurements/openapi.json
Normal file
@ -0,0 +1,927 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"info": {
|
||||||
|
"title": "measurements",
|
||||||
|
"description": "HTTP-сервис измерений по GeoTIFF-растрам (DEM/thermal), читаемым напрямую из S3/MinIO через GDAL.\n\nВсе эндпоинты — POST под префиксом `/api`. `path` в теле запроса указывается в формате `<bucket>:<путь/к/файлу.tif>`; система координат задаётся строкой `proj` (proj4). Пустой `proj` означает, что точки уже в системе координат растра.\n\nАутентификация (JWT в заголовках `authorization`/`identity`) включается переменной `AUTH=1`.",
|
||||||
|
"version": "0.0.1"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/api/point": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Height/temperature at a single point",
|
||||||
|
"description": "Возвращает высоту (h) и/или температуру (t) в одной точке по DEM/термо-растру из S3.",
|
||||||
|
"operationId": "point_api_point_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffPoint"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Point Api Point Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/points": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Height/temperature at multiple points",
|
||||||
|
"description": "Батч-версия /point: массив высот/температур для списка точек.",
|
||||||
|
"operationId": "points_api_points_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffPoints"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Response Points Api Points Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/profile": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Elevation profile along a polyline",
|
||||||
|
"description": "Профиль высот вдоль ломаной; между вершинами добавляются промежуточные точки.",
|
||||||
|
"operationId": "profile_api_profile_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffProfile"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Response Profile Api Profile Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/volume": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Volume inside a polygon",
|
||||||
|
"description": "Объём внутри полигона. mode=cv2 (по умолчанию) или pillow (DEM-режим, требует > 2 точек).",
|
||||||
|
"operationId": "volume_api_volume_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffVolume"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Response Volume Api Volume Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/multiple": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Volume difference between two DEMs",
|
||||||
|
"description": "Разница объёмов между master- и slave-растром в пределах полигона.",
|
||||||
|
"operationId": "multiple_api_multiple_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TiffMultiple"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Multiple Api Multiple Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/transform": {
|
||||||
|
"post": {
|
||||||
|
"summary": "GeoTIFF affine transform",
|
||||||
|
"description": "Возвращает 6 коэффициентов GDAL GeoTransform растра.",
|
||||||
|
"operationId": "transform_api_transform_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 6,
|
||||||
|
"minItems": 6,
|
||||||
|
"title": "Response Transform Api Transform Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/info": {
|
||||||
|
"post": {
|
||||||
|
"summary": "GeoTIFF metadata",
|
||||||
|
"description": "Метаданные растра (размер, проекция, geotransform и т.п.).",
|
||||||
|
"operationId": "info_api_info_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Info Api Info Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/statistics": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Raster min/max statistics",
|
||||||
|
"description": "Пара (min, max) значений растра.",
|
||||||
|
"operationId": "statistics_api_statistics_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2,
|
||||||
|
"title": "Response Statistics Api Statistics Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/bounds": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Tile bounds by zoom range",
|
||||||
|
"description": "Границы тайлов (XYZ) для диапазона zoom_from..zoom_to.",
|
||||||
|
"operationId": "bounds_api_bounds_post",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Tiff"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful Response",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"additionalProperties": {
|
||||||
|
"items": {
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "Response Bounds Api Bounds Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"description": "Validation Error",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/HTTPValidationError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"HTTPValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"detail": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/ValidationError"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Detail"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"title": "HTTPValidationError"
|
||||||
|
},
|
||||||
|
"Tiff": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"zoom_to": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Zoom To",
|
||||||
|
"default": 21
|
||||||
|
},
|
||||||
|
"zoom_from": {
|
||||||
|
"type": "integer",
|
||||||
|
"title": "Zoom From",
|
||||||
|
"default": 14
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"title": "Tiff"
|
||||||
|
},
|
||||||
|
"TiffMultiple": {
|
||||||
|
"properties": {
|
||||||
|
"master_path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Master Path"
|
||||||
|
},
|
||||||
|
"slave_path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Slave Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"master_path",
|
||||||
|
"slave_path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffMultiple",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"master_path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
37.34743572357015,
|
||||||
|
55.68881158675471
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.347128864435845,
|
||||||
|
55.6888704629146
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.34719182945381,
|
||||||
|
55.68896771656465
|
||||||
|
],
|
||||||
|
[
|
||||||
|
37.34732057806979,
|
||||||
|
55.688942063559054
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"slave_path": "geotiff_dems/NTG030521_DEM.tif"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffPoint": {
|
||||||
|
"properties": {
|
||||||
|
"altitude_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Altitude Path"
|
||||||
|
},
|
||||||
|
"temperature_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Temperature Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"point": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2,
|
||||||
|
"title": "Point"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"point"
|
||||||
|
],
|
||||||
|
"title": "TiffPoint",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"point": [
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"point": [
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
],
|
||||||
|
"proj": "",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"point": [
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
],
|
||||||
|
"proj": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffPoints": {
|
||||||
|
"properties": {
|
||||||
|
"altitude_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Altitude Path"
|
||||||
|
},
|
||||||
|
"temperature_path": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Temperature Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffPoints",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"altitude_path": "geotiff_dems/ALTITUDE_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95563056285039,
|
||||||
|
57.966613723012216
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
1494221.1549999786,
|
||||||
|
516495.86600015266
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "",
|
||||||
|
"temperature_path": "geotiff_dems/THERMAL_DEM.tif"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffProfile": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffProfile",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/BLG_080122_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
3334617.713961677,
|
||||||
|
590691.7743950449
|
||||||
|
],
|
||||||
|
[
|
||||||
|
3334977.6348458366,
|
||||||
|
590688.296080064
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"TiffVolume": {
|
||||||
|
"properties": {
|
||||||
|
"path": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Path"
|
||||||
|
},
|
||||||
|
"proj": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Proj",
|
||||||
|
"default": ""
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "null"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"title": "Level"
|
||||||
|
},
|
||||||
|
"points": {
|
||||||
|
"items": {
|
||||||
|
"prefixItems": [
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 2,
|
||||||
|
"minItems": 2
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Points"
|
||||||
|
},
|
||||||
|
"mode": {
|
||||||
|
"allOf": [
|
||||||
|
{
|
||||||
|
"$ref": "#/components/schemas/VolumeMode"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "cv2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"path",
|
||||||
|
"points"
|
||||||
|
],
|
||||||
|
"title": "TiffVolume",
|
||||||
|
"example": [
|
||||||
|
{
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
59.95821631734607,
|
||||||
|
57.9660901731621
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95452603553467,
|
||||||
|
57.96567474229709
|
||||||
|
],
|
||||||
|
[
|
||||||
|
59.95563056285039,
|
||||||
|
57.966613723012216
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": "+proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22 +units=m +no_defs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mode": "pillow",
|
||||||
|
"path": "geotiff_dems/NTG030521_DEM.tif",
|
||||||
|
"points": [
|
||||||
|
[
|
||||||
|
1494221.1549999786,
|
||||||
|
516495.86600015266
|
||||||
|
],
|
||||||
|
[
|
||||||
|
1494214.348999979,
|
||||||
|
516505.3900003205
|
||||||
|
]
|
||||||
|
],
|
||||||
|
"proj": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ValidationError": {
|
||||||
|
"properties": {
|
||||||
|
"loc": {
|
||||||
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"title": "Location"
|
||||||
|
},
|
||||||
|
"msg": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Message"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Error Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"loc",
|
||||||
|
"msg",
|
||||||
|
"type"
|
||||||
|
],
|
||||||
|
"title": "ValidationError"
|
||||||
|
},
|
||||||
|
"VolumeMode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"cv2",
|
||||||
|
"pillow"
|
||||||
|
],
|
||||||
|
"title": "VolumeMode"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
565
apps/measurements/openapi.yaml
Normal file
565
apps/measurements/openapi.yaml
Normal file
@ -0,0 +1,565 @@
|
|||||||
|
openapi: 3.1.0
|
||||||
|
info:
|
||||||
|
title: measurements
|
||||||
|
description: 'HTTP-сервис измерений по GeoTIFF-растрам (DEM/thermal), читаемым напрямую из S3/MinIO
|
||||||
|
через GDAL.
|
||||||
|
|
||||||
|
|
||||||
|
Все эндпоинты — POST под префиксом `/api`. `path` в теле запроса указывается в формате `<bucket>:<путь/к/файлу.tif>`;
|
||||||
|
система координат задаётся строкой `proj` (proj4). Пустой `proj` означает, что точки уже в системе
|
||||||
|
координат растра.
|
||||||
|
|
||||||
|
|
||||||
|
Аутентификация (JWT в заголовках `authorization`/`identity`) включается переменной `AUTH=1`.'
|
||||||
|
version: 0.0.1
|
||||||
|
paths:
|
||||||
|
/api/point:
|
||||||
|
post:
|
||||||
|
summary: Height/temperature at a single point
|
||||||
|
description: Возвращает высоту (h) и/или температуру (t) в одной точке по DEM/термо-растру из S3.
|
||||||
|
operationId: point_api_point_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffPoint'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
title: Response Point Api Point Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/points:
|
||||||
|
post:
|
||||||
|
summary: Height/temperature at multiple points
|
||||||
|
description: 'Батч-версия /point: массив высот/температур для списка точек.'
|
||||||
|
operationId: points_api_points_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffPoints'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
title: Response Points Api Points Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/profile:
|
||||||
|
post:
|
||||||
|
summary: Elevation profile along a polyline
|
||||||
|
description: Профиль высот вдоль ломаной; между вершинами добавляются промежуточные точки.
|
||||||
|
operationId: profile_api_profile_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffProfile'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
additionalProperties:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
type: object
|
||||||
|
type: array
|
||||||
|
title: Response Profile Api Profile Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/volume:
|
||||||
|
post:
|
||||||
|
summary: Volume inside a polygon
|
||||||
|
description: Объём внутри полигона. mode=cv2 (по умолчанию) или pillow (DEM-режим, требует > 2 точек).
|
||||||
|
operationId: volume_api_volume_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffVolume'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
anyOf:
|
||||||
|
- additionalProperties:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
- type: 'null'
|
||||||
|
title: Response Volume Api Volume Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/multiple:
|
||||||
|
post:
|
||||||
|
summary: Volume difference between two DEMs
|
||||||
|
description: Разница объёмов между master- и slave-растром в пределах полигона.
|
||||||
|
operationId: multiple_api_multiple_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TiffMultiple'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
type: number
|
||||||
|
type: object
|
||||||
|
title: Response Multiple Api Multiple Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/transform:
|
||||||
|
post:
|
||||||
|
summary: GeoTIFF affine transform
|
||||||
|
description: Возвращает 6 коэффициентов GDAL GeoTransform растра.
|
||||||
|
operationId: transform_api_transform_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 6
|
||||||
|
minItems: 6
|
||||||
|
title: Response Transform Api Transform Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/info:
|
||||||
|
post:
|
||||||
|
summary: GeoTIFF metadata
|
||||||
|
description: Метаданные растра (размер, проекция, geotransform и т.п.).
|
||||||
|
operationId: info_api_info_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
title: Response Info Api Info Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/statistics:
|
||||||
|
post:
|
||||||
|
summary: Raster min/max statistics
|
||||||
|
description: Пара (min, max) значений растра.
|
||||||
|
operationId: statistics_api_statistics_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
title: Response Statistics Api Statistics Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
/api/bounds:
|
||||||
|
post:
|
||||||
|
summary: Tile bounds by zoom range
|
||||||
|
description: Границы тайлов (XYZ) для диапазона zoom_from..zoom_to.
|
||||||
|
operationId: bounds_api_bounds_post
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Tiff'
|
||||||
|
required: true
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful Response
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
additionalProperties:
|
||||||
|
items:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
type: array
|
||||||
|
type: object
|
||||||
|
title: Response Bounds Api Bounds Post
|
||||||
|
'422':
|
||||||
|
description: Validation Error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/HTTPValidationError'
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
HTTPValidationError:
|
||||||
|
properties:
|
||||||
|
detail:
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/ValidationError'
|
||||||
|
type: array
|
||||||
|
title: Detail
|
||||||
|
type: object
|
||||||
|
title: HTTPValidationError
|
||||||
|
Tiff:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
zoom_to:
|
||||||
|
type: integer
|
||||||
|
title: Zoom To
|
||||||
|
default: 21
|
||||||
|
zoom_from:
|
||||||
|
type: integer
|
||||||
|
title: Zoom From
|
||||||
|
default: 14
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
title: Tiff
|
||||||
|
TiffMultiple:
|
||||||
|
properties:
|
||||||
|
master_path:
|
||||||
|
type: string
|
||||||
|
title: Master Path
|
||||||
|
slave_path:
|
||||||
|
type: string
|
||||||
|
title: Slave Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- master_path
|
||||||
|
- slave_path
|
||||||
|
- points
|
||||||
|
title: TiffMultiple
|
||||||
|
example:
|
||||||
|
- master_path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 37.34743572357015
|
||||||
|
- 55.68881158675471
|
||||||
|
- - 37.347128864435845
|
||||||
|
- 55.6888704629146
|
||||||
|
- - 37.34719182945381
|
||||||
|
- 55.68896771656465
|
||||||
|
- - 37.34732057806979
|
||||||
|
- 55.688942063559054
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
slave_path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
TiffPoint:
|
||||||
|
properties:
|
||||||
|
altitude_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Altitude Path
|
||||||
|
temperature_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Temperature Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
point:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
title: Point
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- point
|
||||||
|
title: TiffPoint
|
||||||
|
example:
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
point:
|
||||||
|
- 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- point:
|
||||||
|
- 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
point:
|
||||||
|
- 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
TiffPoints:
|
||||||
|
properties:
|
||||||
|
altitude_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Altitude Path
|
||||||
|
temperature_path:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: 'null'
|
||||||
|
title: Temperature Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- points
|
||||||
|
title: TiffPoints
|
||||||
|
example:
|
||||||
|
- altitude_path: geotiff_dems/ALTITUDE_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
- - 59.95563056285039
|
||||||
|
- 57.966613723012216
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
- points:
|
||||||
|
- - 1494221.1549999786
|
||||||
|
- 516495.86600015266
|
||||||
|
- - 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
temperature_path: geotiff_dems/THERMAL_DEM.tif
|
||||||
|
TiffProfile:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- points
|
||||||
|
title: TiffProfile
|
||||||
|
example:
|
||||||
|
- path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
- path: geotiff_dems/BLG_080122_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 3334617.713961677
|
||||||
|
- 590691.7743950449
|
||||||
|
- - 3334977.6348458366
|
||||||
|
- 590688.296080064
|
||||||
|
TiffVolume:
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
title: Path
|
||||||
|
proj:
|
||||||
|
type: string
|
||||||
|
title: Proj
|
||||||
|
default: ''
|
||||||
|
level:
|
||||||
|
anyOf:
|
||||||
|
- type: number
|
||||||
|
- type: 'null'
|
||||||
|
title: Level
|
||||||
|
points:
|
||||||
|
items:
|
||||||
|
prefixItems:
|
||||||
|
- type: number
|
||||||
|
- type: number
|
||||||
|
type: array
|
||||||
|
maxItems: 2
|
||||||
|
minItems: 2
|
||||||
|
type: array
|
||||||
|
title: Points
|
||||||
|
mode:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/VolumeMode'
|
||||||
|
default: cv2
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- points
|
||||||
|
title: TiffVolume
|
||||||
|
example:
|
||||||
|
- path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 59.95821631734607
|
||||||
|
- 57.9660901731621
|
||||||
|
- - 59.95452603553467
|
||||||
|
- 57.96567474229709
|
||||||
|
- - 59.95563056285039
|
||||||
|
- 57.966613723012216
|
||||||
|
proj: +proj=tmerc +lat_0=0 +lon_0=60.05 +k=1 +x_0=1500000 +y_0=-5911057.63 +ellps=krass +towgs84=23.57,-140.95,-79.8,0,0.35,0.79,-0.22
|
||||||
|
+units=m +no_defs
|
||||||
|
- mode: pillow
|
||||||
|
path: geotiff_dems/NTG030521_DEM.tif
|
||||||
|
points:
|
||||||
|
- - 1494221.1549999786
|
||||||
|
- 516495.86600015266
|
||||||
|
- - 1494214.348999979
|
||||||
|
- 516505.3900003205
|
||||||
|
proj: ''
|
||||||
|
ValidationError:
|
||||||
|
properties:
|
||||||
|
loc:
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: string
|
||||||
|
- type: integer
|
||||||
|
type: array
|
||||||
|
title: Location
|
||||||
|
msg:
|
||||||
|
type: string
|
||||||
|
title: Message
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
title: Error Type
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- loc
|
||||||
|
- msg
|
||||||
|
- type
|
||||||
|
title: ValidationError
|
||||||
|
VolumeMode:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- cv2
|
||||||
|
- pillow
|
||||||
|
title: VolumeMode
|
||||||
64
apps/subscriptions/.env.example
Normal file
64
apps/subscriptions/.env.example
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# sarex-subscriptions — пример переменных окружения (api + cron-задачи)
|
||||||
|
#
|
||||||
|
# Django-сервис уведомлений: HTTP-API (uwsgi) + две CronJob-задачи рассылки
|
||||||
|
# (run_notifications / run_immediately_notifications). Все компоненты
|
||||||
|
# используют один модуль настроек config.settings.production и общий набор
|
||||||
|
# переменных.
|
||||||
|
#
|
||||||
|
# Скопируйте нужные значения в config.env / .env соответствующего окружения.
|
||||||
|
# Переменные, помеченные (обяз.), обязательны для работы — без них процесс
|
||||||
|
# упадёт при старте либо при обращении к соответствующему сервису.
|
||||||
|
# bool в Django читается как строка: непустая строка = True (см. замечания).
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# --- База данных PostgreSQL/PostGIS (api + cron) ----------------------------
|
||||||
|
# Движок — core.db.backends.postgis (GeoDjango), нужен PostGIS.
|
||||||
|
DATABASE_HOST=127.0.0.1 # (обяз.) хост PostgreSQL
|
||||||
|
DATABASE_PORT=5432 # (обяз.) порт PostgreSQL
|
||||||
|
DATABASE_NAME=subscriptions # (обяз.) имя базы данных
|
||||||
|
DATABASE_USER=user # (обяз.) пользователь БД
|
||||||
|
DATABASE_PASSWORD=password # (обяз.) пароль пользователя БД
|
||||||
|
|
||||||
|
# --- Хранилище S3 / MinIO (api: static + media) -----------------------------
|
||||||
|
# STATICFILES_STORAGE и DEFAULT_FILE_STORAGE = S3Boto3Storage, поэтому креды
|
||||||
|
# фактически обязательны для корректной работы статики/медиа.
|
||||||
|
YC_S3_ACCESS_KEY_ID=access_key # (обяз.) access key
|
||||||
|
YC_S3_SECRET_ACCESS_KEY=secret_key # (обяз.) secret key
|
||||||
|
YC_S3_BUCKET_NAME=subscriptions # (обяз.) имя бакета
|
||||||
|
YC_S3_ENDPOINT_URL=https://storage.yandexcloud.net # (обяз.) endpoint S3
|
||||||
|
|
||||||
|
# --- Внешние сервисы (в основном cron-рассылки) -----------------------------
|
||||||
|
SYSTEM_LOG_HOST=http://localhost:8888 # (обяз.) URL сервиса system-log
|
||||||
|
USER_SERVICE_HOST=http://localhost:8000 # (обяз.) URL сервиса пользователей (Django/ЛК)
|
||||||
|
USER_SERVICE_LOGIN=superuser # логин для авторизации в user-service (по умолч. "")
|
||||||
|
USER_SERVICE_PASSWORD=password # пароль для авторизации в user-service (по умолч. "")
|
||||||
|
ALLOWED_HOST_EMAIL=https://lk.sarex.io # базовый URL для ссылок в письмах (по умолч. https://lk.sarex.io)
|
||||||
|
|
||||||
|
# --- Email через Mailgun (cron) ---------------------------------------------
|
||||||
|
IS_MAILGUN_USE=0 # использовать Mailgun (по умолч. True; 0 — выключить)
|
||||||
|
MAILGUN_API_KEY= # API-ключ Mailgun (по умолч. "")
|
||||||
|
# MAILGUN_BASE_URL=https://api.mailgun.net/v3/mg.sarex.io # базовый URL Mailgun API
|
||||||
|
# MAILGUN_EMAIL_FROM=hello@sarex.io # адрес отправителя
|
||||||
|
|
||||||
|
# --- Email через SMTP (cron; используется если задан хост) ------------------
|
||||||
|
SMTP_EMAIL_HOST= # хост SMTP (по умолч. None — SMTP отключён)
|
||||||
|
SMTP_EMAIL_PORT= # порт SMTP (по умолч. None)
|
||||||
|
# SMTP_EMAIL_FROM=hello@sarex.io # адрес отправителя (по умолч. hello@sarex.io)
|
||||||
|
|
||||||
|
# --- Telegram (cron) --------------------------------------------------------
|
||||||
|
IS_USE_TELEGRAM=false # использовать Telegram (по умолч. True; см. замечание про bool)
|
||||||
|
TELEGRAM_BOT_TOKEN= # токен бота (по умолч. "")
|
||||||
|
|
||||||
|
# --- Трейсинг OpenTelemetry (api; необязательно) ----------------------------
|
||||||
|
# Блок включается, если USE_OTEL задана НЕПУСТОЙ строкой (даже "False" → вкл!).
|
||||||
|
# USE_OTEL=True # включить OTEL-трейсинг и otel-логгер
|
||||||
|
# SERVICE_NAME=subscriptions # имя сервиса в трейсах (по умолч. subscriptions)
|
||||||
|
# TRACER_ENDPOINT=localhost:4375 # адрес OTLP-коллектора (по умолч. localhost:4375)
|
||||||
|
# USE_INSECURE=True # подключение без TLS (та же логика bool, что и USE_OTEL)
|
||||||
|
|
||||||
|
# --- Инфраструктурные / вспомогательные (кодом приложения НЕ читаются) -------
|
||||||
|
# API_ADDRESS=8000 # задаётся в манифестах; порт uwsgi фиксирован в uwsgi.ini (0.0.0.0:8000)
|
||||||
|
# DJANGO_SETTINGS_MODULE=config.settings.production # задаётся в entrypoint.sh / wsgi.py
|
||||||
|
# NEXUS_USERNAME= # build-arg Dockerfile: доступ к приватному PyPI (nexus.infra.sarex.io)
|
||||||
|
# NEXUS_PASSWORD= # build-arg Dockerfile: пароль к приватному PyPI
|
||||||
200
apps/subscriptions/CONFIGURATION.md
Normal file
200
apps/subscriptions/CONFIGURATION.md
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# Конфигурация sarex-subscriptions (api + cron-задачи)
|
||||||
|
|
||||||
|
Документ описывает все переменные окружения и способы конфигурирования сервиса `sarex-subscriptions` — Django-приложения рассылки уведомлений (email/Telegram) по подпискам.
|
||||||
|
|
||||||
|
Компоненты сервиса используют **один и тот же** модуль настроек `config.settings.production` и общий набор переменных окружения:
|
||||||
|
|
||||||
|
- **api** — HTTP-сервис (uwsgi, `config.wsgi:application`), REST API подписок/получателей/шаблонов; отдаёт статику и медиа через S3;
|
||||||
|
- **cron `run_notifications`** (`subscription-periodic`) — периодическая рассылка отложенных уведомлений;
|
||||||
|
- **cron `run_immediately_notifications`** (`subscription-immediately`) — рассылка немедленных уведомлений.
|
||||||
|
|
||||||
|
Обе cron-задачи — это management-команды Django (`server/apps/notification/management/commands`), запускаемые как отдельные `CronJob` из того же образа.
|
||||||
|
|
||||||
|
## Способы конфигурирования
|
||||||
|
|
||||||
|
Сервис настраивается **через переменные окружения** (`os.getenv` в `config/settings/base.py` и `config/settings/production.py`) плюс жёстко заданные в коде настройки. Отдельной библиотеки разбора конфига (как cleanenv в Go-сервисах) здесь нет — используется штатный механизм Django settings.
|
||||||
|
|
||||||
|
Часть настроек **захардкожена** в `base.py` и не выносится в окружение: `SECRET_KEY`, `DEBUG`, `ALLOWED_HOSTS`, `CORS_*`, REST Framework, локаль/таймзона (`ru-ru`, `Europe/Moscow`). В `production.py` `DEBUG=False` и заданы фиксированные `ALLOWED_HOSTS`/`CORS_ALLOWED_ORIGINS`.
|
||||||
|
|
||||||
|
Обязательные переменные не помечены тегами (как в Go), но при их отсутствии процесс падает при старте (подключение к БД) либо при обращении к соответствующему сервису (клиенты system-log/user-service, хранилище S3).
|
||||||
|
|
||||||
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
|
| Способ запуска | Откуда берутся переменные |
|
||||||
|
| --- | --- |
|
||||||
|
| Локально (docker-compose) | `docker-compose.yml` поднимает контейнер `db` (`postgis/postgis`) и сервисы `api`/`migrations`. Значения БД (`DATABASE_USER/PASSWORD/NAME`, `ALLOWED_HOST_EMAIL`) подставляются из окружения/файла `.env` рядом с compose. api собирается из `compose/server/Dockerfile` и стартует через `entrypoint.sh` |
|
||||||
|
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml`: зависимость от `universal-chart`. Блоки `services.api.envs` (обычные значения, per-env `_default/stage/preprod/production`) и `services.api.secretEnvs` (из k8s-секретов). Плюс `cronjobs.periodic` / `cronjobs.immediately` — шаблоны `CronJob` в `.helm/templates/`, наследующие `envs`/`secretEnvs` api |
|
||||||
|
| Kubernetes — этот infra-репозиторий (`iac/apps/subscriptions`) | `base/` — kustomize-манифесты с инъекцией секретов через **HashiCorp Vault** (annotations `vault.hashicorp.com/*`), обычные переменные заданы инлайн в `env:`. Оверлеи: `yc-k8s-test` (base + postgresql), `brusnika-stage` / `brusnika-prod` (Flux `HelmRelease` на `universal-chart`, блоки `envs`/`secretEnvs`) |
|
||||||
|
| CI/CD (GitLab) | `.gitlab-ci.yml` подключает шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`); в `workflow.rules` задаются переменные пайплайна (`SERVICE_NAME`, `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `IMAGE_NAME` и т.п.) |
|
||||||
|
|
||||||
|
**Миграции БД (api).** Выполняются **в entrypoint контейнера api** перед стартом uwsgi: `python manage.py migrate --settings=config.settings.production` (`compose/server/entrypoint.sh`). Cron-задачи миграций не выполняют. Локально в `docker-compose.yml` отдельный сервис `migrations` вызывает `makemigrations` (генерация миграций, не применение).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## api (`sarex-subscriptions`)
|
||||||
|
|
||||||
|
Переменные читаются в `config/settings/base.py` (S3, OTEL) и `config/settings/production.py` (БД и внешние сервисы; `production.py` импортирует всё из `base.py`).
|
||||||
|
|
||||||
|
### База данных (PostgreSQL / PostGIS)
|
||||||
|
|
||||||
|
Движок — `core.db.backends.postgis` (GeoDjango, требуется PostGIS). Все пять переменных обязательны — без них подключение к БД не поднимется.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `DATABASE_HOST` | string | да | — | Хост PostgreSQL |
|
||||||
|
| `DATABASE_PORT` | string | да | — | Порт PostgreSQL |
|
||||||
|
| `DATABASE_NAME` | string | да | — | Имя базы данных |
|
||||||
|
| `DATABASE_USER` | string | да | — | Пользователь БД |
|
||||||
|
| `DATABASE_PASSWORD` | string | да | — | Пароль пользователя БД |
|
||||||
|
|
||||||
|
### Хранилище S3 (static + media)
|
||||||
|
|
||||||
|
`STATICFILES_STORAGE` и `DEFAULT_FILE_STORAGE` = `storages.backends.s3boto3.S3Boto3Storage`, `AWS_DEFAULT_ACL="public-read"`. Значения по умолчанию отсутствуют (`os.getenv` без default → `None`), поэтому для корректной отдачи статики/медиа креды фактически обязательны.
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `YC_S3_ACCESS_KEY_ID` | string | да | Access key (`AWS_ACCESS_KEY_ID`) |
|
||||||
|
| `YC_S3_SECRET_ACCESS_KEY` | string | да | Secret key (`AWS_SECRET_ACCESS_KEY`) |
|
||||||
|
| `YC_S3_BUCKET_NAME` | string | да | Имя бакета (`AWS_STORAGE_BUCKET_NAME`) |
|
||||||
|
| `YC_S3_ENDPOINT_URL` | string | да | Endpoint S3 (`AWS_S3_ENDPOINT_URL`) |
|
||||||
|
|
||||||
|
### Трейсинг (OpenTelemetry)
|
||||||
|
|
||||||
|
Блок OTEL в `base.py` (и обёртка WSGI в `wsgi.py`) включается по `os.getenv('USE_OTEL', False)`. **Важно:** проверяется истинность строки, а не её значение — любая непустая строка (в т.ч. `"False"`, `"0"`) включает трейсинг. Задействует пакет `django_otel_tools`.
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `USE_OTEL` | bool-строка | не задана (выкл.) | Включает OTEL-трейсинг, otel-логгер и `OtelMiddleware` |
|
||||||
|
| `SERVICE_NAME` | string | `subscriptions` | Имя сервиса в трейсах |
|
||||||
|
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
|
||||||
|
| `USE_INSECURE` | bool-строка | не задана (выкл.) | Небезопасное (без TLS) подключение к коллектору; та же логика истинности строки, что и `USE_OTEL` |
|
||||||
|
|
||||||
|
> Захардкожено (не через окружение): `SECRET_KEY`, `DEBUG` (`False` в production), `ALLOWED_HOSTS`, `CORS_ALLOWED_ORIGINS`, `CORS_ALLOW_ALL_ORIGINS=True`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## cron-задачи (`run_notifications`, `run_immediately_notifications`)
|
||||||
|
|
||||||
|
Обе команды используют тот же модуль настроек и, помимо БД, обращаются к внешним сервисам для сбора данных и рассылки. Переменные читаются в `production.py` и потребляются в `server/apps/notification/management/commands/*.py`.
|
||||||
|
|
||||||
|
### Внешние сервисы и рассылка
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `SYSTEM_LOG_HOST` | string | да | — | Базовый URL сервиса system-log (логирование событий) |
|
||||||
|
| `USER_SERVICE_HOST` | string | да | — | Базовый URL сервиса пользователей (Django/ЛК) |
|
||||||
|
| `USER_SERVICE_LOGIN` | string | нет | `""` | Логин для авторизации в user-service |
|
||||||
|
| `USER_SERVICE_PASSWORD` | string | нет | `""` | Пароль для авторизации в user-service |
|
||||||
|
| `ALLOWED_HOST_EMAIL` | string | нет | `https://lk.sarex.io` | Базовый URL для формирования ссылок в письмах (`context_processing/document.py`) |
|
||||||
|
|
||||||
|
### Email — Mailgun
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `IS_MAILGUN_USE` | bool-строка | нет | `True` | Использовать Mailgun для отправки писем |
|
||||||
|
| `MAILGUN_API_KEY` | string | нет | `""` | API-ключ Mailgun |
|
||||||
|
| `MAILGUN_BASE_URL` | string | нет | `https://api.mailgun.net/v3/mg.sarex.io` | Базовый URL Mailgun API |
|
||||||
|
| `MAILGUN_EMAIL_FROM` | string | нет | `hello@sarex.io` | Адрес отправителя |
|
||||||
|
|
||||||
|
### Email — SMTP
|
||||||
|
|
||||||
|
SMTP-ветка активируется, только если **заданы оба** `SMTP_EMAIL_HOST` и `SMTP_EMAIL_PORT` (по умолчанию `None` — SMTP отключён).
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `SMTP_EMAIL_HOST` | string | нет | `None` | Хост SMTP-сервера |
|
||||||
|
| `SMTP_EMAIL_PORT` | string | нет | `None` | Порт SMTP-сервера |
|
||||||
|
| `SMTP_EMAIL_FROM` | string | нет | `hello@sarex.io` | Адрес отправителя |
|
||||||
|
|
||||||
|
### Telegram
|
||||||
|
|
||||||
|
| Переменная | Тип | Обяз. | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `IS_USE_TELEGRAM` | bool-строка | нет | `True` | Использовать Telegram-рассылку |
|
||||||
|
| `TELEGRAM_BOT_TOKEN` | string | нет | `""` | Токен Telegram-бота (`settings.TELEGRAM_BOT_TOKEN`) |
|
||||||
|
|
||||||
|
> Значения `IS_*` читаются как строки Django-настроек: непустая строка истинна. Чтобы выключить канал, инфраструктурные манифесты задают `"false"`/`"0"` — но с точки зрения Python это тоже непустые строки, поэтому фактическое поведение зависит от того, как значение интерпретируется в коде команды (см. замечания).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Инфраструктурные и вспомогательные переменные
|
||||||
|
|
||||||
|
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
|
| Переменная | Где используется | Назначение |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `API_ADDRESS` | `base/*.yaml`, `.helm/values.yaml`, brusnika-оверлеи | Задаётся в манифестах, но **кодом не читается** — порт uwsgi фиксирован в `uwsgi.ini` (`http = 0.0.0.0:8000`) |
|
||||||
|
| `DJANGO_SETTINGS_MODULE` | `entrypoint.sh`, `wsgi.py`, `manage.py` | Модуль настроек Django (`config.settings.production` в проде) |
|
||||||
|
| `NEXUS_USERNAME`, `NEXUS_PASSWORD` | `compose/server/Dockerfile` (build-arg), `.gitlab-ci.yml` | Доступ к приватному PyPI (`nexus.infra.sarex.io`) при сборке образа |
|
||||||
|
| `SERVICE_NAME`, `DOCKERFILE_PATH`, `BUILD_ARGS`, `CI_TRIGGER_SOURCE` | `.gitlab-ci.yml` (`variables`) | Параметры сборки/пайплайна |
|
||||||
|
| `STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS`, `IMAGE_NAME`, `ENABLE_BUILD_IMAGE` | `.gitlab-ci.yml` (`workflow.rules`) | Параметры пайплайна `generic/common-ci` (деплой по окружениям stage/preprod/production) |
|
||||||
|
|
||||||
|
> Обратите внимание: `SERVICE_NAME` встречается в двух ролях — как переменная приложения (имя сервиса в OTEL-трейсах) и как переменная CI (имя сервиса для сборки/чарта). Значения задаются в разных местах и не связаны между собой.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Деплой из этого репозитория (`iac/apps/subscriptions`)
|
||||||
|
|
||||||
|
Здесь используется **kustomize** (не собственный Helm-чарт сервиса). Секреты БД и S3 инъектируются агентом **Vault** и подгружаются в окружение процесса до старта:
|
||||||
|
|
||||||
|
```
|
||||||
|
set -a
|
||||||
|
[ -f /vault/secrets/subscriptions-postgresql ] && . /vault/secrets/subscriptions-postgresql
|
||||||
|
[ -f /vault/secrets/subscriptions-minio ] && . /vault/secrets/subscriptions-minio
|
||||||
|
set +a
|
||||||
|
exec /server/entrypoint.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### `base/`
|
||||||
|
|
||||||
|
- `backend-deployment.yaml` — обычные переменные инлайн в `env:` (`API_ADDRESS`, `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST`, `IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`, `SMTP_EMAIL_FROM/HOST/PORT`). Vault-шаблоны формируют `DATABASE_HOST/PORT/NAME/USER/PASSWORD` (из `secrets/data/postgresql/apps/subscriptions`, БД `subscriptions_db`) и `YC_S3_ACCESS_KEY_ID/SECRET_ACCESS_KEY/BUCKET_NAME/ENDPOINT_URL` (из `secrets/data/minio/apps/subscriptions`).
|
||||||
|
- `backend-service.yaml`, `namespace.yaml` (`istio-injection: enabled`), `serviceaccount.yaml` (`subscriptions-vault`).
|
||||||
|
- `kustomization.yaml` собирает namespace, serviceaccount, deployment и service.
|
||||||
|
|
||||||
|
### Оверлеи
|
||||||
|
|
||||||
|
- **`yc-k8s-test`** — `../base` + `postgresql.yaml` (HelmRelease `postgresql-contour`: создаёт БД `subscriptions_db`, пользователя `subscriptions`, расширения `ltree`/`pg_stat_statements`/`postgis`/`timescaledb`, восстановление из дампа).
|
||||||
|
- **`brusnika-stage`** / **`brusnika-prod`** — Flux `HelmRelease` на `universal-chart` (v0.1.7). Переменные — в `envs` (`DATABASE_HOST/PORT/NAME`, `API_ADDRESS`, `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST`, `IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`, `SMTP_*`), секреты — в `secretEnvs` (`postgres-secret`: `username`/`password`; `yc-s3-secret`: `key_id`/`access_key`/`storage_bucket_name`/`endpoint_url`). Отличаются только `DATABASE_HOST` (`postgres-service` в prod vs `192.168.2.45` в stage).
|
||||||
|
|
||||||
|
> В этих kustomize/brusnika-манифестах **не задаются** `USE_OTEL`, `TELEGRAM_BOT_TOKEN`, `MAILGUN_API_KEY`, `USER_SERVICE_LOGIN/PASSWORD`, `ALLOWED_HOST_EMAIL` — используются дефолты из кода. Telegram и Mailgun выключены (`IS_USE_TELEGRAM=false`, `IS_MAILGUN_USE=0`), рассылка идёт по SMTP.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Отличия от Helm-чарта репозитория (`.helm/values.yaml`)
|
||||||
|
|
||||||
|
Собственный чарт сервиса (`.helm/`) — это **другой** путь деплоя, с более широким набором переменных, чем kustomize/brusnika здесь:
|
||||||
|
|
||||||
|
- дополнительно задаёт `ALLOWED_HOST_EMAIL`, `USE_OTEL=True`, `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE=True` (per-env), а также секреты `MAILGUN_API_KEY` (`mailgun-cred`) и `TELEGRAM_BOT_TOKEN` (`telegram-bot-secret`);
|
||||||
|
- `IS_USE_TELEGRAM=true`, монтирует `uwsgi.ini` через ConfigMap;
|
||||||
|
- определяет `CronJob` `subscription-periodic` (`0,30 * * * *`, `run_notifications`) и `subscription-immediately` (`* * * * *`, `run_immediately_notifications`), наследующие `envs`/`secretEnvs` api.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
|
- **`manage.py` указывает на несуществующий модуль настроек.** По умолчанию `manage.py` задаёт `DJANGO_SETTINGS_MODULE=config.settings.local`, но модуля `local.py` в репозитории нет (есть только `base.py` и `production.py`). Поэтому management-команды нужно запускать с явным `--settings=config.settings.production` (как это и делают entrypoint и cron-задачи). Локальный сервис `migrations` в `docker-compose.yml` вызывает `makemigrations` **без** `--settings` — команда упадёт из-за отсутствия `local`.
|
||||||
|
- **`USE_OTEL`/`USE_INSECURE` работают по истинности строки.** `os.getenv('USE_OTEL', False)` возвращает строку; любое непустое значение (включая `"False"`, `"0"`) включает трейсинг. Чтобы выключить — переменную нужно **не задавать вовсе**, а не ставить `False`.
|
||||||
|
- **Каналы рассылки `IS_MAILGUN_USE` / `IS_USE_TELEGRAM` — тоже строки.** Значения по умолчанию — `True` (Python-объект), но из окружения приходит строка; поведение зависит от того, как значение проверяется в коде команды. При настройке важно учитывать это (в манифестах используют `"false"`/`"0"`).
|
||||||
|
- **S3 обязателен для статики/медиа.** Хранилища заданы как S3 без файлового фолбэка; при отсутствии `YC_S3_*` operations со статикой/медиа будут падать, хотя сам процесс поднимется.
|
||||||
|
- **`SECRET_KEY` захардкожен** в `base.py` и не выносится в окружение — для продакшена это стоит вынести в секрет.
|
||||||
|
- **Расхождения имён БД между окружениями:** infra `base` и postgresql-чарт используют `subscriptions_db`, brusnika-оверлеи — `subscriptions`. При подключении важно использовать значение конкретного окружения.
|
||||||
|
- **Два деплой-пути расходятся по набору переменных** (kustomize/brusnika vs `.helm`): OTEL, Telegram-токен, Mailgun-ключ и `ALLOWED_HOST_EMAIL` присутствуют только в `.helm`. Это стоит учитывать при переносе окружения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Минимальный набор для запуска
|
||||||
|
|
||||||
|
**api** (uwsgi, миграции при старте):
|
||||||
|
|
||||||
|
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
|
||||||
|
- `YC_S3_ACCESS_KEY_ID`, `YC_S3_SECRET_ACCESS_KEY`, `YC_S3_BUCKET_NAME`, `YC_S3_ENDPOINT_URL`
|
||||||
|
- при необходимости — `USE_OTEL` (+ `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE`)
|
||||||
|
|
||||||
|
**cron `run_notifications` / `run_immediately_notifications`**:
|
||||||
|
|
||||||
|
- блок БД (как у api)
|
||||||
|
- `SYSTEM_LOG_HOST`, `USER_SERVICE_HOST` (+ `USER_SERVICE_LOGIN`, `USER_SERVICE_PASSWORD` при авторизации)
|
||||||
|
- канал рассылки: `IS_MAILGUN_USE` + `MAILGUN_API_KEY` **или** `SMTP_EMAIL_HOST` + `SMTP_EMAIL_PORT`; при Telegram — `IS_USE_TELEGRAM` + `TELEGRAM_BOT_TOKEN`
|
||||||
|
- при необходимости — `ALLOWED_HOST_EMAIL` (ссылки в письмах)
|
||||||
|
|
||||||
|
См. пример значений в `.env.example` рядом с этим файлом.
|
||||||
719
apps/subscriptions/openapi.yaml
Normal file
719
apps/subscriptions/openapi.yaml
Normal file
@ -0,0 +1,719 @@
|
|||||||
|
openapi: 3.0.3
|
||||||
|
|
||||||
|
info:
|
||||||
|
title: sarex-subscriptions API
|
||||||
|
version: "1.0.0"
|
||||||
|
description: |
|
||||||
|
REST API сервиса **sarex-subscriptions** — управление подписками на уведомления,
|
||||||
|
получателями, шаблонами и просмотр истории рассылок (события, транзакции, сообщения).
|
||||||
|
|
||||||
|
Сервис на Django REST Framework. Роутинг — `rest_framework.routers.DefaultRouter`,
|
||||||
|
все ресурсы смонтированы под префиксом `/api/v1/` (`config/urls.py`).
|
||||||
|
|
||||||
|
### Аутентификация
|
||||||
|
Настроены `BasicAuthentication` и `SessionAuthentication`
|
||||||
|
(`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`). `DEFAULT_PERMISSION_CLASSES`
|
||||||
|
не заданы, поэтому по умолчанию действует `AllowAny` — эндпоинты доступны без
|
||||||
|
авторизации, а Basic-креды используются опционально.
|
||||||
|
|
||||||
|
### Пагинация
|
||||||
|
`LimitOffsetPagination`, `PAGE_SIZE = 1000`. Списки возвращают объект
|
||||||
|
`{ count, next, previous, results }`. Управление — query-параметрами `limit` и `offset`.
|
||||||
|
|
||||||
|
### Фильтрация
|
||||||
|
Бэкенд фильтров — `DjangoFilterBackend`. Ряд полей принимают список значений
|
||||||
|
через запятую (кастомный `CustomFilterList`, lookup `in`).
|
||||||
|
|
||||||
|
### Замечания (расхождения кода)
|
||||||
|
- `GET /transaction/{id}/` (retrieve) не имеет сериализатора в
|
||||||
|
`serializer_class_by_action` (есть ключи `list` и `get`, но не `retrieve`) —
|
||||||
|
поведение отдельного объекта может отличаться от списка.
|
||||||
|
- `MessageRetrieveSerializer` объявляет поле `created_at`, которого нет в модели
|
||||||
|
`Message` — поле показано в схеме как в коде сериализатора, но фактически может
|
||||||
|
приводить к ошибке.
|
||||||
|
- `RecipientWriteSerializer.extra_kwargs` ссылается на `telegram_user_name`, тогда как
|
||||||
|
в модели поле называется `telegram_username`.
|
||||||
|
|
||||||
|
servers:
|
||||||
|
- url: https://api.sarex.io/api/v1
|
||||||
|
description: Production (через ЛК)
|
||||||
|
- url: https://stage-api.sarex.io/api/v1
|
||||||
|
description: Stage
|
||||||
|
- url: http://sarex-subscriptions-service.subscriptions-prod/api/v1
|
||||||
|
description: Внутрикластерный адрес (ClusterIP)
|
||||||
|
|
||||||
|
tags:
|
||||||
|
- name: subscription
|
||||||
|
description: Подписки на уведомления
|
||||||
|
- name: recipient
|
||||||
|
description: Получатели уведомлений
|
||||||
|
- name: event
|
||||||
|
description: События уведомлений
|
||||||
|
- name: template
|
||||||
|
description: Шаблоны уведомлений
|
||||||
|
- name: transaction
|
||||||
|
description: Транзакции рассылки
|
||||||
|
- name: message
|
||||||
|
description: Отправленные сообщения
|
||||||
|
|
||||||
|
security:
|
||||||
|
- basicAuth: []
|
||||||
|
- {}
|
||||||
|
|
||||||
|
paths:
|
||||||
|
# ==========================================================================
|
||||||
|
# Subscription
|
||||||
|
# ==========================================================================
|
||||||
|
/subscription/:
|
||||||
|
get:
|
||||||
|
tags: [subscription]
|
||||||
|
summary: Список подписок
|
||||||
|
operationId: listSubscriptions
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
- name: user_id
|
||||||
|
in: query
|
||||||
|
description: ID пользователя получателя (фильтр по recipient.user_id)
|
||||||
|
schema: { type: integer }
|
||||||
|
- name: company_id
|
||||||
|
in: query
|
||||||
|
description: ID компании; список значений через запятую
|
||||||
|
schema: { type: string }
|
||||||
|
- name: service_name
|
||||||
|
in: query
|
||||||
|
description: Название сервиса; список значений через запятую
|
||||||
|
schema: { type: string }
|
||||||
|
- name: instance_id
|
||||||
|
in: query
|
||||||
|
description: ID сущности; список значений через запятую
|
||||||
|
schema: { type: string }
|
||||||
|
- name: instance_uid
|
||||||
|
in: query
|
||||||
|
description: UUID сущности; список значений через запятую
|
||||||
|
schema: { type: string }
|
||||||
|
- name: model_name
|
||||||
|
in: query
|
||||||
|
description: Название модели (точное совпадение)
|
||||||
|
schema: { type: string }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/Subscription' }
|
||||||
|
post:
|
||||||
|
tags: [subscription]
|
||||||
|
summary: Создать подписку
|
||||||
|
description: |
|
||||||
|
Требуется хотя бы одно из полей `instance_id` / `instance_uid` /
|
||||||
|
`public_instance_id`. Если подписка с таким `instance_uid`/`instance_id`
|
||||||
|
(+ `model_name`, `recipient`) уже существует — возвращается существующая.
|
||||||
|
Если `period` не передан — устанавливается `TwoTimesDay` c `first_time=9:00`,
|
||||||
|
`second_time=17:00`.
|
||||||
|
operationId: createSubscription
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/SubscriptionWrite' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Subscription' }
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/ValidationError'
|
||||||
|
|
||||||
|
/subscription/{id}/:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PathId'
|
||||||
|
get:
|
||||||
|
tags: [subscription]
|
||||||
|
summary: Получить подписку
|
||||||
|
operationId: retrieveSubscription
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Subscription' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
delete:
|
||||||
|
tags: [subscription]
|
||||||
|
summary: Удалить подписку
|
||||||
|
operationId: destroySubscription
|
||||||
|
responses:
|
||||||
|
'204': { description: No Content }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# Recipient
|
||||||
|
# ==========================================================================
|
||||||
|
/recipient/:
|
||||||
|
get:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Список получателей
|
||||||
|
operationId: listRecipients
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
- name: user_id
|
||||||
|
in: query
|
||||||
|
description: ID пользователя; список значений через запятую
|
||||||
|
schema: { type: string }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/Recipient' }
|
||||||
|
post:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Создать получателя
|
||||||
|
operationId: createRecipient
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/RecipientWrite' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Recipient' }
|
||||||
|
'400':
|
||||||
|
$ref: '#/components/responses/ValidationError'
|
||||||
|
|
||||||
|
/recipient/{user_id}/:
|
||||||
|
description: |
|
||||||
|
Поиск объекта выполняется по полю `user_id` (`lookup_field = "user_id"`),
|
||||||
|
а не по первичному ключу `id`.
|
||||||
|
parameters:
|
||||||
|
- name: user_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: ID пользователя (lookup_field)
|
||||||
|
schema: { type: integer }
|
||||||
|
get:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Получить получателя
|
||||||
|
operationId: retrieveRecipient
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Recipient' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
put:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Обновить получателя
|
||||||
|
description: Поля `email`, `user_id`, `id` доступны только на чтение и не изменяются.
|
||||||
|
operationId: updateRecipient
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/RecipientUpdate' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Recipient' }
|
||||||
|
'400': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
patch:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Частично обновить получателя
|
||||||
|
operationId: partialUpdateRecipient
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/RecipientUpdate' }
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Recipient' }
|
||||||
|
'400': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
delete:
|
||||||
|
tags: [recipient]
|
||||||
|
summary: Удалить получателя
|
||||||
|
operationId: destroyRecipient
|
||||||
|
responses:
|
||||||
|
'204': { description: No Content }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# NotificationEvent
|
||||||
|
# ==========================================================================
|
||||||
|
/event/:
|
||||||
|
get:
|
||||||
|
tags: [event]
|
||||||
|
summary: Список событий
|
||||||
|
operationId: listEvents
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/NotificationEvent' }
|
||||||
|
post:
|
||||||
|
tags: [event]
|
||||||
|
summary: Зарегистрировать событие
|
||||||
|
operationId: createEvent
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationEventWrite' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationEvent' }
|
||||||
|
'400': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/event/{id}/:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PathId'
|
||||||
|
get:
|
||||||
|
tags: [event]
|
||||||
|
summary: Получить событие
|
||||||
|
operationId: retrieveEvent
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationEvent' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# NotificationTemplate
|
||||||
|
# ==========================================================================
|
||||||
|
/template/:
|
||||||
|
get:
|
||||||
|
tags: [template]
|
||||||
|
summary: Список шаблонов
|
||||||
|
operationId: listTemplates
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/NotificationTemplate' }
|
||||||
|
post:
|
||||||
|
tags: [template]
|
||||||
|
summary: Создать шаблон
|
||||||
|
operationId: createTemplate
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationTemplateWrite' }
|
||||||
|
responses:
|
||||||
|
'201':
|
||||||
|
description: Created
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationTemplate' }
|
||||||
|
'400': { $ref: '#/components/responses/ValidationError' }
|
||||||
|
|
||||||
|
/template/{id}/:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PathId'
|
||||||
|
get:
|
||||||
|
tags: [template]
|
||||||
|
summary: Получить шаблон
|
||||||
|
operationId: retrieveTemplate
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationTemplate' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# NotificationTransaction (read-only)
|
||||||
|
# ==========================================================================
|
||||||
|
/transaction/:
|
||||||
|
get:
|
||||||
|
tags: [transaction]
|
||||||
|
summary: Список транзакций рассылки
|
||||||
|
operationId: listTransactions
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/NotificationTransaction' }
|
||||||
|
|
||||||
|
/transaction/{id}/:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PathId'
|
||||||
|
get:
|
||||||
|
tags: [transaction]
|
||||||
|
summary: Получить транзакцию
|
||||||
|
description: >
|
||||||
|
Внимание: для действия retrieve не задан сериализатор
|
||||||
|
(`serializer_class_by_action` содержит только `list`/`get`), поведение
|
||||||
|
одиночного объекта может отличаться.
|
||||||
|
operationId: retrieveTransaction
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/NotificationTransaction' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ==========================================================================
|
||||||
|
# Message (read-only)
|
||||||
|
# ==========================================================================
|
||||||
|
/message/:
|
||||||
|
get:
|
||||||
|
tags: [message]
|
||||||
|
summary: Список сообщений
|
||||||
|
operationId: listMessages
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Limit'
|
||||||
|
- $ref: '#/components/parameters/Offset'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
allOf:
|
||||||
|
- $ref: '#/components/schemas/PaginatedList'
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: { $ref: '#/components/schemas/Message' }
|
||||||
|
|
||||||
|
/message/{id}/:
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/PathId'
|
||||||
|
get:
|
||||||
|
tags: [message]
|
||||||
|
summary: Получить сообщение
|
||||||
|
operationId: retrieveMessage
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Message' }
|
||||||
|
'404': { $ref: '#/components/responses/NotFound' }
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
components:
|
||||||
|
securitySchemes:
|
||||||
|
basicAuth:
|
||||||
|
type: http
|
||||||
|
scheme: basic
|
||||||
|
|
||||||
|
parameters:
|
||||||
|
Limit:
|
||||||
|
name: limit
|
||||||
|
in: query
|
||||||
|
description: Кол-во объектов на странице (LimitOffsetPagination)
|
||||||
|
schema: { type: integer, default: 1000 }
|
||||||
|
Offset:
|
||||||
|
name: offset
|
||||||
|
in: query
|
||||||
|
description: Смещение от начала выборки
|
||||||
|
schema: { type: integer, minimum: 0 }
|
||||||
|
PathId:
|
||||||
|
name: id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
description: Первичный ключ объекта
|
||||||
|
schema: { type: integer }
|
||||||
|
|
||||||
|
responses:
|
||||||
|
NotFound:
|
||||||
|
description: Объект не найден
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: { $ref: '#/components/schemas/Detail' }
|
||||||
|
ValidationError:
|
||||||
|
description: Ошибка валидации
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
example:
|
||||||
|
field_name: ["Обязательное поле."]
|
||||||
|
|
||||||
|
schemas:
|
||||||
|
# ---- Общие ----
|
||||||
|
PaginatedList:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
count: { type: integer, example: 42 }
|
||||||
|
next:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
nullable: true
|
||||||
|
example: "http://host/api/v1/subscription/?limit=1000&offset=1000"
|
||||||
|
previous:
|
||||||
|
type: string
|
||||||
|
format: uri
|
||||||
|
nullable: true
|
||||||
|
results:
|
||||||
|
type: array
|
||||||
|
items: {}
|
||||||
|
required: [count, results]
|
||||||
|
|
||||||
|
Detail:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
detail: { type: string, example: "Не найдено." }
|
||||||
|
|
||||||
|
# ---- Перечисления ----
|
||||||
|
PeriodType:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Периодичность рассылки:
|
||||||
|
* 0 — Два раза в день
|
||||||
|
* 1 — Каждый день (default)
|
||||||
|
* 2 — Один раз в неделю
|
||||||
|
* 3 — Сразу при изменении
|
||||||
|
enum: [0, 1, 2, 3]
|
||||||
|
default: 1
|
||||||
|
|
||||||
|
WeekdayType:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
День недели: 1 — Пн, 2 — Вт, 3 — Ср, 4 — Чт, 5 — Пт, 6 — Сб, 7 — Вс
|
||||||
|
enum: [1, 2, 3, 4, 5, 6, 7]
|
||||||
|
nullable: true
|
||||||
|
|
||||||
|
EventType:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Тип события: 0 — Edit (default), 1 — Create, 2 — Delete, 3 — Copy, 4 — Read
|
||||||
|
enum: [0, 1, 2, 3, 4]
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
MessageServiceType:
|
||||||
|
type: integer
|
||||||
|
description: "Способ отправки: 0 — Email (default), 1 — Telegram"
|
||||||
|
enum: [0, 1]
|
||||||
|
default: 0
|
||||||
|
|
||||||
|
TransactionStatus:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
Статус транзакции: 0 — Новая, 1 — В обработке, 2 — Успешно, 3 — Ошибка
|
||||||
|
enum: [0, 1, 2, 3]
|
||||||
|
|
||||||
|
# ---- Subscription ----
|
||||||
|
Subscription:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
model_name: { type: string, maxLength: 255 }
|
||||||
|
instance_id: { type: integer, nullable: true }
|
||||||
|
instance_uid: { type: string, format: uuid, nullable: true }
|
||||||
|
public_instance_id: { type: string, format: uuid, nullable: true }
|
||||||
|
company_id: { type: integer }
|
||||||
|
service_name: { type: string, maxLength: 255, nullable: true }
|
||||||
|
recipient:
|
||||||
|
type: integer
|
||||||
|
description: ID получателя (FK recipient.Recipient)
|
||||||
|
period: { $ref: '#/components/schemas/PeriodType' }
|
||||||
|
weekday: { $ref: '#/components/schemas/WeekdayType' }
|
||||||
|
first_time: { type: string, maxLength: 6, nullable: true, example: "9:00" }
|
||||||
|
second_time: { type: string, maxLength: 6, nullable: true, example: "17:00" }
|
||||||
|
created_at: { type: string, format: date-time, readOnly: true }
|
||||||
|
updated_at: { type: string, format: date-time, readOnly: true }
|
||||||
|
required: [id, model_name, company_id, recipient]
|
||||||
|
|
||||||
|
SubscriptionWrite:
|
||||||
|
type: object
|
||||||
|
description: >
|
||||||
|
Одно из instance_id / instance_uid / public_instance_id обязательно.
|
||||||
|
properties:
|
||||||
|
model_name: { type: string, maxLength: 255 }
|
||||||
|
instance_id: { type: integer, nullable: true }
|
||||||
|
instance_uid: { type: string, format: uuid, nullable: true }
|
||||||
|
public_instance_id: { type: string, format: uuid, nullable: true }
|
||||||
|
company_id: { type: integer }
|
||||||
|
service_name: { type: string, maxLength: 255, nullable: true }
|
||||||
|
recipient: { type: integer, description: ID получателя }
|
||||||
|
period:
|
||||||
|
allOf: [{ $ref: '#/components/schemas/PeriodType' }]
|
||||||
|
nullable: true
|
||||||
|
weekday: { $ref: '#/components/schemas/WeekdayType' }
|
||||||
|
first_time: { type: string, maxLength: 6, nullable: true }
|
||||||
|
second_time: { type: string, maxLength: 6, nullable: true }
|
||||||
|
required: [model_name, company_id, recipient]
|
||||||
|
|
||||||
|
# ---- Recipient ----
|
||||||
|
Recipient:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
first_name: { type: string, maxLength: 1024 }
|
||||||
|
last_name: { type: string, maxLength: 1024 }
|
||||||
|
phone: { type: string, maxLength: 128, default: "" }
|
||||||
|
telegram_chat_id: { type: string, maxLength: 128, nullable: true }
|
||||||
|
telegram_username: { type: string, maxLength: 256, nullable: true }
|
||||||
|
email: { type: string, format: email }
|
||||||
|
user_id: { type: integer, nullable: true }
|
||||||
|
required: [id]
|
||||||
|
|
||||||
|
RecipientWrite:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
first_name: { type: string, maxLength: 1024 }
|
||||||
|
last_name: { type: string, maxLength: 1024 }
|
||||||
|
phone: { type: string, maxLength: 128 }
|
||||||
|
telegram_chat_id: { type: string, maxLength: 128, nullable: true }
|
||||||
|
telegram_username: { type: string, maxLength: 256, nullable: true }
|
||||||
|
email: { type: string, format: email }
|
||||||
|
user_id: { type: integer, nullable: true }
|
||||||
|
|
||||||
|
RecipientUpdate:
|
||||||
|
type: object
|
||||||
|
description: "email, user_id, id — только для чтения."
|
||||||
|
properties:
|
||||||
|
first_name: { type: string, maxLength: 1024 }
|
||||||
|
last_name: { type: string, maxLength: 1024 }
|
||||||
|
phone: { type: string, maxLength: 128 }
|
||||||
|
telegram_chat_id: { type: string, maxLength: 128, nullable: true }
|
||||||
|
telegram_username: { type: string, maxLength: 256, nullable: true }
|
||||||
|
|
||||||
|
# ---- NotificationEvent ----
|
||||||
|
NotificationEvent:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
registered_at: { type: string, format: date-time }
|
||||||
|
routing_key: { type: string, maxLength: 256, description: "Slug-тег" }
|
||||||
|
attributes: { type: object, additionalProperties: true, default: {} }
|
||||||
|
short_description: { type: string, maxLength: 512, nullable: true }
|
||||||
|
required: [id, registered_at, routing_key]
|
||||||
|
|
||||||
|
NotificationEventWrite:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
registered_at: { type: string, format: date-time }
|
||||||
|
routing_key: { type: string, maxLength: 256 }
|
||||||
|
attributes: { type: object, additionalProperties: true }
|
||||||
|
short_description: { type: string, maxLength: 512, nullable: true }
|
||||||
|
required: [registered_at, routing_key]
|
||||||
|
|
||||||
|
# ---- NotificationTemplate ----
|
||||||
|
NotificationTemplate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
name: { type: string, maxLength: 1024 }
|
||||||
|
title: { type: string, maxLength: 1024 }
|
||||||
|
template_text: { type: string }
|
||||||
|
attributes: { type: object, additionalProperties: true, default: {} }
|
||||||
|
event_type: { $ref: '#/components/schemas/EventType' }
|
||||||
|
model_name: { type: string, maxLength: 255, nullable: true }
|
||||||
|
required: [id, name, title, template_text]
|
||||||
|
|
||||||
|
NotificationTemplateWrite:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name: { type: string, maxLength: 1024 }
|
||||||
|
title: { type: string, maxLength: 1024 }
|
||||||
|
template_text: { type: string }
|
||||||
|
attributes: { type: object, additionalProperties: true }
|
||||||
|
event_type: { $ref: '#/components/schemas/EventType' }
|
||||||
|
model_name: { type: string, maxLength: 255, nullable: true }
|
||||||
|
required: [name, title, template_text]
|
||||||
|
|
||||||
|
# ---- NotificationTransaction ----
|
||||||
|
NotificationTransaction:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
status: { $ref: '#/components/schemas/TransactionStatus' }
|
||||||
|
started_at: { type: string, format: date-time }
|
||||||
|
finished_at: { type: string, format: date-time, nullable: true }
|
||||||
|
error_message: { type: string, nullable: true }
|
||||||
|
event: { type: integer, nullable: true, description: "ID связанного события" }
|
||||||
|
required: [id, status, started_at]
|
||||||
|
|
||||||
|
# ---- Message ----
|
||||||
|
Message:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id: { type: integer, readOnly: true }
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: >
|
||||||
|
Объявлено в сериализаторе; в модели Message поле отсутствует (см. замечания).
|
||||||
|
service_type: { $ref: '#/components/schemas/MessageServiceType' }
|
||||||
|
transaction: { type: integer, description: "ID транзакции (FK)" }
|
||||||
|
subject: { type: string, default: "" }
|
||||||
|
text: { type: string }
|
||||||
|
recipients:
|
||||||
|
type: array
|
||||||
|
items: { type: integer }
|
||||||
|
description: "ID получателей (M2M recipient.Recipient)"
|
||||||
|
required: [id, transaction, text]
|
||||||
@ -1,21 +1,55 @@
|
|||||||
API_PORT=6666
|
# ============================================================================
|
||||||
|
# workspaces-api — пример переменных окружения
|
||||||
|
#
|
||||||
|
# Скопируйте нужные строки в .env в корне репозитория workspaces-api.
|
||||||
|
# Разбор выполняется библиотекой envconfig (config/config.go, config.FromEnv).
|
||||||
|
# envconfig НЕ помечает переменные как required — процесс стартует даже без них,
|
||||||
|
# но без корректных значений БД/documentation сервис работать не будет.
|
||||||
|
# Пометка (нужна) ниже означает практическую обязательность, а не env-required.
|
||||||
|
# bool принимает 1/0, true/false, t/f.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
API_ADDRESS=0.0.0.0:6666
|
# --- HTTP-сервер ------------------------------------------------------------
|
||||||
|
API_ADDRESS=0.0.0.0:6666 # (нужна) адрес прослушивания HTTP-сервера host:port (эндпоинт /ping)
|
||||||
|
|
||||||
POSTGRES_ADDRESS=127.0.0.1
|
# --- PostgreSQL -------------------------------------------------------------
|
||||||
POSTGRES_USER=user
|
POSTGRES_ADDRESS=127.0.0.1 # (нужна) хост PostgreSQL
|
||||||
POSTGRES_PASSWORD=password
|
POSTGRES_PORT=5432 # (нужна) порт PostgreSQL
|
||||||
POSTGRES_DB=workspaces
|
POSTGRES_DB=workspaces # (нужна) имя базы данных
|
||||||
POSTGRES_PORT=5432
|
POSTGRES_USER=user # (нужна) пользователь БД
|
||||||
POSTGRES_EXTERNAL_PORT=5432
|
POSTGRES_PASSWORD=password # (нужна) пароль пользователя БД
|
||||||
POSTGRES_POLL_SIZE=10
|
POSTGRES_POOL_SIZE=10 # размер пула соединений (по умолчанию 0)
|
||||||
|
ENABLE_SQL_QUERY=1 # логировать SQL-запросы (по умолчанию 0)
|
||||||
|
ENABLE_SSL=0 # TLS к PostgreSQL с проверкой по YC-PG-CERTIFICATE (по умолчанию 0)
|
||||||
|
# YC-PG-CERTIFICATE= # содержимое (PEM) CA-сертификата PostgreSQL; нужно при ENABLE_SSL=1
|
||||||
|
|
||||||
ENABLE_SQL_QUERY=1
|
# --- Сервис documentation ---------------------------------------------------
|
||||||
|
DOCUMENTATION_HOST=https://stage-api.sarex.io/documentation # (нужна) базовый URL сервиса documentation
|
||||||
|
DOCUMENTATION_ORIGINATOR=local_ws # идентификатор источника, передаваемый в documentation
|
||||||
|
DOCUMENTATION_LOGGER_FEATURE=1 # фича логирования обращений к documentation (по умолчанию 0)
|
||||||
|
|
||||||
DOCUMENTATION_HOST=https://stage-api.sarex.io/documentation
|
# --- Bundles ----------------------------------------------------------------
|
||||||
DOCUMENTATION_LOGGER_FEATURE=1
|
# BUNDLES_RETRY_COUNT=5 # число ретраев клиента bundles (по умолчанию/при <=0 берётся 3)
|
||||||
DOCUMENTATION_ORIGINATOR=local_ws
|
# BUNDLES_NJOBS=5 # число параллельных задач при работе с bundles (по умолчанию/при <=0 берётся 3)
|
||||||
|
|
||||||
SENTRY_DSN=https://62dbe0a9ee5745eead25f2a705cb35ce@o279218.ingest.sentry.io/6229949
|
# --- Sentry -----------------------------------------------------------------
|
||||||
SENTRY_DEBUG=0
|
SENTRY_DSN=https://62dbe0a9ee5745eead25f2a705cb35ce@o279218.ingest.sentry.io/6229949 # DSN Sentry
|
||||||
ENVIRONMENT=local
|
SENTRY_DEBUG=0 # debug-режим Sentry (по умолчанию 0)
|
||||||
|
ENVIRONMENT=local # имя окружения (передаётся в Sentry как environment)
|
||||||
|
|
||||||
|
# --- Трейсинг OpenTelemetry (необязательно) ---------------------------------
|
||||||
|
# TRACER_USE=false # включить трейсинг и otel-логгер (по умолчанию false)
|
||||||
|
# TRACER_HOST=localhost:4317 # адрес OTLP-коллектора
|
||||||
|
# TRACER_USE_INSECURE=true # подключение без TLS (по умолчанию true)
|
||||||
|
# SERVICE_NAME=workspaces # имя сервиса в трейсах (по умолчанию workspaces)
|
||||||
|
# TRACER_LOGGER_NAME=tracer_logger # имя otel-логгера (по умолчанию tracer_logger)
|
||||||
|
|
||||||
|
# --- Вспомогательные (не читаются config.Config) ----------------------------
|
||||||
|
POSTGRES_EXTERNAL_PORT=5432 # внешний порт проброса контейнера Postgres в docker-compose
|
||||||
|
API_PORT=6666 # порт API в docker-compose (сервис api там закомментирован)
|
||||||
|
# FAKE_API_ADDRESS=0.0.0.0:7777 # адрес фейкового bundle-API (fake_bundle_api/main.go; локальная разработка/тесты)
|
||||||
|
# NAMESPACE=workspaces # задаётся в манифестах/чарте, кодом приложения не читается
|
||||||
|
# INTERNAL_PATH=/internal/ # задаётся в чарте, кодом приложения не читается
|
||||||
|
# DJANGO_HOST=... # задаётся в манифестах iac, но config.Config его НЕ читает
|
||||||
|
# DJANGO_ORIGINATOR=... # задаётся в манифестах iac, но config.Config его НЕ читает
|
||||||
|
# DJANGO_BASIC_AUTH=... # инъектируется из секрета в манифестах iac, но config.Config его НЕ читает
|
||||||
|
|||||||
@ -1,122 +1,152 @@
|
|||||||
# Конфигурация проекта workspaces-api
|
# Конфигурация workspaces-api
|
||||||
|
|
||||||
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
Документ описывает все переменные окружения и способы конфигурирования сервиса `workspaces-api` (репозиторий `pdm/workspaces-api`) и его развёртывания из этого infra-репозитория (`iac/apps/workspaces`).
|
||||||
|
|
||||||
|
`workspaces-api` — HTTP-сервис (`cmd/api`), хранит рабочие пространства (workspaces) и приложения (apps) в PostgreSQL, обращается к сервису documentation и к bundle-сервису. Вместе с ним из одного образа собираются утилиты миграций (`cmd/migrations`) и CLI (`cmd/workspaces-cli`).
|
||||||
|
|
||||||
## Способы конфигурирования
|
## Способы конфигурирования
|
||||||
|
|
||||||
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`envconfig`](https://github.com/kelseyhightower/envconfig) (функция `config.FromEnv()`). Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`envconfig`](https://github.com/kelseyhightower/envconfig) (функция `config.FromEnv()` → `envconfig.Process`). Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||||
|
|
||||||
|
В отличие от `env-required`-подхода, **envconfig здесь не помечает переменные обязательными** — при отсутствии значения `FromEnv()` не завершает процесс, поле остаётся нулевым. Поэтому «обязательность» переменных БД/documentation фактическая, а не форсированная кодом: без них сервис стартует, но работать не будет.
|
||||||
|
|
||||||
Источники переменных по способам запуска:
|
Источники переменных по способам запуска:
|
||||||
|
|
||||||
| Способ запуска | Откуда берутся переменные |
|
| Способ запуска | Откуда берутся переменные |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Локально (docker-compose) | Файл `.env` в корне (`env_file: .env`); поднимает только контейнер Postgres, сам API-сервис в compose закомментирован |
|
| Локально (docker-compose + бинарник) | Файл `.env` в корне репозитория. `docker-compose.yml` (`env_file: .env`) поднимает только контейнер Postgres (`postgres:13`); сам API-сервис в compose закомментирован и запускается бинарником. `Makefile` (`make docker`) прокидывает `.env` в docker-compose |
|
||||||
| Локально (бинарник) | Переменные окружения процесса; пример значений — в `.env`, приватные — в `.private.env` (см. `.private.env.example`) |
|
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml` (`universal-chart`): блоки `envs` (обычные значения по окружениям `_default`/`stage`/`preprod`/`production`) и `secretEnvs` (значения из k8s-секретов). Деплой запускается пайплайнами `generic/common-ci` |
|
||||||
| Kubernetes (Helm) | `.helm/values.yaml`: блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) |
|
| Kubernetes — этот infra-репозиторий (`iac/apps/workspaces`) | `base/` — kustomize-манифесты с инъекцией секретов через **HashiCorp Vault** (annotations `vault.hashicorp.com/*`), обычные переменные заданы инлайн в `env:`. Оверлеи: `yc-k8s-test` (base + postgresql через Flux), `brusnika-stage` и `brusnika-prod` (Flux `HelmRelease` на `universal-chart`, блоки `envs`/`secretEnvs`) |
|
||||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна и build-args для сборки образа |
|
| CI/CD (GitLab) | `.gitlab-ci.yml` подключает шаблоны `generic/common-ci` (`universal-pipeline.yaml`); в `workflow.rules` задаются переменные пайплайна (`STAND`, `NAMESPACE`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `HELM_SET_ARGS` и т.п.), а также build-args образа |
|
||||||
|
|
||||||
Порядок запуска в контейнере (`entrypoint.sh`): сначала выполняются миграции (`/migrations migrate`), затем стартует API (`/api`).
|
**Миграции БД.** Отдельного env-флага для миграций нет: порядок запуска задаёт `entrypoint.sh` — сначала выполняется `/migrations migrate`, затем стартует `/api`. Утилита миграций читает тот же конфиг (`config/config.go`) и использует `ENABLE_SSL`/`YC-PG-CERTIFICATE` для TLS-подключения к БД (`cmd/migrations/main.go`). В kustomize-/Helm-манифестах миграции запускаются той же командой в `args`/`command` контейнера (`set -e; /migrations migrate; exec /api`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Переменные приложения (`config.Config`)
|
## Переменные приложения (`config.Config`)
|
||||||
|
|
||||||
Читаются напрямую по имени. Пустые обязательные значения не приводят к ошибке старта (envconfig не помечает их как required) — но без корректных значений БД/documentation сервис работать не будет.
|
Читаются структурой `config.Config` (`config/config.go`). Тип `bool` в envconfig принимает `1`/`0`, `true`/`false`, `t`/`f`.
|
||||||
|
|
||||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
### HTTP-сервер
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `API_ADDRESS` | string | — | Адрес прослушивания HTTP-сервера (`host:port`), напр. `0.0.0.0:8000`. Эндпоинт `/ping` — liveness/readiness |
|
||||||
|
|
||||||
|
### PostgreSQL
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
|
| `POSTGRES_ADDRESS` | string | — | Хост PostgreSQL |
|
||||||
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
|
| `POSTGRES_PORT` | string | — | Порт PostgreSQL |
|
||||||
|
| `POSTGRES_DB` | string | — | Имя базы данных |
|
||||||
| `POSTGRES_USER` | string | — | Пользователь БД |
|
| `POSTGRES_USER` | string | — | Пользователь БД |
|
||||||
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||||
| `POSTGRES_DB` | string | — | Имя базы данных |
|
|
||||||
| `POSTGRES_POOL_SIZE` | int | `0` | Размер пула соединений к БД |
|
| `POSTGRES_POOL_SIZE` | int | `0` | Размер пула соединений к БД |
|
||||||
| `API_ADDRESS` | string | — | Адрес прослушивания HTTP-сервера (`host:port`), напр. `0.0.0.0:8000` |
|
| `ENABLE_SQL_QUERY` | bool | `false` | Логировать SQL-запросы (query-hook в go-pg) |
|
||||||
| `DOCUMENTATION_HOST` | string | — | Базовый URL сервиса документации (documentation service) |
|
| `ENABLE_SSL` | bool | `false` | Подключаться к PostgreSQL по TLS с проверкой по `YC-PG-CERTIFICATE`; читается и в api, и в миграциях |
|
||||||
| `DOCUMENTATION_ORIGINATOR` | string | — | Идентификатор источника, передаваемый в сервис документации |
|
| `YC-PG-CERTIFICATE` | string | — | Содержимое (PEM) CA-сертификата PostgreSQL; используется при `ENABLE_SSL=1` (`cmd/api/main.go`, `cmd/migrations/main.go`) |
|
||||||
| `DOCUMENTATION_LOGGER_FEATURE` | bool | `false` | Включает фичу логирования обращений к сервису документации |
|
|
||||||
|
### Сервис documentation
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `DOCUMENTATION_HOST` | string | — | Базовый URL сервиса documentation |
|
||||||
|
| `DOCUMENTATION_ORIGINATOR` | string | — | Идентификатор источника, передаваемый в сервис documentation |
|
||||||
|
| `DOCUMENTATION_LOGGER_FEATURE` | bool | `false` | Включает фичу логирования обращений к сервису documentation |
|
||||||
|
|
||||||
|
### Bundles
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `BUNDLES_RETRY_COUNT` | int | `3` | Кол-во ретраев клиента bundles; при значении `<= 0` в `FromEnv()` принудительно берётся `3` |
|
||||||
|
| `BUNDLES_NJOBS` | int | `3` | Кол-во параллельных задач при работе с bundles; при значении `<= 0` берётся `3` |
|
||||||
|
|
||||||
|
### Sentry
|
||||||
|
|
||||||
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
| `SENTRY_DSN` | string | — | DSN для отправки ошибок в Sentry |
|
| `SENTRY_DSN` | string | — | DSN для отправки ошибок в Sentry |
|
||||||
| `SENTRY_DEBUG` | bool | `false` | Debug-режим Sentry |
|
| `SENTRY_DEBUG` | bool | `false` | Debug-режим Sentry |
|
||||||
| `ENVIRONMENT` | string | — | Имя окружения (передаётся в Sentry как environment) |
|
| `ENVIRONMENT` | string | — | Имя окружения (передаётся в Sentry как environment) |
|
||||||
| `BUNDLES_RETRY_COUNT` | int | `3` | Кол-во ретраев клиента bundles (если задано `<= 0`, берётся `3`) |
|
|
||||||
| `BUNDLES_NJOBS` | int | `3` | Кол-во параллельных задач при работе с bundles (если `<= 0`, берётся `3`) |
|
### Трейсинг (OpenTelemetry)
|
||||||
| `ENABLE_SQL_QUERY` | bool | `false` | Логировать SQL-запросы (добавляет query-hook в go-pg) |
|
|
||||||
| `YC-PG-CERTIFICATE` | string | — | Содержимое (PEM) CA-сертификата PostgreSQL; используется при `ENABLE_SSL=1` |
|
| Переменная | Тип | По умолчанию | Назначение |
|
||||||
| `ENABLE_SSL` | bool | `false` | Подключаться к PostgreSQL по TLS с проверкой по `YC-PG-CERTIFICATE` |
|
| --- | --- | --- | --- |
|
||||||
| `TRACER_USE` | bool | `false` | Включает OpenTelemetry-трейсинг и otel-логгер |
|
| `TRACER_USE` | bool | `false` | Включает OpenTelemetry-трейсинг и otel-логгер |
|
||||||
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора |
|
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора |
|
||||||
| `SERVICE_NAME` | string | `workspaces` | Имя сервиса в трейсах |
|
|
||||||
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
|
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
|
||||||
|
| `SERVICE_NAME` | string | `workspaces` | Имя сервиса в трейсах |
|
||||||
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя otel-логгера |
|
| `TRACER_LOGGER_NAME` | string | `tracer_logger` | Имя otel-логгера |
|
||||||
|
|
||||||
> Тип `bool` в envconfig принимает `1`/`0`, `true`/`false`, `t`/`f` и т.п.
|
> Дефолты `TRACER_*` и `SERVICE_NAME` заданы прямо в тегах `default:"..."` структуры `config.Config`; остальные поля дефолтов не имеют (нулевое значение типа).
|
||||||
|
|
||||||
## Переменные инфраструктуры, сборки и вспомогательных утилит
|
---
|
||||||
|
|
||||||
Не читаются основным кодом приложения, но участвуют в запуске/сборке/деплое.
|
## Инфраструктурные, сборочные и вспомогательные переменные
|
||||||
|
|
||||||
|
Не читаются основным кодом приложения (`config.Config`), но участвуют в запуске/сборке/деплое.
|
||||||
|
|
||||||
| Переменная | Где используется | Назначение |
|
| Переменная | Где используется | Назначение |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `POSTGRES_EXTERNAL_PORT` | `docker-compose.yml` | Внешний порт проброса контейнера Postgres |
|
| `POSTGRES_EXTERNAL_PORT` | `.env`, `docker-compose.yml` | Внешний порт проброса контейнера Postgres |
|
||||||
| `API_PORT` | `.env`, `docker-compose.yml` (закомментированный сервис) | Порт API при локальном запуске в контейнере |
|
| `API_PORT` | `.env`, `docker-compose.yml` (закомментированный сервис api) | Порт API при локальном запуске в контейнере |
|
||||||
| `FAKE_API_ADDRESS` | `fake_bundle_api/main.go` | Адрес фейкового bundle-API (только для локальной разработки/тестов) |
|
| `FAKE_API_ADDRESS` | `fake_bundle_api/main.go` (`envconfig`) | Адрес фейкового bundle-API для локальной разработки/тестов; читается отдельной утилитой, не основным сервисом |
|
||||||
| `GITLAB_CREDENTIALS` | `api.Dockerfile` (build-arg) | Креды `https://<user>:<token>@gitlab.sarex.io` для доступа к приватным Go-модулям при сборке |
|
| `GITLAB_CREDENTIALS` | `api.Dockerfile` (build-arg) | Креды `https://<user>:<token>@gitlab.sarex.io` для доступа к приватным Go-модулям при сборке |
|
||||||
| `CI_COMMIT_SHORT_SHA` | `api.Dockerfile` (build-arg) → `APP_VERSION` | Версия/хэш коммита, зашиваемая в бинарь при сборке (`make api`) |
|
| `CI_COMMIT_SHORT_SHA` | `api.Dockerfile` (build-arg) → `APP_VERSION` | Хэш коммита, прокидываемый в сборку `make api` |
|
||||||
| `APP_VERSION` | `Makefile` (`make api`) | Версия приложения при сборке |
|
| `APP_VERSION` | `Makefile` (`make api`) | Версия приложения при сборке (значение — из `CI_COMMIT_SHORT_SHA`) |
|
||||||
|
| `NAMESPACE` | `.helm/values.yaml`, `base/*.yaml`, brusnika-оверлеи | Задаётся в манифестах, но **кодом приложения не читается** |
|
||||||
|
| `INTERNAL_PATH` | `.helm/values.yaml` | Задаётся в чарте, но **кодом приложения не читается** |
|
||||||
|
| `DJANGO_HOST`, `DJANGO_ORIGINATOR` | `base/backend-deployment.yaml`, brusnika-оверлеи | Заданы в манифестах, но **`config.Config` их не читает** (в текущем коде полей Django нет) |
|
||||||
|
| `DJANGO_BASIC_AUTH` | `base/backend-deployment.yaml` (Vault), brusnika `secretEnvs` | Инъектируется из секрета, но **`config.Config` его не читает** |
|
||||||
|
|
||||||
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
---
|
||||||
|
|
||||||
Обычные переменные (`envs`) — переопределяют дефолты по окружениям (`_default`/`stage`/`preprod`/`production`):
|
## Деплой из этого репозитория (`iac/apps/workspaces`)
|
||||||
|
|
||||||
| Переменная | Значение `_default` | Примечание |
|
Здесь используется **kustomize** (`base/` + оверлеи), а не собственный Helm-чарт сервиса. Секреты БД и Django инъектируются агентом **Vault** и подгружаются в окружение процесса до старта (`set -a; . /vault/secrets/...; exec /api`).
|
||||||
| --- | --- | --- |
|
|
||||||
| `POSTGRES_POOL_SIZE` | `3` | |
|
|
||||||
| `BUNDLES_RETRY_COUNT` | `5` | |
|
|
||||||
| `BUNDLES_NJOBS` | `5` | |
|
|
||||||
| `API_ADDRESS` | `0.0.0.0:8000` | В preprod/production — порт `8080` |
|
|
||||||
| `NAMESPACE` | `workspaces` | Задаётся в чарте, но **кодом приложения не читается** |
|
|
||||||
| `ENABLE_SQL_QUERY` | `0` | |
|
|
||||||
| `ENABLE_SSL` | `1` | |
|
|
||||||
| `DOCUMENTATION_HOST` | `http://documentations-api-svc.documentations:8000` | Зависит от окружения |
|
|
||||||
| `DOCUMENTATION_LOGGER_FEATURE` | `0` | |
|
|
||||||
| `DOCUMENTATION_ORIGINATOR` | `stage_ws` | В prod — `prod_ws` |
|
|
||||||
| `SENTRY_DSN` | `https://…@o279218.ingest.sentry.io/6229949` | |
|
|
||||||
| `SENTRY_DEBUG` | `0` | |
|
|
||||||
| `ENVIRONMENT` | `stage` | В prod — `prod` |
|
|
||||||
| `TRACER_USE` | `1` | |
|
|
||||||
| `TRACER_HOST` | `signoz-otel-collector.signoz.svc.cluster.local:4317` | Зависит от окружения |
|
|
||||||
| `SERVICE_NAME` | `workspaces-api.platform` | Зависит от окружения |
|
|
||||||
| `TRACER_USE_INSECURE` | `1` | |
|
|
||||||
| `INTERNAL_PATH` | `/internal/` | Задаётся в чарте, но **кодом приложения не читается** |
|
|
||||||
|
|
||||||
Переменные из секретов (`secretEnvs`, секрет `workspaces-postgresql-secret` / `ya-pg-secret`):
|
### `base/`
|
||||||
|
|
||||||
| Переменная | Ключ секрета |
|
- `backend-deployment.yaml` (api, namespace `workspaces`) — обычные переменные заданы инлайн в `env:` (`POSTGRES_POOL_SIZE`, `BUNDLES_*`, `API_ADDRESS`, `NAMESPACE`, `ENABLE_SQL_QUERY`, `ENABLE_SSL`, `DOCUMENTATION_*`, `ENVIRONMENT`, `DJANGO_HOST`, `DJANGO_ORIGINATOR`). Vault-шаблоны формируют файл `/vault/secrets/workspaces-db` с `POSTGRES_ADDRESS/PORT/DB/USER/PASSWORD` (из `secrets/data/postgresql/apps/workspaces`) и `/vault/secrets/workspaces-django-auth` с `DJANGO_BASIC_AUTH` (из `secrets/data/vault/common/django_auth`). Контейнер стартует через `command: /bin/sh -ec` + `args`, который подгружает эти файлы (`set -a; . /vault/secrets/...`) и `exec /api`. Vault-роль — `workspaces`, ServiceAccount — `workspaces-vault`. Namespace размечен `istio-injection: enabled`.
|
||||||
| --- | --- |
|
- `frontend-deployment.yaml` (`frontend`, образ `workspaces-v2-frontend`) и `frontend-service.yaml` — статический фронтенд, переменных окружения не имеет.
|
||||||
| `POSTGRES_USER` | `username` |
|
- `backend-service.yaml` (`backend-svc`, `:80 → 8000`), `namespace.yaml`, `serviceaccount.yaml`.
|
||||||
| `POSTGRES_PASSWORD` | `password` |
|
- `kustomization.yaml` собирает namespace, serviceaccount, backend/frontend deployments и services (namespace `workspaces`).
|
||||||
| `YC-PG-CERTIFICATE` | `ca.crt` |
|
|
||||||
| `POSTGRES_ADDRESS` | `host` |
|
### Оверлеи
|
||||||
| `POSTGRES_PORT` | `port` |
|
|
||||||
| `POSTGRES_DB` | `database` |
|
- **`yc-k8s-test`** — `../base` + `postgresql.yaml` (Flux `HelmRelease` `postgresql-contour`: БД `workspaces_db`, пользователь `workspaces`, расширение `uuid-ossp`, восстановление из дампа, интеграция с Vault). Патч `replicas.yaml` (`replicas: 1` для `workspaces-api`).
|
||||||
|
- **`brusnika-stage`** и **`brusnika-prod`** — Flux `HelmRelease` на `universal-chart` (`0.1.7`) для api и фронтенда. Переменные — в `envs`, секреты — в `secretEnvs` (`postgres-secret` → `POSTGRES_USER`/`POSTGRES_PASSWORD`, `django-auth` → `DJANGO_BASIC_AUTH`). Отличаются значениями `POSTGRES_ADDRESS` и `DOCUMENTATION_HOST` (stage: `192.168.2.45` / `https://test.sarex.brusnika.tech/documentations`; prod: `postgres-service` / `https://cde.brusnika.ru/documentations`). Api-под запускает `/migrations migrate` перед `/api` в `args`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## JWT-ключи
|
## JWT-ключи
|
||||||
|
|
||||||
RSA-ключи для JWT лежат в `.pub_keys/` и при сборке образа копируются в `/etc/sarex/keys` (см. `api.Dockerfile`). Пути к ключам передаются в функции работы с токенами (`pkg/gotools/auth/jwt.go`) как аргументы, отдельной переменной окружения для пути к ключам в текущем коде нет.
|
RSA-ключи для JWT лежат в `.pub_keys/` (`prod.rsa.pub`, `stage.rsa.pub`, `test.rsa*`) и при сборке образа копируются в `/etc/sarex/keys` (см. `api.Dockerfile`). Пути к ключам передаются в код работы с токенами как аргументы; отдельной переменной окружения для пути к ключам в текущем коде нет.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Замечания и потенциальные проблемы
|
## Замечания и потенциальные проблемы
|
||||||
|
|
||||||
- В файле `.env` переменная названа `POSTGRES_POLL_SIZE` (опечатка), тогда как код читает `POSTGRES_POOL_SIZE`. При локальном запуске из `.env` размер пула не применится и останется `0`.
|
- **Опечатка в `.env`:** переменная названа `POSTGRES_POLL_SIZE`, тогда как код читает `POSTGRES_POOL_SIZE`. При локальном запуске из `.env` размер пула не применится и останется `0`.
|
||||||
|
- **`env-required` не используется:** отсутствие обязательных значений (БД, documentation) не приводит к ошибке `FromEnv()` — процесс стартует с пустыми полями и падает позже при обращении к БД/сервисам.
|
||||||
|
- **Django-переменные не читаются кодом:** `DJANGO_HOST`, `DJANGO_ORIGINATOR`, `DJANGO_BASIC_AUTH` заданы в манифестах `iac` (base + brusnika-оверлеи), но в `config.Config` соответствующих полей нет — значения игнорируются приложением.
|
||||||
|
- **`NAMESPACE` и `INTERNAL_PATH`** задаются в манифестах/чарте, но кодом приложения не читаются.
|
||||||
- Имя переменной `YC-PG-CERTIFICATE` содержит дефисы — envconfig сопоставляет её по точному совпадению тега.
|
- Имя переменной `YC-PG-CERTIFICATE` содержит дефисы — envconfig сопоставляет её по точному совпадению тега.
|
||||||
- В `.helm/values.yaml` у `secretEnvs.POSTGRES_PORT` значение `_default.secretName` указано как `a-pg-secret` (похоже на опечатку, ожидается `ya-pg-secret`/`workspaces-postgresql-secret`).
|
- **Расхождение секретов между источниками деплоя:** в `.helm/values.yaml` `secretEnvs.POSTGRES_PORT._default.secretName` указан как `a-pg-secret` (похоже на опечатку от `ya-pg-secret`); ключи секрета БД различаются между окружениями (`workspaces-postgresql-secret` с ключами `username`/`ca.crt` vs `ya-pg-secret`/`yc-pg-certificate`). В kustomize (`base/`) те же значения приходят из Vault, а не из k8s-секретов.
|
||||||
- Переменные `NAMESPACE` и `INTERNAL_PATH` задаются в Helm-чарте, но не используются в коде приложения.
|
- **Порты различаются по окружениям:** локально/`_default` — `8000`, в preprod/production Helm-чарта — `8080` (см. `API_ADDRESS` и probes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Минимальный набор для локального запуска
|
## Минимальный набор для локального запуска
|
||||||
|
|
||||||
Для запуска сервиса локально (Postgres — через docker-compose, API — бинарником) нужно задать:
|
Postgres — через docker-compose, api — бинарником (миграции применяются `entrypoint.sh`/вручную перед стартом):
|
||||||
|
|
||||||
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`
|
|
||||||
- `API_ADDRESS` (напр. `0.0.0.0:6666`)
|
- `API_ADDRESS` (напр. `0.0.0.0:6666`)
|
||||||
|
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`
|
||||||
- `DOCUMENTATION_HOST`, `DOCUMENTATION_ORIGINATOR`
|
- `DOCUMENTATION_HOST`, `DOCUMENTATION_ORIGINATOR`
|
||||||
- `ENVIRONMENT` (напр. `local`)
|
- `ENVIRONMENT` (напр. `local`)
|
||||||
- при необходимости — `SENTRY_DSN`, `ENABLE_SQL_QUERY`
|
- при необходимости — `ENABLE_SQL_QUERY`, `SENTRY_DSN`, `ENABLE_SSL` (+ `YC-PG-CERTIFICATE`), `TRACER_*`
|
||||||
|
|
||||||
См. пример значений в `.env`.
|
См. пример значений в `.env` / `.env.example`.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user