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 `. Поддерживаются два режима: 1. **Zitadel** — если передан дополнительный заголовок `identity` (`Identity `), полезная нагрузка (`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 `. Алгоритм `RS512`, ключ `JWT_AUTH_PUBLIC_KEY`. Разбор выполняется без проверки подписи (`verify_signature=False`). identityToken: type: apiKey in: header name: identity description: | Опциональный заголовок `identity` (`Identity `) для режима 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]