Add OpenAPI specification for system-log service.

This commit is contained in:
emelinda 2026-07-13 20:51:21 +03:00
parent 5f7fe11748
commit f184c7b8d9

View File

@ -0,0 +1,427 @@
openapi: 3.0.3
info:
title: system-log API
version: "1.0.0"
description: |
REST API сервиса **system-log** (`platform/system-log`) — приём и выборка
записей журнала системных событий (кто, над чем и какое действие совершил).
Сервис написан на Go (фреймворк **Fiber v2**). Роутинг собирается в
`internal/controller/http/v0`: базовая группа `/api` → подгруппа `v0` →
ресурс `/system_log`. Плюс служебный эндпоинт `/ping`.
### Аутентификация
Аутентификация на уровне приложения не настроена — эндпоинты доступны без
авторизации. Ограничение доступа обеспечивается сетевым слоем (ClusterIP /
Istio), а не самим сервисом.
### Пагинация
Списочные ответы возвращают объект `{ count, limit, offset, next, prev, result }`.
Управление — параметрами `limit` и `offset`. Значения по умолчанию:
`limit = 100`, `offset = 0` (константы `_defaultLimit` / `_defaultOffset`
в `internal/controller/http/v0/systemlog/controller.go`). Ссылки `next`/`prev`
формируются в `pkg/pagination`. У эндпоинта поиска (`POST /search`) полей
`next`/`prev` нет.
### Фильтрация
Массивные фильтры (`actor_ids`, `event_names`, `model_names`, `instance_ids`,
`target_ids`, `company_ids`, `statuses`, `message`) в GET-запросе передаются
списком значений через запятую и парсятся Fiber `QueryParser`. Поля
`instance_uuids` и `resource_uuids` разбираются вручную (`strings.Split` +
`uuid.Parse`) — при невалидном UUID возвращается `400`.
### Замечания (расхождения кода)
- `metadata` в фильтре объявлено как `map[string]interface{}` с query-тегом,
но при передаче в query-строке Fiber не восстановит вложенный объект —
фильтрация по `metadata` практически применима через тело `POST /search`.
- Поле `is_processed_by_worker` присутствует в фильтре, но отдаётся в выборку
наравне с остальными; используется воркером обогащения.
- Ответ `POST /system_log/` при успехе — простая строка `"OK"` (не объект).
servers:
- url: https://api.sarex.io
description: Production (ingress)
- url: https://stage-api.sarex.io
description: Stage (ingress)
- url: http://backend-svc.system-log.svc.cluster.local
description: Внутрикластерный адрес (ClusterIP, порт 80 → 8000)
- url: http://localhost:8888
description: Локальный запуск (config.env)
tags:
- name: system_log
description: Записи журнала системных событий
- name: service
description: Служебные эндпоинты
paths:
# ==========================================================================
# Service
# ==========================================================================
/ping:
get:
tags: [service]
summary: Проверка живости
description: Liveness/readiness-проба (используется в probes деплоймента). Всегда `200 OK` без тела.
operationId: ping
security: []
responses:
'200':
description: OK
# ==========================================================================
# System log
# ==========================================================================
/api/v0/system_log/:
get:
tags: [system_log]
summary: Список записей журнала (с фильтрами)
description: |
Возвращает отфильтрованные записи журнала с пагинацией.
Массивные параметры принимают список значений через запятую.
operationId: getSystemLogRecords
parameters:
- $ref: '#/components/parameters/Limit'
- $ref: '#/components/parameters/Offset'
- name: actor_ids
in: query
description: ID авторов событий; список значений через запятую
example: "1,2,3"
schema: { type: string }
- name: event_names
in: query
description: Названия событий (напр. create, edit, delete, transfer); через запятую
example: "create,edit,delete"
schema: { type: string }
- name: model_names
in: query
description: Названия моделей (напр. document, bundle, project); через запятую
example: "document,bundle"
schema: { type: string }
- name: instance_ids
in: query
description: ID сущностей; список значений через запятую
example: "1,2,3"
schema: { type: string }
- name: instance_uuids
in: query
description: UUID сущностей; список значений через запятую (валидируются как UUID)
example: "3fa85f64-5717-4562-b3fc-2c963f66afa6"
schema: { type: string }
- name: resource_uuids
in: query
description: UUID ресурсов; список значений через запятую (валидируются как UUID)
schema: { type: string }
- name: registered_at_gte
in: query
description: Нижняя граница времени регистрации (>=)
example: "2025-02-16T15:04:05Z"
schema: { type: string, format: date-time }
- name: registered_at_lte
in: query
description: Верхняя граница времени регистрации (<=)
example: "2025-02-16T15:04:05Z"
schema: { type: string, format: date-time }
- name: instance_path
in: query
description: Путь сущности (ltree), точное совпадение
example: "1.79899.80131"
schema: { type: string }
- name: target_ids
in: query
description: ID целей; список значений через запятую
example: "1,2,3"
schema: { type: string }
- name: target_path
in: query
description: Путь цели (ltree), точное совпадение
example: "4.3.2.1"
schema: { type: string }
- name: company_ids
in: query
description: ID компаний; список значений через запятую
example: "1,2,3"
schema: { type: string }
- name: statuses
in: query
description: Подстатусы события; список значений через запятую
example: "add_bundle,delete_bundle,rename_document"
schema: { type: string }
- name: message
in: query
description: Сообщения; список значений через запятую
schema: { type: string }
- name: is_processed_by_worker
in: query
description: Признак обработки записи воркером обогащения
schema: { type: boolean }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/SystemLogListResponse' }
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalError'
post:
tags: [system_log]
summary: Создать записи журнала
description: |
Принимает пакет записей в поле `system_logs`. Для каждой записи должно
быть задано **ровно одно** из `instance_id` / `instance_uuid`
(не оба и не ни одного — иначе `400`). Обязательны `event_name` и
`model_name`. При успехе возвращается строка `"OK"`.
operationId: createSystemLogRecords
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SystemLogCreateRequest' }
responses:
'200':
description: Записи созданы
content:
application/json:
schema:
type: string
example: "OK"
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalError'
/api/v0/system_log/search:
post:
tags: [system_log]
summary: Поиск записей журнала (фильтры в теле)
description: |
Аналог GET-выборки, но фильтры передаются в теле запроса
(`filters`). Позволяет использовать сложные фильтры (в т.ч. `metadata`),
которые неудобно кодировать в query-строке. В ответе нет `next`/`prev`.
operationId: searchSystemLogs
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SystemLogSearchRequest' }
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/SystemLogSearchResponse' }
'400':
$ref: '#/components/responses/BadRequest'
'500':
$ref: '#/components/responses/InternalError'
# ============================================================================
components:
parameters:
Limit:
name: limit
in: query
description: Кол-во записей на странице
schema: { type: integer, format: uint64, default: 100 }
Offset:
name: offset
in: query
description: Смещение от начала выборки
schema: { type: integer, format: uint64, default: 0, minimum: 0 }
responses:
BadRequest:
description: Ошибка разбора/валидации запроса
content:
application/json:
schema: { $ref: '#/components/schemas/ControllerError' }
InternalError:
description: Внутренняя ошибка сервиса
content:
application/json:
schema: { $ref: '#/components/schemas/ControllerError' }
schemas:
# ---- Общие ----
ControllerError:
type: object
description: Формат ошибки (`internal/controller/errors.go`, `ErrorToResponse`).
properties:
message: { type: string, example: "error parse body" }
status_code: { type: integer, example: 400 }
# ---- Доменная запись ----
SystemLog:
type: object
description: Запись журнала системных событий (`internal/entity/system_log.go`).
properties:
actor_id:
type: integer
format: uint64
description: ID автора события
example: 1023
event_name:
type: string
description: Название события (напр. edit, create, copy)
example: "edit"
model_name:
type: string
description: Над какой сущностью произошло событие (напр. document, inspection)
example: "document"
instance_id:
type: integer
format: uint64
nullable: true
description: ID сущности (для моделей с числовым ключом)
example: 80131
instance_uuid:
type: string
format: uuid
nullable: true
description: UUID сущности (для моделей с UUID-ключом)
registered_at:
type: string
format: date-time
nullable: true
description: Время регистрации события
example: "2023-05-04T08:09:53.852916Z"
instance_path:
type: string
nullable: true
description: Путь сущности (ltree)
example: "1.79899.80131"
target_id:
type: integer
format: uint64
nullable: true
description: ID цели события
company_id:
type: integer
format: uint64
nullable: true
description: ID компании (может обогащаться воркером)
example: 1
metadata:
type: object
additionalProperties: true
description: Произвольные дополнительные данные о событии
example: { "document_name": "КСГ.pdf", "count_bundle": 3 }
message:
type: string
nullable: true
description: Произвольное сообщение
example: "bundle was deleted from document"
status:
type: string
nullable: true
description: Подстатус события
example: "delete_bundle"
resource_uuid:
type: string
format: uuid
nullable: true
description: UUID связанного ресурса
target_path:
type: string
nullable: true
description: Путь цели (ltree)
required: [event_name, model_name]
# ---- Запросы ----
SystemLogCreateRequest:
type: object
description: >
Пакет записей для создания. Для каждой записи обязательно ровно одно из
instance_id / instance_uuid.
properties:
system_logs:
type: array
minItems: 1
items: { $ref: '#/components/schemas/SystemLog' }
required: [system_logs]
SystemLogFilter:
type: object
description: Набор фильтров выборки (`entity.SystemLogFilter`).
properties:
limit: { type: integer, format: uint64, default: 100 }
offset: { type: integer, format: uint64, default: 0 }
actor_ids:
type: array
items: { type: integer, format: uint64 }
event_names:
type: array
items: { type: string }
model_names:
type: array
items: { type: string }
instance_ids:
type: array
items: { type: integer, format: uint64 }
instance_uuids:
type: array
items: { type: string, format: uuid }
registered_at_gte: { type: string, format: date-time, nullable: true }
registered_at_lte: { type: string, format: date-time, nullable: true }
instance_path: { type: string, nullable: true }
target_ids:
type: array
items: { type: integer, format: uint64 }
target_path: { type: string, nullable: true }
company_ids:
type: array
items: { type: integer, format: uint64 }
statuses:
type: array
items: { type: string }
message: { type: string, nullable: true }
metadata:
type: object
additionalProperties: true
resource_uuids:
type: array
items: { type: string, format: uuid }
is_processed_by_worker: { type: boolean, nullable: true }
SystemLogSearchRequest:
type: object
description: Тело запроса поиска (`SearchParams` в `dto.go`).
properties:
limit: { type: integer, format: uint64, default: 100 }
offset: { type: integer, format: uint64, default: 0 }
filters: { $ref: '#/components/schemas/SystemLogFilter' }
# ---- Ответы ----
SystemLogListResponse:
type: object
description: Ответ списочной выборки (`entity.SystemLogRecordsRequest`).
properties:
count: { type: integer, format: uint64, example: 1226 }
limit: { type: integer, format: uint64, nullable: true, example: 100 }
offset: { type: integer, format: uint64, nullable: true, example: 0 }
next:
type: string
nullable: true
example: "http://localhost:8888/api/v0/system_log?limit=100&offset=100"
prev:
type: string
nullable: true
result:
type: array
items: { $ref: '#/components/schemas/SystemLog' }
required: [count, result]
SystemLogSearchResponse:
type: object
description: Ответ поиска (`entity.SystemLogRecordsSearch`) — без next/prev.
properties:
count: { type: integer, format: uint64, example: 1226 }
limit: { type: integer, format: uint64, nullable: true, example: 100 }
offset: { type: integer, format: uint64, nullable: true, example: 0 }
result:
type: array
items: { $ref: '#/components/schemas/SystemLog' }
required: [count, result]