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]