LogOperator (log_operator колекція)
#draft Модель: LogOperatorModel.ts.
Audit-log lifecycle-подій оператора в рамках chathouse family: призначення/зняття supervisor, створення/видалення/блокування family, логін. Зберігає уніфікований формат через ILogOperator (stack-commons).
Golden і Prime на відміну від chathouse/udate зберігають ці ж логи у власних мікросервісах і читаються через RMQ (
namespace.GoldenLogOperatorFindMany). Для читання вLogOperatorServiceвони конвертуються адаптеромGoldenLogsAdapter.convertOperatorLogвILogOperator. Актуальна задача — мігрувати Golden в цю ж колекцію (план нижче).Udate зберігає в
udate_log_operator(UdateLogOperatorModel) — окрема колекція, але концептуально той самий формат, лише з string-enum’ами замість numeric.
Поля
| Поле | Тип | Обов’язкове | Призначення |
|---|---|---|---|
id | string | ні | mongo _id (transform plugin) |
timestamp | number | ні (default: Date.now) | коли сталась подія |
initiatorId | string | так | хто ініціював дію (актор — director/HR/supervisor, що виконав операцію в UI). У більшості методів захардкожений '61d4457949e705e11376aea6' (director) — реальний ініціатор не прокидається. В createFamilyChathouseOperator помилково записується supervisorFamilyId (тобто на кого призначають, а не хто призначає) |
initiatorType | LogInitiatorType (numeric) | так | тип ініціатора — числовий enum |
supervisorFamilyId | string | так | id IFamilyOperator-запису supervisor’а |
operatorFamilyId | string | так | id IFamilyOperator-запису оператора |
event | LogEventType (numeric) | так | тип події — числовий enum |
isBlocked | boolean | ні (default: false) | чи був заблокований оператор на момент події |
family | Families | так | 'chathouse' для всіх записів у цій колекції |
event: LogEventType (numeric)
| Значення | Ключ | Опис |
|---|---|---|
1 | create_family | Family створена (supervisor призначений вперше) |
2 | delete_family | Family видалена (оператор переведений в іншу або звільнений) |
3 | recovery_delete_family | Відновлення після видалення |
4 | block_family | Family заблокована (при блокуванні юзера) |
5 | recovery_block_family | Розблокування family |
6 | change_admin | Зміна адміна — існує лише в LogEventType, відсутній у StackOperatorLogEventType. Golden/Udate цю подію записати не можуть |
7 | set_supervisor | Призначення нового supervisor |
8 | off_supervisor | Зняття supervisor |
9 | login | Логін оператора |
initiatorType: LogInitiatorType (numeric)
| Значення | Ключ | Відповідник у StackInitiatorLogType |
|---|---|---|
1 | director | 'director' |
2 | topManager | 'topManager' |
3 | hr | 'hr' |
4 | clientManager | 'clientManager' |
5 | supervisor | 'supervisor' |
6 | operator | 'operator' |
7 | auto | 'auto' + 'worker' + 'Off all' — всі три маппляться в 7, конвертація втратна |
Індекси
| Індекс | Опис |
|---|---|
{ timestamp: 1 } | Сортування хронології |
⚠️ Відсутні складені індекси, які є в
UdateLogOperatorModel:
{ operatorFamilyId: 1, event: 1, timestamp: 1 }— основний фільтр вLogOperatorService{ supervisorFamilyId: 1, operatorFamilyId: 1, event: 1, timestamp: 1 }— фільтр по supervisorПри зростанні обсягу колекції (особливо після міграції Golden) їх варто додати.
Де пишеться
Всі записи створюються в ChathouseOperatorService.ts через пряму Mongoose-вставку LogOperatorModel.create(...).
| Метод | event | initiatorType | Примітка |
|---|---|---|---|
createFamilyChathouseOperator | create_family | supervisor | initiatorId = supervisorFamilyId ⚠️ — записується id supervisor’а якого призначають, а не хто виконав дію |
blockFamilyChathouseOperator | block_family | передається ззовні | initiatorId передається ззовні |
deleteFamilyChathouseOperator | delete_family | director | initiatorId захардкожений |
recoveryFamilyChathouseOperator | recovery_delete_family | director | initiatorId захардкожений |
dropChathouseOperatorFromSupervisor | off_supervisor | передається ззовні | initiatorId захардкожений |
updateSupervisorOperator (private) | set_supervisor | director | initiatorId захардкожений |
⚠️
initiatorIdмає бути stackUserId того, хто виконав дію (director/HR/supervisor що натиснув кнопку в UI), а не id об’єкта на який призначають. Зараз це порушено: у більшості методів захардкожений директорський id, уcreateFamilyChathouseOperator— помилковоsupervisorFamilyId. Виправлення потребує прокидання реальногоuser.idз контролера через сервіс до логу.
Де читається
- operator-log.service.ts —
getLogsForOperator,getRawLogs(chathouse логи читаються напряму черезLogOperatorModel.find) - golden-logs.adapter.ts —
getOperatorLogs(лише Golden, через RMQ) - StackOperatorService.ts —
getAllLogsBlockedOperators
Порівняння форматів: chathouse vs udate vs golden
| Поле | log_operator (chathouse) | udate_log_operator (udate) | Golden (RMQ) |
|---|---|---|---|
event тип | LogEventType (numeric) | StackOperatorLogEventType (string) | StackOperatorLogEventType (string) |
initiatorType тип | LogInitiatorType (numeric) | StackInitiatorLogType (string) | StackInitiatorLogType (string) |
isBlocked / operatorWasBlocked | isBlocked | operatorWasBlocked | operatorWasBlocked |
family поле | ✅ required | ❌ відсутнє | ❌ відсутнє |
supervisorFamilyId | required | default: null (nullable) | required (але деякі методи передають ?? null) |
| Де зберігається | MongoDB (локально) | MongoDB (локально) | Зовнішній мікросервіс (RMQ) |
План міграції: Golden → log_operator
Мета
Перенести Golden operator-логи з зовнішнього мікросервісу в колекцію log_operator, щоб:
- читати всі family-логи без RMQ-запиту
- уніфікувати формат між chathouse і golden
- спростити
LogOperatorService(прибрати RMQ-гілку для golden)
Розбіжності і перетворення
| # | Поле | Golden (джерело) | log_operator (ціль) | Дія |
|---|---|---|---|---|
| 1 | event | StackOperatorLogEventType (string) | LogEventType (numeric) | Маппінг через GoldenLogsAdapter.convertOperatorEventType — вже реалізований |
| 2 | initiatorType | StackInitiatorLogType (string) | LogInitiatorType (numeric) | Маппінг через GoldenLogsAdapter.convertOperatorInitiatorType — вже реалізований. Втрата: 'worker' і 'Off all' → auto (7) |
| 3 | operatorWasBlocked | boolean | isBlocked: boolean | Rename поля |
| 4 | family | ❌ відсутнє | Families.golden | Hardcode |
| 5 | supervisorFamilyId | Іноді null (в logDeleteFamily, logBlockFamily) | required в схемі | Схему треба зробити nullable або заповнювати порожнім рядком |
⚠️ Втрата
initiatorType: після конвертаціїworker/Off all→autoвідновити оригінал буде неможливо. Якщо ця деталізація важлива — зберегти оригінальний string у окремому поліinitiatorTypeRawперед міграцією.
Кроки
Крок 1 — Змінити схему: зробити supervisorFamilyId nullable
// LogOperatorModel.ts
supervisorFamilyId: { type: String, default: null }, // було: required: trueКрок 2 — Додати індекси
schema.index({ operatorFamilyId: 1, event: 1, timestamp: 1 });
schema.index({ supervisorFamilyId: 1, operatorFamilyId: 1, event: 1, timestamp: 1 });Крок 3 — Одноразовий скрипт міграції наявних Golden-логів
// scripts/migrate-golden-operator-logs.ts (псевдокод)
const goldenLogs: IGoldenLogOperator[] = await rmqController.request(namespace.GoldenLogOperatorFindMany.routingKey, { query: {} });
const converted = goldenLogs.map((log) => ({
timestamp: log.timestamp,
initiatorId: log.initiatorId,
initiatorType: convertOperatorInitiatorType(log.initiatorType), // string → numeric
supervisorFamilyId: log.supervisorFamilyId ?? null,
operatorFamilyId: log.operatorFamilyId,
event: convertOperatorEventType(log.event), // string → numeric
isBlocked: log.operatorWasBlocked,
family: Families.golden,
}));
await LogOperatorModel.insertMany(converted, { ordered: false });Функції convertOperatorInitiatorType / convertOperatorEventType — взяти з GoldenLogsAdapter (вже реалізовано).
Крок 4 — Переключити запис нових Golden-логів
У GoldenLogOperatorService.ts замість:
await this.rmqController.request(namespace.GoldenLogOperatorCreate.routingKey, { ... });писати напряму через LogOperatorModel.create(...) з конвертацією enum’ів.
Крок 5 — Оновити LogOperatorService
У operator-log.service.ts у getTeamLeadAssignmentLogs та getRawLogs:
- прибрати RMQ-запит для
Families.golden - замінити на
LogOperatorModel.find({ operatorFamilyId, family: Families.golden })
Крок 6 — Оновити GoldenLogsAdapter
У getOperatorLogs прибрати RMQ-запит, читати з LogOperatorModel (або залишити адаптер але перемкнути джерело).
Ризики
| Ризик | Деталь |
|---|---|
Втрата initiatorType деталізації | worker/off_all → auto (незворотньо) |
supervisorFamilyId null у Golden | Деякі події (delete_family, block_family) передають null — треба ослабити схему |
| Дублювання при повторному запуску скрипту | Додати унікальний індекс або idempotency check перед insertMany |
| Downtime | Скрипт і переключення запису треба координувати, щоб не втратити логи між кроками 3 і 4 |