@stamhoofd/backend 2.56.0 → 2.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
- import { ArrayDecoder, AutoEncoder, AutoEncoderPatchType, BooleanDecoder, DateDecoder, EnumDecoder, Field, IntegerDecoder, isPatchableArray, PatchableArray, StringDecoder } from '@simonbackx/simple-encoding';
2
- import { AuditLog, Group, Member, Registration } from '@stamhoofd/models';
3
- import { Address, AuditLogPatchItem, AuditLogReplacement, AuditLogReplacementType, AuditLogType, BooleanStatus, FinancialSupportSettings, MemberDetails, Parent, ParentTypeHelper, Platform, PlatformConfig } from '@stamhoofd/structures';
1
+ import { AutoEncoder, AutoEncoderPatchType } from '@simonbackx/simple-encoding';
2
+ import { AuditLog, Group, Member, Organization, Registration, Event } from '@stamhoofd/models';
3
+ import { AuditLogReplacement, AuditLogReplacementType, AuditLogType, GroupType, MemberDetails, OrganizationMetaData, OrganizationPrivateMetaData, PlatformConfig, PlatformPrivateConfig } from '@stamhoofd/structures';
4
4
  import { Context } from '../helpers/Context';
5
- import { Formatter } from '@stamhoofd/utility';
5
+ import { explainPatch } from './explainPatch';
6
6
 
