LogLady (лог рольових подій TU: supervisor / operator / client manager)
#draft Модель (chathouse): LogLadyModel.ts (log_lady).
Family-специфічні реалізації того самого концепту:
- Chathouse — LogLadyModel.ts, локальна колекція
log_lady, пишеться напряму (LogLadyModel.create(...)). - Udate — UdateLogLadyModel.ts, локальна колекція
udate_log_lady, пишеться через UdateLogLadyService.ts. - Golden — GoldenLogLadyService.ts, зберігається у зовнішньому мікросервісі, пишеться через RMQ (
namespace.GoldenLogLadyCreate/namespace.GoldenLog). - Prime — той самий підхід, що й Golden (зовнішній мікросервіс через RMQ,
namespace.PrimeLog);TYPES.PrimeLogLadyServiceзаявлений у DI, але локальної реалізації в цьому репо немає.
Пов’язані, але окремі колекції (не входять у порівняння нижче — інша семантика):
- LogLadyEventsModel.ts (
log_lady_events) — lifecycle TU (LadyEvent: created/deleted/activated/deactivated), вже уніфікована колекція для всіх family. - LogLadyCardModel.ts (
log_lady_card) — події на рівні картки TU (create/set_cm/off_cm/set_lady/off_lady), теж вже уніфікована.
Формат рольових подій (supervisor/operator/client manager) описаних нижче — НЕ уніфікований: однакові за змістом поля мають різні назви та типи в кожній family. Це заважає читати логи по всіх family одним запитом (Golden/Prime — взагалі RMQ, не queryable локально).
Зведена таблиця полів (порівняння форматів + пропозиція перейменування)
| # | Концепція | Пропоную поле | Пропоную тип | Chathouse log_lady | Golden (RMQ) | Prime (RMQ) | Udate udate_log_lady |
|---|---|---|---|---|---|---|---|
| 1 | Час події, default | timestamp | number (default: Date.now) | timestamp: number | timestamp: number | timestamp: number (за аналогією з golden) | timestamp: number (default: Date.now) |
| 3 | Хто ініціював дію | initiatorId | string, userId | initiatorId: string — бага: фактично хардкод директора / familyId ініціатора / 'system', не реальний actor | initiatorId: string — та сама бага (напр. operator_login пише operatorFamilyId замість реального actor’а) | initiatorId: string — та сама бага | initiatorId: string — та сама бага |
| 4 | Тип ініціатора | initiatorType | LogInitiatorType (string enum) | initiatorType: LogInitiatorType — numeric enum, відрізняється від решти | initiatorType: StackInitiatorLogType (string) | initiatorType: StackInitiatorLogType (string) | initiatorType: StackInitiatorLogType (string) |
| 5 | Ід TU (lady) в stack-базі | tuId | string | ladyDbId: string | ladyMongoId: string | ladyMongoId: string | ladyMongoId: string |
| 6 | Зовнішній (family-side) ід TU | (іде в data) | number | string | api_id: Mixed — по факту завжди хардкод 0, не використовується | ladyId_api: number | prime_id (тип не задокументовано в схемі, за використанням — number) | ladyId_api: number |
| 7 | Ід запису supervisor’а (family-scope) | assignmentFamilyId (+ role: StackRoles.supervisor) | string | supervisorFamilyId: string | supervisorFamilyId: string | supervisorFamilyId: string | supervisorFamilyId: string (default: null) |
| 8 | Ід запису operator’а (family-scope) | assignmentFamilyId (+ role: StackRoles.operator) | string | operatorFamilyId: string | operatorFamilyId: string | operatorFamilyId: string | operatorFamilyId: string (default: null) |
| 9 | Ід запису client manager’а (family-scope) | assignmentFamilyId (+ role: StackRoles.client_manager) | string | cmFamilyId: string — є в реальній схемі, хоч і не згадана в нотатках по set_supervisor/operator (використовується лише для set_cm/off_cm, які нотатки для chathouse взагалі не описували) | clManFamilyId: string | clManFamilyId: string | clManFamilyId: string (default: null) |
| 10 | Скільки бонусів на момент події | bonuses (лишається в data, бо реалізовано по-різному) | family-specific | bonuses: number | bonuses: { [key in GoldenStatisticsTypes]: number } — об’єкт, а не число | bonuses: number | bonuses: number (default: 0, required) |
| 11 | Позначка адміна | adminId | string (⚠️ крім golden) | admin: Mixed — по факту adminId (string), в реальному коді не заповнюється (лишається undefined) | golden_admin: number — число, зовнішній api-ід адміна, НЕ adminId з бази | prime_admin: string | udate_admin: string (default: null) |
| 12 | Причина переміщення TU | (планується видалити) | StackResonMoveLady | ❌ відсутнє | resonMoveLady: StackResonMoveLady | resonMoveLady: StackResonMoveLady — сам автор нотаток планує видалити | resonMoveLady: StackResonMoveLady (default: not_move) |
| 13 | Кількість існуючих supervisor’ів на момент події | (планується видалити) | number | ❌ відсутнє | ❌ відсутнє | existSupervisors — планується видалити | existSupervisors: number (default: null) |
| 14 | Тип події | event | LogLadyEventTypeV2 (numeric, привести всіх до цього) і спростити, вказано нижче | event: LogLadyEventType (numeric) | event: StackLadyLogEventType (string) | event: StackLadyLogEventType (string) | event: StackLadyLogEventType (string) |
| 15 | Family-приналежність запису | family | Families (required) | family: Families (в схемі required) | не зберігається явно — визначається джерелом (RMQ namespace); додати при міграції | не зберігається явно; додати при міграції | не зберігається явно (колекція udate_log_lady вже сама по собі = udate); додати при міграції |
existSupervisors - перепровірю
Старий event: export enum LogLadyEventType { set_supervisor = 1, off_supervisor = 2, set_operator = 3, off_operator = 4, set_cm = 5, off_cm = 6, operator_login = 7, } Новий event: export enum LogLadyEventTypeV2 { set = 1, off = 2, operator_login = 3, }
Поля за подіями (event) для кожної family
Той самий матеріал, що й вище, але розрізаний по конкретних подіях — корисно як чеклист при написанні конвертера/міграції.
Chathouse (log_lady)
| Подія | Поля, що записуються | Примітка |
|---|---|---|
set_supervisor / off_supervisor | initiatorId, initiatorType, ladyDbId, supervisorFamilyId, bonuses, event, family, admin | admin варто заповнювати chathouseFamilySupervisor.admin, зараз не покращено |
set_operator / off_operator / operator_login | initiatorId, initiatorType, ladyDbId, operatorFamilyId, supervisorFamilyId, bonuses, event, family, admin | initiatorId/initiatorType потребують переробки (реальний actor не прокидається) |
set_cm / off_cm | initiatorId (хардкод '61d4457949e705e11376aea6', director), initiatorType (хардкод director), ladyDbId, cmFamilyId, bonuses: 0, api_id: 0, event, family | Ці поля не потрапили в оригінальні нотатки автора (top-of-file коментар описував лише supervisor/operator для chathouse) — виявлено з реального коду StackClientManagerService.ts |
Golden (RMQ, зовнішній сервіс)
| Подія | Поля, що записуються | Примітка |
|---|---|---|
set_supervisor / off_supervisor | initiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, supervisorFamilyId, bonuses, golden_admin | |
set_operator / off_operator | initiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, bonuses, golden_admin | |
set_cm / off_cm | clManFamilyId, ladyMongoId, ladyId_api, event, resonMoveLady: user, bonuses: 0 (авто) | initiatorId/initiatorType не прокидаються окремо в цьому виклику (GoldenLog routing) |
operator_login | initiatorId (= operatorFamilyId, бага), initiatorType: operator, operatorFamilyId, supervisorFamilyId, ladyMongoId, ladyId_api, event, bonuses: 0, golden_admin |
Prime (RMQ, зовнішній сервіс)
| Подія | Поля, що записуються | Примітка |
|---|---|---|
operator_login | initiatorId (= operatorFamilyId, та сама бага), initiatorType: operator, ladyMongoId, prime_id, event, operatorFamilyId, bonuses, prime_admin, supervisorFamilyId | |
set_cm / off_cm | clManFamilyId, initiatorId: 'director' (хардкод), initiatorType: director, ladyMongoId, prime_id, event, resonMoveLady: user, bonuses: 0 | |
set_supervisor / off_supervisor | initiatorId, initiatorType, ladyMongoId, prime_id, event, resonMoveLady, supervisorFamilyId, existSupervisors, bonuses, prime_admin | existSupervisors — планується видалити |
set_operator / off_operator | initiatorId, initiatorType, ladyMongoId, prime_id, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, bonuses, prime_admin |
Udate (udate_log_lady)
| Подія | Поля, що записуються | Примітка |
|---|---|---|
set_cm / off_cm | clManFamilyId, initiatorId: 'director' (хардкод), initiatorType: director, ladyMongoId, ladyId_api, event, resonMoveLady: user, bonuses: 0 | |
set_supervisor / off_supervisor | initiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, supervisorFamilyId, udate_admin, existSupervisors | existSupervisors — планується видалити |
set_operator / off_operator | initiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, udate_admin |
TypeScript типи: поточний (старий) формат, по event для кожної family
Формалізація таблиць з розділу вище — по одному type/interface на кожну групу подій, для конвертерів/валідації при міграції. Це типи “як є зараз” — нічого спільного між family не перевикористовується (кожна family має власний, ізольований набір полів).
Chathouse (log_lady)
// set_supervisor / off_supervisor
interface IChathouseLogLady_Supervisor {
initiatorId: string; // бага: хардкод director / familyId ініціатора / 'system', не реальний actor
initiatorType: LogInitiatorType;
ladyDbId: string;
supervisorFamilyId: string;
bonuses: number;
event: LogLadyEventType.set_supervisor | LogLadyEventType.off_supervisor;
family: Families.chathouse;
admin: string; // adminId, зараз не заповнюється (chathouseFamilySupervisor.admin)
}
// set_operator / off_operator / operator_login
interface IChathouseLogLady_Operator {
initiatorId: string; // та сама бага, що й у Supervisor
initiatorType: LogInitiatorType;
ladyDbId: string;
operatorFamilyId: string;
supervisorFamilyId: string;
bonuses: number;
event: LogLadyEventType.set_operator | LogLadyEventType.off_operator | LogLadyEventType.operator_login;
family: Families.chathouse;
admin: string;
}
// set_cm / off_cm — виявлено з реального коду StackClientManagerService.ts, не було в оригінальних нотатках
interface IChathouseLogLady_ClientManager {
initiatorId: string; // хардкод '61d4457949e705e11376aea6' (director)
initiatorType: LogInitiatorType.director; // хардкод
ladyDbId: string;
cmFamilyId: string;
bonuses: 0; // хардкод
api_id: 0; // хардкод, поле по факту не використовується
event: LogLadyEventType.set_cm | LogLadyEventType.off_cm;
family: Families.chathouse;
}
type IChathouseLogLadyEvent = IChathouseLogLady_Supervisor | IChathouseLogLady_Operator | IChathouseLogLady_ClientManager;Golden (RMQ, зовнішній сервіс)
// set_supervisor / off_supervisor
interface IGoldenLogLady_Supervisor {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
resonMoveLady: StackResonMoveLady;
supervisorFamilyId: string;
bonuses: { [key in GoldenStatisticsTypes]?: number };
golden_admin: number; // зовнішній api-ід адміна, НЕ adminId з бази
}
// set_operator / off_operator
interface IGoldenLogLady_Operator {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
resonMoveLady: StackResonMoveLady;
operatorFamilyId: string;
supervisorFamilyId: string;
bonuses: { [key in GoldenStatisticsTypes]?: number };
golden_admin: number;
}
// set_cm / off_cm — initiatorId/initiatorType не прокидаються окремо в цьому RMQ-виклику
interface IGoldenLogLady_ClientManager {
clManFamilyId: string;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
resonMoveLady: StackResonMoveLady.user;
bonuses: 0;
}
// operator_login
interface IGoldenLogLady_OperatorLogin {
initiatorId: string; // бага: фактично = operatorFamilyId
initiatorType: StackInitiatorLogType.operator;
operatorFamilyId: string;
supervisorFamilyId: string;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.operator_login;
bonuses: 0;
golden_admin: number;
}
type IGoldenLogLadyEvent = IGoldenLogLady_Supervisor | IGoldenLogLady_Operator | IGoldenLogLady_ClientManager | IGoldenLogLady_OperatorLogin;Prime (RMQ, зовнішній сервіс)
// set_supervisor / off_supervisor
interface IPrimeLogLady_Supervisor {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
prime_id: number; // специфічне tuId для prime family
event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
resonMoveLady: StackResonMoveLady; // планується видалити
supervisorFamilyId: string;
existSupervisors: number; // планується видалити
bonuses: number;
prime_admin: string;
}
// set_operator / off_operator
interface IPrimeLogLady_Operator {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
prime_id: number;
event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
resonMoveLady: StackResonMoveLady; // планується видалити
operatorFamilyId: string;
supervisorFamilyId: string;
bonuses: number;
prime_admin: string;
}
// set_cm / off_cm
interface IPrimeLogLady_ClientManager {
clManFamilyId: string;
initiatorId: 'director'; // хардкод
initiatorType: StackInitiatorLogType.director; // хардкод
ladyMongoId: string;
prime_id: number;
event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
resonMoveLady: StackResonMoveLady.user;
bonuses: 0;
}
// operator_login
interface IPrimeLogLady_OperatorLogin {
initiatorId: string; // бага: фактично = operatorFamilyId
initiatorType: StackInitiatorLogType.operator;
ladyMongoId: string;
prime_id: number;
event: StackLadyLogEventType.operator_login;
operatorFamilyId: string;
bonuses: number;
prime_admin: string;
supervisorFamilyId: string;
}
type IPrimeLogLadyEvent = IPrimeLogLady_Supervisor | IPrimeLogLady_Operator | IPrimeLogLady_ClientManager | IPrimeLogLady_OperatorLogin;Udate (udate_log_lady)
// set_supervisor / off_supervisor
interface IUdateLogLady_Supervisor {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
resonMoveLady: StackResonMoveLady; // планується видалити
supervisorFamilyId: string;
udate_admin: string;
existSupervisors: number; // планується видалити
}
// set_operator / off_operator
interface IUdateLogLady_Operator {
initiatorId: string;
initiatorType: StackInitiatorLogType;
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
resonMoveLady: StackResonMoveLady; // планується видалити
operatorFamilyId: string;
supervisorFamilyId: string;
udate_admin: string;
}
// set_cm / off_cm
interface IUdateLogLady_ClientManager {
clManFamilyId: string;
initiatorId: 'director'; // хардкод
initiatorType: StackInitiatorLogType.director; // хардкод
ladyMongoId: string;
ladyId_api: number;
event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
resonMoveLady: StackResonMoveLady.user;
bonuses: 0;
}
type IUdateLogLadyEvent = IUdateLogLady_Supervisor | IUdateLogLady_Operator | IUdateLogLady_ClientManager;TypeScript типи: новий (уніфікований) формат
На відміну від старого формату (вище — 4 повністю ізольовані набори полів), новий формат складається з:
- Спільної частини (
ILogLadyEventCommon) — однакова буквально для всіх подій без винятку (id,timestamp,tuId,family,data). - Розбивки по групах подій (
ILogLadyRoleEvent/ILogLadyLifecycleEvent) — бо різніeventреально потребують різний набір полів (див. розділ нижче “Розбивка по подіях”). - Family-специфічної частини (
data) — окремий тип на кожну family; тут лишається все, що не вдалось уніфікувати (напр.bonuses, бо в golden це об’єкт, а не число).
Подієвий enum спрощується: замість окремих set_supervisor/off_supervisor/set_operator/off_operator/set_cm/off_cm (6 значень) лишається set/off + окремо operator_login, а те, кого саме призначили/зняли (supervisor/operator/client manager), визначає поле role.
// Новий event — замінює LogLadyEventType (chathouse) і StackLadyLogEventType (golden/prime/udate)
enum LogLadyEventTypeV2 {
set = 1,
off = 2,
operator_login = 3,
// lifecycle TU, перенесено з окремої колекції log_lady_events (ILadyEventLog.event) - див. розділ нижче
created = 4,
deleted = 5,
activated = 6,
deactivated = 7,
}Розбивка по подіях (event) — чому вона потрібна
На відміну від розбивки по role (яку ми визнали зайвою — там форма полів однакова для всіх ролей), розбивка по групах event виправдана, бо форма полів реально відрізняється:
set/off/operator_login— це рольові події: завжди маютьinitiatorId,initiatorType,role,assignmentFamilyId,adminId.created/deleted/activated/deactivated(lifecycle, з колишньоїlog_lady_events) — взагалі не мають жодного з цих полів, бо пишуться без actor’а й без прив’язки до ролі.
Тобто критерій “чи варто дискримінувати” — чи справді відрізняється набір/форма полів. Для event — так (0 спільних полів понад ILogLadyEventCommon), для role — ні (усі три ролі мають однаковий assignmentFamilyId: string).
// Спільні поля - буквально для КОЖНОЇ події, без винятків
interface ILogLadyEventCommon<TFamily extends Families, TData> {
id: string;
timestamp: number;
tuId: string; // замінює ladyDbId (chathouse) / ladyMongoId (golden/prime/udate)
family: TFamily;
data: TData;
}
// set / off / operator_login - рольові події, завжди мають actor'а і роль
interface ILogLadyRoleEvent<TFamily extends Families, TData> extends ILogLadyEventCommon<TFamily, TData> {
event: LogLadyEventTypeV2.set | LogLadyEventTypeV2.off | LogLadyEventTypeV2.operator_login;
initiatorId: string; // TODO: бага з реальним actor'ом лишається й після міграції - виправляється окремо
initiatorType: StackInitiatorLogType;
role: StackRoles; // supervisor | operator | client_manager; для operator_login завжди StackRoles.operator
assignmentFamilyId: string; // замінює supervisorFamilyId / operatorFamilyId / cmFamilyId / clManFamilyId
adminId: string; // ⚠️ у golden - зовнішнє число (`golden_admin`), а не adminId з бази
}
// created / deleted / activated / deactivated - lifecycle TU, жодних role/actor полів немає взагалі
interface ILogLadyLifecycleEvent<TFamily extends Families, TData> extends ILogLadyEventCommon<TFamily, TData> {
event: LogLadyEventTypeV2.created | LogLadyEventTypeV2.deleted | LogLadyEventTypeV2.activated | LogLadyEventTypeV2.deactivated;
}
// Дискримінована унія по event - звужується автоматично при перевірці event у switch/if
type ILogLadyEvent<TFamily extends Families, TData> = ILogLadyRoleEvent<TFamily, TData> | ILogLadyLifecycleEvent<TFamily, TData>;Перевага порівняно з попереднім варіантом (де role/assignmentFamilyId/adminId/initiatorId/initiatorType були просто позначені як опціональні ? в одному спільному типі): компілятор тепер не дозволить випадково підставити role в lifecycle-подію чи, навпаки, забути assignmentFamilyId в set/off — раніше опціональність цього не гарантувала (можна було або забути обов’язкове поле для role-події, або помилково додати зайве поле до lifecycle-події, і TS цього не ловив).
Family-специфічні типи (data)
Лишається тільки те, що реально не вдається привести до спільного вигляду.
// Chathouse - нічого специфічного, крім bonuses (число)
interface IChathouseLogLadyDataV2 {
bonuses: number;
}
type IChathouseLogLadyEventV2 = ILogLadyEvent<Families.chathouse, IChathouseLogLadyDataV2>;
// Golden - ladyId_api (зовнішній ід) + bonuses як мапа по GoldenStatisticsTypes (не число)
interface IGoldenLogLadyDataV2 {
ladyId_api: number;
bonuses: { [key in GoldenStatisticsTypes]?: number };
}
type IGoldenLogLadyEventV2 = ILogLadyEvent<Families.golden, IGoldenLogLadyDataV2>;
// Prime - prime_id (зовнішній ід, специфічний для prime) + bonuses (число)
interface IPrimeLogLadyDataV2 {
prime_id: number;
bonuses: number;
}
type IPrimeLogLadyEventV2 = ILogLadyEvent<Families.prime, IPrimeLogLadyDataV2>;
// Udate - лише ladyId_api, bonuses в udate теж число, але лишено в data через різні типи в golden
interface IUdateLogLadyDataV2 {
ladyId_api: number;
bonuses: number;
}
type IUdateLogLadyEventV2 = ILogLadyEvent<Families.udate, IUdateLogLadyDataV2>;
// Повна унія - по family ТА по event одночасно
type ILogLadyEventV2 = IChathouseLogLadyEventV2 | IGoldenLogLadyEventV2 | IPrimeLogLadyEventV2 | IUdateLogLadyEventV2;
existSupervisorsіresonMoveLadyв нові типи не переносяться — заплановані на видалення (див. таблицю нижче).api_id(chathouse, хардкод0, реально не використовується) також не переноситься.
Зведення: що глобальне, що family-специфічне
| Тип поля | Старий формат | Новий формат | Перевикористовується? |
|---|---|---|---|
id, timestamp, tuId, family, data | різні набори по family | ILogLadyEventCommon | ✅ глобальне, для КОЖНОЇ події |
initiatorId, initiatorType, role, assignmentFamilyId, adminId | supervisorFamilyId/operatorFamilyId/cmFamilyId/clManFamilyId + admin/golden_admin/prime_admin/udate_admin (окремі поля) | ILogLadyRoleEvent (лише для set/off/operator_login) | ✅ глобальне, але лише для рольових подій — відсутнє у lifecycle |
event | LogLadyEventType (numeric, 7 значень) / StackLadyLogEventType (string, 7 значень) | LogLadyEventTypeV2 (7 значень: set/off/operator_login + 4 lifecycle) | ✅ глобальне (і спрощене для рольової частини) |
bonuses | різний тип у golden (об’єкт) проти інших (число) | IChathouseLogLadyDataV2.bonuses / IGoldenLogLadyDataV2.bonuses / … | ❌ family-специфічне (лишається в data) |
ladyId_api / prime_id / api_id | різні назви й наявність у кожній family | IGoldenLogLadyDataV2.ladyId_api / IPrimeLogLadyDataV2.prime_id / IUdateLogLadyDataV2.ladyId_api | ❌ family-специфічне (chathouse взагалі не має) |
existSupervisors, resonMoveLady | Prime + Udate (частково Golden) | — | 🗑️ видаляється, нікуди не переноситься |
Чи варто робити жорсткі Assign/Unassign-інтерфейси на кожну роль?
Питання: замість плоского role: StackRoles + assignmentFamilyId: string у ILogLadyRoleEvent — робити окремі інтерфейси на кшталт IAssignSupervisorEvent / IUnassignSupervisorEvent / IAssignOperatorEvent / … (discriminated union по role, а не тільки по event/family)?
Поки що — ні, не варто. Причина: role/assignmentFamilyId сьогодні мають однакову форму для всіх трьох ролей (supervisor/operator/client manager) — просто string-ід запису в family. Жодна роль не додає своїх власних полів понад те, що вже описано в data (family-специфіка). Якщо форма даних однакова, окремі інтерфейси на роль лише збільшують кількість типів (3 ролі × 4 family × 2 дії set/off = 24 комбінації) без жодної додаткової перевірки компілятором — чистий boilerplate. Це відрізняється від розбивки по event вище — там форма полів дійсно різна (0 спільних полів у lifecycle проти 5 обов’язкових у рольових подій).
Приклад, як це виглядало б, якби знадобилось (для довідки, не для реалізації зараз):
// Так МОЖНА зробити, якщо колись роль отримає власні поля (яких зараз немає)
interface IAssignRoleEvent<TFamily extends Families, TData, TRole extends StackRoles> extends ILogLadyEventCommon<TFamily, TData> {
event: LogLadyEventTypeV2.set;
role: TRole;
assignmentFamilyId: string;
}
interface IUnassignRoleEvent<TFamily extends Families, TData, TRole extends StackRoles> extends ILogLadyEventCommon<TFamily, TData> {
event: LogLadyEventTypeV2.off;
role: TRole;
assignmentFamilyId: string;
}Коли це стане виправданим (тригер для перегляду рішення):
- Якщо для конкретної ролі з’явиться власне поле, якого немає в інших ролей (наприклад, лише в client manager — причина зняття, лише в supervisor — якийсь ліміт).
- Якщо потрібна exhaustiveness-перевірка компілятором у
switch (role)при записі логу (сьогодні цього немає —logLadyEvent({ role, assignmentFamilyId, ... })і так приймає один спільний тип, бо поля однакові).
До того часу простіший варіант (role + assignmentFamilyId як звичайні поля в ILogLadyRoleEvent, без generic-параметризації по ролі) — саме те, що потрібно: менше типів, той самий рівень типобезпеки для наявних полів.
План уніфікації в одну колекцію
Мета — одна колекція (за аналогією з планом міграції в LogOperator), де family: Families вказує, як інтерпретувати data. Конкретні типи глобальних і family-специфічних полів — див. розділ “TypeScript типи: новий (уніфікований) формат” вище.
Поля, що плануються на видалення
| Поле | Де зустрічається | Причина видалення (з нотаток) |
|---|---|---|
existSupervisors | Prime, Udate | не пояснено детально в нотатках — позначено як “видалити” |
resonMoveLady | Golden, Prime, Udate | позначено як “видалити”; в chathouse цього поля й так немає |
Об’єднання з lifecycle-логами (log_lady_events / ILadyEventLog)
LogLadyEventsModel.ts (log_lady_events) вже уніфікована по всіх family, але це окрема колекція з зовсім іншим, набагато простішим набором полів:
enum LadyEvent {
Created = 'created',
Deleted = 'deleted',
Activated = 'activated ',
Deactivated = 'deactivated',
}
interface ILadyEventLog {
ladyId: string; // = tuId
timestamp: number; // default Date.now()
event: LadyEvent;
family: Families;
}У неї немає initiatorId/initiatorType/role/assignmentFamilyId/adminId — lifecycle TU (створення/видалення/активація/деактивація) сьогодні пишеться без actor’а й без прив’язки до ролі. Саме тому в новому форматі вище (розділ “Розбивка по подіях”) ці lifecycle-події винесені в окремий ILogLadyLifecycleEvent, який структурно не містить цих полів (а не позначає їх опціональними) — так компілятор гарантує, що lifecycle-запис ніколи не матиме зайвих рольових полів. Щоб влити ці записи в ту саму нову колекцію:
- Розширити
LogLadyEventTypeV2значеннями lifecycle-подій (окремий діапазон, щоб не плутати зset/off/operator_login) — уже враховано в enum вище (created = 4…deactivated = 7). - Записувати їх через
ILogLadyLifecycleEvent<TFamily, TData>(розділ вище) — жодних змін в базовому типі не треба, він вже це підтримує. - Міграція даних —
log_lady_eventsпереливається вlog_lady(чи як буде названа нова уніфікована колекція) через маппінгladyId → tuId,event → LogLadyEventTypeV2(за enum вище); поляinitiatorId/role/assignmentFamilyId/adminIdпросто відсутні в цих документах (а неnull/undefinedв required полі). Стару колекціюlog_lady_eventsможна прибрати лише після того, як усі читачі (LadyCardLogServiceй аналогічні) перейдуть на нову колекцію.
⚠️ Якщо в майбутньому lifecycle-подіям все ж знадобиться actor (
initiatorId) — це вже структурна зміна типу (ILogLadyLifecycleEventдовелось би розширити цими полями чи завести четвертий варіант унії), а не просто “почати заповнювати опціональне поле”.