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
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_DB=workspaces
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_EXTERNAL_PORT=5432
|
||||
POSTGRES_POLL_SIZE=10
|
||||
# --- PostgreSQL -------------------------------------------------------------
|
||||
POSTGRES_ADDRESS=127.0.0.1 # (нужна) хост PostgreSQL
|
||||
POSTGRES_PORT=5432 # (нужна) порт PostgreSQL
|
||||
POSTGRES_DB=workspaces # (нужна) имя базы данных
|
||||
POSTGRES_USER=user # (нужна) пользователь БД
|
||||
POSTGRES_PASSWORD=password # (нужна) пароль пользователя БД
|
||||
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
|
||||
DOCUMENTATION_LOGGER_FEATURE=1
|
||||
DOCUMENTATION_ORIGINATOR=local_ws
|
||||
# --- Bundles ----------------------------------------------------------------
|
||||
# BUNDLES_RETRY_COUNT=5 # число ретраев клиента bundles (по умолчанию/при <=0 берётся 3)
|
||||
# BUNDLES_NJOBS=5 # число параллельных задач при работе с bundles (по умолчанию/при <=0 берётся 3)
|
||||
|
||||
SENTRY_DSN=https://62dbe0a9ee5745eead25f2a705cb35ce@o279218.ingest.sentry.io/6229949
|
||||
SENTRY_DEBUG=0
|
||||
ENVIRONMENT=local
|
||||
# --- Sentry -----------------------------------------------------------------
|
||||
SENTRY_DSN=https://62dbe0a9ee5745eead25f2a705cb35ce@o279218.ingest.sentry.io/6229949 # DSN Sentry
|
||||
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 закомментирован |
|
||||
| Локально (бинарник) | Переменные окружения процесса; пример значений — в `.env`, приватные — в `.private.env` (см. `.private.env.example`) |
|
||||
| Kubernetes (Helm) | `.helm/values.yaml`: блок `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) |
|
||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна и build-args для сборки образа |
|
||||
| Локально (docker-compose + бинарник) | Файл `.env` в корне репозитория. `docker-compose.yml` (`env_file: .env`) поднимает только контейнер Postgres (`postgres:13`); сам API-сервис в compose закомментирован и запускается бинарником. `Makefile` (`make docker`) прокидывает `.env` в docker-compose |
|
||||
| Kubernetes — собственный Helm-чарт репозитория | `.helm/values.yaml` (`universal-chart`): блоки `envs` (обычные значения по окружениям `_default`/`stage`/`preprod`/`production`) и `secretEnvs` (значения из k8s-секретов). Деплой запускается пайплайнами `generic/common-ci` |
|
||||
| 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` подключает шаблоны `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`)
|
||||
|
||||
Читаются напрямую по имени. Пустые обязательные значения не приводят к ошибке старта (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_PORT` | string | — | Порт PostgreSQL |
|
||||
| `POSTGRES_DB` | string | — | Имя базы данных |
|
||||
| `POSTGRES_USER` | string | — | Пользователь БД |
|
||||
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||
| `POSTGRES_DB` | string | — | Имя базы данных |
|
||||
| `POSTGRES_POOL_SIZE` | int | `0` | Размер пула соединений к БД |
|
||||
| `API_ADDRESS` | string | — | Адрес прослушивания HTTP-сервера (`host:port`), напр. `0.0.0.0:8000` |
|
||||
| `DOCUMENTATION_HOST` | string | — | Базовый URL сервиса документации (documentation service) |
|
||||
| `DOCUMENTATION_ORIGINATOR` | string | — | Идентификатор источника, передаваемый в сервис документации |
|
||||
| `DOCUMENTATION_LOGGER_FEATURE` | bool | `false` | Включает фичу логирования обращений к сервису документации |
|
||||
| `ENABLE_SQL_QUERY` | bool | `false` | Логировать SQL-запросы (query-hook в go-pg) |
|
||||
| `ENABLE_SSL` | bool | `false` | Подключаться к PostgreSQL по TLS с проверкой по `YC-PG-CERTIFICATE`; читается и в api, и в миграциях |
|
||||
| `YC-PG-CERTIFICATE` | string | — | Содержимое (PEM) CA-сертификата PostgreSQL; используется при `ENABLE_SSL=1` (`cmd/api/main.go`, `cmd/migrations/main.go`) |
|
||||
|
||||
### Сервис 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_DEBUG` | bool | `false` | Debug-режим Sentry |
|
||||
| `ENVIRONMENT` | string | — | Имя окружения (передаётся в Sentry как environment) |
|
||||
| `BUNDLES_RETRY_COUNT` | int | `3` | Кол-во ретраев клиента bundles (если задано `<= 0`, берётся `3`) |
|
||||
| `BUNDLES_NJOBS` | int | `3` | Кол-во параллельных задач при работе с bundles (если `<= 0`, берётся `3`) |
|
||||
| `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` |
|
||||
|
||||
### Трейсинг (OpenTelemetry)
|
||||
|
||||
| Переменная | Тип | По умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `TRACER_USE` | bool | `false` | Включает OpenTelemetry-трейсинг и otel-логгер |
|
||||
| `TRACER_HOST` | string | `localhost:4317` | Адрес OTLP-коллектора |
|
||||
| `SERVICE_NAME` | string | `workspaces` | Имя сервиса в трейсах |
|
||||
| `TRACER_USE_INSECURE` | bool | `true` | Небезопасное (без TLS) подключение к коллектору |
|
||||
| `SERVICE_NAME` | string | `workspaces` | Имя сервиса в трейсах |
|
||||
| `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 |
|
||||
| `API_PORT` | `.env`, `docker-compose.yml` (закомментированный сервис) | Порт API при локальном запуске в контейнере |
|
||||
| `FAKE_API_ADDRESS` | `fake_bundle_api/main.go` | Адрес фейкового bundle-API (только для локальной разработки/тестов) |
|
||||
| `POSTGRES_EXTERNAL_PORT` | `.env`, `docker-compose.yml` | Внешний порт проброса контейнера Postgres |
|
||||
| `API_PORT` | `.env`, `docker-compose.yml` (закомментированный сервис api) | Порт 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-модулям при сборке |
|
||||
| `CI_COMMIT_SHORT_SHA` | `api.Dockerfile` (build-arg) → `APP_VERSION` | Версия/хэш коммита, зашиваемая в бинарь при сборке (`make api`) |
|
||||
| `APP_VERSION` | `Makefile` (`make api`) | Версия приложения при сборке |
|
||||
| `CI_COMMIT_SHORT_SHA` | `api.Dockerfile` (build-arg) → `APP_VERSION` | Хэш коммита, прокидываемый в сборку `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` | Примечание |
|
||||
| --- | --- | --- |
|
||||
| `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/` | Задаётся в чарте, но **кодом приложения не читается** |
|
||||
Здесь используется **kustomize** (`base/` + оверлеи), а не собственный Helm-чарт сервиса. Секреты БД и Django инъектируются агентом **Vault** и подгружаются в окружение процесса до старта (`set -a; . /vault/secrets/...; exec /api`).
|
||||
|
||||
Переменные из секретов (`secretEnvs`, секрет `workspaces-postgresql-secret` / `ya-pg-secret`):
|
||||
### `base/`
|
||||
|
||||
| Переменная | Ключ секрета |
|
||||
| --- | --- |
|
||||
| `POSTGRES_USER` | `username` |
|
||||
| `POSTGRES_PASSWORD` | `password` |
|
||||
| `YC-PG-CERTIFICATE` | `ca.crt` |
|
||||
| `POSTGRES_ADDRESS` | `host` |
|
||||
| `POSTGRES_PORT` | `port` |
|
||||
| `POSTGRES_DB` | `database` |
|
||||
- `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` — статический фронтенд, переменных окружения не имеет.
|
||||
- `backend-service.yaml` (`backend-svc`, `:80 → 8000`), `namespace.yaml`, `serviceaccount.yaml`.
|
||||
- `kustomization.yaml` собирает namespace, serviceaccount, backend/frontend deployments и services (namespace `workspaces`).
|
||||
|
||||
### Оверлеи
|
||||
|
||||
- **`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-ключи
|
||||
|
||||
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 сопоставляет её по точному совпадению тега.
|
||||
- В `.helm/values.yaml` у `secretEnvs.POSTGRES_PORT` значение `_default.secretName` указано как `a-pg-secret` (похоже на опечатку, ожидается `ya-pg-secret`/`workspaces-postgresql-secret`).
|
||||
- Переменные `NAMESPACE` и `INTERNAL_PATH` задаются в Helm-чарте, но не используются в коде приложения.
|
||||
- **Расхождение секретов между источниками деплоя:** в `.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-секретов.
|
||||
- **Порты различаются по окружениям:** локально/`_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`)
|
||||
- `POSTGRES_ADDRESS`, `POSTGRES_PORT`, `POSTGRES_DB`, `POSTGRES_USER`, `POSTGRES_PASSWORD`
|
||||
- `DOCUMENTATION_HOST`, `DOCUMENTATION_ORIGINATOR`
|
||||
- `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