7
7
  export type MemberAddedAuditOptions = {
8
8
  type: AuditLogType.MemberAdded;
@@ -24,42 +24,87 @@ export type MemberRegisteredAuditOptions = {
24
24
  };
25
25
 
26
26
  export type PlatformConfigChangeAuditOptions = {
27
- type: AuditLogType.PlatformSettingChanged;
27
+ type: AuditLogType.PlatformSettingsChanged;
28
+ } & ({
29
+ oldConfig: PlatformPrivateConfig;
30
+ patch: PlatformPrivateConfig | AutoEncoderPatchType<PlatformPrivateConfig>;
31
+ } | {
28
32
  oldConfig: PlatformConfig;
29
33
  patch: PlatformConfig | AutoEncoderPatchType<PlatformConfig>;
34
+ });
35
+
36
+ export type OrganizationConfigChangeAuditOptions = {
37
+ type: AuditLogType.OrganizationSettingsChanged;
38
+ organization: Organization;
39
+ } & ({
40
+ oldMeta: OrganizationMetaData;
41
+ patch: OrganizationMetaData | AutoEncoderPatchType<OrganizationMetaData>;
42
+ } | {
43
+ oldMeta: OrganizationPrivateMetaData;
44
+ patch: OrganizationPrivateMetaData | AutoEncoderPatchType<OrganizationPrivateMetaData>;
45
+ });
46
+
47
+ export type EventAuditOptions = {
48
+ type: AuditLogType.EventAdded | AuditLogType.EventEdited | AuditLogType.EventDeleted;
49
+ event: Event;
50
+ oldData?: AutoEncoder;
51
+ patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
30
52
  };
31
53
 
32
- export type AuditLogOptions = MemberAddedAuditOptions | MemberEditedAuditOptions | MemberRegisteredAuditOptions | PlatformConfigChangeAuditOptions;
54
+ export type GroupAuditOptions = {
55
+ type: AuditLogType.GroupAdded | AuditLogType.GroupEdited | AuditLogType.GroupDeleted;
56
+ group: Group;
57
+ oldData?: AutoEncoder;
58
+ patch?: AutoEncoder | AutoEncoderPatchType<AutoEncoder>;
59
+ };
60
+
61
+ export type AuditLogOptions = GroupAuditOptions | EventAuditOptions | MemberAddedAuditOptions | MemberEditedAuditOptions | MemberRegisteredAuditOptions | PlatformConfigChangeAuditOptions | OrganizationConfigChangeAuditOptions;
33
62
 
34
63
  export const AuditLogService = {
35
64
  async log(options: AuditLogOptions) {
36
- const userId = Context.optionalAuth?.user?.id ?? null;
37
- const organizationId = Context.organization?.id ?? null;
65
+ try {
66
+ const userId = Context.optionalAuth?.user?.id ?? null;
67
+ const organizationId = Context.organization?.id ?? null;
38
68
 
39
- const model = new AuditLog();
69
+ const model = new AuditLog();
40
70
 
41
- model.type = options.type;
42
- model.userId = userId;
43
- model.organizationId = organizationId;
71
+ model.type = options.type;
72
+ model.userId = userId;
73
+ model.organizationId = organizationId;
44
74
 
45
- if (options.type === AuditLogType.MemberRegistered) {
46
- this.fillForMemberRegistered(model, options);
47
- }
48
- else if (options.type === AuditLogType.MemberUnregistered) {
49
- this.fillForMemberRegistered(model, options);
50
- }
51
- else if (options.type === AuditLogType.MemberEdited) {
52
- this.fillForMemberEdited(model, options);
53
- }
54
- else if (options.type === AuditLogType.MemberAdded) {
55
- this.fillForMemberAdded(model, options);
75
+ if (options.type === AuditLogType.MemberRegistered) {
76
+ this.fillForMemberRegistered(model, options);
77
+ }
78
+ else if (options.type === AuditLogType.MemberUnregistered) {
79
+ this.fillForMemberRegistered(model, options);
80
+ }
81
+ else if (options.type === AuditLogType.MemberEdited) {
82
+ this.fillForMemberEdited(model, options);
83
+ }
84
+ else if (options.type === AuditLogType.MemberAdded) {
85
+ this.fillForMemberAdded(model, options);
86
+ }
87
+ else if (options.type === AuditLogType.PlatformSettingsChanged) {
88
+ this.fillForPlatformConfig(model, options);
89
+ }
90
+ else if (options.type === AuditLogType.OrganizationSettingsChanged) {
91
+ this.fillForOrganizationConfig(model, options);
92
+ }
93
+ else if (options.type === AuditLogType.EventAdded || options.type === AuditLogType.EventEdited || options.type === AuditLogType.EventDeleted) {
94
+ this.fillForEvent(model, options);
95
+ }
96
+ else if (options.type === AuditLogType.GroupAdded || options.type === AuditLogType.GroupEdited || options.type === AuditLogType.GroupDeleted) {
97
+ this.fillForGroup(model, options);
98
+ }
99
+
100
+ // In the future we might group these saves together in one query to improve performance
101
+ await model.save();
102
+
103
+ console.log('Audit log', model.id, options);
56
104
  }
57
- else if (options.type === AuditLogType.PlatformSettingChanged) {
58
- this.fillForPlatformConfig(model, options);
105
+ catch (e) {
106
+ console.error('Failed to save log', options, e);
59
107
  }
60
-
61
- // In the future we might group these saves together in one query to improve performance
62
- await model.save();
63
108
  },
64
109
 
65
110
  fillForMemberRegistered(model: AuditLog, options: MemberRegisteredAuditOptions) {
@@ -116,566 +161,72 @@ export const AuditLogService = {
116
161
  fillForPlatformConfig(model: AuditLog, options: PlatformConfigChangeAuditOptions) {
117
162
  model.objectId = null;
118
163
 
119
- let word = 'Platforminstellingen';
120
- let c = 2;
121
- let id: string | null = null;
122
-
123
- const changedProps = Object.keys(options.patch).filter((prop) => {
124
- return !prop.startsWith('_') && options.patch[prop] && (!(options.patch[prop] instanceof PatchableArray) || options.patch[prop].changes.length > 0);
125
- });
126
-
127
- if (changedProps.length === 1) {
128
- const prop = changedProps[0] as keyof PlatformConfig;
129
- id = prop;
130
-
131
- switch (prop) {
132
- case 'financialSupport':
133
- word = options.oldConfig.financialSupport?.title || FinancialSupportSettings.defaultTitle;
134
- c = 2;
135
- break;
136
-
137
- case 'dataPermission':
138
- word = 'De instellingen voor toestemming gegevensverzameling';
139
- c = 2;
140
- break;
141
-
142
- case 'tags':
143
- word = 'De hierarchie';
144
- c = 1;
145
- break;
146
-
147
- case 'premiseTypes':
148
- word = 'De soorten lokalen';
149
- c = 2;
150
- break;
151
-
152
- case 'recordsConfiguration':
153
- word = 'De persoonsgegevens van leden';
154
- c = 2;
155
- break;
156
-
157
- case 'defaultAgeGroups':
158
- word = 'De standaard leeftijdsgroepen';
159
- c = 2;
160
- break;
161
-
162
- case 'responsibilities':
163
- word = 'De functies van leden';
164
- c = 2;
165
- break;
166
-
167
- case 'membershipTypes':
168
- word = 'De aansluitingen en verzekeringen';
169
- c = 2;
170
- break;
171
-
172
- case 'eventTypes':
173
- word = 'De soorten activiteiten';
174
- c = 2;
175
- break;
176
-
177
- case 'featureFlags':
178
- word = 'Feature flags';
179
- c = 2;
180
- break;
181
-
182
- case 'coverPhoto':
183
- word = 'De omslagfoto';
184
- c = 2;
185
- break;
186
-
187
- case 'expandLogo':
188
- case 'squareLogo':
189
- case 'horizontalLogo':
190
- word = 'Het logo';
191
- c = 1;
192
- break;
193
-
194
- case 'squareLogoDark':
195
- case 'horizontalLogoDark':
196
- word = 'Het logo in donkere modus';
197
- c = 1;
198
- break;
199
-
200
- case 'logoDocuments':
201
- word = 'Het logo op documenten';
202
- c = 1;
203
- break;
204
-
205
- case 'privacy':
206
- word = 'Privacyinstellingen';
207
- c = 2;
208
- break;
209
-
210
- case 'color':
211
- word = 'De huisstijkleur';
212
- c = 1;
213
- break;
214
-
215
- case 'name':
216
- word = 'De naam van het platform';
217
- c = 1;
218
- break;
219
- }
220
- }
164
+ // Generate changes list
165
+ model.patchList = explainPatch(options.oldConfig, options.patch);
166
+ },
221
167
 
222
- console.log('changedProps', changedProps, options.patch);
168
+ fillForOrganizationConfig(model: AuditLog, options: OrganizationConfigChangeAuditOptions) {
169
+ model.objectId = options.organization.id;
170
+ model.organizationId = options.organization.id;
223
171
 
224
172
  model.replacements = new Map([
225
173
  ['o', AuditLogReplacement.create({
226
- id: id ?? undefined,
227
- value: word,
228
- count: c,
174
+ id: options.organization.id,
175
+ value: options.organization.name,
176
+ type: AuditLogReplacementType.Organization,
229
177
  })],
230
178
  ]);
231
179
 
232
180
  // Generate changes list
233
- model.patchList = explainPatch(options.oldConfig, options.patch);
181
+ model.patchList = explainPatch(options.oldMeta, options.patch);
234
182
  },
235
- };
236
-
237
- export type PatchExplainer = {
238
- key: string;
239
- handler: (oldValue: unknown, value: unknown) => AuditLogPatchItem[];
240
- };
241
-
242
- function createStringChangeHandler(key: string) {
243
- return (oldValue: unknown, value: unknown) => {
244
- if ((typeof oldValue !== 'string' && oldValue !== null) || (typeof value !== 'string' && value !== null)) {
245
- return [];
246
- }
247
- if (oldValue === value) {
248
- return [];
249
- }
250
- return [
251
- AuditLogPatchItem.create({
252
- key: key,
253
- oldValue: oldValue ?? undefined,
254
- value: value ?? undefined,
255
- }),
256
- ];
257
- };
258
- }
259
-
260
- function createIntegerChangeHandler(key: string) {
261
- return (oldValue: unknown, value: unknown) => {
262
- if ((typeof oldValue !== 'number' && oldValue !== null) || (typeof value !== 'number' && value !== null)) {
263
- return [];
264
- }
265
- if (oldValue === value) {
266
- return [];
267
- }
268
- return [
269
- AuditLogPatchItem.create({
270
- key: key,
271
- oldValue: oldValue !== null ? Formatter.integer(oldValue) : undefined,
272
- value: value !== null ? Formatter.integer(value) : undefined,
273
- }),
274
- ];
275
- };
276
- }
277
-
278
- function createDateChangeHandler(key: string) {
279
- return (oldValue: unknown, value: unknown) => {
280
- if ((!(oldValue instanceof Date) && oldValue !== null) || (!(value instanceof Date)) && value !== null) {
281
- return [];
282
- }
283
-
284
- if (oldValue?.getTime() === value?.getTime()) {
285
- return [];
286
- }
287
- return [
288
- AuditLogPatchItem.create({
289
- key: key,
290
- oldValue: oldValue ? Formatter.dateNumber(oldValue, true) : undefined,
291
- value: value ? Formatter.dateNumber(value, true) : undefined,
292
- }),
293
- ];
294
- };
295
- }
296
-
297
- function createBooleanChangeHandler(key: string) {
298
- return (oldValue: unknown, value: unknown) => {
299
- if (typeof oldValue !== 'boolean' && oldValue !== null) {
300
- return [];
301
- }
302
183
 
303
- if (typeof value !== 'boolean' && value !== null) {
304
- return [];
305
- }
306
-
307
- if (oldValue === value) {
308
- return [];
309
- }
184
+ fillForEvent(model: AuditLog, options: EventAuditOptions) {
185
+ model.objectId = options.event.id;
310
186
 
311
- return [
312
- AuditLogPatchItem.create({
313
- key: key,
314
- oldValue: oldValue === true ? 'Aangevinkt' : (oldValue === false ? 'Uitgevinkt' : undefined),
315
- value: value === true ? 'Aangevinkt' : (value === false ? 'Uitgevinkt' : undefined),
316
- }),
317
- ];
318
- };
319
- }
320
-
321
- function getAutoEncoderName(autoEncoder: unknown) {
322
- if (typeof autoEncoder === 'string') {
323
- return autoEncoder;
324
- }
325
-
326
- if (autoEncoder instanceof Parent) {
327
- return autoEncoder.name + ` (${ParentTypeHelper.getName(autoEncoder.type)})`;
328
- }
329
-
330
- if (autoEncoder instanceof Address) {
331
- return autoEncoder.shortString();
332
- }
333
-
334
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'name' in autoEncoder && typeof autoEncoder.name === 'string') {
335
- return autoEncoder.name;
336
- }
337
- return null;
338
- }
339
-
340
- function createArrayChangeHandler(key: string) {
341
- return (oldValue: unknown, value: unknown) => {
342
- if (!isPatchableArray(value)) {
343
- // Not supported
344
- return [];
187
+ if (options.patch) {
188
+ // Generate changes list
189
+ model.patchList = explainPatch(options.oldData ?? null, options.patch);
345
190
  }
346
- if (!Array.isArray(oldValue)) {
347
- // Not supported
348
- return [];
349
- }
350
-
351
- const items: AuditLogPatchItem[] = [];
352
- const createdIdSet = new Set<string>();
353
-
354
- const keySingular = key.replace(/ies$/, 'y').replace(/s$/, '');
355
-
356
- for (const { put } of value.getPuts()) {
357
- if (!(put instanceof AutoEncoder)) {
358
- // Not supported
359
- continue;
360
- }
361
-
362
- // Little hack: detect PUT/DELETE behaviour:
363
- let original = 'id' in put ? oldValue.find(v => v.id === put.id) : null;
364
- if (original && !(original instanceof AutoEncoder)) {
365
- // Not supported
366
- original = null;
367
- }
368
191
 
369
- // Added a new parent
370
- if (!original) {
371
- items.push(
372
- AuditLogPatchItem.create({
373
- key: keySingular,
374
- value: getAutoEncoderName(put) || keySingular,
375
- }),
376
- );
377
- }
378
-
379
- if ('id' in put && typeof put.id === 'string') {
380
- createdIdSet.add(put.id);
381
- }
382
-
383
- items.push(
384
- ...explainPatch(
385
- original ?? null,
386
- put,
387
- ).map((i) => {
388
- i.name = getAutoEncoderName(put) || keySingular;
389
- return i;
390
- }),
391
- );
392
- }
393
-
394
- for (const patch of value.getPatches()) {
395
- const original = oldValue.find(v => v.id === patch.id);
396
- if (!original) {
397
- // Not supported
398
- continue;
399
- }
400
- if (!(original instanceof AutoEncoder)) {
401
- // Not supported
402
- continue;
403
- }
404
-
405
- items.push(
406
- ...explainPatch(
407
- original,
408
- patch,
409
- ).map((i) => {
410
- i.name = getAutoEncoderName(original) || keySingular;
411
- return i;
412
- }),
413
- );
414
- }
415
-
416
- for (const id of value.getDeletes()) {
417
- if (typeof id !== 'string') {
418
- continue;
419
- }
420
- const original = oldValue.find(v => v.id === id);
421
- if (!original) {
422
- // Not supported
423
- continue;
424
- }
425
- if (!(original instanceof AutoEncoder)) {
426
- // Not supported
427
- continue;
428
- }
429
-
430
- if (createdIdSet.has(id)) {
431
- // DELETE + PUT happened
432
- continue;
433
- }
434
-
435
- items.push(
436
- AuditLogPatchItem.create({
437
- key: keySingular,
438
- value: undefined,
439
- oldValue: getAutoEncoderName(original) || keySingular,
440
- }),
441
- );
442
- }
443
-
444
- if (value.getMoves().length > 0) {
445
- items.push(
446
- AuditLogPatchItem.create({
447
- key: '_order',
448
- }),
449
- );
450
- }
451
- return items;
452
- };
453
- }
454
-
455
- function createSimpleArrayChangeHandler(key: string) {
456
- return (oldValue: unknown, value: unknown) => {
457
- if (!Array.isArray(oldValue)) {
458
- // Not supported
459
- return [];
460
- }
461
- const keySingular = key.replace(/ies$/, 'y').replace(/s$/, '');
462
-
463
- if (Array.isArray(value)) {
464
- if (!value.every(v => typeof v === 'string')) {
465
- // Not supported
466
- return [];
467
- }
468
- if (!oldValue.every(v => typeof v === 'string')) {
469
- // Not supported
470
- return [];
471
- }
472
-
473
- // Simple change
474
- const valueStr = (value as string[]).join(', ');
475
- const oldValueStr = (oldValue as string[]).join(', ');
476
-
477
- if (valueStr === oldValueStr) {
478
- return [];
479
- }
480
-
481
- return [
482
- AuditLogPatchItem.create({
483
- key: keySingular,
484
- oldValue: oldValue.length ? oldValueStr : undefined,
485
- value: value.length ? valueStr : undefined,
486
- }),
487
- ];
488
- }
489
-
490
- if (!isPatchableArray(value)) {
491
- // Not supported
492
- return [];
493
- }
494
-
495
- const items: AuditLogPatchItem[] = [];
496
- const createdIdSet = new Set<string>();
497
-
498
- for (const { put } of value.getPuts()) {
499
- if (typeof put !== 'string') {
500
- // Not supported
501
- continue;
502
- }
503
-
504
- // Little hack: detect PUT/DELETE behaviour:
505
- const original = oldValue.find(v => v === put);
506
-
507
- // Added a new parent
508
- if (!original) {
509
- items.push(
510
- AuditLogPatchItem.create({
511
- key: keySingular,
512
- value: put,
513
- }),
514
- );
515
- }
516
- createdIdSet.add(put);
517
- }
518
-
519
- for (const id of value.getDeletes()) {
520
- if (typeof id !== 'string') {
521
- continue;
522
- }
523
-
524
- if (createdIdSet.has(id)) {
525
- // DELETE + PUT happened
526
- continue;
527
- }
528
-
529
- const original = oldValue.find(v => v === id);
530
- if (!original || typeof original !== 'string') {
531
- // Not supported
532
- continue;
533
- }
192
+ model.replacements = new Map([
193
+ ['e', AuditLogReplacement.create({
194
+ id: options.event.id,
195
+ value: options.event.name,
196
+ type: AuditLogReplacementType.Event,
197
+ })],
198
+ ]);
199
+ },
534
200
 
535
- items.push(
536
- AuditLogPatchItem.create({
537
- key: keySingular,
538
- value: undefined,
539
- oldValue: original,
540
- }),
541
- );
542
- }
201
+ fillForGroup(model: AuditLog, options: GroupAuditOptions) {
202
+ model.objectId = options.group.id;
543
203
 
544
- if (value.getMoves().length > 0) {
545
- items.push(
546
- AuditLogPatchItem.create({
547
- key: '_order',
548
- }),
549
- );
204
+ if (options.patch) {
205
+ // Generate changes list
206
+ model.patchList = explainPatch(options.oldData ?? null, options.patch);
550
207
  }
551
- return items;
552
- };
553
- }
554
-
555
- function getExplainerForField(field: Field<any>) {
556
- if (field.decoder === StringDecoder || field.decoder instanceof EnumDecoder) {
557
- return createStringChangeHandler(field.property);
558
- }
559
-
560
- if (field.decoder === DateDecoder) {
561
- return createDateChangeHandler(field.property);
562
- }
563
-
564
- if (field.decoder === BooleanDecoder) {
565
- return createBooleanChangeHandler(field.property);
566
- }
567
-
568
- if (field.decoder === IntegerDecoder) {
569
- return createIntegerChangeHandler(field.property);
570
- }
571
-
572
- if (field.decoder instanceof ArrayDecoder && field.decoder.decoder === StringDecoder) {
573
- return createSimpleArrayChangeHandler(field.property);
574
- }
575
-
576
- if (field.decoder instanceof ArrayDecoder) {
577
- return createArrayChangeHandler(field.property);
578
- }
579
-
580
- if (field.decoder === BooleanStatus) {
581
- return (oldValue: unknown, value: unknown) => {
582
- if (value === undefined) {
583
- return [];
584
- }
585
-
586
- const wasTrueOld = oldValue instanceof BooleanStatus ? oldValue.value : null;
587
- const isTrue = value instanceof BooleanStatus ? value.value : null;
588
208
 
589
- if (wasTrueOld === isTrue) {
590
- return [];
591
- }
592
-
593
- return [
594
- AuditLogPatchItem.create({
595
- key: field.property,
596
- oldValue: wasTrueOld === true ? 'Aangevinkt' : (wasTrueOld === false ? 'Uitgevinkt' : undefined),
597
- value: isTrue === true ? 'Aangevinkt' : (isTrue === false ? 'Uitgevinkt' : undefined),
598
- }),
599
- ];
600
- };
601
- }
602
-
603
- if ((field.decoder as any).prototype instanceof AutoEncoder || field.decoder === AutoEncoder) {
604
- return (oldValue: unknown, value: unknown) => {
605
- if (!(value instanceof AutoEncoder) && value !== null) {
606
- return [];
607
- }
608
-
609
- if (oldValue === value) {
610
- return [];
611
- }
612
-
613
- if (!oldValue && getAutoEncoderName(value as AutoEncoder)) {
614
- // Simplify addition
615
- return [
616
- AuditLogPatchItem.create({
617
- key: field.property,
618
- value: getAutoEncoderName(value as AutoEncoder) || field.property,
619
- }),
620
- ];
621
- }
622
-
623
- if (value === null) {
624
- return [
625
- AuditLogPatchItem.create({
626
- key: field.property,
627
- oldValue: getAutoEncoderName(oldValue as AutoEncoder) || field.property,
628
- }),
629
- ];
209
+ if (options.group.type === GroupType.WaitingList) {
210
+ // Change event type
211
+ switch (options.type) {
212
+ case AuditLogType.GroupAdded:
213
+ model.type = AuditLogType.WaitingListAdded;
214
+ break;
215
+ case AuditLogType.GroupEdited:
216
+ model.type = AuditLogType.WaitingListEdited;
217
+ break;
218
+ case AuditLogType.GroupDeleted:
219
+ model.type = AuditLogType.WaitingListDeleted;
220
+ break;
630
221
  }
631
-
632
- return explainPatch(oldValue as AutoEncoder | null, value).map((i) => {
633
- i.key = field.property + '.' + i.key;
634
- return i;
635
- });
636
- };
637
- }
638
-
639
- // Simple addition/delete/change detection
640
- return (oldValue: unknown, value: unknown) => {
641
- if (value === undefined) {
642
- return [];
643
- }
644
-
645
- if (oldValue === value) {
646
- return [];
647
- }
648
-
649
- return [
650
- AuditLogPatchItem.create({
651
- key: field.property,
652
- }),
653
- ];
654
- };
655
- }
656
-
657
- function explainPatch<T extends AutoEncoder>(original: T | null, patch: AutoEncoderPatchType<T> | T): AuditLogPatchItem[] {
658
- const items: AuditLogPatchItem[] = [];
659
-
660
- for (const key in patch) {
661
- const field = original ? original.static.fields.find(f => f.property === key) : patch.static.fields.find(f => f.property === key);
662
- if (!field) {
663
- continue;
664
- }
665
-
666
- const oldValue = original?.[key] ?? null;
667
- const value = patch[key];
668
-
669
- if (patch.isPut() && key === 'id') {
670
- continue;
671
- }
672
-
673
- const handler = getExplainerForField(field);
674
- if (!handler) {
675
- continue;
676
222
  }
677
223
 
678
- items.push(...handler(oldValue, value));
679
- }
680
- return items;
681
- }
224
+ model.replacements = new Map([
225
+ ['g', AuditLogReplacement.create({
226
+ id: options.group.id,
227
+ value: options.group.settings.name,
228
+ type: AuditLogReplacementType.Group,
229
+ })],
230
+ ]);
231
+ },
232
+ };