Add example .env files and detailed configuration documentation for checklists, contracts, drawings, and eav services.
This commit is contained in:
parent
9cff5d6e39
commit
cce76e48a9
31
apps/checklists/.env.example
Normal file
31
apps/checklists/.env.example
Normal file
@ -0,0 +1,31 @@
|
||||
# Piccolo (обязательно для запуска)
|
||||
PYTHONPATH=src
|
||||
PICCOLO_CONF=db.config
|
||||
|
||||
# App
|
||||
DEBUG=true
|
||||
|
||||
# HTTP app
|
||||
HTTP_APP_HOST=0.0.0.0
|
||||
HTTP_APP_PORT=8000
|
||||
HTTP_APP_ROOT_PATH=""
|
||||
HTTP_APP_WORKERS=1
|
||||
HTTP_APP_ADMIN_ENABLE=true
|
||||
|
||||
# Database
|
||||
DATABASE_HOST=postgres
|
||||
DATABASE_PORT=5432
|
||||
DATABASE_NAME=postgres
|
||||
DATABASE_USER=postgres
|
||||
DATABASE_PASSWORD=postgres
|
||||
|
||||
# OpenTelemetry
|
||||
OTEL_ENABLE=false
|
||||
OTEL_URL=http://signoz-otel-collector-external.signoz.svc.cluster.local:4317
|
||||
OTEL_SERVICE_NAME=checklists-backend.checklists-stage
|
||||
OTEL_INSECURE=true
|
||||
|
||||
# Auth (JWT)
|
||||
# При JWT_AUTH_ENABLE=false middleware отключён и используется дефолтный пользователь
|
||||
JWT_AUTH_ENABLE=false
|
||||
JWT_AUTH_PUBLIC_KEY=key
|
||||
171
apps/checklists/CONFIGURATION.md
Normal file
171
apps/checklists/CONFIGURATION.md
Normal file
@ -0,0 +1,171 @@
|
||||
# Конфигурация проекта checklists-backend
|
||||
|
||||
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
Сервис настраивается **только через переменные окружения**. Разбор выполняется в `src/config.py` через библиотеку [`pydantic-settings`](https://docs.pydantic.dev/latest/concepts/pydantic_settings/).
|
||||
|
||||
В отличие от единого класса настроек, конфигурация разбита на несколько независимых классов `BaseSettings`, у каждого — **свой** `env_prefix` (плоские имена, без вложенного разделителя):
|
||||
|
||||
| Класс | `env_prefix` | Раздел |
|
||||
| --- | --- | --- |
|
||||
| `Config` | *(нет префикса)* | `debug` |
|
||||
| `HTTPAppConfig` | `HTTP_APP_` | Параметры HTTP-приложения/uvicorn |
|
||||
| `DatabaseConfig` | `DATABASE_` | Подключение к PostgreSQL |
|
||||
| `OTELConfig` | `OTEL_` | Трейсинг/логи OpenTelemetry |
|
||||
| `JWTAuthConfig` | `JWT_AUTH_` | Аутентификация по JWT |
|
||||
|
||||
Подклассы подключаются к корневому `Config` как поля со значениями по умолчанию (`http_app`, `database`, `otel`, `jwt_auth`) и читают окружение в момент импорта. Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||
|
||||
Особенности:
|
||||
|
||||
- **`.env` не загружается автоматически** — в `config.py` не задан `env_file`, зависимости `python-dotenv` нет. Файл `.env.template` — это шаблон; переменные нужно экспортировать в окружение самому (напр. `set -a && . ./.env && set +a`) либо задавать через `--env`/манифесты k8s.
|
||||
- Помимо переменных приложения, для запуска нужны две инфраструктурные переменные Piccolo: `PYTHONPATH=src` и `PICCOLO_CONF=db.config` (заданы в `.env.template` и в `Dockerfile`).
|
||||
|
||||
Источники переменных по способам запуска:
|
||||
|
||||
| Способ запуска | Откуда берутся переменные |
|
||||
| --- | --- |
|
||||
| Локально (`make run`) | Переменные окружения процесса. `.env.template` — шаблон, приложение его **не подхватывает** автоматически |
|
||||
| Контейнер | `docker/http/Dockerfile` задаёт `PYTHONPATH`/`PICCOLO_CONF`; прочие переменные пробрасываются при запуске |
|
||||
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) чарта `universal-chart` |
|
||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: переменные пайплайна (`workflow.rules`) и `HELM_SET_ARGS` |
|
||||
|
||||
Способы запуска (`Makefile`):
|
||||
|
||||
| Команда | Точка входа | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `make run` | `src/cmd/http/main.py` → `uvicorn` (factory `app.http:create_app`) | HTTP API |
|
||||
| `make migrate` | `piccolo migrations forwards all` | Применение миграций БД |
|
||||
| `make migrations` | `piccolo migrations new checklists --auto` | Генерация новой миграции |
|
||||
| `make format` / `make format-check` | `ruff` | Форматирование/линт |
|
||||
|
||||
Порядок запуска в контейнере (`docker/http/entrypoint.sh`): сначала выполняются миграции (`piccolo migrations forwards all`), затем стартует приложение (`src/cmd/http/main.py`). Uvicorn запускается в режиме фабрики; `reload` включается при `DEBUG=true`, число воркеров — из `HTTP_APP_WORKERS`.
|
||||
|
||||
## Переменные приложения
|
||||
|
||||
В столбце «Переменная» указано полное имя (префикс + поле). Дефолт `—` означает, что значение обязательно.
|
||||
|
||||
### App (`Config`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DEBUG` | bool | `True` | Режим отладки. Влияет на `reload` uvicorn, логирование SQL-запросов Piccolo (`log_queries`/`log_responses`), а также на `production`-флаг и `debug` piccolo-admin |
|
||||
|
||||
### HTTP-приложение (`HTTP_APP_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `HTTP_APP_HOST` | string | `0.0.0.0` | Адрес прослушивания |
|
||||
| `HTTP_APP_PORT` | int | `8000` | Порт |
|
||||
| `HTTP_APP_ROOT_PATH` | string | `""` | Root path (префикс за реверс-прокси; в k8s — `/checklists`) |
|
||||
| `HTTP_APP_WORKERS` | int | `1` | Число воркеров uvicorn |
|
||||
| `HTTP_APP_ADMIN_ENABLE` | bool | `True` | Монтировать ли piccolo-admin по пути `/admin/` |
|
||||
|
||||
### Database (`DATABASE_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DATABASE_HOST` | string | `postgres` | Хост PostgreSQL |
|
||||
| `DATABASE_PORT` | int | `5432` | Порт PostgreSQL |
|
||||
| `DATABASE_NAME` | string | `postgres` | Имя базы данных |
|
||||
| `DATABASE_USER` | string | `postgres` | Пользователь БД |
|
||||
| `DATABASE_PASSWORD` | string | `postgres` | Пароль пользователя БД |
|
||||
|
||||
Подключение собирается в `src/db/config.py` (`PostgresEngine`). SSL-параметров в настройках нет; в prod TLS обеспечивается на уровне подключения/CA-сертификата (см. `docker/http/ca.crt`).
|
||||
|
||||
### OpenTelemetry (`OTEL_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `OTEL_ENABLE` | bool | `False` | Включить трейсинг/логи OTEL. При `True` подключаются `fastapi-otel-tools` и инструментирование FastAPI |
|
||||
| `OTEL_URL` | string | `http://signoz-otel-collector-external.signoz.svc.cluster.local:4317` | Адрес OTLP-коллектора |
|
||||
| `OTEL_SERVICE_NAME` | string | `checklists-backend.checklists-stage` | Имя сервиса в трейсах |
|
||||
| `OTEL_INSECURE` | bool | `True` | Небезопасное (без TLS) подключение к коллектору |
|
||||
|
||||
> При `OTEL_ENABLE=true` `access_log` uvicorn отключается (логи идут через OTEL-обработчик).
|
||||
|
||||
### Auth (`JWT_AUTH_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `JWT_AUTH_ENABLE` | bool | `False` | Включить `JWTAuthMiddleware`. При `False` middleware не подключается, и в контекст подставляется дефолтный пользователь (для локальной разработки) |
|
||||
| `JWT_AUTH_PUBLIC_KEY` | string | `key` | Публичный RSA-ключ для JWT (алгоритм `RS512`) |
|
||||
|
||||
## Переменные инфраструктуры и сборки
|
||||
|
||||
Не читаются кодом приложения, но участвуют в запуске/сборке.
|
||||
|
||||
| Переменная | Где используется | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `PYTHONPATH` | `.env.template`, `Dockerfile` | Путь к исходникам (`src`) |
|
||||
| `PICCOLO_CONF` | `.env.template`, `Dockerfile` | Путь к конфигу Piccolo (`db.config`) |
|
||||
| `SERVICE_NAME` | `.gitlab-ci.yml` | Имя сервиса (`checklists-backend`) |
|
||||
| `DOCKERFILE_PATH` | `.gitlab-ci.yml` | Путь к Dockerfile (`./docker/http/Dockerfile`) |
|
||||
| `IMAGE_NAME` | `.gitlab-ci.yml` (`HELM_SET_ARGS`) | Имя собираемого образа |
|
||||
| `CHART_NAME` / `CHART_VERSION` / `RELEASE_NAME` | `.gitlab-ci.yml` | Параметры релиза Helm |
|
||||
|
||||
Базовый образ — `python:3.13-slim-bookworm`; менеджер зависимостей — `uv` (`uv sync --locked`). В образ добавляется CA-сертификат Yandex (`docker/http/ca.crt`).
|
||||
|
||||
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||
|
||||
Сервис деплоится подключаемым чартом `universal-chart` (OCI-зависимость). Окружение выбирается ключом `universal-chart.global.env` (`stage`/`preprod`/`production`); для каждой переменной значение берётся из блока с ключом текущего окружения либо из `_default`.
|
||||
|
||||
Обычные значения (блок `envs`) переопределяют дефолты кода, в частности:
|
||||
|
||||
| Переменная | Значение в чарте |
|
||||
| --- | --- |
|
||||
| `HTTP_APP_ROOT_PATH` | `/checklists` |
|
||||
| `HTTP_APP_WORKERS` | `3` |
|
||||
| `HTTP_APP_ADMIN_ENABLE` | `true` |
|
||||
| `DATABASE_PORT` | `6432` (PgBouncer) |
|
||||
| `DATABASE_NAME` | `checklists_db` (stage), `checklists` (preprod/production) |
|
||||
| `OTEL_ENABLE` | `true` |
|
||||
| `OTEL_URL` | `http://otel-collector.opentelemetry-collector.svc.cluster.local:4317` |
|
||||
| `OTEL_SERVICE_NAME` | `checklists-backend.proc` (stage), `…checklists-preprod`, `…checklists-prod` |
|
||||
| `JWT_AUTH_ENABLE` | `true` |
|
||||
| `DEBUG` | `false` |
|
||||
|
||||
Значения из секретов (блок `secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||
|
||||
| Переменная | Секрет (`secretName`) | Ключ (`secretKey`) |
|
||||
| --- | --- | --- |
|
||||
| `DATABASE_USER` | `checklists-postgresql-secret` (stage) / `ya-pg-secret` (preprod, production) | `user` |
|
||||
| `DATABASE_PASSWORD` | `checklists-postgresql-secret` / `ya-pg-secret` | `password` |
|
||||
| `DATABASE_HOST` | `checklists-postgresql-secret` / `ya-pg-secret` | `host` |
|
||||
| `JWT_AUTH_PUBLIC_KEY` | `jwt-secret` | `public-key` |
|
||||
|
||||
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, реплики — 1 по умолчанию, 2 в production, ресурсы), `image.*`, `service.*` (ClusterIP, порт `80` → `8000`). Проверки `liveness`/`readiness` отключены.
|
||||
|
||||
## Переменные в CI (`.gitlab-ci.yml`)
|
||||
|
||||
Пайплайн подключает общие шаблоны из `generic/common-ci` (`universal-pipeline.yaml`, `common-security-scan.yaml`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||
|
||||
| Условие | STAND | Namespace |
|
||||
| --- | --- | --- |
|
||||
| ветка `stage` | `stage` | `proc` |
|
||||
| ветка `master` | `preprod` | `checklists-preprod` |
|
||||
| тег (`CI_COMMIT_TAG`) | `production` | `checklists-prod` |
|
||||
|
||||
Ключевые переменные пайплайна: `SERVICE_NAME`, `DOCKERFILE_PATH`, `RELEASE_NAME`, `CHART_NAME`, `CHART_VERSION`, `K8S_HUSTLER_BRANCH`, `HELM_SET_ARGS` (проброс `image.name`, `global.env`, метаданных коммита). Job `lint` прогоняет `ruff check`/`ruff format --check` на образе `uv`.
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
- Приложение **не загружает `.env` автоматически** — переменные нужно экспортировать в окружение вручную либо задавать в манифестах.
|
||||
- Аутентификация JWT в коде выполняет разбор токена с `options={"verify_signature": False}` в обоих режимах (sarex-backend и Zitadel) — подпись фактически не проверяется на уровне приложения, доверие обеспечивается сетевым слоем (Istio). При `JWT_AUTH_ENABLE=false` middleware не подключается и используется дефолтный пользователь из `entity/context.py`.
|
||||
- Внутренние эндпоинты (`/internal/*`) аутентификации на уровне приложения не требуют.
|
||||
- SSL-настроек подключения к БД в коде нет; в prod используется PgBouncer (`DATABASE_PORT=6432`) и CA-сертификат, вшитый в образ.
|
||||
- Piccolo-admin доступен по `/admin/` при `HTTP_APP_ADMIN_ENABLE=true`; в режиме `DEBUG=false` он поднимается в `production`-режиме.
|
||||
|
||||
## Минимальный набор для локального запуска
|
||||
|
||||
Нужен доступный PostgreSQL. Помимо `PYTHONPATH=src` и `PICCOLO_CONF=db.config`, для запуска достаточно значений по умолчанию — обязательных переменных без дефолта нет. Практически стоит задать:
|
||||
|
||||
- `DEBUG` (`true` локально)
|
||||
- `HTTP_APP_HOST`, `HTTP_APP_PORT`
|
||||
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
|
||||
- `JWT_AUTH_ENABLE` (`false` для локальной разработки — тогда используется дефолтный пользователь)
|
||||
- `OTEL_ENABLE` (`false` локально)
|
||||
|
||||
Готовые значения-примеры приведены в `.env.example`.
|
||||
963
apps/checklists/openapi.yaml
Normal file
963
apps/checklists/openapi.yaml
Normal file
@ -0,0 +1,963 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: Checklists
|
||||
version: "0.1.0"
|
||||
description: |
|
||||
REST API сервиса **checklists-backend** — управление чек-листами
|
||||
(`Checklist`) и их результатами (`ChecklistResult`).
|
||||
|
||||
Сервис написан на Python (**FastAPI** + ORM **Piccolo**). Приложение
|
||||
собирается фабрикой `create_app` в `src/app/http.py`. Роутинг состоит из
|
||||
двух групп:
|
||||
|
||||
- публичный API — префикс `/api/v1` (`controller/http/api`);
|
||||
- внутренний API — префикс `/internal/v1` (`controller/http/internal_api`),
|
||||
предназначен для вызовов внутри кластера (через ingress не публикуется).
|
||||
|
||||
Интерактивная документация (ReDoc) доступна по `/docs/`, схема —
|
||||
по `/openapi.json/` (с учётом `root_path`). Админ-панель Piccolo монтируется
|
||||
по `/admin/` при `HTTP_APP_ADMIN_ENABLE=true`.
|
||||
|
||||
### Аутентификация
|
||||
Аутентификация включается флагом `JWT_AUTH_ENABLE`. При включённом
|
||||
`JWTAuthMiddleware` (`controller/http/middlewares.py`) публичные эндпоинты
|
||||
требуют заголовок `Authorization: Bearer <jwt>`. Поддерживаются два режима:
|
||||
|
||||
1. **Zitadel** — если передан дополнительный заголовок `identity`
|
||||
(`Identity <jwt>`), полезная нагрузка (`user_id`, `company_ids`) берётся
|
||||
из этого токена (`urn:zitadel:iam:user:metadata`).
|
||||
2. **sarex-backend** — если заголовка `identity` нет, разбирается основной
|
||||
токен (алгоритм `RS512`, ключ `JWT_AUTH_PUBLIC_KEY`).
|
||||
|
||||
В обоих режимах разбор выполняется с `verify_signature=False` — подпись на
|
||||
уровне приложения не проверяется, доверие обеспечивается сетевым слоем.
|
||||
Внутренние эндпоинты (`/internal/*`) и пути `/docs/`, `/openapi.json/`,
|
||||
`/admin/*` аутентификацию пропускают. При `JWT_AUTH_ENABLE=false` middleware
|
||||
не подключается и используется дефолтный пользователь.
|
||||
|
||||
### Пагинация
|
||||
Списочные ответы используют пагинацию limit/offset и оборачиваются в
|
||||
`PaginatedResponse` — `{ count, result }`, где `count` — число объектов в
|
||||
текущем ответе, `result` — сами объекты. Параметры: `limit` (по умолчанию
|
||||
`100`), `offset` (по умолчанию `0`), сортировка — `order_by`/`ascending`.
|
||||
|
||||
### Обработка ошибок
|
||||
Доменные ошибки (`controller/http/errors.py`) возвращаются как
|
||||
`application/json` с телом `{ "detail": "<текст>" }`. Маппинг:
|
||||
|
||||
- `ResourceNotPermittedError` → **403**;
|
||||
- `ResourceNotFoundError` → **404**;
|
||||
- `StateConflictError` → **409**;
|
||||
- `ChecklistResultValidationError` → **422**;
|
||||
- прочее → **500**.
|
||||
|
||||
Ошибки валидации тела/параметров запроса (Pydantic) отдаются FastAPI в
|
||||
стандартном формате `422` (`HTTPValidationError`). Все публичные эндпоинты
|
||||
(`/api/*`) при невалидном/отсутствующем токене возвращают `401`.
|
||||
|
||||
contact:
|
||||
name: checklists-backend
|
||||
url: https://gitlab/proc/checklists-backend
|
||||
|
||||
servers:
|
||||
- url: https://api.sarex.io/checklists
|
||||
description: Production (ingress, root_path=/checklists)
|
||||
- url: https://stage-api.sarex.io/checklists
|
||||
description: Stage (ingress, root_path=/checklists)
|
||||
- url: http://checklists-backend-service.proc.svc.cluster.local
|
||||
description: Внутрикластерный адрес (ClusterIP, порт 80 → 8000). Единственный способ достучаться до /internal/v1
|
||||
- url: http://localhost:8000
|
||||
description: Локальный запуск (Uvicorn, порт по умолчанию 8000)
|
||||
|
||||
tags:
|
||||
- name: Checklists
|
||||
description: Чек-листы — создание, просмотр, поиск, удаление
|
||||
- name: Checklist results
|
||||
description: Результаты чек-листов — создание, просмотр, обновление, удаление
|
||||
- name: internal
|
||||
description: Внутренние эндпоинты (только внутри кластера)
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
|
||||
paths:
|
||||
# ==========================================================================
|
||||
# Checklists
|
||||
# ==========================================================================
|
||||
/api/v1/checklists/:
|
||||
post:
|
||||
tags: [Checklists]
|
||||
summary: Создать чек-лист
|
||||
operationId: createChecklist
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Созданный чек-лист
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistReadFull'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
get:
|
||||
tags: [Checklists]
|
||||
summary: Список чек-листов
|
||||
operationId: listChecklists
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/OrderByChecklist' }
|
||||
- { $ref: '#/components/parameters/Ascending' }
|
||||
- { $ref: '#/components/parameters/Limit' }
|
||||
- { $ref: '#/components/parameters/Offset' }
|
||||
- name: company_id
|
||||
in: query
|
||||
required: false
|
||||
description: Фильтр по ID компаний
|
||||
schema:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
'200':
|
||||
description: Страница чек-листов (компактное представление)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedResponse_ChecklistReadCompact'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
/api/v1/checklists/filter/:
|
||||
post:
|
||||
tags: [Checklists]
|
||||
summary: Список чек-листов (фильтры в теле)
|
||||
description: Аналог `GET /api/v1/checklists/`, но фильтры и пагинация передаются в теле запроса.
|
||||
operationId: filterChecklists
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistFiltersWithPagination'
|
||||
responses:
|
||||
'200':
|
||||
description: Страница чек-листов (компактное представление)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedResponse_ChecklistReadCompact'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
/api/v1/checklists/{instance_id}/:
|
||||
get:
|
||||
tags: [Checklists]
|
||||
summary: Чек-лист по id
|
||||
operationId: getChecklist
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/InstanceId' }
|
||||
responses:
|
||||
'200':
|
||||
description: Чек-лист (полное представление)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistReadFull'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
delete:
|
||||
tags: [Checklists]
|
||||
summary: Удалить чек-лист
|
||||
operationId: deleteChecklist
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/InstanceId' }
|
||||
responses:
|
||||
'204':
|
||||
description: Чек-лист удалён
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
# ==========================================================================
|
||||
# Checklist results
|
||||
# ==========================================================================
|
||||
/api/v1/results/:
|
||||
post:
|
||||
tags: [Checklist results]
|
||||
summary: Создать результат чек-листа
|
||||
description: |
|
||||
Создаёт результат по `checklist_id`. Данные чек-листа переносятся бэком
|
||||
автоматически; в теле передаются метаданные и список значений инпутов
|
||||
(`input_values`).
|
||||
operationId: createChecklistResult
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Созданный результат
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultRead'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
get:
|
||||
tags: [Checklist results]
|
||||
summary: Список результатов чек-листов
|
||||
operationId: listChecklistResults
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/OrderByChecklistResult' }
|
||||
- { $ref: '#/components/parameters/Ascending' }
|
||||
- { $ref: '#/components/parameters/Limit' }
|
||||
- { $ref: '#/components/parameters/Offset' }
|
||||
- { $ref: '#/components/parameters/FilterResultId' }
|
||||
- { $ref: '#/components/parameters/FilterChecklistId' }
|
||||
- { $ref: '#/components/parameters/FilterCreatorId' }
|
||||
- { $ref: '#/components/parameters/FilterIsDraft' }
|
||||
- { $ref: '#/components/parameters/FilterIsLocked' }
|
||||
responses:
|
||||
'200':
|
||||
description: Страница результатов
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedResponse_ChecklistResultRead'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
/api/v1/results/{instance_id}/:
|
||||
get:
|
||||
tags: [Checklist results]
|
||||
summary: Результат чек-листа по id
|
||||
operationId: getChecklistResult
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/InstanceId' }
|
||||
responses:
|
||||
'200':
|
||||
description: Результат чек-листа
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultRead'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
patch:
|
||||
tags: [Checklist results]
|
||||
summary: Обновить результат чек-листа
|
||||
description: Частичное обновление значений инпутов и флага черновика.
|
||||
operationId: updateChecklistResult
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/InstanceId' }
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultPartialUpdate'
|
||||
responses:
|
||||
'200':
|
||||
description: Обновлённый результат
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultRead'
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
delete:
|
||||
tags: [Checklist results]
|
||||
summary: Удалить результат чек-листа
|
||||
operationId: deleteChecklistResult
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/InstanceId' }
|
||||
responses:
|
||||
'204':
|
||||
description: Результат удалён
|
||||
'401': { $ref: '#/components/responses/Unauthorized' }
|
||||
'403': { $ref: '#/components/responses/Forbidden' }
|
||||
'404': { $ref: '#/components/responses/NotFound' }
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
# ==========================================================================
|
||||
# Internal
|
||||
# ==========================================================================
|
||||
/internal/v1/results/:
|
||||
get:
|
||||
tags: [internal]
|
||||
summary: Список результатов (внутренний)
|
||||
description: Внутрикластерный эндпоинт. Аутентификация на уровне приложения не выполняется.
|
||||
operationId: internalListChecklistResults
|
||||
security: []
|
||||
parameters:
|
||||
- { $ref: '#/components/parameters/OrderByChecklistResult' }
|
||||
- { $ref: '#/components/parameters/Ascending' }
|
||||
- { $ref: '#/components/parameters/Limit' }
|
||||
- { $ref: '#/components/parameters/Offset' }
|
||||
- { $ref: '#/components/parameters/FilterResultId' }
|
||||
- { $ref: '#/components/parameters/FilterChecklistId' }
|
||||
- { $ref: '#/components/parameters/FilterCreatorId' }
|
||||
- { $ref: '#/components/parameters/FilterIsDraft' }
|
||||
- { $ref: '#/components/parameters/FilterIsLocked' }
|
||||
responses:
|
||||
'200':
|
||||
description: Страница результатов
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PaginatedResponse_ChecklistResultRead'
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
/internal/v1/results/lock:
|
||||
patch:
|
||||
tags: [internal]
|
||||
summary: Массовое обновление блокировки результатов
|
||||
description: Устанавливает флаг `is_locked` для списка результатов по их id. Внутрикластерный эндпоинт.
|
||||
operationId: internalBulkUpdateLock
|
||||
security: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ChecklistResultBulkLockingPartialUpdate'
|
||||
responses:
|
||||
'204':
|
||||
description: Флаги блокировки обновлены
|
||||
'422': { $ref: '#/components/responses/ValidationError' }
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
JWT в заголовке `Authorization: Bearer <token>`. Алгоритм `RS512`,
|
||||
ключ `JWT_AUTH_PUBLIC_KEY`. Разбор выполняется без проверки подписи
|
||||
(`verify_signature=False`).
|
||||
identityToken:
|
||||
type: apiKey
|
||||
in: header
|
||||
name: identity
|
||||
description: |
|
||||
Опциональный заголовок `identity` (`Identity <jwt>`) для режима Zitadel.
|
||||
При его наличии полезная нагрузка (`user_id`, `company_ids`) берётся из
|
||||
этого токена.
|
||||
|
||||
parameters:
|
||||
InstanceId:
|
||||
name: instance_id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
Limit:
|
||||
name: limit
|
||||
in: query
|
||||
required: false
|
||||
description: Максимальное количество объектов
|
||||
schema:
|
||||
type: integer
|
||||
default: 100
|
||||
Offset:
|
||||
name: offset
|
||||
in: query
|
||||
required: false
|
||||
description: Количество пропущенных объектов
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
Ascending:
|
||||
name: ascending
|
||||
in: query
|
||||
required: false
|
||||
description: Сортировка по возрастанию
|
||||
schema:
|
||||
type: boolean
|
||||
default: true
|
||||
OrderByChecklist:
|
||||
name: order_by
|
||||
in: query
|
||||
required: false
|
||||
description: Поле для сортировки
|
||||
schema:
|
||||
type: string
|
||||
enum: [id]
|
||||
default: id
|
||||
OrderByChecklistResult:
|
||||
name: order_by
|
||||
in: query
|
||||
required: false
|
||||
description: Поле для сортировки
|
||||
schema:
|
||||
type: string
|
||||
enum: [id]
|
||||
default: id
|
||||
FilterResultId:
|
||||
name: id
|
||||
in: query
|
||||
required: false
|
||||
description: Фильтр по ID результата
|
||||
schema:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
FilterChecklistId:
|
||||
name: checklist_id
|
||||
in: query
|
||||
required: false
|
||||
description: Фильтр по ID чек-листа
|
||||
schema:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
FilterCreatorId:
|
||||
name: creator_id
|
||||
in: query
|
||||
required: false
|
||||
description: Фильтр по ID создателя результата
|
||||
schema:
|
||||
type: array
|
||||
nullable: true
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
FilterIsDraft:
|
||||
name: is_draft
|
||||
in: query
|
||||
required: false
|
||||
description: Признак «чернового» результата
|
||||
schema:
|
||||
type: boolean
|
||||
nullable: true
|
||||
FilterIsLocked:
|
||||
name: is_locked
|
||||
in: query
|
||||
required: false
|
||||
description: Признак блокировки результата
|
||||
schema:
|
||||
type: boolean
|
||||
nullable: true
|
||||
|
||||
responses:
|
||||
Unauthorized:
|
||||
description: Токен не предоставлен или невалиден
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPError'
|
||||
Forbidden:
|
||||
description: Недостаточно прав
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPError'
|
||||
NotFound:
|
||||
description: Запрошенный ресурс не найден
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPError'
|
||||
Conflict:
|
||||
description: Конфликт состояния
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPError'
|
||||
ValidationError:
|
||||
description: |
|
||||
Ошибка валидации тела/параметров запроса (FastAPI/Pydantic) либо
|
||||
доменная ошибка валидации результата (`{ detail }`).
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
|
||||
schemas:
|
||||
# ---- Общие ----
|
||||
HTTPError:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: string
|
||||
description: Описание ошибки
|
||||
required: [detail]
|
||||
|
||||
HTTPValidationError:
|
||||
type: object
|
||||
properties:
|
||||
detail:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/ValidationError'
|
||||
|
||||
ValidationError:
|
||||
type: object
|
||||
properties:
|
||||
loc:
|
||||
type: array
|
||||
items:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: integer
|
||||
msg:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
required: [loc, msg, type]
|
||||
|
||||
# ---- Ограничения инпутов ----
|
||||
InputChoiceOption:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Уникальный идентификатор опции
|
||||
name:
|
||||
type: string
|
||||
description: Название опции
|
||||
example: "Да"
|
||||
order:
|
||||
type: integer
|
||||
description: Порядковый номер опции
|
||||
example: 1
|
||||
color:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Цвет опции (hex или именованный html-цвет)
|
||||
example: "#00aa00"
|
||||
selected_tip:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Заметка при выборе опции
|
||||
example: 'Рекомендуется добавить комментарий при ответе "Нет"'
|
||||
alt_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Название опции для записи в историю
|
||||
example: "Одобрено"
|
||||
required: [id, name, order, selected_tip]
|
||||
|
||||
InputChoiceConstraints:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [choice]
|
||||
options:
|
||||
type: array
|
||||
nullable: true
|
||||
description: Список опций для выбора
|
||||
items:
|
||||
$ref: '#/components/schemas/InputChoiceOption'
|
||||
required: [type, options]
|
||||
|
||||
InputStringConstraints:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
enum: [string]
|
||||
min_length:
|
||||
type: integer
|
||||
minimum: 0
|
||||
default: 0
|
||||
description: Минимально допустимое количество символов
|
||||
max_length:
|
||||
type: integer
|
||||
minimum: 1
|
||||
default: 1000
|
||||
description: Максимально допустимое количество символов
|
||||
required: [type]
|
||||
|
||||
InputConstraints:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/InputChoiceConstraints'
|
||||
- $ref: '#/components/schemas/InputStringConstraints'
|
||||
discriminator:
|
||||
propertyName: type
|
||||
mapping:
|
||||
choice: '#/components/schemas/InputChoiceConstraints'
|
||||
string: '#/components/schemas/InputStringConstraints'
|
||||
|
||||
# ---- Чек-лист (создание) ----
|
||||
ChecklistInputCreate:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
description: Название
|
||||
example: "Комментарий"
|
||||
order:
|
||||
type: integer
|
||||
description: Порядковый номер
|
||||
is_required:
|
||||
type: boolean
|
||||
description: Обязательное ли поле для заполнения
|
||||
constraints:
|
||||
$ref: '#/components/schemas/InputConstraints'
|
||||
required: [name, order, is_required, constraints]
|
||||
|
||||
ChecklistItemCreate:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
maxLength: 8192
|
||||
description: Описание шага
|
||||
order:
|
||||
type: integer
|
||||
description: Порядковый номер
|
||||
inputs:
|
||||
type: array
|
||||
description: Список элементов ввода
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistInputCreate'
|
||||
required: [description, order, inputs]
|
||||
|
||||
ChecklistCreate:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
maxLength: 250
|
||||
description: Название
|
||||
example: "Чек-лист проверки документов"
|
||||
description:
|
||||
type: string
|
||||
maxLength: 8192
|
||||
description: Описание чек-листа
|
||||
company_id:
|
||||
type: integer
|
||||
minimum: 1
|
||||
description: ID компании
|
||||
example: 1
|
||||
items:
|
||||
type: array
|
||||
description: Список шагов чек-листа
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistItemCreate'
|
||||
required: [name, description, company_id, items]
|
||||
|
||||
# ---- Чек-лист (чтение) ----
|
||||
ChecklistInputRead:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
name:
|
||||
type: string
|
||||
maxLength: 1024
|
||||
order:
|
||||
type: integer
|
||||
is_required:
|
||||
type: boolean
|
||||
constraints:
|
||||
$ref: '#/components/schemas/InputConstraints'
|
||||
required: [id, created_at, updated_at, name, order, is_required, constraints]
|
||||
|
||||
ChecklistItemRead:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
description:
|
||||
type: string
|
||||
maxLength: 8192
|
||||
order:
|
||||
type: integer
|
||||
inputs:
|
||||
type: array
|
||||
description: Список элементов ввода
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistInputRead'
|
||||
required: [id, created_at, updated_at, description, order, inputs]
|
||||
|
||||
ChecklistReadCompact:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
name:
|
||||
type: string
|
||||
description:
|
||||
type: string
|
||||
company_id:
|
||||
type: integer
|
||||
minimum: 1
|
||||
required: [id, created_at, updated_at, name, description, company_id]
|
||||
|
||||
ChecklistReadFull:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/ChecklistReadCompact'
|
||||
- type: object
|
||||
properties:
|
||||
items:
|
||||
type: array
|
||||
description: Список шагов чек-листа
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistItemRead'
|
||||
required: [items]
|
||||
|
||||
ChecklistFiltersWithPagination:
|
||||
type: object
|
||||
properties:
|
||||
order_by:
|
||||
type: string
|
||||
enum: [id]
|
||||
default: id
|
||||
ascending:
|
||||
type: boolean
|
||||
default: true
|
||||
limit:
|
||||
type: integer
|
||||
default: 100
|
||||
offset:
|
||||
type: integer
|
||||
default: 0
|
||||
company_id:
|
||||
type: array
|
||||
nullable: true
|
||||
description: Фильтр по ID компаний
|
||||
items:
|
||||
type: integer
|
||||
minimum: 1
|
||||
|
||||
# ---- Результаты чек-листов ----
|
||||
ChecklistResultInputCreate:
|
||||
type: object
|
||||
properties:
|
||||
input_id:
|
||||
type: integer
|
||||
description: ID инпута, для которого устанавливается значение
|
||||
example: 1
|
||||
value:
|
||||
description: 'Значение (тип зависит от инпута: id опции для choice, строка для string)'
|
||||
nullable: true
|
||||
example: "Да"
|
||||
required: [input_id, value]
|
||||
|
||||
ChecklistResultCreate:
|
||||
type: object
|
||||
properties:
|
||||
entity_type:
|
||||
type: string
|
||||
description: Сущность, для которой создан результат
|
||||
example: "review"
|
||||
entity_id:
|
||||
type: string
|
||||
description: ID сущности, для которой создан результат
|
||||
example: "1"
|
||||
checklist_id:
|
||||
type: integer
|
||||
description: ID чек-листа
|
||||
example: 1
|
||||
accessible_by:
|
||||
type: array
|
||||
description: SA ID роли/места/пользователя, которым доступен результат
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
is_draft:
|
||||
type: boolean
|
||||
description: Является ли результат черновым
|
||||
input_values:
|
||||
type: array
|
||||
description: Список устанавливаемых значений
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistResultInputCreate'
|
||||
required: [entity_type, entity_id, checklist_id, accessible_by, is_draft, input_values]
|
||||
|
||||
ChecklistResultPartialUpdate:
|
||||
type: object
|
||||
properties:
|
||||
input_values:
|
||||
type: array
|
||||
description: Список устанавливаемых значений
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistResultInputCreate'
|
||||
is_draft:
|
||||
type: boolean
|
||||
description: Является ли результат черновым
|
||||
required: [input_values, is_draft]
|
||||
|
||||
ChecklistResultInput:
|
||||
type: object
|
||||
properties:
|
||||
input_id:
|
||||
type: integer
|
||||
description: ID инпута, для которого создан результат
|
||||
name:
|
||||
type: string
|
||||
description: Название
|
||||
order:
|
||||
type: integer
|
||||
description: Порядковый номер
|
||||
value:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Введённое значение (строка или UUID выбранной опции)
|
||||
value_text:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Текстовое представление введённого значения
|
||||
color:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Цвет значения
|
||||
required: [input_id, name, order, value, value_text, color]
|
||||
|
||||
ChecklistResultItem:
|
||||
type: object
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
description: Описание шага
|
||||
order:
|
||||
type: integer
|
||||
description: Порядковый номер
|
||||
inputs:
|
||||
type: array
|
||||
description: Список введённых значений
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistResultInput'
|
||||
required: [description, order, inputs]
|
||||
|
||||
ChecklistResultRead:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
example: 1
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
entity_type:
|
||||
type: string
|
||||
example: "review"
|
||||
entity_id:
|
||||
type: string
|
||||
example: "1"
|
||||
checklist_id:
|
||||
type: integer
|
||||
accessible_by:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
format: uuid
|
||||
is_draft:
|
||||
type: boolean
|
||||
company_id:
|
||||
type: integer
|
||||
description: ID компании
|
||||
is_locked:
|
||||
type: boolean
|
||||
description: Заблокирован ли результат для изменений
|
||||
items:
|
||||
type: array
|
||||
description: Список шагов чек-листа
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistResultItem'
|
||||
required:
|
||||
- id
|
||||
- created_at
|
||||
- updated_at
|
||||
- entity_type
|
||||
- entity_id
|
||||
- checklist_id
|
||||
- accessible_by
|
||||
- is_draft
|
||||
- company_id
|
||||
- is_locked
|
||||
- items
|
||||
|
||||
ChecklistResultBulkLockingPartialUpdate:
|
||||
type: object
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
description: Список id результатов, которым нужно обновить флаг
|
||||
items:
|
||||
type: integer
|
||||
example: [1, 2, 3]
|
||||
is_locked:
|
||||
type: boolean
|
||||
description: Заблокирован ли результат для изменений
|
||||
required: [ids, is_locked]
|
||||
|
||||
# ---- Пагинация ----
|
||||
PaginatedResponse_ChecklistReadCompact:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
description: Количество объектов
|
||||
example: 1
|
||||
result:
|
||||
type: array
|
||||
description: Объекты
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistReadCompact'
|
||||
required: [count, result]
|
||||
|
||||
PaginatedResponse_ChecklistResultRead:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
description: Количество объектов
|
||||
example: 1
|
||||
result:
|
||||
type: array
|
||||
description: Объекты
|
||||
items:
|
||||
$ref: '#/components/schemas/ChecklistResultRead'
|
||||
required: [count, result]
|
||||
19
apps/contracts/.env.example
Normal file
19
apps/contracts/.env.example
Normal file
@ -0,0 +1,19 @@
|
||||
# App
|
||||
LOG_LEVEL=debug
|
||||
ADDRESS=:8080
|
||||
|
||||
# Auth
|
||||
# Публичный RSA-ключ (PEM) для проверки JWT.
|
||||
# Читается напрямую через os.Getenv("PUBLIC_KEY") в cmd/http/main.go.
|
||||
PUBLIC_KEY=
|
||||
|
||||
# Database
|
||||
# DSN подключения к PostgreSQL (pgx), напр. postgres://postgres:admin@127.0.0.1:5432/postgres?sslmode=disable
|
||||
DB_URL=postgres://postgres:admin@127.0.0.1:5432/postgres?sslmode=disable
|
||||
DB_POOL_SIZE=10
|
||||
|
||||
# CLI (миграции) — cmd/cli
|
||||
# Путь к каталогу с миграциями (по умолчанию migrations)
|
||||
DB_MIGRATIONS_PATH=migrations
|
||||
# Необязательно: путь к .env-файлу для cli (эквивалент флага -env-file)
|
||||
# ENV_FILE=.env
|
||||
135
apps/contracts/CONFIGURATION.md
Normal file
135
apps/contracts/CONFIGURATION.md
Normal file
@ -0,0 +1,135 @@
|
||||
# Конфигурация проекта contracts
|
||||
|
||||
Документ описывает все переменные окружения и способы конфигурирования сервиса `contracts` (Go, HTTP API + CLI миграций).
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
Сервис настраивается **только через переменные окружения**. Разбор выполняется библиотекой [`github.com/sethvargo/go-envconfig`](https://github.com/sethvargo/go-envconfig) по структуре `Config` в `internal/app/http/config.go`. Дополнительно `.env`-файл автоматически подгружается через [`github.com/joho/godotenv`](https://github.com/joho/godotenv):
|
||||
|
||||
- HTTP-процесс (`cmd/http/main.go`) вызывает `godotenv.Load(".env")` перед разбором конфигурации — если файл `.env` есть в рабочем каталоге, его переменные попадают в окружение;
|
||||
- CLI-процесс (`cmd/cli/main.go`) загружает файл из `ENV_FILE` (или `.env` по умолчанию), путь можно задать флагом `-env-file`.
|
||||
|
||||
Особенности разбора (`go-envconfig`):
|
||||
|
||||
- глобального префикса нет — верхнеуровневые поля читаются по своим именам (`LOG_LEVEL`, `ADDRESS`);
|
||||
- вложенные секции задаются префиксом на уровне структуры: `Database` → `env:", prefix=DB_"`, `Auth` → `env:", prefix=AUTH_"`;
|
||||
- значения по умолчанию заданы в тегах через `default=…`; поля без `default` при отсутствии переменной остаются пустыми (нулевым значением типа), а не приводят к панике на этапе разбора — ошибки всплывают позже (например, невалидный `DB_URL` или пустой `PUBLIC_KEY`).
|
||||
|
||||
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||
|
||||
Источники переменных по способам запуска:
|
||||
|
||||
| Способ запуска | Откуда берутся переменные |
|
||||
| --- | --- |
|
||||
| Локально (бинарник) | `.env` в рабочем каталоге (авто-загрузка `godotenv`) + переменные окружения процесса |
|
||||
| Локально (docker-compose) | `docker-compose.yml`: сервис `contracts` берёт переменные из `env_file: .env`; поднимается вместе с `postgres` |
|
||||
| Kubernetes (Helm) | `.helm/values-<env>.yaml`: блоки `envs` (обычные значения) и `secrets` (значения из k8s-секретов); шаблон `.helm/templates/deployment.yaml` |
|
||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci` и переменные `workflow.rules` (namespace, release, chart) |
|
||||
|
||||
Способы запуска процессов:
|
||||
|
||||
| Команда | Точка входа | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `http` | `cmd/http/main.go` | HTTP API (Fiber v3), слушает `ADDRESS` |
|
||||
| `cli migrate` | `cmd/cli/main.go` | Применение миграций БД (`golang-migrate`), каталог `DB_MIGRATIONS_PATH` |
|
||||
|
||||
Порядок запуска в контейнере (`entrypoint.sh`): сначала `./cli migrate`, затем `./http`.
|
||||
|
||||
## Переменные приложения
|
||||
|
||||
В столбце «Переменная» указано полное имя (с учётом префикса секции). Дефолт `—` означает, что значения по умолчанию нет.
|
||||
|
||||
### App
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `LOG_LEVEL` | string | `debug` | Уровень логирования (zap): `debug`/`info`/`warn`/`error` и т.п. |
|
||||
| `ADDRESS` | string | `:8080` | Адрес и порт прослушивания HTTP-сервера (Fiber) |
|
||||
|
||||
### Database (`DB_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DB_URL` | string | — | DSN подключения к PostgreSQL (`pgxpool.ParseConfig`), напр. `postgres://user:pass@host:5432/db?sslmode=verify-full` |
|
||||
| `DB_POOL_SIZE` | int32 | `10` | Максимальный размер пула соединений (`pgxpool.Config.MaxConns`) |
|
||||
|
||||
### Auth (`AUTH_*`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `AUTH_PUBLIC_KEY` | string | — | Публичный RSA-ключ (PEM) для проверки JWT. См. замечание ниже — фактически используется `PUBLIC_KEY` |
|
||||
| `PUBLIC_KEY` | string | — | Публичный RSA-ключ (PEM). Читается напрямую в `cmd/http/main.go` через `os.Getenv("PUBLIC_KEY")` и записывается в `config.Auth.PublicKey`, перекрывая `AUTH_PUBLIC_KEY` |
|
||||
|
||||
> При старте `AuthProvider` парсит ключ (`pem.Decode` + `x509.ParsePKIXPublicKey`). Если `PUBLIC_KEY` пустой или невалидный — приложение падает с `panic` ещё до старта HTTP-сервера.
|
||||
|
||||
## Переменные CLI (миграции)
|
||||
|
||||
Читаются в `cmd/cli/main.go` (структура `cliConfig`, префикс `DB_`).
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DB_URL` | string | — | DSN подключения к PostgreSQL для применения миграций (обязателен, иначе ошибка `DB_URL is required`) |
|
||||
| `DB_MIGRATIONS_PATH` | string | `migrations` | Путь к каталогу с SQL-миграциями (`golang-migrate`) |
|
||||
| `ENV_FILE` | string | `.env` | Путь к `.env`-файлу, из которого CLI загружает переменные (можно задать флагом `-env-file=PATH`) |
|
||||
|
||||
## Переменные инфраструктуры и сборки
|
||||
|
||||
Не читаются кодом приложения, но участвуют в запуске/сборке/деплое.
|
||||
|
||||
| Переменная / параметр | Где используется | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` | `docker-compose.yml` | Параметры локального контейнера PostgreSQL (`postgres`/`admin`/`postgres`) |
|
||||
| build-stage `golang:1.24` | `Dockerfile` | Базовый образ для сборки бинарников `http` и `cli` |
|
||||
| runtime `alpine:latest` | `Dockerfile` | Финальный образ; копируются `http`, `cli`, `migrations/`, `entrypoint.sh`; открыт порт `8080` |
|
||||
|
||||
## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)
|
||||
|
||||
Обычные значения задаются в блоке `envs` (в текущих values он пуст: `envs: []`). Значения из секретов (блок `secrets`) монтируются как env через `secretKeyRef` в `.helm/templates/deployment.yaml`:
|
||||
|
||||
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
|
||||
| --- | --- | --- |
|
||||
| `DB_URL` | `ya-pg-secret` | `db_url` |
|
||||
| `PUBLIC_KEY` | `public-key` | `key` |
|
||||
|
||||
Прочие значения чарта (не переменные приложения): `deployment.*` (имя, образ, порт, реплики, ресурсы, `service_name`/`service_port`), `api.*` (host/prefix/path ingress), `imagePullSecrets`.
|
||||
|
||||
Помимо env, чарт монтирует CA-сертификат PostgreSQL из секрета `ya-pg-secret` (ключ `certificate`) как файл `/opt/.postgresql/root.crt` (см. `deployment.yaml`). Секрет `ya-pg-secret` при отсутствии создаётся шаблоном `ya-pg-secret.yaml` со случайными значениями и политикой `helm.sh/resource-policy: keep`.
|
||||
|
||||
Параметры окружений (`.helm/values-<env>.yaml`):
|
||||
|
||||
| Окружение | `api.host` | `deployment.service_port` |
|
||||
| --- | --- | --- |
|
||||
| stage | `stage-api.sarex.io` | `8080` |
|
||||
| preprod | `api.preprod.sarex.io` | `80` |
|
||||
| production | `api.sarex.io` | `8080` |
|
||||
|
||||
## Переменные в CI (`.gitlab-ci.yml`)
|
||||
|
||||
Пайплайн подключает общие шаблоны из `generic/common-ci` (`universal-pipeline-*`, `common-security-scan`, `common-build`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||
|
||||
| Условие | STAND | Namespace | Chart version |
|
||||
| --- | --- | --- | --- |
|
||||
| ветка `master` | `preprod` | `contracts-preprod` | `0.0.1-preprod` |
|
||||
| ветка `stage` | `stage` | `contracts-stage` | `0.0.1-stage` |
|
||||
| тег (`CI_COMMIT_TAG`) | `prod` | `contracts-prod` | `0.0.1-prod` |
|
||||
|
||||
Общие переменные пайплайна: `RELEASE_NAME=contracts`, `CHART_NAME=contracts`, `IMAGE_PATH=deployment.image`, `HELM_SET_ARGS="--set deployment.image=${IMAGE_NAME}"`, `DOCKERFILE_PATH=Dockerfile`, флаги `ENABLE_BUILD_CHART`/`ENABLE_BUILD_IMAGE`/`ENABLE_STATE_UPDATE`/`ENABLE_DEPLOY` (`true`), `ENABLE_LINTER` (`false`). Для merge request-ов пайплайн запускается без деплоя.
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
- **Дублирование ключа авторизации.** В `Config` объявлено поле `Auth.PublicKey` с тегом `AUTH_PUBLIC_KEY`, но `cmd/http/main.go` дополнительно читает `os.Getenv("PUBLIC_KEY")` и перезаписывает им значение. В Helm секрет прокидывается как `PUBLIC_KEY`. Практически используется именно `PUBLIC_KEY`; `AUTH_PUBLIC_KEY` в текущем деплое не задаётся.
|
||||
- **`.env` загружается автоматически** (в отличие от Python-сервисов): `godotenv.Load(".env")` в HTTP-процессе и `godotenv.Load(ENV_FILE|.env)` в CLI. Файл `.env` при этом попадает под `.gitignore` (`*.env`) и в репозиторий не коммитится.
|
||||
- **Пустой `PUBLIC_KEY` — фатально.** `auth.New` делает `panic`, если ключ не удаётся распарсить как PEM/PKIX. Для локального запуска нужен валидный публичный ключ.
|
||||
- **`DB_URL` обязателен и для http, и для cli.** Невалидный DSN приводит к ошибке `pgxpool.ParseConfig`/подключения; в CLI пустой `DB_URL` даёт явную ошибку `DB_URL is required`.
|
||||
- **`envs: []` в values.** Все прикладные переменные в k8s сейчас приходят только из секретов (`DB_URL`, `PUBLIC_KEY`); `LOG_LEVEL`/`ADDRESS` используют дефолты (`debug`, `:8080`).
|
||||
|
||||
## Минимальный набор для локального запуска
|
||||
|
||||
PostgreSQL поднимается через `docker-compose up postgres`, приложение — сборкой `cmd/http` (или целиком через docker-compose). Минимально необходимо задать:
|
||||
|
||||
- `DB_URL` — DSN до PostgreSQL (для локали обычно `?sslmode=disable`)
|
||||
- `PUBLIC_KEY` — валидный публичный RSA-ключ (PEM) для проверки JWT
|
||||
- при необходимости: `LOG_LEVEL`, `ADDRESS`, `DB_POOL_SIZE` (иначе применяются дефолты)
|
||||
- для миграций (`cli migrate`): `DB_URL` и, при нестандартном расположении, `DB_MIGRATIONS_PATH`
|
||||
|
||||
Готовые значения-примеры приведены в `.env.example`.
|
||||
64
apps/contracts/ENDPOINTS.md
Normal file
64
apps/contracts/ENDPOINTS.md
Normal file
@ -0,0 +1,64 @@
|
||||
# Эндпоинты, с которыми взаимодействует contracts-frontend
|
||||
|
||||
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `contracts-frontend`).
|
||||
|
||||
## Как устроено взаимодействие
|
||||
|
||||
Запросы сгруппированы по доменам в каталоге `src/shared/api/fetch/*.api.ts`. Каждая функция вызывает соответствующий метод `httpService` (`src/shared/api/http-service.ts`), который создаётся фабрикой `createHttpService` из `@sarex-team/sdk-js` (поверх `axios`). Для запроса указываются:
|
||||
|
||||
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||
- `url` — путь запроса (добавляется к базовому хосту сервиса);
|
||||
- `data` — тело запроса (для `post`/`put`);
|
||||
- `axiosConfig.params` — query-параметры;
|
||||
- `cache`, `queryKey` — опции кеширования (react-query-подобные ключи из `src/shared/api/keys/*`);
|
||||
- `isCSRF` — включение CSRF-обработки (для `departments`).
|
||||
|
||||
Базовый хост подставляется по значению `service` и текущему окружению `__BUILD_ENV__` (`local`/`stage`/`preprod`/`prod`, по умолчанию `prod`; см. `http-service.ts`). В режиме `local` для http-сервиса устанавливается тип `zitadel` (`setTypeOfHttpService("zitadel")`). Итоговый URL = `<базовый хост сервиса>` + `url`.
|
||||
|
||||
## Базовые хосты по сервисам и окружениям
|
||||
|
||||
Значения из `src/shared/api/hosts.ts`. Ниже перечислены сервисы, **фактически используемые** запросами модуля; в реестре хостов определены и другие сервисы (`bim`, `bimv2`, `workflows`, `workspaces`, `documentations`, `comparisons`, `remarks`, `projects`, `eavV1`, `notifications`, `google`, `sarexApi`, `zitadel`), но обращений к ним в `fetch/*` нет.
|
||||
|
||||
| Сервис (`service`) | Назначение | `stage` | `prod` |
|
||||
| --- | --- | --- | --- |
|
||||
| `contracts` | Сервис договоров (contracts-backend) | `https://stage-api.sarex.io/contracts` | `https://api.sarex.io/contracts` |
|
||||
| `sarex` | Локальный backend Sarex (core/admin) | `""` (относительные пути) | `""` |
|
||||
| `gateway` | Gateway/API Sarex (ресурсы/проекты) | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||
|
||||
> Также определены окружения `local` и `preprod`. В `local` сервисы проксируются на относительные пути (`contracts` → `/sarex-contracts`, `sarex` → `/sarex-backend`, `gateway` → `/sarex-gateway`, `zitadel` → `/zitadel`). Значения `preprod` используют домен `api.preprod.sarex.io`.
|
||||
|
||||
## Эндпоинты по сервисам
|
||||
|
||||
### `contracts` — Сервис договоров
|
||||
|
||||
Определены в `src/shared/api/fetch/contract.api.ts`.
|
||||
|
||||
| Функция | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchContractsByResourceId` | GET | `/api/v0/contracts` | Список договоров (query: `limit`, `offset`, `resource_id`, `tenant_id`) |
|
||||
| `fetchCreateContractByResourceId` | POST | `/api/v0/contracts` | Создать договор |
|
||||
| `fetchUpdateContract` | PUT | `/api/v0/contracts/{contract.id}` | Обновить договор по id |
|
||||
|
||||
> Функция удаления `fetchDeleteContract` (`DELETE /api/v0/contracts/{contractId}`) присутствует в коде, но закомментирована.
|
||||
|
||||
### `sarex` — Локальный backend Sarex (core/admin)
|
||||
|
||||
Определены в `company.api.ts`, `contractor.api.ts`, `department.api.ts`.
|
||||
|
||||
| Функция | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchCompanies` | GET | `/api/core/admin/companies/` | Список компаний (кешируется, ключ `companies`) |
|
||||
| `fetchContractors` | GET | `/api/core/admin/contractors/?company_id={companyId}` | Контрагенты компании (кешируется, ключ `contractors`) |
|
||||
| `fetchDepartments` | GET | `/api/core/admin/departments/` | Отделы (кешируется, ключ `departments`, `isCSRF: true`) |
|
||||
|
||||
### `gateway` — Gateway/API Sarex
|
||||
|
||||
Определён в `project.api.ts`.
|
||||
|
||||
| Функция | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchProjects` | GET | `/api/v1/resources/?company_id={companyId}` | Список ресурсов/проектов компании (кешируется, ключ `projects`) |
|
||||
|
||||
## Обработка запросов и кеширование
|
||||
|
||||
Кеширование включается флагом `cache: true` с ключом `queryKey` (значения ключей — в `src/shared/api/keys/*.ts`: `companies`, `projects`, `contractors`, `departments`). Обработка ошибок и авторизация (в т.ч. режим `zitadel` для `local`) выполняются внутри `httpService` из `@sarex-team/sdk-js`.
|
||||
404
apps/contracts/openapi.yaml
Normal file
404
apps/contracts/openapi.yaml
Normal file
@ -0,0 +1,404 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: Contracts Service API
|
||||
version: "1.0.0"
|
||||
description: |
|
||||
REST API сервиса **contracts** (`platform/contracts`) — управление
|
||||
договорами (контрактами): создание (в т.ч. массовое), получение по id,
|
||||
списочный вывод с фильтрами и пагинацией, обновление.
|
||||
|
||||
Сервис написан на Go (**Fiber v3**). Приложение собирается в
|
||||
`internal/app/http/app.go` (`New`). Роутинг вложен под общий префикс
|
||||
`/api/v0` (`app.server.Group("/api/v0")`), внутри — группа
|
||||
`/contracts` (`internal/controller/http/v0`).
|
||||
|
||||
### Аутентификация
|
||||
Все эндпоинты группы `/api/v0/contracts` защищены middleware
|
||||
(`internal/adapter/auth/middleware.go`). Поддерживаются два режима:
|
||||
|
||||
1. **Zitadel** — если передан заголовок `Identity: Bearer <jwt>`,
|
||||
пользователь берётся из полезной нагрузки этого токена
|
||||
(`urn:zitadel:iam:user:metadata`). Подпись на уровне приложения
|
||||
не проверяется (валидность обеспечивается сетевым слоем/Istio).
|
||||
2. **sarex-backend** — если заголовка `Identity` нет, подпись основного
|
||||
токена `Authorization: Bearer <jwt>` проверяется публичным RSA-ключом
|
||||
(`PUBLIC_KEY`).
|
||||
|
||||
Корневой эндпоинт `GET /api/v0/` (health/ping) аутентификации не требует.
|
||||
|
||||
### Идентификаторы
|
||||
Идентификатор договора — **ULID** (строка), парсится через
|
||||
`ulid.Parse`. `resource_id` — **UUID**.
|
||||
|
||||
### Пагинация
|
||||
Списочный вывод использует `limit`/`offset` (query-параметры, по умолчанию
|
||||
`limit=100`, `offset=0`).
|
||||
|
||||
### Обработка ошибок
|
||||
Ошибки возвращаются с соответствующим HTTP-статусом; тело — либо строка
|
||||
с описанием, либо `{"error": "..."}` (при внутренней панике). Коды:
|
||||
`400` — некорректный запрос/невалидный id, `401` — проблемы аутентификации,
|
||||
`403` — пользователь не состоит в компании (`tenant_id`), `404` — договор
|
||||
не найден, `500` — внутренняя ошибка, `501` — метод не реализован.
|
||||
|
||||
### Замечания (расхождения кода)
|
||||
- `PATCH` и `DELETE` (как по коллекции, так и по id) возвращают
|
||||
**`501 Not Implemented`** — обработчики-заглушки.
|
||||
- `POST /api/v0/contracts` принимает **как одиночный объект, так и массив**:
|
||||
тип создания выбирается по форме тела (объект → создание одного договора,
|
||||
массив → пакетное создание).
|
||||
- `GET /api/v0/contracts` (список) требует query-параметр `tenant_id`
|
||||
(middleware `UserInCompanyMiddleware` проверяет, что пользователь состоит
|
||||
в этой компании; иначе `400`/`403`).
|
||||
|
||||
servers:
|
||||
- url: https://api.sarex.io/contracts
|
||||
description: production
|
||||
- url: https://stage-api.sarex.io/contracts
|
||||
description: stage
|
||||
- url: https://api.preprod.sarex.io/contracts
|
||||
description: preprod
|
||||
|
||||
security:
|
||||
- bearerAuth: []
|
||||
|
||||
tags:
|
||||
- name: contracts
|
||||
description: Договоры
|
||||
- name: service
|
||||
description: Служебные эндпоинты
|
||||
|
||||
paths:
|
||||
/api/v0/:
|
||||
get:
|
||||
tags: [service]
|
||||
summary: Health / ping
|
||||
description: Возвращает `200 OK` без тела. Аутентификация не требуется.
|
||||
security: []
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
|
||||
/api/v0/contracts:
|
||||
post:
|
||||
tags: [contracts]
|
||||
summary: Создать договор или несколько договоров
|
||||
description: |
|
||||
Принимает либо одиночный объект `CreateContractRequest`, либо массив
|
||||
таких объектов. Форма тела определяет режим создания.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/CreateContractRequest"
|
||||
- type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CreateContractRequest"
|
||||
responses:
|
||||
"201":
|
||||
description: Договор(ы) создан(ы)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: "#/components/schemas/ContractResponse"
|
||||
- type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ContractResponse"
|
||||
"400":
|
||||
description: Некорректное тело запроса / ошибка валидации
|
||||
"401":
|
||||
description: Ошибка аутентификации
|
||||
"500":
|
||||
description: Внутренняя ошибка
|
||||
get:
|
||||
tags: [contracts]
|
||||
summary: Список договоров
|
||||
description: |
|
||||
Возвращает договоры с фильтрацией и пагинацией. Требует `tenant_id`
|
||||
(проверяется принадлежность пользователя к компании).
|
||||
parameters:
|
||||
- name: tenant_id
|
||||
in: query
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Идентификатор компании/арендатора
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
default: 100
|
||||
- name: offset
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
default: 0
|
||||
- name: resource_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
- name: contractor_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
"200":
|
||||
description: Список договоров
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ContractPaginatedResponse"
|
||||
"400":
|
||||
description: Некорректные параметры запроса / отсутствует tenant_id
|
||||
"401":
|
||||
description: Ошибка аутентификации
|
||||
"403":
|
||||
description: Пользователь не состоит в указанной компании
|
||||
"500":
|
||||
description: Внутренняя ошибка
|
||||
put:
|
||||
tags: [contracts]
|
||||
summary: Пакетное обновление договоров
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/UpdateContractRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Договоры обновлены
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ContractResponse"
|
||||
"400":
|
||||
description: Некорректное тело запроса
|
||||
"401":
|
||||
description: Ошибка аутентификации
|
||||
"500":
|
||||
description: Внутренняя ошибка
|
||||
patch:
|
||||
tags: [contracts]
|
||||
summary: Пакетное частичное обновление (не реализовано)
|
||||
responses:
|
||||
"501":
|
||||
description: Not Implemented
|
||||
delete:
|
||||
tags: [contracts]
|
||||
summary: Пакетное удаление (не реализовано)
|
||||
responses:
|
||||
"501":
|
||||
description: Not Implemented
|
||||
|
||||
/api/v0/contracts/{id}:
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
description: Идентификатор договора (ULID)
|
||||
get:
|
||||
tags: [contracts]
|
||||
summary: Получить договор по id
|
||||
responses:
|
||||
"200":
|
||||
description: Договор
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ContractResponse"
|
||||
"400":
|
||||
description: Невалидный id
|
||||
"401":
|
||||
description: Ошибка аутентификации
|
||||
"404":
|
||||
description: Договор не найден
|
||||
"500":
|
||||
description: Внутренняя ошибка
|
||||
put:
|
||||
tags: [contracts]
|
||||
summary: Обновить договор
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateContractRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Договор обновлён
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ContractResponse"
|
||||
"400":
|
||||
description: Невалидный id / некорректное тело
|
||||
"401":
|
||||
description: Ошибка аутентификации
|
||||
"404":
|
||||
description: Договор не найден
|
||||
"500":
|
||||
description: Внутренняя ошибка
|
||||
patch:
|
||||
tags: [contracts]
|
||||
summary: Частичное обновление (не реализовано)
|
||||
responses:
|
||||
"501":
|
||||
description: Not Implemented
|
||||
delete:
|
||||
tags: [contracts]
|
||||
summary: Удалить договор (не реализовано)
|
||||
responses:
|
||||
"501":
|
||||
description: Not Implemented
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: |
|
||||
Основной токен `Authorization: Bearer <jwt>` (проверяется по RSA-ключу).
|
||||
Опционально может передаваться заголовок `Identity: Bearer <jwt>`
|
||||
(режим Zitadel), который имеет приоритет.
|
||||
|
||||
schemas:
|
||||
Contractor:
|
||||
type: object
|
||||
description: Произвольный JSON-объект с данными контрагента (в БД — JSONB).
|
||||
additionalProperties: true
|
||||
|
||||
CreateContractRequest:
|
||||
type: object
|
||||
required: [number, tenant_id, started_at, deadline_at]
|
||||
properties:
|
||||
number:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Номер договора
|
||||
tenant_id:
|
||||
type: integer
|
||||
format: int64
|
||||
description: Идентификатор компании/арендатора
|
||||
resource_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
contractor:
|
||||
$ref: "#/components/schemas/Contractor"
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
deadline_at:
|
||||
type: string
|
||||
format: date-time
|
||||
cost:
|
||||
type: number
|
||||
format: double
|
||||
minimum: 0
|
||||
description:
|
||||
type: string
|
||||
|
||||
UpdateContractRequest:
|
||||
type: object
|
||||
required: [number, tenant_id, started_at, deadline_at]
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ULID договора
|
||||
number:
|
||||
type: string
|
||||
minLength: 1
|
||||
tenant_id:
|
||||
type: integer
|
||||
format: int64
|
||||
resource_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
contractor:
|
||||
$ref: "#/components/schemas/Contractor"
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
deadline_at:
|
||||
type: string
|
||||
format: date-time
|
||||
cost:
|
||||
type: number
|
||||
format: double
|
||||
minimum: 0
|
||||
description:
|
||||
type: string
|
||||
|
||||
ContractResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: ULID договора
|
||||
number:
|
||||
type: string
|
||||
tenant_id:
|
||||
type: integer
|
||||
format: int64
|
||||
resource_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
contractor:
|
||||
$ref: "#/components/schemas/Contractor"
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
started_at:
|
||||
type: string
|
||||
format: date-time
|
||||
deadline_at:
|
||||
type: string
|
||||
format: date-time
|
||||
cost:
|
||||
type: number
|
||||
format: double
|
||||
description:
|
||||
type: string
|
||||
|
||||
ContractPaginatedResponse:
|
||||
type: object
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
format: int64
|
||||
limit:
|
||||
type: integer
|
||||
format: int64
|
||||
offset:
|
||||
type: integer
|
||||
format: int64
|
||||
results:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/ContractResponse"
|
||||
162
apps/control-interface/ENDPOINTS.md
Normal file
162
apps/control-interface/ENDPOINTS.md
Normal file
@ -0,0 +1,162 @@
|
||||
# Эндпоинты, с которыми взаимодействует srx-admin
|
||||
|
||||
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается приложение `srx-admin` (панель администрирования, деплой `control-interface`).
|
||||
|
||||
## Как устроено взаимодействие
|
||||
|
||||
`srx-admin` — это монорепозиторий (`admin-monorepo`) c двумя фронтенд-сервисами и общим пакетом:
|
||||
|
||||
- `services/admin` — хост-приложение (основной админ-интерфейс);
|
||||
- `services/assets` — федеративный модуль (Module Federation), встраиваемый в хост;
|
||||
- `packages/app-kit` — общий пакет с реестром API-функций и таблицей хостов.
|
||||
|
||||
Запросы описаны не единым реестром, а по доменам — в файлах `shared/api/fetch/*.api.ts`. Каждый домен экспортирует фабрику (например `UserApi`, `AssetApi`, `ProjectApi`), которая принимает `httpService` и возвращает набор методов. Внутри метода вызывается `httpService.getRequest`/`postRequest`/`putRequest`/`patchRequest`/`deleteRequest` со структурой:
|
||||
|
||||
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||
- `url` — путь запроса (с подстановкой параметров прямо в строку или через `axiosConfig.params`);
|
||||
- `data` — тело запроса (для POST/PUT/PATCH);
|
||||
- `axiosConfig`, `cache`, `queryKey`, `controller`, `isCSRF` — опции axios, кеширования, ключа запроса, отмены и CSRF-токена.
|
||||
|
||||
`httpService` создаётся в `shared/api/http-service.ts` через `createHttpService` из `@sarex-team/sdk-js`. Базовый хост подставляется по логическому имени `service` из `packages/app-kit/src/shared/api/hosts.ts` в зависимости от окружения сборки `__ENDPOINT__` (`BUILD_ENV`, по умолчанию `prod`). Итоговый URL = `<базовый хост сервиса>` + `url`.
|
||||
|
||||
## Базовые хосты по сервисам и окружениям
|
||||
|
||||
Значения из `packages/app-kit/src/shared/api/hosts.ts`. Ниже приведены `stage` и `prod`; дополнительно определены окружения `local`, `contour` и `preprod` (см. примечание). Сервисы, к которым `srx-admin` реально обращается, отмечены значком «●» в колонке «Используется».
|
||||
|
||||
| Сервис (`service`) | Назначение | Используется | `stage` | `prod` |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `iam` | IAM: пользователи, отделы, должности, группы, права | ● | `https://stage-api.sarex.io/iam` | `https://api.sarex.io/iam` |
|
||||
| `eavV1` | EAV: ассеты, атрибуты, права на ассеты, модули | ● | `https://stage-api.sarex.io/eav` | `https://api.sarex.io/eav` |
|
||||
| `gateway` | Gateway: ресурсы (проекты) и права на ресурсы | ● | `https://stage-api.sarex.io/gateway` | `https://api.sarex.io/gateway` |
|
||||
| `sarex` | Локальный сервис данных (`/api/core`, `/api/pm`, `/api/commons`) | ● | `""` (относительные пути) | `""` |
|
||||
| `bimv2` | BIM v2: модели статусов | ● | `https://stage-api.sarex.io/bimv2` | `https://api.sarex.io/bimv2` |
|
||||
| `premises` | Сервис помещений | ● | `https://stage-api.sarex.io/premises` | `https://api.sarex.io/premises` |
|
||||
| `notifications` | Лямбда уведомлений (email) | ● | `https://stage-api.sarex.io/lambdas/notification` | `https://api.sarex.io/lambdas/notification` |
|
||||
| `documentations` | Сервис документации | | `https://stage-api.sarex.io/documentations` | `https://api.sarex.io/documentations` |
|
||||
| `workspaces` | Сервис рабочих областей | | `https://stage-api.sarex.io/workspaces` | `https://api.sarex.io/workspaces` |
|
||||
| `workflows` | Сервис обработки документов | | `https://stage-api.sarex.io/workflows` | `https://api.sarex.io/workflows` |
|
||||
| `comparisons` | Сервис сравнений | | `https://stage-api.sarex.io/comparisons` | `https://api.sarex.io/comparisons` |
|
||||
| `remarks` | Сервис замечаний | | `https://stage-api.sarex.io/remarks` | `https://api.sarex.io/remarks` |
|
||||
| `projects` | Сервис проектов | | `https://stage-api.sarex.io/projects` | `https://api.sarex.io/projects` |
|
||||
| `bim` | BIM-API | | `https://stage-api.sarex.io/bim` | `https://api.sarex.io/bim` |
|
||||
| `sarexApi` | Gateway/API Sarex (корень) | | `https://stage-api.sarex.io` | `https://api.sarex.io` |
|
||||
| `google` | Временное хранилище (GCS) | | `https://storage.googleapis.com/srx-tmp` | `https://storage.googleapis.com/srx-tmp` |
|
||||
| `zitadel` | IdP (аутентификация) | | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` |
|
||||
|
||||
> В окружении `local` сервисы проксируются на относительные пути (`sarex` → `/sarex-backend`, `gateway` → `/sarex-gateway`, `eavV1` → `/sarex-eav-v1`, `notifications` → `/sarex-notifications`, `iam` → `/iam`, `premises` → `/premises` и т. д.). Окружение `contour` использует относительные пути для изолированного контура. Подключаемые удалённые модули (Module Federation) описаны отдельно в `services/*/config/endpoints.ts` (см. раздел «Удалённые модули»).
|
||||
|
||||
## Эндпоинты по сервисам
|
||||
|
||||
### `iam` — IAM (пользователи, отделы, должности, группы, права)
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchUsers` | GET | `/api/admin/v0/users/?{query}` | Список пользователей компании (пагинация, поиск, фильтры) |
|
||||
| `fetchCreateUser` | POST | `/api/admin/v0/users/` | Создать пользователя |
|
||||
| `fetchUpdateUser` | PATCH | `/api/admin/v0/users/{id}/` | Обновить пользователя |
|
||||
| `fetchBulkUpdateUsers` | PATCH | `/api/admin/v0/users/` | Массовое обновление пользователей |
|
||||
| `fetchBulkUpdateUsersActivation` | POST | `/api/admin/v0/users/activation/` | Массовая активация/деактивация пользователей |
|
||||
| `fetchDepartments` | GET | `/api/admin/v0/departments/` | Список отделов (пагинация, поиск, фильтр по компании) |
|
||||
| `fetchCreateDepartment` | POST | `/api/admin/v0/departments/` | Создать отдел (CSRF) |
|
||||
| `fetchUpdateDepartment` | PUT | `/api/admin/v0/departments/{id}/` | Обновить отдел (CSRF) |
|
||||
| `fetchDeleteDepartment` | DELETE | `/api/admin/v0/departments/{id}/` | Удалить отдел (CSRF) |
|
||||
| `fetchPositions` | GET | `/api/admin/v0/positions` | Список должностей (пагинация, поиск, фильтр по компании) |
|
||||
| `fetchCreatePosition` | POST | `/api/admin/v0/positions/` | Создать должность (CSRF) |
|
||||
| `fetchUpdatePosition` | PUT | `/api/admin/v0/positions/{id}/` | Обновить должность (CSRF) |
|
||||
| `fetchDeletePosition` | DELETE | `/api/admin/v0/positions/{id}/` | Удалить должность (CSRF) |
|
||||
| `fetchGroups` | POST | `/api/admin/v0/groups/search/` | Поиск функциональных групп (фильтры, пагинация, сортировка) |
|
||||
| `createGroup` | POST | `/api/admin/v0/groups` | Создать группу |
|
||||
| `updateGroup` | PATCH | `/api/admin/v0/groups/{id}` | Обновить группу |
|
||||
| `deleteGroup` | DELETE | `/api/admin/v0/groups/{id}` | Удалить группу |
|
||||
| `fetchPermissions` | POST | `/api/admin/v0/permissions/search/` | Поиск прав (фильтры, пагинация, сортировка) |
|
||||
| `createPermission` | POST | `/api/admin/v0/permissions` | Создать право |
|
||||
| `deletePermission` | DELETE | `/api/admin/v0/permissions/{id}` | Удалить право |
|
||||
|
||||
### `eavV1` — EAV (ассеты, атрибуты, права на ассеты, модули)
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchGetAssetsV4` | GET | `/api/v4/assets/` | Список ассетов (v4, параметры фильтрации) |
|
||||
| `fetchGetAssetsV5` | GET | `/api/v2/assets/` | Список ассетов (v5) |
|
||||
| `fetchCreateBulkAssetsV4` | POST | `/api/v4/assets/` | Массовое создание ассетов (v4) |
|
||||
| `fetchCreateBulkAssetsV5` | POST | `/api/v2/assets/` | Массовое создание ассетов (v5) |
|
||||
| `fetchUpdateBulkAssetsV4` | PATCH | `/api/v4/assets/` | Массовое обновление ассетов (v4) |
|
||||
| `fetchUpdateBulkAssetsV5` | PATCH | `/api/v2/assets/` | Массовое обновление ассетов (v5) |
|
||||
| `fetchDeleteAssetV4` | DELETE | `/api/v4/assets/{assetId}/` | Удалить ассет (v4) |
|
||||
| `fetchDeleteAssetV5` | DELETE | `/api/v2/assets/{assetId}/` | Удалить ассет (v5) |
|
||||
| `fetchCopyRootAsset` | POST | `/api/v4/assets/{asset_id}/copy/` | Копировать корневой ассет |
|
||||
| `fetchCopyAssets` | POST | `/api/v2/assets/copy-to-destination-bulk/` | Массовое копирование ассетов в назначения |
|
||||
| `fecthGetAssetPermissions` | GET | `/api/v4/permissions/?asset_id={id}&service_account_id={id}` | Права доступа ассета |
|
||||
| `fetchPostCreateAssetPermissions` | POST | `/api/v4/permissions/` | Создать права на ассет |
|
||||
| `fetchPostUpdateAssetPermissions` | PATCH | `/api/v4/permissions/` | Обновить права на ассет |
|
||||
| `fetchDeleteAssetPermissions` | DELETE | `/api/v4/permissions/{permissionId}/` | Удалить права на ассет |
|
||||
| `fetchGetAssetPermissionsTree` | GET | `/api/v4/permissions/relative/?asset_id={id}` | Дерево наследуемых прав ассета |
|
||||
| `fetchAttributes` | GET | `/api/v1/attribute/` | Список атрибутов компании (пагинация, поиск, фильтр по id) |
|
||||
| `createAttribute` | POST | `/api/v1/attribute/` | Создать атрибут |
|
||||
| `updateAttribute` | PUT | `/api/v1/attribute/{id}/` | Обновить атрибут |
|
||||
| `deleteAttribute` | DELETE | `/api/v1/attribute/{attributeId}/` | Удалить атрибут |
|
||||
| `fetchModules` | GET | `/api/v1/modules/` | Список модулей (CSRF) |
|
||||
|
||||
### `gateway` — Gateway (ресурсы/проекты, права на ресурсы)
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchProjects` | GET | `/api/v2/resources?show_all=true&limit=10000&company_id={id}` | Список проектов компании (кешируется) |
|
||||
| `fetchGetProjectByProjectId` | GET | `/api/v2/resources/{projectId}` | Проект по id (кешируется) |
|
||||
| `fetchCreateProject` | POST | `/api/v2/resources` | Создать проект/ресурс |
|
||||
| `fetchUpdateProjectByProjectId` | PATCH | `/api/v2/resources/{projectId}` | Обновить проект |
|
||||
| `fetchDeleteProjectByProjectId` | DELETE | `/api/v2/resources/{projectId}` | Удалить проект |
|
||||
| `fetchParentDocumentByResourceId` | GET | `/api/v1/resources-rpc/parent-document-by-resource-id/{resourceId}` | Родительский документ по resource id |
|
||||
| `fetchResources` | GET | `/api/v1/resources/?company_id={id}` | Список ресурсов компании (кешируется) |
|
||||
| `fetchCreatePermission` | POST | `/api/v1/resource-permissions/` | Выдать права на ресурсы сервисному аккаунту |
|
||||
| `fetchResourcesByUsersId` | POST | `/api/v1/resources/users-with-resources/` | Ресурсы по набору пользователей |
|
||||
| `fetchBulkUpdateUsersPermissions` | PATCH | `/api/v1/resources/permissions-bulk/` | Массовое обновление прав на ресурсы |
|
||||
| `fetchBulkUpdateUsersCompanyResourcesPermission` | POST | `/api/v1/company-resource-permissions/bulk/` | Массовая выдача прав на ресурсы компании |
|
||||
|
||||
### `sarex` — Локальный сервис данных
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchCoordinates` | GET | `/api/commons/cs/` | Справочник систем координат (кешируется) |
|
||||
| `fetchLinksToPlanningByProjectId` | GET | `/api/pm/msp/projects/?resource_id={projectId}&strict=true` | Связи проекта с планированием (кешируется) |
|
||||
| `fetchBulkUpdateUserNotifications` | PATCH | `/api/core/users/bulk/notifications/` | Массовое переключение уведомлений пользователей |
|
||||
| `getMrpas` | POST | `/api/core/mrpa/list/` | Список МРПА (пагинация, фильтры, агрегации) |
|
||||
| `createMrpa` | POST | `/api/core/mrpa/` | Загрузить МРПА (multipart/form-data) |
|
||||
| `deleteMrpa` | DELETE | `/api/core/mrpa/{id}/` | Удалить МРПА |
|
||||
|
||||
### `bimv2` — BIM v2 (модели статусов)
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchCreateCompanyStatusModel` | POST | `/api/v1/companies/{companyId}/status_model` | Создать модель статусов компании |
|
||||
| `fetchGetCompanyStatusModels` | GET | `/api/v1/companies/{companyId}/status_model` | Модели статусов компании |
|
||||
| `fetchGetBIMStatusModels` | GET | `/api/v1/bims/{bimId}/status_models` | Модели статусов BIM |
|
||||
| `fetchUpdateBIMStatusModel` | POST | `/api/v1/bims/{bimId}/status_model` | Обновить модель статусов BIM |
|
||||
| `fetchGetBIMStatuses` | POST | `/api/v1/bims/{bimId}/statuses?{search}` | Статусы BIM (с фильтром) |
|
||||
|
||||
### `premises` — Сервис помещений
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `getPremises` | POST | `/api/v1/premises/filter/` | Помещения по локациям и ресурсу |
|
||||
|
||||
### `notifications` — Лямбда уведомлений
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `fetchSendEmail` | POST | `/` | Отправить email-уведомление (from `hello@sarex.io`) |
|
||||
|
||||
## Удалённые модули (Module Federation)
|
||||
|
||||
Помимо HTTP-API, `srx-admin` подгружает удалённые микрофронтенды через `remoteEntry.js`. Адреса заданы в `services/admin/config/endpoints.ts` и `services/assets/config/endpoints.ts` (объект `moduleEndpoints`).
|
||||
|
||||
| Модуль | `stage` / `local` | `prod` | `contour` |
|
||||
| --- | --- | --- | --- |
|
||||
| `documentations` | `https://stage-modules.sarex.io/documentations/static/module/remoteEntry.js` | `https://modules.sarex.io/documentations/static/module/remoteEntry.js` | `/documentations/static/module/remoteEntry.js` |
|
||||
| `assets` | `https://stage-modules.sarex.io/control-interface/modules/assets/remoteEntry.js` | `https://modules.sarex.io/control-interface/modules/assets/remoteEntry.js` | `/control-interface/modules/assets/remoteEntry.js` |
|
||||
|
||||
> В `preprod` используются хосты вида `https://modules.preprod.sarex.io/...`.
|
||||
|
||||
## Обработка ошибок и авторизация
|
||||
|
||||
Запросы выполняются через `httpService` (`@sarex-team/sdk-js` поверх `axios`). Для части эндпоинтов (`iam`: отделы, должности, создание пользователей/групп; `eavV1`: модули) передаётся флаг `isCSRF: true` — добавляется CSRF-токен. Ошибки обрабатываются на уровне SDK и сторов приложения; человекочитаемые сообщения задаются в сторах (`errorMessage`), например «Произошла ошибка при запросе пользователей» / «мест работы» / «ролей» / «функциональных групп».
|
||||
50
apps/cross-section/ENDPOINTS.md
Normal file
50
apps/cross-section/ENDPOINTS.md
Normal file
@ -0,0 +1,50 @@
|
||||
# Эндпоинты, с которыми взаимодействует cross-section
|
||||
|
||||
Документ описывает все HTTP-эндпоинты внешних сервисов, к которым обращается модуль (микрофронтенд `cross-section`).
|
||||
|
||||
## Как устроено взаимодействие
|
||||
|
||||
Запросы описаны в двух API-объектах в `module/api/endpoints.ts`: `crossSectionApi` (поперечные сечения) и `exportsApi` (экспорт и скачивание вложений). Каждый метод вызывает соответствующий хелпер `httpService` (`getRequest`/`postRequest`/`deleteRequest`) со структурой:
|
||||
|
||||
- `service` — логическое имя сервиса (см. таблицу хостов ниже);
|
||||
- `url` — путь запроса (с подстановкой параметров/query);
|
||||
- `data` — опционально, тело запроса (для `POST`/`PUT`).
|
||||
|
||||
`httpService` создаётся функцией `createHttpService` из `@sarex-team/sdk-js` в `module/api/http-service.ts`. Базовый хост подставляется по ключу `service` из `module/api/hosts.ts` в зависимости от `BUILD_ENV` (по умолчанию `prod`). В окружении `local` тип сервиса переключается на `original` (`setTypeOfHttpService("original")`). Итоговый URL = `<базовый хост сервиса>` + `url` эндпоинта.
|
||||
|
||||
## Базовые хосты по сервисам и окружениям
|
||||
|
||||
Значения из `module/api/hosts.ts`. Определены окружения `local`, `stage`, `prod`, `preprod`.
|
||||
|
||||
| Сервис (`service`) | Назначение | `local` | `stage` | `prod` | `preprod` |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `gateway` | Gateway/API Sarex (используется всеми эндпоинтами модуля) | `https://stage-api.sarex.io/gateway/` | `https://stage-api.sarex.io/gateway/` | `https://api.sarex.io/gateway/` | `https://api.preprod.sarex.io/gateway/` |
|
||||
| `drawings` | Сервис чертежей | `https://stage-api.sarex.io/drawings/` | `https://stage-api.sarex.io/drawings/` | `https://api.sarex.io/drawings/` | `https://api.preprod.sarex.io/drawings/` |
|
||||
| `sarex` | Локальный сервис данных (относительные пути) | `https://stage.sarex.io/` | `""` | `""` | `""` |
|
||||
| `zitadel` | IdP (аутентификация) | `https://idp.dev.stage.sarex.io` | `https://idp.dev.stage.sarex.io` | `https://login.sarex.io` | `https://login.preprod.sarex.io` |
|
||||
|
||||
> Фактически все эндпоинты модуля обращаются к сервису `gateway`. Сервисы `drawings`, `sarex` и `zitadel` объявлены в реестре хостов, но напрямую в `endpoints.ts` не используются.
|
||||
|
||||
## Эндпоинты по сервисам
|
||||
|
||||
### `gateway` — Gateway/API Sarex
|
||||
|
||||
#### `crossSectionApi` — поперечные сечения
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `getCrossSections` | GET | `api/v1/drawings/cross-sections?instance_id={uuid}` | Список поперечных сечений по инстансу |
|
||||
| `getCrossSectionData` | GET | `api/v1/drawings/cross-sections/{uuid}/data` | Данные поперечного сечения по uuid |
|
||||
| `createCrossSections` | POST | `api/v1/drawings/cross-sections` | Создать поперечное сечение (тело — `model`) |
|
||||
| `removeCrossSections` | DELETE | `api/v1/drawings/cross-sections/{uuid}/` | Удалить поперечное сечение |
|
||||
|
||||
#### `exportsApi` — экспорт
|
||||
|
||||
| Ключ | Метод | Путь | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `createExport` | POST | `api/v1/drawings/exports` | Создать экспорт (тело: `company_id`, `cross_section_id`, `file_type`; по умолчанию `file_type = "dwg"`) |
|
||||
| `downloadExport` | GET | `api/v1/attachments/{attachment_id}` | Скачать вложение экспорта по id |
|
||||
|
||||
## Обработка ошибок
|
||||
|
||||
В модуле нет отдельного слоя маппинга ошибок (аналога `module/api/errors.ts`): обработка HTTP-ошибок выполняется на уровне `httpService` из `@sarex-team/sdk-js`. Каждый метод возвращает `response.data` (для `createExport` — весь ответ).
|
||||
30
apps/drawings/.env.example
Normal file
30
apps/drawings/.env.example
Normal file
@ -0,0 +1,30 @@
|
||||
# drawings-api — пример переменных окружения.
|
||||
# Конфигурация читается через github.com/kelseyhightower/envconfig
|
||||
# (config/config.go). Приложение НЕ загружает .env автоматически —
|
||||
# переменные нужно экспортировать в окружение процесса самому,
|
||||
# напр.: set -a && . ./.env && set +a
|
||||
|
||||
# API
|
||||
API_ADDRESS=localhost:6666
|
||||
|
||||
# Postgres
|
||||
POSTGRES_USER=user
|
||||
POSTGRES_PASSWORD=password
|
||||
POSTGRES_DB=drawings
|
||||
POSTGRES_ADDRESS=localhost:6432
|
||||
POSTGRES_POOL_SIZE=10
|
||||
# TLS-подключение к БД. При ENABLE_SSL=true используется сертификат
|
||||
# из YC-PG-CERTIFICATE (PEM-содержимое, не путь к файлу)
|
||||
ENABLE_SSL=false
|
||||
YC-PG-CERTIFICATE=
|
||||
|
||||
# Workflow (интеграция с workflows-api через sdk-go)
|
||||
WORKFLOW_HOST=http://workflows-api-service.proc/
|
||||
CONTAINER_REGISTRY=cr.yandex/crp3ccidau046kdj8g9q
|
||||
IMAGE_NAME_EXPORT_TO_DWG=cross-sections-to-dwg
|
||||
IMAGE_TAG=develop
|
||||
TASK_VERSION=1
|
||||
# Внутренний URL самого drawings-api — на него workflow вызывает webhook
|
||||
DRAWING_INTERNAL_URL=http://drawings-api-service.aero/
|
||||
# URL сервиса attachments (передаётся в задачу экспорта)
|
||||
ATTACHMENT_URL=http://attachments-service.documentations.svc.cluster.local:80
|
||||
120
apps/drawings/CONFIGURATION.md
Normal file
120
apps/drawings/CONFIGURATION.md
Normal file
@ -0,0 +1,120 @@
|
||||
# Конфигурация проекта drawings-api
|
||||
|
||||
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
Сервис написан на **Go** (`gitlab.com/sarex-team/rnd/drawings-api`) и настраивается **только через переменные окружения**. Разбор выполняется в `config/config.go` через библиотеку [`kelseyhightower/envconfig`](https://github.com/kelseyhightower/envconfig) (`envconfig.Process("", &Config)`).
|
||||
|
||||
Особенности разбора:
|
||||
|
||||
- **префикса нет** — переменные читаются по именам из тега `envconfig:"..."` (напр. `POSTGRES_ADDRESS`, `API_ADDRESS`);
|
||||
- вложенных секций через разделитель нет: `Config` — плоская композиция трёх структур (`Postgres`, `API`, `Workflow`), у каждого поля своё явное имя переменной;
|
||||
- часть полей имеет дефолт через тег `default:"..."` (напр. `CONTAINER_REGISTRY`, `TASK_VERSION`); поля без дефолта при отсутствии переменной получают нулевое значение типа (пустая строка / `0` / `false`), ошибки старта из-за «обязательности» нет;
|
||||
- при ошибке разбора (`envconfig.Process`) приложение завершается с `logger.Fatalf` (`config.MustParse`).
|
||||
|
||||
Отдельного конфиг-файла (yaml/toml) у приложения нет. Файл `.env` в репозитории — только шаблон; приложение его **не загружает автоматически** (в коде нет чтения `.env`/dotenv), переменные нужно экспортировать в окружение самому.
|
||||
|
||||
Источники переменных по способам запуска:
|
||||
|
||||
| Способ запуска | Откуда берутся переменные |
|
||||
| --- | --- |
|
||||
| Локально (бинарник) | Переменные окружения процесса. `.env` — шаблон, экспортируется вручную, напр. `set -a && . ./.env && set +a`. Сборка — `make drawings-api` / `make migrations` |
|
||||
| Локально (контейнер) | `Dockerfile` (multi-stage, `golang:1.22`) + `entrypoint.sh`. Переменные пробрасываются через `--env`/`--env-file` при запуске контейнера |
|
||||
| Kubernetes (Helm) | `.helm/values.yaml`: блоки `envs` (обычные значения) и `secretEnvs` (значения из k8s-секретов) чарта `universal-chart` |
|
||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci` (`universal-pipeline.yaml`, ref `apps-business`) и выбор окружения по ветке/тегу через `workflow.rules` |
|
||||
|
||||
Порядок запуска в контейнере (`entrypoint.sh`): сначала применяются миграции (`migrations migrate`), затем стартует основной бинарник (`drawings-api`).
|
||||
|
||||
Точки входа (`cmd/`):
|
||||
|
||||
| Команда | Точка входа | Назначение |
|
||||
| --- | --- | --- |
|
||||
| `drawings-api` | `cmd/drawings-api` | HTTP API-сервер (gorilla/mux) |
|
||||
| `migrations migrate` | `cmd/migrations` | Применение миграций БД (`robinjoseph08/go-pg-migrations`) |
|
||||
|
||||
## Переменные приложения
|
||||
|
||||
Все переменные ниже читаются кодом приложения (`config/config.go`). В столбце «Значение по умолчанию» указан дефолт из тега `default:"..."`; `—` означает, что дефолта нет (при отсутствии переменной поле получает нулевое значение типа).
|
||||
|
||||
### API (`API`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `API_ADDRESS` | string | — | Адрес прослушивания HTTP-сервера, напр. `0.0.0.0:8080` |
|
||||
|
||||
### Postgres (`Postgres`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `POSTGRES_USER` | string | — | Пользователь БД |
|
||||
| `POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||
| `POSTGRES_DB` | string | — | Имя базы данных |
|
||||
| `POSTGRES_ADDRESS` | string | — | Адрес PostgreSQL в формате `host:port` |
|
||||
| `POSTGRES_POOL_SIZE` | int | — | Размер пула соединений (`pg.Options.PoolSize`) |
|
||||
| `ENABLE_SSL` | bool | — | Подключение к БД по TLS. При `true` строится `tls.Config` из `YC-PG-CERTIFICATE` |
|
||||
| `YC-PG-CERTIFICATE` | string | — | PEM-содержимое CA-сертификата PostgreSQL (не путь к файлу). Используется только при `ENABLE_SSL=true` |
|
||||
|
||||
> При `ENABLE_SSL=true` из содержимого `YC-PG-CERTIFICATE` собирается пул корневых сертификатов; `ServerName` берётся из хостовой части `POSTGRES_ADDRESS`, при этом в коде выставлен `InsecureSkipVerify: true`. Имя переменной `YC-PG-CERTIFICATE` содержит дефисы (нестандартно для env), но именно так указано в теге `envconfig`.
|
||||
|
||||
### Workflow (`Workflow`)
|
||||
|
||||
Интеграция с сервисом workflows через `gitlab.com/sarex-team/sdk-go/pkg/workflows`: создание workflow экспорта cross-section в DWG (задача парсинга + задача webhook-уведомления).
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `WORKFLOW_HOST` | string | — | Базовый URL сервиса workflows |
|
||||
| `CONTAINER_REGISTRY` | string | `cr.yandex/crp3ccidau046kdj8g9q` | Реестр образов для задач workflow |
|
||||
| `IMAGE_NAME_EXPORT_TO_DWG` | string | — | Имя образа задачи экспорта cross-section в DWG |
|
||||
| `IMAGE_TAG` | string | — | Тег образов задач workflow |
|
||||
| `TASK_VERSION` | string | `1` | Версия задачи экспорта (параметр `version`) |
|
||||
| `DRAWING_INTERNAL_URL` | string | — | Внутренний URL самого drawings-api; на него workflow вызывает webhook `POST {DRAWING_INTERNAL_URL}internal/v1/exports/{export_id}/webhook` |
|
||||
| `ATTACHMENT_URL` | string | — | URL сервиса attachments (передаётся в задачу экспорта как `attachment_url`) |
|
||||
|
||||
## Переменные из Helm-чарта (`.helm/values.yaml`)
|
||||
|
||||
Деплой выполняется через `universal-chart` (`Chart.yaml`, зависимость `universal-chart`). Сервис `drawings-api` слушает порт `8080`; probes настроены на `/ping` (в чарте выключены). Обычные значения задаются в блоке `envs`, значения из секретов — в `secretEnvs`. Значения различаются по окружениям через ключи `_default` / `stage` / `preprod` / `production`.
|
||||
|
||||
Обычные значения (`envs`) — те же переменные приложения, что описаны выше (`API_ADDRESS`, `ENABLE_SSL`, `WORKFLOW_HOST`, `CONTAINER_REGISTRY`, `IMAGE_NAME_EXPORT_TO_DWG`, `IMAGE_TAG`, `TASK_VERSION`, `DRAWING_INTERNAL_URL`, `ATTACHMENT_URL`), различаются адресами сервисов, тегами образов и флагом `ENABLE_SSL` по окружениям.
|
||||
|
||||
Значения из секретов (`secretEnvs`, монтируются как env через `secretKeyRef`):
|
||||
|
||||
| Переменная | Секрет (`_default`) | Секрет (`stage`) | Ключ |
|
||||
| --- | --- | --- | --- |
|
||||
| `POSTGRES_USER` | `ya-pg-secret` | `drawings-postgresql-secret` | `username` |
|
||||
| `POSTGRES_PASSWORD` | `ya-pg-secret` | `drawings-postgresql-secret` | `password` |
|
||||
| `POSTGRES_DB` | `ya-pg-secret` | `drawings-postgresql-secret` | `database` |
|
||||
| `POSTGRES_POOL_SIZE` | `ya-pg-secret` | `drawings-postgresql-secret` | `pool-size` |
|
||||
| `POSTGRES_ADDRESS` | `ya-pg-secret` | `drawings-postgresql-secret` | `address` |
|
||||
| `YC-PG-CERTIFICATE` | `yc-pg-certificate` | `drawings-postgresql-secret` | `ca.crt` |
|
||||
|
||||
## Переменные в CI (`.gitlab-ci.yml`)
|
||||
|
||||
Пайплайн подключает общие шаблоны из `generic/common-ci` (`common-security-scan.yaml`, `universal-pipeline.yaml` ref `apps-business`) и переключает окружение по ветке/тегу через `workflow.rules`:
|
||||
|
||||
| Условие | STAND | NAMESPACE | CHART_VERSION | K8S_HUSTLER_BRANCH |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| ветка `stage` | `stage` | `aero` | `0.0.1-stage` | `universal-chart-stage` |
|
||||
| ветка `master` | `preprod` | `drawings-preprod` | `0.0.1-preprod` | `universal-chart-preprod` |
|
||||
| тег (`CI_COMMIT_TAG`) | `production` | `drawings-prod` | `0.0.1-prod` | `universal-chart-production` |
|
||||
| merge request | — | — | — | сборка образа отключена (`ENABLE_BUILD_IMAGE=false`) |
|
||||
|
||||
Ключевые переменные пайплайна: `SERVICE_NAME=drawings-api`, `DOCKERFILE_PATH=./Dockerfile`, `RELEASE_NAME=drawings-api`, `CHART_NAME=${SERVICE_NAME}`, `BUILD_ARGS` (`--build-arg CI_COMMIT_SHORT_SHA=…`), `HELM_SET_ARGS` (`--set universal-chart.services.drawings-api.image.name.<env>=…`, `--set universal-chart.global.env=<env>`, а также `commitSha`/`gitlabUri`/`gitlabJobUrl`/`owner`).
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
- **Нет обязательности полей.** В отличие от pydantic-конфигов других сервисов, `envconfig` не помечает поля обязательными — при отсутствии переменной поле молча получает нулевое значение. Например, пустой `POSTGRES_ADDRESS` не вызовет ошибку старта конфига, но приведёт к ошибке при подключении к БД.
|
||||
- **Имя `YC-PG-CERTIFICATE` с дефисами** нестандартно для переменных окружения, но именно так задано в теге `envconfig` и в Helm-секрете. В отличие от других сервисов, здесь это **содержимое** сертификата (PEM), а не путь к файлу.
|
||||
- **TLS к БД с `InsecureSkipVerify: true`.** При `ENABLE_SSL=true` корневой сертификат подхватывается, но проверка имени/цепочки фактически ослаблена флагом `InsecureSkipVerify`.
|
||||
- **Webhook-петля.** `DRAWING_INTERNAL_URL` должен указывать на сам drawings-api внутри кластера — по нему workflow дергает `POST internal/v1/exports/{export_id}/webhook` для перевода экспорта в статус `done`. Неверный URL оставит экспорты в статусе `running`.
|
||||
- **`.env` не загружается автоматически** — переменные нужно экспортировать вручную либо задавать через окружение контейнера.
|
||||
|
||||
## Минимальный набор для локального запуска
|
||||
|
||||
Минимально необходимо задать:
|
||||
|
||||
- `API_ADDRESS` (напр. `localhost:6666`)
|
||||
- `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_ADDRESS`, `POSTGRES_POOL_SIZE`, `ENABLE_SSL` (`false` локально; тогда `YC-PG-CERTIFICATE` не нужен)
|
||||
- `WORKFLOW_HOST`, `IMAGE_NAME_EXPORT_TO_DWG`, `IMAGE_TAG`, `DRAWING_INTERNAL_URL`, `ATTACHMENT_URL` (для сценариев экспорта; `CONTAINER_REGISTRY` и `TASK_VERSION` имеют дефолты)
|
||||
|
||||
Готовые значения-примеры приведены в `.env.example`.
|
||||
426
apps/drawings/openapi.yaml
Normal file
426
apps/drawings/openapi.yaml
Normal file
@ -0,0 +1,426 @@
|
||||
openapi: 3.0.3
|
||||
|
||||
info:
|
||||
title: Drawings API
|
||||
version: "0.0.1"
|
||||
description: |
|
||||
REST API сервиса **drawings-api** (`gitlab.com/sarex-team/rnd/drawings-api`) —
|
||||
управление разрезами (cross-sections) чертежей, их данными и экспортом
|
||||
в DWG через сервис workflows.
|
||||
|
||||
Сервис написан на **Go** (gorilla/mux, go-pg). Роутер собирается в
|
||||
`cmd/drawings-api/bootstrap.go`. Помимо служебных эндпоинтов
|
||||
(`/ping`, `/metrics`) есть две группы бизнес-маршрутов с одинаковым
|
||||
набором операций:
|
||||
|
||||
- `/api/v1/*` — публичный роутинг;
|
||||
- `/internal/v1/*` — внутренний роутинг (набор тот же плюс webhook
|
||||
экспорта, вызываемый воркером workflow).
|
||||
|
||||
На все бизнес-маршруты навешены middleware: JSON-ответ (`rest.JSONResponse`),
|
||||
request-id (`reqid.Middleware`) и логирование. Явной аутентификации в коде
|
||||
сервиса нет — доступ ограничивается на уровне ingress/сети кластера.
|
||||
|
||||
### Экспорт в DWG
|
||||
`POST /exports` создаёт запись экспорта и запускает workflow из двух задач
|
||||
(парсинг cross-section в DWG + webhook-уведомление). По завершении workflow
|
||||
вызывает `POST /internal/v1/exports/{export_id}/webhook`, который переводит
|
||||
экспорт в статус `done`.
|
||||
|
||||
servers:
|
||||
- url: /api/v1
|
||||
description: Публичный префикс
|
||||
- url: /internal/v1
|
||||
description: Внутренний префикс
|
||||
|
||||
tags:
|
||||
- name: service
|
||||
description: Служебные эндпоинты
|
||||
- name: cross-sections
|
||||
description: Разрезы чертежей
|
||||
- name: exports
|
||||
description: Экспорт разрезов в DWG
|
||||
|
||||
paths:
|
||||
/ping:
|
||||
get:
|
||||
tags: [service]
|
||||
summary: Healthcheck
|
||||
description: Возвращает статус готовности. Доступен в корне (без префикса).
|
||||
responses:
|
||||
"200":
|
||||
description: Сервис готов
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
example: ready
|
||||
|
||||
/metrics:
|
||||
get:
|
||||
tags: [service]
|
||||
summary: Prometheus-метрики
|
||||
description: Метрики в формате Prometheus. Доступен в корне (без префикса).
|
||||
responses:
|
||||
"200":
|
||||
description: Метрики
|
||||
content:
|
||||
text/plain:
|
||||
schema:
|
||||
type: string
|
||||
|
||||
# ----- Публичные маршруты (/api/v1) и внутренние (/internal/v1) идентичны,
|
||||
# кроме webhook, который есть только на /internal/v1. Пути ниже указаны
|
||||
# относительно префикса из блока servers. -----
|
||||
|
||||
/cross-sections:
|
||||
post:
|
||||
tags: [cross-sections]
|
||||
summary: Создать разрез
|
||||
description: Создаёт cross-section вместе с его данными (`data.raw_data`).
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateCrossSectionRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Созданный разрез
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CrossSection"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
get:
|
||||
tags: [cross-sections]
|
||||
summary: Список разрезов по instance_id
|
||||
parameters:
|
||||
- name: instance_id
|
||||
in: query
|
||||
required: true
|
||||
description: UUID инстанса (чертежа)
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
"200":
|
||||
description: Массив разрезов (пустой, если ничего не найдено)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/CrossSection"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
/cross-sections/{cs_id}:
|
||||
delete:
|
||||
tags: [cross-sections]
|
||||
summary: Удалить разрез (soft-delete)
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/CrossSectionId"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/OK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"404":
|
||||
description: Разрез не найден (возможно, уже удалён)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
/cross-sections/{cs_id}/data:
|
||||
get:
|
||||
tags: [cross-sections]
|
||||
summary: Данные разреза
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/CrossSectionId"
|
||||
responses:
|
||||
"200":
|
||||
description: Данные разреза
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Data"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
/exports:
|
||||
post:
|
||||
tags: [exports]
|
||||
summary: Создать экспорт разреза в DWG
|
||||
description: |
|
||||
Создаёт запись экспорта и запускает workflow экспорта в DWG.
|
||||
В ответе `workflow_status` = `running`.
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/CreateExportRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Созданный экспорт
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Export"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
get:
|
||||
tags: [exports]
|
||||
summary: Список экспортов по фильтрам
|
||||
description: |
|
||||
Хотя бы один из фильтров должен быть задан, иначе `400`.
|
||||
Каждый параметр — список UUID/чисел через запятую.
|
||||
parameters:
|
||||
- name: cross_section_ids
|
||||
in: query
|
||||
required: false
|
||||
description: UUID разрезов через запятую
|
||||
schema:
|
||||
type: string
|
||||
- name: export_ids
|
||||
in: query
|
||||
required: false
|
||||
description: UUID экспортов через запятую
|
||||
schema:
|
||||
type: string
|
||||
- name: workflow_ids
|
||||
in: query
|
||||
required: false
|
||||
description: UUID workflow через запятую
|
||||
schema:
|
||||
type: string
|
||||
- name: attachment_ids
|
||||
in: query
|
||||
required: false
|
||||
description: ID вложений (целые) через запятую
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
description: Массив экспортов (пустой, если ничего не найдено)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Export"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
/exports/{export_id}:
|
||||
delete:
|
||||
tags: [exports]
|
||||
summary: Удалить экспорт
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ExportId"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/OK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
/exports/{export_id}/webhook:
|
||||
post:
|
||||
tags: [exports]
|
||||
summary: Webhook завершения экспорта (только /internal/v1)
|
||||
description: |
|
||||
Вызывается воркером workflow по завершении экспорта. Переводит
|
||||
экспорт в статус `done`. Доступен только по внутреннему префиксу
|
||||
`/internal/v1`.
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/ExportId"
|
||||
responses:
|
||||
"200":
|
||||
$ref: "#/components/responses/OK"
|
||||
"400":
|
||||
$ref: "#/components/responses/BadRequest"
|
||||
"500":
|
||||
$ref: "#/components/responses/StorageError"
|
||||
|
||||
components:
|
||||
parameters:
|
||||
CrossSectionId:
|
||||
name: cs_id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID разреза
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
ExportId:
|
||||
name: export_id
|
||||
in: path
|
||||
required: true
|
||||
description: UUID экспорта
|
||||
schema:
|
||||
type: string
|
||||
format: uuid
|
||||
|
||||
responses:
|
||||
OK:
|
||||
description: Успешно (тело — строка `"OK"`)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: string
|
||||
example: OK
|
||||
BadRequest:
|
||||
description: Некорректный запрос
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
StorageError:
|
||||
description: Внутренняя ошибка (ошибка хранилища)
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/Error"
|
||||
|
||||
schemas:
|
||||
CrossSection:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
deleted_at:
|
||||
type: string
|
||||
format: date-time
|
||||
instance_id:
|
||||
type: string
|
||||
format: uuid
|
||||
documents:
|
||||
type: object
|
||||
additionalProperties:
|
||||
$ref: "#/components/schemas/ConnectedDocument"
|
||||
exports:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/Export"
|
||||
|
||||
ConnectedDocument:
|
||||
type: object
|
||||
properties:
|
||||
color:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
|
||||
Data:
|
||||
type: object
|
||||
properties:
|
||||
cross_section_id:
|
||||
type: string
|
||||
format: uuid
|
||||
raw_data:
|
||||
type: string
|
||||
|
||||
CreateCrossSectionRequest:
|
||||
type: object
|
||||
description: Разрез плюс его данные. Наследует поля CrossSection.
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/CrossSection"
|
||||
- type: object
|
||||
properties:
|
||||
data:
|
||||
$ref: "#/components/schemas/Data"
|
||||
|
||||
Author:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
first_name:
|
||||
type: string
|
||||
last_name:
|
||||
type: string
|
||||
|
||||
Export:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
author:
|
||||
$ref: "#/components/schemas/Author"
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
deleted_at:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
cross_section_id:
|
||||
type: string
|
||||
format: uuid
|
||||
workflow_id:
|
||||
type: string
|
||||
format: uuid
|
||||
nullable: true
|
||||
workflow_status:
|
||||
type: string
|
||||
nullable: true
|
||||
enum: [done, running, error]
|
||||
file_type:
|
||||
type: string
|
||||
enum: [dwg]
|
||||
attachment_id:
|
||||
type: integer
|
||||
nullable: true
|
||||
|
||||
CreateExportRequest:
|
||||
type: object
|
||||
required: [cross_section_id, author, file_type, company_id]
|
||||
properties:
|
||||
cross_section_id:
|
||||
type: string
|
||||
format: uuid
|
||||
author:
|
||||
$ref: "#/components/schemas/Author"
|
||||
file_type:
|
||||
type: string
|
||||
enum: [dwg]
|
||||
company_id:
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
Error:
|
||||
type: object
|
||||
description: Ответ об ошибке (gotools/httperror).
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
54
apps/eav/.env.example
Normal file
54
apps/eav/.env.example
Normal file
@ -0,0 +1,54 @@
|
||||
# Django
|
||||
DJANGO_SETTINGS_MODULE=config.settings.production
|
||||
DJANGO_DEBUG=False
|
||||
DJANGO_SECRET_KEY='v628rpgi^!!57jq9y7y3^by04c1bc@#6%0_a(ekxfmyat8gxew'
|
||||
|
||||
# App
|
||||
SERVICE_NAME=eav
|
||||
VERSION=1.0.0
|
||||
|
||||
# Database (PostgreSQL) — читается только в config.settings.production
|
||||
DJANGO_POSTGRES_HOST=127.0.0.1
|
||||
DJANGO_POSTGRES_PORT=6432
|
||||
DJANGO_POSTGRES_DATABASE=eav_db
|
||||
DJANGO_POSTGRES_USER=sarex
|
||||
DJANGO_POSTGRES_PASSWORD=password
|
||||
|
||||
# Auth / JWT (RS512) — обязательны в config.settings.production
|
||||
SIMPLE_JWT_ISSUER=django
|
||||
# Replace newlines with \n
|
||||
JWT_PRIVATE_KEY=''
|
||||
JWT_PUBLIC_KEY=''
|
||||
|
||||
# S3 (Yandex Object Storage, бото3)
|
||||
YC_S3_ACCESS_KEY_ID=
|
||||
YC_S3_SECRET_ACCESS_KEY=
|
||||
YC_S3_BUCKET_NAME=eav
|
||||
YC_S3_ENDPOINT_URL=https://storage.yandexcloud.net
|
||||
|
||||
# Kafka
|
||||
KAFKA_ENABLED=True
|
||||
KAFKA_HOST=
|
||||
KAFKA_USERNAME=platform
|
||||
KAFKA_PASSWORD=
|
||||
KAFKA_SSL_CAFILE=/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt
|
||||
SASL_MECHANISM=SCRAM-SHA-512
|
||||
SECURITY_PROTOCOL=SASL_SSL
|
||||
ASSETS_TOPIC=assets_broadcast_test
|
||||
|
||||
# Kafka topics (события EAV)
|
||||
KAFKA_TOPIC_ATTRIBUTE_CREATED=eav.attribute.created.v1
|
||||
KAFKA_TOPIC_ATTRIBUTE_UPDATED=eav.attribute.updated.v1
|
||||
KAFKA_TOPIC_ATTRIBUTE_DELETED=eav.attribute.deleted.v1
|
||||
KAFKA_TOPIC_VALUE_OPTION_CREATED=eav.value_option.created.v1
|
||||
KAFKA_TOPIC_VALUE_OPTION_DELETED=eav.value_option.deleted.v1
|
||||
|
||||
# OpenTelemetry (трейсинг включается только если USE_OTEL задана)
|
||||
USE_OTEL=False
|
||||
SERVICE_NAME=eav.eav-backend
|
||||
TRACER_ENDPOINT=localhost:4375
|
||||
USE_INSECURE=False
|
||||
ENVIRONMENT=prod
|
||||
MODULE=eav
|
||||
TEAM=platform_team
|
||||
COMPONENT=backend
|
||||
186
apps/eav/CONFIGURATION.md
Normal file
186
apps/eav/CONFIGURATION.md
Normal file
@ -0,0 +1,186 @@
|
||||
# Конфигурация проекта eav-python
|
||||
|
||||
Документ описывает все переменные окружения и способы конфигурирования сервиса.
|
||||
|
||||
## Способы конфигурирования
|
||||
|
||||
Сервис — это Django-приложение (**Django 4.1 + Django REST Framework**), запускаемое как WSGI (`config.wsgi`) через **uWSGI** (порт `8000`, см. `compose/eav-backend/uwsgi.ini`). Настройки читаются из переменных окружения в `src/config/settings/base.py` и `src/config/settings/production.py`. Разбор выполняется частично через библиотеку [`django-environ`](https://django-environ.readthedocs.io/) (объект `env = environ.Env()`), частично напрямую через `os.getenv`.
|
||||
|
||||
Особенности разбора:
|
||||
|
||||
- **префикса/делимитера у секций нет** — каждая настройка задаётся плоской переменной окружения (напр. `DJANGO_POSTGRES_HOST`, `KAFKA_HOST`, `YC_S3_BUCKET_NAME`);
|
||||
- **`.env` не загружается автоматически** — в коде нет вызова `environ.Env.read_env()` / `load_dotenv`, хотя `python-dotenv` присутствует в зависимостях. Переменные нужно экспортировать в окружение самому (напр. `set -a && . ./.env && set +a`) либо задавать через `--env`/манифесты k8s. Файл `.env` при этом в `.gitignore`;
|
||||
- **выбор набора настроек** задаётся `DJANGO_SETTINGS_MODULE`: `config.settings.production` (боевой набор с БД, CORS, JWT), `config.settings.test` (только `base`), либо `config.settings.local` (по умолчанию в `manage.py`, в репозитории отсутствует, `.gitignore`);
|
||||
- **часть переменных читается только в `production.py`** — БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`); в `base.py`/`test.py` их нет.
|
||||
|
||||
Отдельного конфиг-файла (yaml/toml) у приложения нет.
|
||||
|
||||
Источники переменных по способам запуска:
|
||||
|
||||
| Способ запуска | Откуда берутся переменные |
|
||||
| --- | --- |
|
||||
| Локально (manage.py / uWSGI) | Переменные окружения процесса (`.env` нужно экспортировать вручную) |
|
||||
| Локально (docker-compose) | `docker-compose.yml`: блок `environment` сервиса `backend` + образ `postgres` (timescaledb-postgis) |
|
||||
| Kubernetes (Helm, репозиторий приложения) | `.helm/values-<env>.yaml`: блоки `backend.deployment.envs` (обычные значения) и `backend.deployment.secrets` (из k8s-секретов через `secretKeyRef`); шаблон `templates/server.yaml`, роутинг — `templates/mesh-config.yaml` (Istio VirtualService) |
|
||||
| Kubernetes (infra, Flux/Kustomize) | `infra/iac/apps/eav/base/backend-deployment.yaml`: секреты инъектируются Vault-агентом (`vault.hashicorp.com/agent-inject-*`) и экспортируются в окружение в `args`; настройки `production.py` монтируются из `django-configmap` |
|
||||
| CI/CD (GitLab) | `.gitlab-ci.yml`: общие шаблоны `generic/common-ci`, переменные пайплайна в `workflow.rules` |
|
||||
|
||||
Способы запуска процессов:
|
||||
|
||||
| Процесс | Точка входа | Назначение |
|
||||
| --- | --- | --- |
|
||||
| HTTP API | `compose/eav-backend/entrypoint.sh` → `uwsgi --ini uwsgi.ini` (`config.wsgi`, порт 8000) | REST API |
|
||||
| Миграции | `entrypoint.sh` → `python3 manage.py migrate` (выполняется перед стартом uWSGI) | Миграции БД |
|
||||
| Kafka-продюсер | `config/kafka.py` (инициализируется при импорте, если `KAFKA_ENABLED`) | Публикация событий EAV в топики |
|
||||
|
||||
Порядок запуска в контейнере (`entrypoint.sh`): сначала `manage.py migrate`, затем `uwsgi` (оба под `opentelemetry-instrument`).
|
||||
|
||||
## Переменные приложения
|
||||
|
||||
Дефолт `—` означает, что значения по умолчанию в коде нет.
|
||||
|
||||
### Django / приложение
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DJANGO_SETTINGS_MODULE` | string | `config.settings.local` (в `manage.py`); в контейнере — `config.settings.production` | Какой набор настроек Django загружать |
|
||||
| `DJANGO_DEBUG` | bool | `False` | Режим отладки Django (в `production.py` жёстко `False`) |
|
||||
| `DJANGO_SECRET_KEY` | string | (захардкоженный дефолт) | Секретный ключ Django. В проде обязателен свой |
|
||||
| `SERVICE_NAME` | string | `eav` | Имя сервиса (в `base.py`); в OTEL-секции дефолт `eav.eav-backend` |
|
||||
| `VERSION` | string | `1.0.0` | Версия приложения |
|
||||
|
||||
### Database — PostgreSQL (только `config.settings.production`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `DJANGO_POSTGRES_HOST` | string | — | Хост PostgreSQL |
|
||||
| `DJANGO_POSTGRES_PORT` | int | `6432` | Порт PostgreSQL (в infra-манифесте — `5432`) |
|
||||
| `DJANGO_POSTGRES_DATABASE` | string | — | Имя базы данных |
|
||||
| `DJANGO_POSTGRES_USER` | string | — | Пользователь БД |
|
||||
| `DJANGO_POSTGRES_PASSWORD` | string | — | Пароль пользователя БД |
|
||||
|
||||
> Engine — `django.db.backends.postgresql`. В `docker-compose.yml` поднимается `timescale/timescaledb-postgis` (проекту нужны расширения PostGIS/ltree).
|
||||
|
||||
### Auth / JWT (только `config.settings.production`)
|
||||
|
||||
Используются два механизма аутентификации (`REST_FRAMEWORK.DEFAULT_AUTHENTICATION_CLASSES`): `ZitadelJWTAuthentication` (заголовок `Identity`) и `rest_framework_simplejwt` (RS512).
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `SIMPLE_JWT_ISSUER` | string | `django` | Значение claim `iss` (проверяется при верификации токена) |
|
||||
| `JWT_PRIVATE_KEY` | string (PEM) | — | Приватный RSA-ключ (подпись). Экранированные `\n` заменяются на переводы строк. Обязателен |
|
||||
| `JWT_PUBLIC_KEY` | string (PEM) | — | Публичный RSA-ключ (проверка). Экранированные `\n` заменяются на переводы строк. Обязателен |
|
||||
|
||||
> `SIMPLE_JWT`: алгоритм `RS512`, `ACCESS_TOKEN_LIFETIME` 5 мин, `REFRESH_TOKEN_LIFETIME` 1 день, тип заголовка `Bearer`, claim пользователя — `user_id`.
|
||||
|
||||
### S3 — Yandex Object Storage (`base.py`, boto3/django-storages)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `YC_S3_ACCESS_KEY_ID` | string | `None` | Access key |
|
||||
| `YC_S3_SECRET_ACCESS_KEY` | string | `None` | Secret key |
|
||||
| `YC_S3_BUCKET_NAME` | string | `None` | Бакет по умолчанию |
|
||||
| `YC_S3_ENDPOINT_URL` | string | `None` | Эндпоинт S3 |
|
||||
|
||||
> `DEFAULT_FILE_STORAGE`/`STATICFILES_STORAGE` — `storages.backends.s3boto3.S3Boto3Storage`, `AWS_DEFAULT_ACL=public-read`.
|
||||
|
||||
### Kafka (`base.py`, `config/kafka.py`)
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `KAFKA_ENABLED` | bool | `True` | Включить реального продюсера (иначе `MockProducer` — события не отправляются) |
|
||||
| `KAFKA_HOST` | string | `""` | Адрес брокера (`bootstrap_servers`) |
|
||||
| `KAFKA_USERNAME` | string | `platform` | Пользователь SASL |
|
||||
| `KAFKA_PASSWORD` | string | `""` | Пароль SASL |
|
||||
| `KAFKA_SSL_CAFILE` | string | `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt` | CA-сертификат для TLS |
|
||||
| `SASL_MECHANISM` | string | `SCRAM-SHA-512` | Механизм SASL |
|
||||
| `SECURITY_PROTOCOL` | string | `SASL_SSL` | Протокол безопасности Kafka |
|
||||
| `ASSETS_TOPIC` | string | `assets_broadcast_test` | Топик рассылки по ассетам |
|
||||
|
||||
Топики событий EAV:
|
||||
|
||||
| Переменная | Значение по умолчанию |
|
||||
| --- | --- |
|
||||
| `KAFKA_TOPIC_ATTRIBUTE_CREATED` | `eav.attribute.created.v1` |
|
||||
| `KAFKA_TOPIC_ATTRIBUTE_UPDATED` | `eav.attribute.updated.v1` |
|
||||
| `KAFKA_TOPIC_ATTRIBUTE_DELETED` | `eav.attribute.deleted.v1` |
|
||||
| `KAFKA_TOPIC_VALUE_OPTION_CREATED` | `eav.value_option.created.v1` |
|
||||
| `KAFKA_TOPIC_VALUE_OPTION_DELETED` | `eav.value_option.deleted.v1` |
|
||||
|
||||
### OpenTelemetry (`base.py`)
|
||||
|
||||
Блок трейсинга активируется, только если задана переменная `USE_OTEL` (проверяется через `os.getenv('USE_OTEL', False)` — истинно при любом непустом значении). Используется `django-otel-tools`; при включении в начало `MIDDLEWARE` добавляется `OtelMiddleware`.
|
||||
|
||||
| Переменная | Тип | Значение по умолчанию | Назначение |
|
||||
| --- | --- | --- | --- |
|
||||
| `USE_OTEL` | bool/string | `False` | Включить трейсинг и OTEL-логгер |
|
||||
| `SERVICE_NAME` | string | `eav.eav-backend` | Имя сервиса в трейсах |
|
||||
| `TRACER_ENDPOINT` | string | `localhost:4375` | Адрес OTLP-коллектора |
|
||||
| `USE_INSECURE` | bool/string | `False` | Небезопасное (без TLS) подключение к коллектору |
|
||||
| `ENVIRONMENT` | string | `prod` | Атрибут ресурса `environment` |
|
||||
| `MODULE` | string | `eav` | Атрибут ресурса `module` |
|
||||
| `TEAM` | string | `platform_team` | Атрибут ресурса `team` |
|
||||
| `COMPONENT` | string | `backend` | Атрибут ресурса `component` |
|
||||
|
||||
## Переменные из Helm-чарта (`.helm/values-<env>.yaml`)
|
||||
|
||||
Обычные значения задаются в блоке `backend.deployment.envs` для каждого окружения (`stage`/`preprod`/`production`): `DJANGO_SETTINGS_MODULE`, `USE_OTEL`, `SERVICE_NAME`, `TRACER_ENDPOINT`, `USE_INSECURE`, `ENVIRONMENT`, `KAFKA_HOST`, `ASSETS_TOPIC` (различаются адресами коллектора/брокера и именами топиков).
|
||||
|
||||
Значения из секретов (блок `secrets`, монтируются как env через `secretKeyRef`):
|
||||
|
||||
| Переменная | Секрет (`secret_name`) | Ключ (`secret_key`) |
|
||||
| --- | --- | --- |
|
||||
| `DJANGO_POSTGRES_HOST` | `yc-pg-secret` | `host` |
|
||||
| `DJANGO_POSTGRES_DATABASE` | `yc-pg-secret` | `database` |
|
||||
| `DJANGO_POSTGRES_PORT` | `yc-pg-secret` | `port` (только preprod) |
|
||||
| `DJANGO_POSTGRES_USER` | `yc-pg-secret` | `user` |
|
||||
| `DJANGO_POSTGRES_PASSWORD` | `yc-pg-secret` | `password` |
|
||||
| `DJANGO_CLICKHOUSE_HOST` | `yc-ch-secret` | `host` |
|
||||
| `DJANGO_CLICKHOUSE_DATABASE` | `yc-ch-secret` | `database` |
|
||||
| `DJANGO_CLICKHOUSE_USER` | `yc-ch-secret` | `user` |
|
||||
| `DJANGO_CLICKHOUSE_PASSWORD` | `yc-ch-secret` | `password` |
|
||||
| `YC_S3_ACCESS_KEY_ID` | `yc-s3-secret` | `key_id` |
|
||||
| `YC_S3_SECRET_ACCESS_KEY` | `yc-s3-secret` | `access_key` |
|
||||
| `YC_S3_BUCKET_NAME` | `yc-s3-secret` | `storage_bucket_name` |
|
||||
| `YC_S3_ENDPOINT_URL` | `yc-s3-secret` | `endpoint_url` |
|
||||
| `JWT_PRIVATE_KEY` | `jwt-secret` | `private_key` |
|
||||
| `JWT_PUBLIC_KEY` | `jwt-secret` | `public_key` |
|
||||
| `KAFKA_USERNAME` | `kafka-secret` / `yc-kafka-secret` | `username` |
|
||||
| `KAFKA_PASSWORD` | `kafka-secret` / `yc-kafka-secret` | `password` |
|
||||
| `KAFKA_HOST` | `yc-kafka-secret` | `host` (prod/stage) |
|
||||
|
||||
Помимо env, чарт монтирует CA-сертификаты: PostgreSQL (`yc-pg-certificate` → `~/.postgresql/root.crt`) и Yandex Internal Root CA (`yc-ch-certificate` → `/usr/local/share/ca-certificates/Yandex/YandexInternalRootCA.crt`, тот же путь, что в `KAFKA_SSL_CAFILE`), а также конфиг clickhouse-client.
|
||||
|
||||
## Переменные в infra-манифесте (Flux/Kustomize, `infra/iac/apps/eav`)
|
||||
|
||||
В отличие от Helm-чарта приложения, боевой деплой Sarex использует Vault-инъекцию (`base/backend-deployment.yaml`). Секреты рендерятся Vault-агентом в файлы `/vault/secrets/*` и экспортируются в окружение в `args` контейнера перед запуском `entrypoint.sh`:
|
||||
|
||||
| Переменная(ые) | Источник (Vault path) |
|
||||
| --- | --- |
|
||||
| `DJANGO_POSTGRES_HOST/PORT/DATABASE/USER/PASSWORD` | `secrets/data/postgresql/apps/eav` |
|
||||
| `YC_S3_ENDPOINT_URL/BUCKET_NAME/ACCESS_KEY_ID/SECRET_ACCESS_KEY` | `secrets/data/minio/apps/eav` |
|
||||
| `JWT_PRIVATE_KEY` / `JWT_PUBLIC_KEY` | `secrets/data/vault/common/rsa_keys` |
|
||||
|
||||
Прямо в `env` деплоймента задаются `KAFKA_ENABLED=False`, `ASSETS_TOPIC=sarex`, `DJANGO_SETTINGS_MODULE=config.settings.production`. Файл `production.py` монтируется из `django-configmap` (переопределяет `production.py` из образа; в нём `DEBUG=True`, `ALLOWED_HOSTS=['*']`, свои CORS/CSRF-домены и имена cookie `eav-sessionid`/`eav-csrftoken`).
|
||||
|
||||
## Замечания и потенциальные проблемы
|
||||
|
||||
- Приложение **не загружает `.env` автоматически** (нет `read_env`/`load_dotenv`). `python-dotenv` установлен, но не используется в настройках — переменные нужно экспортировать в окружение самому.
|
||||
- Переменные БД (`DJANGO_POSTGRES_*`) и JWT (`JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY`) читаются **только** в `config.settings.production`. При `test`/`base` их отсутствие не мешает старту, но БД по умолчанию не сконфигурирована.
|
||||
- `JWT_PRIVATE_KEY`/`JWT_PUBLIC_KEY` в `production.py` читаются через `env.str(...)` **без дефолта** — их отсутствие приводит к ошибке старта. В infra-варианте (`django-configmap`) используется `get_env_variable` с тем же требованием.
|
||||
- `DJANGO_CLICKHOUSE_*` присутствуют в Helm-секретах, но **кодом приложения не читаются** (в текущих настройках ClickHouse не используется) — это подготовка/наследие инфраструктуры.
|
||||
- `KAFKA_ENABLED`: при ложном значении используется `MockProducer` — события EAV в Kafka не публикуются (так сделано в infra-деплое: `KAFKA_ENABLED=False`). Значение разбирается `django-environ` как bool.
|
||||
- Флаги OTEL (`USE_OTEL`, `USE_INSECURE`) читаются через `os.getenv(..., False)` и трактуются как истинные при **любой непустой строке**, включая `"False"`. Чтобы отключить — переменную нужно не задавать вовсе.
|
||||
- `SERVICE_NAME` определяется дважды: как имя приложения (`base.py`, дефолт `eav`) и как имя сервиса в OTEL (дефолт `eav.eav-backend`) — фактически одна и та же переменная окружения.
|
||||
- В `docker-compose.yml` захардкожен пароль БД (`zealot096`) — только для локального окружения.
|
||||
|
||||
## Минимальный набор для локального запуска (`config.settings.production`)
|
||||
|
||||
- `DJANGO_SETTINGS_MODULE=config.settings.production`
|
||||
- `DJANGO_POSTGRES_HOST`, `DJANGO_POSTGRES_PORT`, `DJANGO_POSTGRES_DATABASE`, `DJANGO_POSTGRES_USER`, `DJANGO_POSTGRES_PASSWORD`
|
||||
- `JWT_PRIVATE_KEY`, `JWT_PUBLIC_KEY` (обязательны; можно тестовую RSA-пару)
|
||||
- `KAFKA_ENABLED=False` (чтобы не поднимать брокер) либо `KAFKA_HOST`/`KAFKA_USERNAME`/`KAFKA_PASSWORD`
|
||||
- при работе с файлами: `YC_S3_ACCESS_KEY_ID`, `YC_S3_SECRET_ACCESS_KEY`, `YC_S3_BUCKET_NAME`, `YC_S3_ENDPOINT_URL`
|
||||
- `USE_OTEL` — не задавать (иначе включится трейсинг)
|
||||
|
||||
Готовые значения-примеры для всех переменных приведены в `.env.example`.
|
||||
1303
apps/eav/openapi.yaml
Normal file
1303
apps/eav/openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user