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.

Поля

ПолеТипОбов’язковеПризначення
idstringніmongo _id (transform plugin)
timestampnumberні (default: Date.now)коли сталась подія
initiatorIdstringтакхто ініціював дію (актор — director/HR/supervisor, що виконав операцію в UI). У більшості методів захардкожений '61d4457949e705e11376aea6' (director) — реальний ініціатор не прокидається. В createFamilyChathouseOperator помилково записується supervisorFamilyId (тобто на кого призначають, а не хто призначає)
initiatorTypeLogInitiatorType (numeric)тактип ініціатора — числовий enum
supervisorFamilyIdstringтакid IFamilyOperator-запису supervisor’а
operatorFamilyIdstringтакid IFamilyOperator-запису оператора
eventLogEventType (numeric)тактип події — числовий enum
isBlockedbooleanні (default: false)чи був заблокований оператор на момент події
familyFamiliesтак'chathouse' для всіх записів у цій колекції

event: LogEventType (numeric)

ЗначенняКлючОпис
1create_familyFamily створена (supervisor призначений вперше)
2delete_familyFamily видалена (оператор переведений в іншу або звільнений)
3recovery_delete_familyВідновлення після видалення
4block_familyFamily заблокована (при блокуванні юзера)
5recovery_block_familyРозблокування family
6change_adminЗміна адміна — існує лише в LogEventType, відсутній у StackOperatorLogEventType. Golden/Udate цю подію записати не можуть
7set_supervisorПризначення нового supervisor
8off_supervisorЗняття supervisor
9loginЛогін оператора

initiatorType: LogInitiatorType (numeric)

ЗначенняКлючВідповідник у StackInitiatorLogType
1director'director'
2topManager'topManager'
3hr'hr'
4clientManager'clientManager'
5supervisor'supervisor'
6operator'operator'
7auto'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(...).

МетодeventinitiatorTypeПримітка
createFamilyChathouseOperatorcreate_familysupervisorinitiatorId = supervisorFamilyId ⚠️ — записується id supervisor’а якого призначають, а не хто виконав дію
blockFamilyChathouseOperatorblock_familyпередається ззовніinitiatorId передається ззовні
deleteFamilyChathouseOperatordelete_familydirectorinitiatorId захардкожений
recoveryFamilyChathouseOperatorrecovery_delete_familydirectorinitiatorId захардкожений
dropChathouseOperatorFromSupervisoroff_supervisorпередається ззовніinitiatorId захардкожений
updateSupervisorOperator (private)set_supervisordirectorinitiatorId захардкожений

⚠️ initiatorId має бути stackUserId того, хто виконав дію (director/HR/supervisor що натиснув кнопку в UI), а не id об’єкта на який призначають. Зараз це порушено: у більшості методів захардкожений директорський id, у createFamilyChathouseOperator — помилково supervisorFamilyId. Виправлення потребує прокидання реального user.id з контролера через сервіс до логу.

Де читається


Порівняння форматів: 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 / operatorWasBlockedisBlockedoperatorWasBlockedoperatorWasBlocked
family полеrequired❌ відсутнє❌ відсутнє
supervisorFamilyIdrequireddefault: null (nullable)required (але деякі методи передають ?? null)
Де зберігаєтьсяMongoDB (локально)MongoDB (локально)Зовнішній мікросервіс (RMQ)

План міграції: Golden → log_operator

Мета

Перенести Golden operator-логи з зовнішнього мікросервісу в колекцію log_operator, щоб:

  • читати всі family-логи без RMQ-запиту
  • уніфікувати формат між chathouse і golden
  • спростити LogOperatorService (прибрати RMQ-гілку для golden)

Розбіжності і перетворення

#ПолеGolden (джерело)log_operator (ціль)Дія
1eventStackOperatorLogEventType (string)LogEventType (numeric)Маппінг через GoldenLogsAdapter.convertOperatorEventType — вже реалізований
2initiatorTypeStackInitiatorLogType (string)LogInitiatorType (numeric)Маппінг через GoldenLogsAdapter.convertOperatorInitiatorType — вже реалізований. Втрата: 'worker' і 'Off all'auto (7)
3operatorWasBlockedbooleanisBlocked: booleanRename поля
4family❌ відсутнєFamilies.goldenHardcode
5supervisorFamilyIdІноді nulllogDeleteFamily, logBlockFamily)required в схеміСхему треба зробити nullable або заповнювати порожнім рядком

⚠️ Втрата initiatorType: після конвертації worker/Off allauto відновити оригінал буде неможливо. Якщо ця деталізація важлива — зберегти оригінальний 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_allauto (незворотньо)
supervisorFamilyId null у GoldenДеякі події (delete_family, block_family) передають null — треба ослабити схему
Дублювання при повторному запуску скриптуДодати унікальний індекс або idempotency check перед insertMany
DowntimeСкрипт і переключення запису треба координувати, щоб не втратити логи між кроками 3 і 4