iac/apps/rfi/openapi.yaml

1219 lines
46 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

openapi: 3.0.3
info:
title: RFI project API
version: "0.0.1"
description: |
REST API сервиса **rfi-backend** (`proc/rfi-backend`) — управление
запросами RFI (Request For Information), сообщениями к ним, статусами и
приоритетами (и их моделями), а также журналом изменений.
Сервис написан на Python (**Django 5.1 / Django REST Framework**). Роутинг
строится `DefaultRouter` (`src/config/api/v1/router.py`), в который
собираются роутеры приложений `request`, `message`, `status`, `priority`,
`change_history`. Все ресурсы доступны под префиксом `/api/v1/`
(`config/urls.py` → `config/api/urls.py` → `config/api/v1/urls.py`).
Схема OpenAPI генерируется `drf-spectacular` и доступна по `/api/schema/`,
Swagger UI — по `/api/schema/swagger-ui/`, ReDoc — по `/api/schema/redoc/`.
Django-admin — по `/admin/`.
### Аутентификация
Все эндпоинты требуют аутентификации (`DEFAULT_PERMISSION_CLASSES =
IsAuthenticated`). Режим задаётся переменной `JWT_AUTH_ENABLE`
(`config/settings/base.py`):
1. **JWT включён** (`JWT_AUTH_ENABLE=true`) — активны
`ZitadelJWTStatelessUserAuthentication` и `JWTStatelessUserAuthentication`.
Основной токен передаётся заголовком `Authorization: Bearer <jwt>`
(алгоритм `RS512`). Если дополнительно передан заголовок
`Identity: Bearer <jwt>`, полезная нагрузка берётся из него
(`urn:zitadel:iam:user:metadata`); подпись при этом сервисом не
проверяется (доверие обеспечивается сетевым слоем/Istio).
2. **JWT выключен** (`JWT_AUTH_ENABLE=false`) — `DefaultUserAuthentication`
подставляет демо-пользователя `DefaultUser` (только для локальной
разработки).
### Авторизация (права)
Эндпоинты `rfi` и `messages` используют `RFITokenBasedPermission`:
проверяется принадлежность к компании (`company_id ∈ user.company_ids`) и
модульные права из токена (`core.can_view_RFI`, `core.can_create_RFI`,
`core.can_edit_RFI`, `core.can_delete_RFI`, `core.can_copy_RFI`).
Редактирование доступно автору или ответственному (service account),
либо администратору.
### Пагинация
Списочные ответы используют `LimitOffsetPagination` (`PAGE_SIZE = 100`).
Ответ оборачивается в объект `{ count, next, previous, results }`.
Параметры — `limit` и `offset`.
### Фильтрация
Списки RFI поддерживают фильтрацию как через query-параметры (GET), так и
через тело запроса (POST `/api/v1/rfi/filter/`) — за счёт
`GetAndPostDjangoFilterBackend`. Дополнительно `CompanyFilterBackend`
неявно ограничивает выборку компаниями пользователя, если `company_id`
не передан явно.
### Особенности
- Удаление объектов — «мягкое» (soft-delete через `deleted_at`), базовый
класс `core.models.BaseModel`. `DELETE` возвращает `204`.
- Создание/обновление RFI принимает `multipart/form-data` (есть поле-файл
`image`); ответ формируется сериализатором чтения (`RFIRetrieveSerializer`).
- При создании RFI и сообщений отправляются уведомления (Celery-задачи),
если включено (`USE_ASYNC_FUNCTIONS`, `NOTIFICATIONS_ENABLE`).
contact:
name: rfi-backend
url: https://gitlab/proc/rfi-backend
servers:
- url: https://api.sarex.io/rfi
description: Production (ingress, префикс /rfi)
- url: https://stage-api.sarex.io/rfi
description: Stage (ingress, префикс /rfi)
- url: http://rfi-backend.rfi-prod.svc.cluster.local
description: Внутрикластерный адрес (ClusterIP, порт 80 → 8000)
- url: http://localhost:8000
description: Локальный запуск (uwsgi / runserver, порт 8000)
tags:
- name: rfi
description: Запросы RFI — CRUD, копирование, фильтрация, счётчики, история
- name: messages
description: Сообщения в запросах RFI
- name: statuses
description: Статусы
- name: status-models
description: Модели статусов
- name: priorities
description: Приоритеты
- name: priority-models
description: Модели приоритетов
- name: change-history
description: Журнал изменений RFI (только чтение)
security:
- bearerAuth: []
paths:
# ==========================================================================
# RFI
# ==========================================================================
/api/v1/rfi/:
get:
tags: [rfi]
summary: Список RFI
description: Постраничный список RFI с фильтрацией через query-параметры.
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: id, in: query, schema: { type: string }, description: "Один или несколько id (повторяющийся параметр)" }
- { name: resource_id, in: query, schema: { type: string }, description: "UUID ресурса (повторяющийся)" }
- { name: author_id, in: query, schema: { type: string } }
- { name: company_id, in: query, schema: { type: string } }
- { name: status_id, in: query, schema: { type: string } }
- { name: priority_id, in: query, schema: { type: string } }
- { name: responsible_users, in: query, schema: { type: string }, description: "UUID service account (overlap)" }
- { name: created_at_gte, in: query, schema: { type: string, format: date-time } }
- { name: created_at_lte, in: query, schema: { type: string, format: date-time } }
- { name: message_at_gte, in: query, schema: { type: string, format: date-time }, description: "Есть сообщение с created_at >= значения" }
- { name: message_at_lte, in: query, schema: { type: string, format: date-time } }
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/PaginatedRFIList"
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [rfi]
summary: Создать RFI
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/RFIWrite"
application/json:
schema:
$ref: "#/components/schemas/RFIWrite"
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/RFI" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/rfi/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [rfi]
summary: Получить RFI по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/RFI" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [rfi]
summary: Полное обновление RFI
requestBody:
required: true
content:
multipart/form-data:
schema: { $ref: "#/components/schemas/RFIWrite" }
application/json:
schema: { $ref: "#/components/schemas/RFIWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/RFI" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [rfi]
summary: Частичное обновление RFI
requestBody:
required: true
content:
multipart/form-data:
schema: { $ref: "#/components/schemas/RFIWrite" }
application/json:
schema: { $ref: "#/components/schemas/RFIWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/RFI" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [rfi]
summary: Удалить RFI (soft-delete)
responses:
"204": { description: Удалено }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
/api/v1/rfi/filter/:
post:
tags: [rfi]
summary: Список RFI по фильтру (POST)
description: |
Аналог `GET /api/v1/rfi/`, но фильтры передаются в теле запроса.
Тело валидируется `RFIFilterRequestSerializer`. Пагинация — через
query-параметры `limit`/`offset`.
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
requestBody:
required: false
content:
application/json:
schema: { $ref: "#/components/schemas/RFIFilterRequest" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedRFIList" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/rfi/history/:
get:
tags: [rfi]
summary: История изменений по списку RFI
description: |
Журнал изменений для всех RFI, попадающих под те же фильтры, что и
список (`GET /api/v1/rfi/`). Отсортирован по `created_at` убыв.
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: resource_id, in: query, schema: { type: string } }
- { name: company_id, in: query, schema: { type: string } }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedRFIChangeRecordList" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/rfi/{id}/history/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [rfi]
summary: История изменений одного RFI
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedRFIChangeRecordList" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
/api/v1/rfi/status-count/:
get:
tags: [rfi]
summary: Количество RFI по статусам
description: Для каждого `resource_id` возвращает его статусы и количество RFI.
parameters:
- { name: resource_id, in: query, schema: { type: string } }
- { name: company_id, in: query, schema: { type: string } }
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/RFIStatusCount" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [rfi]
summary: Количество RFI по статусам (POST-фильтр)
requestBody:
required: false
content:
application/json:
schema: { $ref: "#/components/schemas/RFIFilterRequest" }
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/RFIStatusCount" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/rfi/priority-count/:
get:
tags: [rfi]
summary: Количество RFI по приоритетам
parameters:
- { name: resource_id, in: query, schema: { type: string } }
- { name: company_id, in: query, schema: { type: string } }
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/RFIPriorityCount" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [rfi]
summary: Количество RFI по приоритетам (POST-фильтр)
requestBody:
required: false
content:
application/json:
schema: { $ref: "#/components/schemas/RFIFilterRequest" }
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items: { $ref: "#/components/schemas/RFIPriorityCount" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/rfi/{id}/copy/:
parameters:
- $ref: "#/components/parameters/PathId"
post:
tags: [rfi]
summary: Копировать RFI
description: Создаёт копию RFI с новым именем.
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/CopyName" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/RFI" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Messages
# ==========================================================================
/api/v1/messages/:
get:
tags: [messages]
summary: Список сообщений
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: id, in: query, schema: { type: string } }
- { name: author_id, in: query, schema: { type: string } }
- { name: request_id, in: query, schema: { type: string } }
- { name: reply_to_id, in: query, schema: { type: string } }
- { name: company_id, in: query, schema: { type: string } }
- { name: resource_id, in: query, schema: { type: string, format: uuid } }
- { name: created_at_gte, in: query, schema: { type: string, format: date-time } }
- { name: created_at_lte, in: query, schema: { type: string, format: date-time } }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedMessageList" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [messages]
summary: Создать сообщение
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/messages/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [messages]
summary: Сообщение по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/Message" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [messages]
summary: Обновить сообщение
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [messages]
summary: Частичное обновление сообщения (напр. отметить решением)
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/MessageWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [messages]
summary: Удалить сообщение (soft-delete)
responses:
"204": { description: Удалено }
"403": { $ref: "#/components/responses/Forbidden" }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Statuses
# ==========================================================================
/api/v1/statuses/:
get:
tags: [statuses]
summary: Список статусов
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: company_id, in: query, schema: { type: string } }
- { name: status_model, in: query, schema: { type: integer }, description: "id модели статусов (повторяющийся)" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedStatusList" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [statuses]
summary: Создать статус
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/statuses/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [statuses]
summary: Статус по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/Status" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [statuses]
summary: Обновить статус
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
"400": { $ref: "#/components/responses/ValidationError" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [statuses]
summary: Частичное обновление статуса
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/StatusCreate" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [statuses]
summary: Удалить статус (soft-delete)
responses:
"204": { description: Удалено }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Status models
# ==========================================================================
/api/v1/status-models/:
get:
tags: [status-models]
summary: Список моделей статусов
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: company_id, in: query, schema: { type: integer } }
- { name: resource_id, in: query, schema: { type: string, format: uuid } }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedStatusModelList" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [status-models]
summary: Создать модель статусов
description: |
При создании первой модели статусов для компании автоматически
создаётся набор статусов по умолчанию (`DEFAULT_STATUSES`).
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/status-models/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [status-models]
summary: Модель статусов по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelDetail" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [status-models]
summary: Обновить модель статусов
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [status-models]
summary: Частичное обновление модели статусов
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/StatusModelWrite" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [status-models]
summary: Удалить модель статусов (soft-delete)
responses:
"204": { description: Удалено }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Priorities
# ==========================================================================
/api/v1/priorities/:
get:
tags: [priorities]
summary: Список приоритетов
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: company_id, in: query, schema: { type: string } }
- { name: priority_model, in: query, schema: { type: integer }, description: "id модели приоритетов (повторяющийся)" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedPriorityList" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [priorities]
summary: Создать приоритет
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/priorities/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [priorities]
summary: Приоритет по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/Priority" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [priorities]
summary: Обновить приоритет
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [priorities]
summary: Частичное обновление приоритета
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityWrite" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [priorities]
summary: Удалить приоритет (soft-delete)
responses:
"204": { description: Удалено }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Priority models
# ==========================================================================
/api/v1/priority-models/:
get:
tags: [priority-models]
summary: Список моделей приоритетов
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: company_id, in: query, schema: { type: integer } }
- { name: resource_id, in: query, schema: { type: string, format: uuid } }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedPriorityModelList" }
"403": { $ref: "#/components/responses/Forbidden" }
post:
tags: [priority-models]
summary: Создать модель приоритетов
description: |
При создании первой модели приоритетов для компании автоматически
создаётся набор приоритетов по умолчанию (`DEFAULT_PRIORITIES`).
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
responses:
"201":
description: Создано
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
"400": { $ref: "#/components/responses/ValidationError" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/priority-models/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [priority-models]
summary: Модель приоритетов по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModel" }
"404": { $ref: "#/components/responses/NotFound" }
put:
tags: [priority-models]
summary: Обновить модель приоритетов
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
"404": { $ref: "#/components/responses/NotFound" }
patch:
tags: [priority-models]
summary: Частичное обновление модели приоритетов
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PriorityModelWrite" }
"404": { $ref: "#/components/responses/NotFound" }
delete:
tags: [priority-models]
summary: Удалить модель приоритетов (soft-delete)
responses:
"204": { description: Удалено }
"404": { $ref: "#/components/responses/NotFound" }
# ==========================================================================
# Change history (read-only)
# ==========================================================================
/api/v1/change-history/:
get:
tags: [change-history]
summary: Журнал изменений RFI
parameters:
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- { name: request_id, in: query, schema: { type: integer } }
- { name: resource_id, in: query, schema: { type: string, format: uuid } }
- { name: company_id, in: query, schema: { type: string } }
- { name: created_at_gte, in: query, schema: { type: string, format: date-time } }
- { name: created_at_lte, in: query, schema: { type: string, format: date-time } }
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/PaginatedRFIChangeRecordList" }
"403": { $ref: "#/components/responses/Forbidden" }
/api/v1/change-history/{id}/:
parameters:
- $ref: "#/components/parameters/PathId"
get:
tags: [change-history]
summary: Запись журнала изменений по id
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/RFIChangeRecord" }
"404": { $ref: "#/components/responses/NotFound" }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: |
`Authorization: Bearer <jwt>` (RS512). Опционально —
`Identity: Bearer <jwt>` для режима Zitadel.
parameters:
Limit:
name: limit
in: query
required: false
schema: { type: integer, default: 100 }
description: Количество элементов на странице (LimitOffsetPagination).
Offset:
name: offset
in: query
required: false
schema: { type: integer }
description: Смещение от начала выборки.
PathId:
name: id
in: path
required: true
schema: { type: integer }
responses:
ValidationError:
description: Ошибка валидации (DRF)
content:
application/json:
schema:
type: object
additionalProperties: true
example: { field_name: ["Обязательное поле."] }
Forbidden:
description: Недостаточно прав / не аутентифицирован
content:
application/json:
schema: { $ref: "#/components/schemas/Detail" }
NotFound:
description: Не найдено
content:
application/json:
schema: { $ref: "#/components/schemas/Detail" }
schemas:
Detail:
type: object
properties:
detail: { type: string }
Link:
type: object
properties:
name: { type: string, maxLength: 255 }
link: { type: string, format: uri }
required: [name, link]
RFI:
description: Чтение RFI (RFIListSerializer / RFIRetrieveSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string, maxLength: 255 }
author_id: { type: integer }
description: { type: string, nullable: true }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
responsible_users:
type: array
items: { type: string, format: uuid }
completion_date: { type: string, format: date-time, nullable: true }
image: { type: string, nullable: true, description: "URL изображения (по умолчанию rfi-default.png)" }
attributes: { type: object, additionalProperties: true }
links:
type: array
items: { $ref: "#/components/schemas/Link" }
created_at: { type: string, format: date-time, readOnly: true }
updated_at: { type: string, format: date-time, readOnly: true }
status: { type: integer, description: "id статуса (status_id)" }
priority: { type: integer, description: "id приоритета (priority_id)" }
RFIWrite:
description: Запись RFI (RFIWriteSerializer).
type: object
properties:
name: { type: string, maxLength: 255 }
author_id: { type: integer }
description: { type: string, nullable: true }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
responsible_users:
type: array
items: { type: string, format: uuid }
completion_date: { type: string, format: date-time, nullable: true }
image: { type: string, format: binary, nullable: true }
attributes: { type: object, additionalProperties: true, default: {} }
links:
type: array
items: { $ref: "#/components/schemas/Link" }
status_id: { type: integer, description: "PK статуса" }
priority_id: { type: integer, description: "PK приоритета" }
required: [name, author_id, company_id, responsible_users, status_id, priority_id]
RFIFilterRequest:
description: Тело фильтра для POST /rfi/filter/, /rfi/status-count/, /rfi/priority-count/.
type: object
properties:
id: { type: array, items: { type: string } }
resource_id: { type: array, items: { type: string } }
author_id: { type: array, items: { type: string } }
company_id: { type: integer }
status_id: { type: array, items: { type: string } }
priority_id: { type: array, items: { type: string } }
responsible_users: { type: array, items: { type: string } }
created_at_gte: { type: string, format: date-time }
created_at_lte: { type: string, format: date-time }
message_at_gte: { type: string, format: date-time }
message_at_lte: { type: string, format: date-time }
CopyName:
type: object
properties:
name: { type: string, maxLength: 255 }
required: [name]
Message:
description: Чтение сообщения (MessageReadSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
author_id: { type: integer }
text: { type: string }
is_solution: { type: boolean }
request: { type: integer, description: "id запроса (request_id)" }
reply_to: { type: integer, nullable: true, description: "id родительского сообщения" }
created_at: { type: string, format: date-time, readOnly: true }
updated_at: { type: string, format: date-time, readOnly: true }
MessageWrite:
description: Запись сообщения (MessageWriteSerializer).
type: object
properties:
author_id: { type: integer }
text: { type: string }
is_solution: { type: boolean }
request_id: { type: integer }
reply_to_id: { type: integer, nullable: true }
required: [author_id, text, request_id]
Status:
description: Чтение статуса (StatusReadSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string, maxLength: 512 }
label: { type: string, maxLength: 512 }
color: { type: string, description: "HEX-цвет", example: "#FFFFFF" }
status_model: { type: integer, description: "id модели статусов" }
StatusShort:
type: object
properties:
id: { type: integer }
name: { type: string }
label: { type: string }
color: { type: string }
StatusCreate:
description: Создание/обновление статуса (StatusCreateSerializer).
type: object
properties:
name: { type: string, maxLength: 512 }
label: { type: string, maxLength: 512 }
color: { type: string, example: "#FFFFFF" }
status_model: { type: integer, description: "PK модели статусов" }
required: [name, label, status_model]
StatusModelList:
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string, maxLength: 512 }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
statuses:
type: array
items: { $ref: "#/components/schemas/StatusShort" }
StatusModelDetail:
allOf:
- $ref: "#/components/schemas/StatusModelList"
- type: object
properties:
created_at: { type: string, format: date-time, readOnly: true }
updated_at: { type: string, format: date-time, readOnly: true }
StatusModelWrite:
type: object
properties:
name: { type: string, maxLength: 512 }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
required: [name, company_id]
Priority:
description: Чтение приоритета (PriorityReadSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string, maxLength: 512 }
label: { type: string, maxLength: 512 }
color: { type: string, example: "#FFFFFF" }
priority_model: { type: integer, description: "id модели приоритетов" }
PriorityShort:
type: object
properties:
id: { type: integer }
name: { type: string }
label: { type: string }
color: { type: string }
PriorityWrite:
description: Создание/обновление приоритета (PriorityWriteSerializer).
type: object
properties:
name: { type: string, maxLength: 512 }
label: { type: string, maxLength: 512 }
color: { type: string, example: "#FFFFFF" }
priority_model_id: { type: integer, description: "PK модели приоритетов (write-only)" }
required: [name, label, priority_model_id]
PriorityModel:
description: Чтение модели приоритетов (PriorityModelReadSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
name: { type: string, maxLength: 512 }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
priorities:
type: array
items: { $ref: "#/components/schemas/PriorityShort" }
PriorityModelWrite:
type: object
properties:
name: { type: string, maxLength: 512 }
company_id: { type: integer }
resource_id: { type: string, format: uuid, nullable: true }
required: [name, company_id]
CountItem:
type: object
properties:
id: { type: integer }
name: { type: string }
label: { type: string }
color: { type: string }
count: { type: integer }
RFIStatusCount:
type: object
properties:
resource_id: { type: string, format: uuid }
statuses:
type: array
items: { $ref: "#/components/schemas/CountItem" }
RFIPriorityCount:
type: object
properties:
resource_id: { type: string, format: uuid }
priorities:
type: array
items: { $ref: "#/components/schemas/CountItem" }
RFIChangeRecord:
description: Запись журнала изменений (RFIChangeRecordSerializer).
type: object
properties:
id: { type: integer, readOnly: true }
created_at: { type: string, format: date-time, readOnly: true }
request_id: { type: integer, readOnly: true }
created_by: { type: integer, description: "ID пользователя, совершившего изменение" }
field_name: { type: string, maxLength: 128 }
attribute_name: { type: string, maxLength: 256, nullable: true }
was: { type: string, description: "Старое значение (raw)" }
became: { type: string, description: "Новое значение (raw)" }
was_text: { type: string, description: "Человекочитаемое старое значение" }
became_text: { type: string, description: "Человекочитаемое новое значение" }
# ---- Пагинированные обёртки (LimitOffsetPagination) ----
PaginatedBase:
type: object
properties:
count: { type: integer }
next: { type: string, format: uri, nullable: true }
previous: { type: string, format: uri, nullable: true }
PaginatedRFIList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/RFI" }
PaginatedMessageList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/Message" }
PaginatedStatusList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/Status" }
PaginatedStatusModelList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/StatusModelList" }
PaginatedPriorityList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/Priority" }
PaginatedPriorityModelList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/PriorityModel" }
PaginatedRFIChangeRecordList:
allOf:
- $ref: "#/components/schemas/PaginatedBase"
- type: object
properties:
results:
type: array
items: { $ref: "#/components/schemas/RFIChangeRecord" }