Skip to content

История сущностей и Audit Timeline ​

Audit timeline хранит продуктовую историю действий по ключевым сущностям и auth/security-событиям. V1 реализован как модуль internal/modules/audit внутри текущего Go API, без отдельного HTTP-сервиса.

Архитектура ​

  • API пишет событие в audit_outbox через audit.Emitter из service layer.
  • Запись аудита best-effort: бизнес-операция не откатывается, если outbox недоступен; ошибка только логируется.
  • Фоновый worker в существующем worker-процессе забирает pending rows через FOR UPDATE SKIP LOCKED и вставляет их в audit_events.
  • Вставка в audit_events идемпотентная по event_id: ON CONFLICT (event_id) DO NOTHING.
  • audit_events хранится бессрочно. audit_outbox является технической очередью со статусами, retry и last_error.

Связанные таблицы:

  • audit_outbox: raw payload, статус, attempts, next_attempt_at, last_error.
  • audit_events: нормализованная timeline-запись с entity, actor, request metadata, изменёнными полями, changes и metadata.

API ​

Entity Timeline ​

GET /api/v1/entities/:type/:id/timeline

Поддержанные type в v1:

  • project
  • task
  • company
  • contact
  • document
  • logistic_operation
  • equipment_request

Фильтры:

  • event_type
  • actor_id
  • from
  • to
  • limit
  • offset

Ответ возвращается через стандартный list envelope: items и meta.

Admin Audit Events ​

GET /api/v1/audit/events

Доступ: admin, system.global_admin или permission audit.view.

Фильтры:

  • category
  • entity_type
  • entity_id
  • actor_id
  • event_type
  • request_id
  • from
  • to
  • limit
  • offset

Поля события ​

AuditEventDTO содержит:

  • id, event_id, occurred_at, category, event_type, action
  • entity_type, entity_id
  • actor_id, nullable actor { id, full_name, email }
  • client_ip, user_agent, request_id, method, path
  • changed_fields, changes, metadata

Для update-событий изменения хранятся только по whitelisted полям:

json
{
  "changed_fields": ["status"],
  "changes": {
    "status": {
      "from": "todo",
      "to": "done"
    }
  }
}

События v1 ​

  • project.*: create, update, archive, status/spec submit/approve/reject.
  • task.*: create, update, delete, status/start/finish/comment added.
  • company.*, contact.*: create, update, deactivate.
  • document.*: created/status changed через существующий document EventBus.
  • logistic_operation.*: create, update, delete, confirm, close, finance approve/reject.
  • equipment_request.*: create, update, cancel, submit, finance approve/reject, procurement and plan transitions.
  • auth.*: login success/failure, refresh success/failure, logout.

Существующие project_team_history и logistic_operation_events не мигрируются и не ломаются. Новые релевантные события дополнительно дублируются в общий audit timeline.

Безопасность данных ​

Нельзя сохранять в audit:

  • пароли;
  • access/refresh JWT;
  • reset/verification tokens;
  • Authorization headers;
  • бинарные payload файлов и документов;
  • raw request body.

Failed auth-события имеют nullable actor. В metadata допускаются только masked principal и reason code без секретов.

IP определяется через gin.Context.ClientIP() и список доверенных прокси APP_TRUSTED_PROXIES. Raw X-Forwarded-For не используется без trusted proxy.

Конфигурация ​

EnvDefaultНазначение
APP_TRUSTED_PROXIES127.0.0.1,::1,172.16.0.0/12Доверенные прокси для ClientIP()
APP_AUDIT_ENABLEDtrueВключает emission событий в outbox
APP_AUDIT_WORKER_ENABLEDtrueВключает worker обработки outbox
APP_AUDIT_WORKER_INTERVAL2sИнтервал polling worker
APP_AUDIT_WORKER_BATCH_SIZE100Batch size для claim pending rows

Worker использует max attempts 10. После исчерпания попыток событие остаётся в audit_outbox со статусом failed.

Интерфейс ​

С 2026-09-30 журнал виден в интерфейсе:

  • «Администрирование → Журнал действий» (GET /audit/events, capability audit_view в /org/me: администратор, audit.view или system.global_admin). Разделы «Действия», «Входы», «Безопасность»; фильтры по периоду, объекту, событию и сотруднику; серверная пагинация. Строка — кто, когда, что произошло и что поменялось («Статус: В работе → Сдан»); подробности события — в боковой панели со ссылкой на объект.
  • Вкладка «История» в карточках проекта, компании и задачи (GET /entities/:type/:id/timeline) — видит тот, кто видит сам объект.

Подписи событий, объектов и полей — rms_crm_front/src/data/auditLabels.ts и справочники auditCategory, auditEntity. Незнакомый код события получает подпись по действию («Изменение»), незнакомое поле — «Другое поле»: сырые коды в интерфейс не попадают. Значения показываются словами интерфейса: статусы — по справочнику статусов, суммы — в рублях, даты — ДД.ММ.ГГГГ; изменения без разницы (было = стало) скрыты.

Загружаем документацию…