diff --git a/apps/system-log/openapi.yaml b/apps/system-log/openapi.yaml new file mode 100644 index 0000000..3143872 --- /dev/null +++ b/apps/system-log/openapi.yaml @@ -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]