1171 lines
44 KiB
YAML
1171 lines
44 KiB
YAML
openapi: 3.0.3
|
||
|
||
info:
|
||
title: iams-v2 API
|
||
version: "1.0.0"
|
||
description: |
|
||
REST API сервиса **iams** (`platform/iams-v2`) — управление пользователями,
|
||
ресурсами/проектами, оргструктурой (должности, подразделения, группы) и
|
||
правами доступа (IAM).
|
||
|
||
Сервис написан на Go (**Fiber**). Приложение собирается в
|
||
`internal/controller/http/server.go` (`NewServer`). Роутинг состоит из двух
|
||
контуров:
|
||
|
||
- **внутренний** — группы `/api/v0`, `/api/admin/v0`, `/api/v1`, `/api/v2`;
|
||
аутентификации на уровне приложения нет, доступ ограничивается сетевым
|
||
слоем (через ingress не публикуется);
|
||
- **внешний** — группы `/external/api/v0`, `/external/api/admin/v0`,
|
||
`/external/api/v1`, `/external/api/v2`; регистрируется только при
|
||
`AUTH_ENABLED=true`, публикует подмножество внутренних маршрутов (часть —
|
||
только на чтение).
|
||
|
||
Health-check доступен по `GET /api/health`.
|
||
|
||
### Аутентификация
|
||
Внешние эндпоинты требуют заголовок `Authorization: Bearer <jwt>`
|
||
(`internal/controller/http/jwt/parser.go`). **Подпись токена не проверяется** —
|
||
доверие делегируется вышестоящему gateway/virtual service. Схема разбора
|
||
выбирается по заголовку `Identity`:
|
||
|
||
1. заголовок `Identity` отсутствует/пуст → legacy-формат (Django SimpleJWT);
|
||
2. заголовок `Identity` задан → Zitadel (клейм
|
||
`urn:zitadel:iam:user:metadata`, значения полей — base64).
|
||
|
||
Внутренние эндпоинты (`/api/*`) аутентификации на уровне приложения не требуют.
|
||
|
||
### Пагинация
|
||
Списочные ответы используют `limit`/`offset` (по умолчанию `limit=1000`) и
|
||
оборачиваются в `{ count, results }`.
|
||
|
||
### Обработка ошибок
|
||
Ошибки возвращаются как JSON `{ error, id }` (`httpx.Response`). Доменный
|
||
`Kind` маппится на HTTP-статус (`httpx/error_resolve.go`):
|
||
`InvalidArgument`/`OutOfRange` → `400`, `Unauthenticated` → `401`,
|
||
`PermissionDenied` → `403`, `NotFound` → `404`,
|
||
`Aborted`/`AlreadyExists` → `409`, `PreconditionFailed` → `412`,
|
||
`ResourceExhausted` → `429`, `Unavailable` → `503`,
|
||
`Internal`/`Unknown`/`DataLoss` → `500`.
|
||
|
||
servers:
|
||
- url: /api
|
||
description: Внутренний контур (без auth)
|
||
- url: /external/api
|
||
description: Внешний контур (JWT, только при AUTH_ENABLED=true)
|
||
|
||
tags:
|
||
- name: health
|
||
- name: users
|
||
- name: permission-check
|
||
- name: groups
|
||
- name: positions
|
||
- name: departments
|
||
- name: django-permissions
|
||
- name: resource-types
|
||
- name: resources-v0
|
||
- name: projects-v0
|
||
- name: widgets-v0
|
||
- name: resources
|
||
- name: widgets
|
||
- name: resource-permissions
|
||
- name: company-resource-permissions
|
||
- name: service-accounts
|
||
|
||
paths:
|
||
/health:
|
||
get:
|
||
tags: [health]
|
||
summary: Health-check
|
||
description: Проверка БД и (при включении) Kafka. Обслуживается как `/api/health`.
|
||
security: []
|
||
responses:
|
||
"200":
|
||
description: Сервис доступен
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
status: { type: string, example: ok }
|
||
"503":
|
||
description: Недоступна зависимость (БД/Kafka)
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
status: { type: string, example: unhealthy }
|
||
error: { type: string, example: database_unavailable }
|
||
|
||
/v0/users:
|
||
get:
|
||
tags: [users]
|
||
summary: Список пользователей
|
||
parameters:
|
||
- $ref: "#/components/parameters/Limit"
|
||
- $ref: "#/components/parameters/Offset"
|
||
- { name: username, in: query, schema: { type: string } }
|
||
- { name: search, in: query, schema: { type: string } }
|
||
- { name: is_active, in: query, schema: { type: boolean } }
|
||
- { name: is_staff, in: query, schema: { type: boolean } }
|
||
- { name: is_superuser, in: query, schema: { type: boolean } }
|
||
- { name: service_account_id, in: query, schema: { type: string, format: uuid } }
|
||
- { name: id, in: query, description: "CSV из id", schema: { type: string } }
|
||
- { name: company_id, in: query, schema: { type: integer } }
|
||
- { name: departments, in: query, description: "CSV", schema: { type: string } }
|
||
- { name: positions, in: query, description: "CSV", schema: { type: string } }
|
||
- { name: groups, in: query, description: "CSV", schema: { type: string } }
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
count: { type: integer }
|
||
results:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/User" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [users]
|
||
summary: Создать пользователя
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/UserCreateInput" }
|
||
responses:
|
||
"201":
|
||
description: Создан
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/User" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
patch:
|
||
tags: [users]
|
||
summary: Массовое обновление пользователей (legacy bulk_update)
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/UserBulkPatchItem" }
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/users/{id}:
|
||
parameters:
|
||
- { name: id, in: path, required: true, schema: { type: integer, format: int64 } }
|
||
get:
|
||
tags: [users]
|
||
summary: Пользователь по id
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/User" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
put:
|
||
tags: [users]
|
||
summary: Полное обновление пользователя
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/UserUpdateInput" }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
patch:
|
||
tags: [users]
|
||
summary: Частичное обновление пользователя
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { type: object }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
delete:
|
||
tags: [users]
|
||
summary: Удалить пользователя
|
||
responses:
|
||
"204": { description: Удалён }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/admin/v0/users/activation:
|
||
post:
|
||
tags: [users]
|
||
summary: Активация/деактивация пользователей компании
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/CompanyUserActivationItem" }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v1/users-with-resources:
|
||
post:
|
||
tags: [users]
|
||
summary: Пользователи с их ресурсами
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [tenant_id, id]
|
||
properties:
|
||
tenant_id: { type: integer, format: int64 }
|
||
id:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v1/users-grouped-by-resource/:
|
||
get:
|
||
tags: [users]
|
||
summary: Пользователи, сгруппированные по ресурсу
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/permissions-check:
|
||
post:
|
||
tags: [permission-check]
|
||
summary: Проверка прав пользователя
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/UserPermissionCheckInput" }
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/UserPermissionCheckOutput" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/groups:
|
||
get:
|
||
tags: [groups]
|
||
summary: Список групп
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [groups]
|
||
summary: Создать группу
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/GroupCreateInput" }
|
||
responses:
|
||
"201": { description: Создана }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/groups/search:
|
||
post:
|
||
tags: [groups]
|
||
summary: Поиск групп
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { type: object }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/groups/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [groups]
|
||
summary: Группа по id
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
put:
|
||
tags: [groups]
|
||
summary: Обновить группу
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/GroupUpdateInput" } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
patch:
|
||
tags: [groups]
|
||
summary: Частично обновить группу
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { type: object } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
delete:
|
||
tags: [groups]
|
||
summary: Удалить группу
|
||
responses:
|
||
"204": { description: Удалена }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/positions:
|
||
get:
|
||
tags: [positions]
|
||
summary: Список должностей
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [positions]
|
||
summary: Создать должность
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/PositionCreateInput" } } }
|
||
responses:
|
||
"201": { description: Создана }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/positions/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [positions]
|
||
summary: Должность по id
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
put:
|
||
tags: [positions]
|
||
summary: Обновить должность
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/PositionUpdateInput" } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
patch:
|
||
tags: [positions]
|
||
summary: Частично обновить должность
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
delete:
|
||
tags: [positions]
|
||
summary: Удалить должность
|
||
responses:
|
||
"204": { description: Удалена }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/departments:
|
||
get:
|
||
tags: [departments]
|
||
summary: Список подразделений
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [departments]
|
||
summary: Создать подразделение
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/DepartmentCreateInput" } } }
|
||
responses:
|
||
"201": { description: Создано }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/departments/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [departments]
|
||
summary: Подразделение по id
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
put:
|
||
tags: [departments]
|
||
summary: Обновить подразделение
|
||
requestBody:
|
||
required: true
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/DepartmentUpdateInput" } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
patch:
|
||
tags: [departments]
|
||
summary: Частично обновить подразделение
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
delete:
|
||
tags: [departments]
|
||
summary: Удалить подразделение
|
||
responses:
|
||
"204": { description: Удалено }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/permissions:
|
||
get:
|
||
tags: [django-permissions]
|
||
summary: Список permission'ов
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [django-permissions]
|
||
summary: Создать permission
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [name, codename]
|
||
properties:
|
||
name: { type: string }
|
||
codename: { type: string }
|
||
responses:
|
||
"201": { description: Создан }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/permissions/search:
|
||
post:
|
||
tags: [django-permissions]
|
||
summary: Поиск permission'ов
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/permissions/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [django-permissions]
|
||
summary: Permission по id
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
delete:
|
||
tags: [django-permissions]
|
||
summary: Удалить permission
|
||
responses:
|
||
"204": { description: Удалён }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/resource-types:
|
||
get:
|
||
tags: [resource-types]
|
||
summary: Список типов ресурсов
|
||
responses:
|
||
"200": { description: OK }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [resource-types]
|
||
summary: Создать тип
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [name]
|
||
properties:
|
||
name: { type: string }
|
||
parent_id: { type: integer, format: int64, nullable: true }
|
||
responses:
|
||
"201": { description: Создан }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v0/resource-types/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [resource-types]
|
||
summary: Тип по id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
put:
|
||
tags: [resource-types]
|
||
summary: Обновить тип
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [resource-types]
|
||
summary: Частично обновить тип
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [resource-types]
|
||
summary: Удалить тип
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/resources:
|
||
get:
|
||
tags: [resources-v0]
|
||
summary: Список ресурсов (v0)
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
count: { type: integer }
|
||
results:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/ResourceV0Output" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [resources-v0]
|
||
summary: Создать ресурс
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [name, tenant_id]
|
||
properties:
|
||
name: { type: string }
|
||
parent_id: { type: string, nullable: true }
|
||
type_id: { type: integer, format: int64 }
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
created_by: { type: integer, format: int64 }
|
||
responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/resources/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: string } } ]
|
||
get:
|
||
tags: [resources-v0]
|
||
summary: Ресурс по id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
put:
|
||
tags: [resources-v0]
|
||
summary: Обновить ресурс
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [resources-v0]
|
||
summary: Частично обновить ресурс
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [resources-v0]
|
||
summary: Удалить ресурс
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/projects:
|
||
get:
|
||
tags: [projects-v0]
|
||
summary: Список проектов
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
post:
|
||
tags: [projects-v0]
|
||
summary: Создать проект
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/projects/{publicID}:
|
||
parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ]
|
||
get:
|
||
tags: [projects-v0]
|
||
summary: Проект по public id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
put:
|
||
tags: [projects-v0]
|
||
summary: Обновить проект
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [projects-v0]
|
||
summary: Частично обновить проект
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [projects-v0]
|
||
summary: Удалить проект
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/widgets:
|
||
get:
|
||
tags: [widgets-v0]
|
||
summary: Список виджетов
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
post:
|
||
tags: [widgets-v0]
|
||
summary: Создать виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v0/widgets/{publicID}:
|
||
parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ]
|
||
get:
|
||
tags: [widgets-v0]
|
||
summary: Виджет по public id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
put:
|
||
tags: [widgets-v0]
|
||
summary: Обновить виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [widgets-v0]
|
||
summary: Частично обновить виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [widgets-v0]
|
||
summary: Удалить виджет
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/resource:
|
||
get:
|
||
tags: [resources]
|
||
summary: Список ресурсов (v1)
|
||
parameters:
|
||
- $ref: "#/components/parameters/Limit"
|
||
- $ref: "#/components/parameters/Offset"
|
||
- { name: parent_id, in: query, schema: { type: string, format: uuid } }
|
||
- { name: type, in: query, schema: { type: integer } }
|
||
- { name: tenant_id, in: query, description: "CSV", schema: { type: string } }
|
||
- { name: name, in: query, schema: { type: string } }
|
||
- { name: code, in: query, schema: { type: string } }
|
||
- { name: public_id, in: query, schema: { type: string, format: uuid } }
|
||
- { name: target_id, in: query, schema: { type: integer } }
|
||
- { name: service_accounts, in: query, description: "CSV из uuid", schema: { type: string } }
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
count: { type: integer }
|
||
results:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/Resource" }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
post:
|
||
tags: [resources]
|
||
summary: Создать ресурс
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/ResourceCreateInput" }
|
||
responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/resource/{publicID}:
|
||
parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ]
|
||
get:
|
||
tags: [resources]
|
||
summary: Ресурс по public id
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/Resource" } } }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
put:
|
||
tags: [resources]
|
||
summary: Обновить ресурс
|
||
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/ResourceUpdateInput" } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [resources]
|
||
summary: Частично обновить ресурс
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [resources]
|
||
summary: Удалить ресурс
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/targets/{target_id}/resource:
|
||
parameters: [ { name: target_id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [resources]
|
||
summary: Ресурс по target id (только внутренний контур)
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content: { application/json: { schema: { $ref: "#/components/schemas/Resource" } } }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v1/resources-grouped-by-sa:
|
||
get:
|
||
tags: [resources]
|
||
summary: Ресурсы, сгруппированные по сервисному аккаунту
|
||
description: Регистрируется, только если включён соответствующий use case.
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/service-accounts:
|
||
get:
|
||
tags: [service-accounts]
|
||
summary: Список сервисных аккаунтов
|
||
responses:
|
||
"200":
|
||
description: OK
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
properties:
|
||
count: { type: integer }
|
||
results:
|
||
type: array
|
||
items: { type: string, format: uuid }
|
||
default: { $ref: "#/components/responses/Error" }
|
||
|
||
/v2/resource:
|
||
get:
|
||
tags: [widgets]
|
||
summary: Список виджетов (v2)
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
post:
|
||
tags: [widgets]
|
||
summary: Создать виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/WidgetCreateInput" } } } }
|
||
responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v2/resource/{publicID}:
|
||
parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ]
|
||
get:
|
||
tags: [widgets]
|
||
summary: Виджет по public id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
put:
|
||
tags: [widgets]
|
||
summary: Обновить виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
patch:
|
||
tags: [widgets]
|
||
summary: Частично обновить виджет
|
||
requestBody: { required: true, content: { application/json: { schema: { type: object } } } }
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [widgets]
|
||
summary: Удалить виджет
|
||
responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/resource_permission:
|
||
get:
|
||
tags: [resource-permissions]
|
||
summary: Список прав на ресурсы
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
post:
|
||
tags: [resource-permissions]
|
||
summary: Создать права (пакет)
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [items]
|
||
properties:
|
||
items:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/ResourcePermissionCreateInput" }
|
||
responses: { "201": { description: Создано }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/resource_permission/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [resource-permissions]
|
||
summary: Право по id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [resource-permissions]
|
||
summary: Удалить право
|
||
responses: { "204": { description: Удалено }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/permissions-bulk:
|
||
patch:
|
||
tags: [resource-permissions]
|
||
summary: Массовое изменение прав
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [tenant_id, permissions]
|
||
properties:
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
permissions:
|
||
type: object
|
||
description: "map[resource_uuid][]permission_uuid"
|
||
additionalProperties:
|
||
type: array
|
||
items: { type: string, format: uuid }
|
||
unrestricted_permissions:
|
||
type: object
|
||
additionalProperties: true
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/company_resource_permission:
|
||
get:
|
||
tags: [company-resource-permissions]
|
||
summary: Список прав компании
|
||
parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ]
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
post:
|
||
tags: [company-resource-permissions]
|
||
summary: Создать права компании (пакет)
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
type: object
|
||
required: [items]
|
||
properties:
|
||
items:
|
||
type: array
|
||
items: { $ref: "#/components/schemas/CompanyResourcePermissionCreateInput" }
|
||
responses: { "201": { description: Создано }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
/v1/company_resource_permission/{id}:
|
||
parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ]
|
||
get:
|
||
tags: [company-resource-permissions]
|
||
summary: Право компании по id
|
||
responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } }
|
||
delete:
|
||
tags: [company-resource-permissions]
|
||
summary: Удалить право компании
|
||
responses: { "204": { description: Удалено }, default: { $ref: "#/components/responses/Error" } }
|
||
|
||
components:
|
||
securitySchemes:
|
||
bearerAuth:
|
||
type: http
|
||
scheme: bearer
|
||
bearerFormat: JWT
|
||
description: |
|
||
`Authorization: Bearer <jwt>`. Требуется во внешнем контуре
|
||
(`/external/api/*`). Дополнительный заголовок `Identity` переключает
|
||
разбор в режим Zitadel. Подпись не проверяется приложением.
|
||
|
||
parameters:
|
||
Limit:
|
||
name: limit
|
||
in: query
|
||
description: Размер страницы (по умолчанию 1000)
|
||
schema: { type: integer, default: 1000, minimum: 1 }
|
||
Offset:
|
||
name: offset
|
||
in: query
|
||
schema: { type: integer, default: 0, minimum: 0 }
|
||
|
||
responses:
|
||
Error:
|
||
description: Ошибка
|
||
content:
|
||
application/json:
|
||
schema: { $ref: "#/components/schemas/ApiError" }
|
||
|
||
schemas:
|
||
ApiError:
|
||
type: object
|
||
properties:
|
||
error: { type: string, description: Человекочитаемое сообщение }
|
||
id: { type: string, description: Стабильный slug доменной ошибки }
|
||
|
||
User:
|
||
type: object
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
username: { type: string }
|
||
email: { type: string, format: email }
|
||
first_name: { type: string }
|
||
last_name: { type: string }
|
||
is_staff: { type: boolean }
|
||
is_active: { type: boolean }
|
||
is_superuser: { type: boolean }
|
||
job_title: { type: string }
|
||
companies:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
|
||
UserCreateInput:
|
||
type: object
|
||
required: [username, email, first_name, last_name]
|
||
properties:
|
||
username: { type: string }
|
||
email: { type: string, format: email }
|
||
first_name: { type: string }
|
||
last_name: { type: string }
|
||
is_staff: { type: boolean }
|
||
is_active: { type: boolean }
|
||
is_superuser: { type: boolean }
|
||
job_title: { type: string }
|
||
companies:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
departments:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
groups:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
enable_notifications: { type: boolean }
|
||
|
||
UserUpdateInput:
|
||
type: object
|
||
required: [username, email]
|
||
properties:
|
||
username: { type: string }
|
||
email: { type: string, format: email }
|
||
first_name: { type: string }
|
||
last_name: { type: string }
|
||
is_staff: { type: boolean }
|
||
is_active: { type: boolean }
|
||
is_superuser: { type: boolean }
|
||
job_title: { type: string }
|
||
companies:
|
||
type: array
|
||
items: { type: integer, format: int64 }
|
||
|
||
UserBulkPatchItem:
|
||
type: object
|
||
required: [id]
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
username: { type: string }
|
||
email: { type: string, format: email }
|
||
first_name: { type: string }
|
||
last_name: { type: string }
|
||
is_staff: { type: boolean }
|
||
is_active: { type: boolean }
|
||
is_superuser: { type: boolean }
|
||
job_title: { type: string }
|
||
companies: { type: array, items: { type: integer, format: int64 } }
|
||
departments: { type: array, items: { type: integer, format: int64 } }
|
||
groups: { type: array, items: { type: integer, format: int64 } }
|
||
enable_notifications: { type: boolean }
|
||
|
||
CompanyUserActivationItem:
|
||
type: object
|
||
required: [user_id, company_id]
|
||
properties:
|
||
user_id: { type: integer, format: int64, minimum: 1 }
|
||
company_id: { type: integer, format: int64, minimum: 1 }
|
||
is_active: { type: boolean }
|
||
|
||
UserPermissionCheckInput:
|
||
type: object
|
||
required: [user_id, checks]
|
||
properties:
|
||
user_id: { type: integer, format: int64, minimum: 1 }
|
||
tenant_id: { type: integer, format: int64 }
|
||
checks:
|
||
type: array
|
||
minItems: 1
|
||
maxItems: 100
|
||
items:
|
||
type: object
|
||
required: [permission]
|
||
properties:
|
||
permission: { type: string }
|
||
public_resource_id: { type: string, format: uuid, nullable: true }
|
||
|
||
UserPermissionCheckOutput:
|
||
type: object
|
||
properties:
|
||
results:
|
||
type: array
|
||
items:
|
||
type: object
|
||
properties:
|
||
permission: { type: string }
|
||
public_resource_id: { type: string, format: uuid, nullable: true }
|
||
allowed: { type: boolean }
|
||
reason: { type: string }
|
||
|
||
GroupCreateInput:
|
||
type: object
|
||
required: [name]
|
||
properties:
|
||
name: { type: string }
|
||
description: { type: string }
|
||
is_public: { type: boolean }
|
||
company_id: { type: integer, format: int64, nullable: true }
|
||
permission_ids: { type: array, items: { type: integer, format: int64 } }
|
||
|
||
GroupUpdateInput:
|
||
allOf:
|
||
- $ref: "#/components/schemas/GroupCreateInput"
|
||
- type: object
|
||
required: [id]
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
|
||
PositionCreateInput:
|
||
type: object
|
||
required: [name, company_id]
|
||
properties:
|
||
name: { type: string }
|
||
description: { type: string }
|
||
company_id: { type: integer, format: int64, minimum: 1 }
|
||
users: { type: array, items: { type: integer, format: int64 } }
|
||
groups: { type: array, items: { type: integer, format: int64 } }
|
||
|
||
PositionUpdateInput:
|
||
allOf:
|
||
- $ref: "#/components/schemas/PositionCreateInput"
|
||
- type: object
|
||
required: [id]
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
|
||
DepartmentCreateInput:
|
||
type: object
|
||
required: [name, company_id]
|
||
properties:
|
||
name: { type: string }
|
||
description: { type: string }
|
||
company_id: { type: integer, format: int64, minimum: 1 }
|
||
legal_entity: { type: string }
|
||
contractor: { type: object, nullable: true }
|
||
users: { type: array, items: { type: integer, format: int64 } }
|
||
groups: { type: array, items: { type: integer, format: int64 } }
|
||
|
||
DepartmentUpdateInput:
|
||
allOf:
|
||
- $ref: "#/components/schemas/DepartmentCreateInput"
|
||
- type: object
|
||
required: [id]
|
||
properties:
|
||
id: { type: integer, format: int64 }
|
||
|
||
Resource:
|
||
type: object
|
||
properties:
|
||
public_id: { type: string, format: uuid }
|
||
parent_id: { type: string, format: uuid, nullable: true }
|
||
type_id: { type: integer, format: int64 }
|
||
tenant_id: { type: integer, format: int64 }
|
||
name: { type: string }
|
||
description: { type: string }
|
||
code: { type: string }
|
||
target_id: { type: integer, format: int64 }
|
||
show_in_overview: { type: boolean }
|
||
location_verbose: { type: string }
|
||
latitude: { type: number, format: double }
|
||
longitude: { type: number, format: double }
|
||
widgets: { type: object, additionalProperties: true }
|
||
attributes: { type: object, additionalProperties: true }
|
||
meta: { type: object, additionalProperties: true }
|
||
|
||
ResourceCreateInput:
|
||
type: object
|
||
required: [name, tenant_id]
|
||
properties:
|
||
parent_id: { type: string, format: uuid, nullable: true, description: "public_id родителя" }
|
||
name: { type: string }
|
||
type_id: { type: integer, format: int64, description: "0/опущено — тип «Проект»" }
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
created_by: { type: integer, format: int64 }
|
||
target_id: { type: integer, format: int64 }
|
||
code: { type: string }
|
||
|
||
ResourceUpdateInput:
|
||
type: object
|
||
required: [name, tenant_id]
|
||
properties:
|
||
parent_id: { type: string, format: uuid, nullable: true }
|
||
type_id: { type: integer, format: int64 }
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
name: { type: string }
|
||
description: { type: string }
|
||
code: { type: string }
|
||
target_id: { type: integer, format: int64 }
|
||
planning_widget_id: { type: integer, format: int64, nullable: true }
|
||
work_schedule_project_id: { type: integer, format: int64, nullable: true }
|
||
show_in_overview: { type: boolean }
|
||
location_verbose: { type: string }
|
||
latitude: { type: number, format: double, minimum: -90, maximum: 90 }
|
||
longitude: { type: number, format: double, minimum: -180, maximum: 180 }
|
||
widgets: { type: object, additionalProperties: true }
|
||
attributes: { type: object, additionalProperties: true }
|
||
meta: { type: object, additionalProperties: true }
|
||
|
||
ResourceV0Output:
|
||
type: object
|
||
properties:
|
||
id: { type: string }
|
||
public_id: { type: string }
|
||
parent_id: { type: string, nullable: true }
|
||
path: { type: string }
|
||
type_id: { type: integer, format: int64 }
|
||
tenant_id: { type: integer, format: int64 }
|
||
name: { type: string }
|
||
created_by: { type: integer, format: int64 }
|
||
created_at: { type: string }
|
||
updated_at: { type: string }
|
||
|
||
WidgetCreateInput:
|
||
type: object
|
||
required: [name, tenant_id]
|
||
properties:
|
||
parent_id: { type: string, format: uuid, nullable: true }
|
||
type_id: { type: integer, format: int64 }
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
name: { type: string }
|
||
description: { type: string }
|
||
code: { type: string }
|
||
target_id: { type: integer, format: int64 }
|
||
show_in_overview: { type: boolean }
|
||
latitude: { type: number, format: double, minimum: -90, maximum: 90 }
|
||
longitude: { type: number, format: double, minimum: -180, maximum: 180 }
|
||
coordinate_system: { type: integer, format: int64 }
|
||
widgets: { type: object, additionalProperties: true }
|
||
attributes: { type: object, additionalProperties: true }
|
||
meta: { type: object, additionalProperties: true }
|
||
|
||
ResourcePermissionCreateInput:
|
||
type: object
|
||
required: [public_resource_id, service_account]
|
||
properties:
|
||
public_resource_id: { type: string, format: uuid }
|
||
type: { type: integer, format: int64, minimum: 0 }
|
||
service_account: { type: string, format: uuid }
|
||
created_by: { type: integer, format: int64 }
|
||
|
||
CompanyResourcePermissionCreateInput:
|
||
type: object
|
||
required: [tenant_id, service_account]
|
||
properties:
|
||
tenant_id: { type: integer, format: int64, minimum: 1 }
|
||
service_account: { type: string, format: uuid }
|
||
created_by: { type: integer, format: int64 }
|
||
|
||
security:
|
||
- bearerAuth: []
|