@stamhoofd/backend 2.59.0 → 2.61.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.
Files changed (34) hide show
  1. package/index.ts +4 -0
  2. package/package.json +10 -10
  3. package/src/audit-logs/DocumentTemplateLogger.ts +22 -0
  4. package/src/audit-logs/EmailAddressLogger.ts +45 -0
  5. package/src/audit-logs/EmailLogger.ts +67 -0
  6. package/src/audit-logs/EmailTemplateLogger.ts +33 -0
  7. package/src/audit-logs/MemberPlatformMembershipLogger.ts +6 -3
  8. package/src/audit-logs/ModelLogger.ts +23 -6
  9. package/src/audit-logs/OrderLogger.ts +2 -2
  10. package/src/audit-logs/OrganizationLogger.ts +1 -11
  11. package/src/audit-logs/UserLogger.ts +45 -0
  12. package/src/crons/amazon-ses.ts +324 -0
  13. package/src/crons/clearExcelCache.ts +3 -0
  14. package/src/crons/endFunctionsOfUsersWithoutRegistration.ts +3 -0
  15. package/src/crons/index.ts +4 -0
  16. package/src/crons/postmark.ts +223 -0
  17. package/src/crons.ts +3 -315
  18. package/src/endpoints/global/members/PatchOrganizationMembersEndpoint.ts +3 -3
  19. package/src/endpoints/global/platform/PatchPlatformEnpoint.ts +2 -1
  20. package/src/endpoints/global/registration/RegisterMembersEndpoint.ts +38 -25
  21. package/src/endpoints/organization/dashboard/organization/PatchOrganizationEndpoint.ts +2 -1
  22. package/src/endpoints/organization/dashboard/registration-periods/PatchOrganizationRegistrationPeriodsEndpoint.ts +3 -11
  23. package/src/helpers/MemberUserSyncer.ts +11 -7
  24. package/src/helpers/PeriodHelper.ts +2 -1
  25. package/src/helpers/SetupStepUpdater.ts +503 -0
  26. package/src/seeds/1726847064-setup-steps.ts +1 -1
  27. package/src/seeds/1733319079-fill-paying-organization-ids.ts +68 -0
  28. package/src/services/AuditLogService.ts +19 -14
  29. package/src/services/DocumentService.ts +43 -0
  30. package/src/services/RegistrationService.ts +2 -0
  31. package/src/services/diff.ts +514 -0
  32. package/src/sql-filters/events.ts +13 -1
  33. package/src/crons/updateSetupSteps.ts +0 -9
  34. package/src/services/explainPatch.ts +0 -851
@@ -1,851 +0,0 @@
1
- import { ArrayDecoder, AutoEncoder, BooleanDecoder, DateDecoder, EnumDecoder, Field, getOptionalId, IntegerDecoder, isPatchableArray, isPatchMap, MapDecoder, StringDecoder, SymbolDecoder } from '@simonbackx/simple-encoding';
2
- import { AuditLogPatchItem, AuditLogPatchItemType, AuditLogReplacement, AuditLogReplacementType, BooleanStatus, Image, isEmptyFilter, isUuid, PropertyFilter, RichText, Version } from '@stamhoofd/structures';
3
- import { Formatter } from '@stamhoofd/utility';
4
- import { get } from 'http';
5
-
6
- export type PatchExplainer = {
7
- key: string;
8
- handler: (oldValue: unknown, value: unknown) => AuditLogPatchItem[];
9
- };
10
-
11
- function createStringChangeHandler(key: string) {
12
- return (oldValue: unknown, value: unknown) => {
13
- if (oldValue === value) {
14
- return [];
15
- }
16
-
17
- if (value === undefined) {
18
- // Not altered
19
- return [];
20
- }
21
-
22
- return [
23
- AuditLogPatchItem.create({
24
- key: getAutoEncoderKey(key),
25
- oldValue: getAutoEncoderValue(oldValue, key) || getAutoEncoderName(oldValue) || undefined,
26
- value: getAutoEncoderValue(value, key) || getAutoEncoderName(value) || undefined,
27
- }).autoType(),
28
- ];
29
- };
30
- }
31
-
32
- function createEnumChangeHandler(key: string) {
33
- return (oldValue: unknown, value: unknown) => {
34
- if (oldValue === value) {
35
- return [];
36
- }
37
-
38
- if (value === undefined) {
39
- // Not altered
40
- return [];
41
- }
42
-
43
- return [
44
- AuditLogPatchItem.create({
45
- key: getAutoEncoderKey(key),
46
- oldValue: typeof oldValue === 'string' ? AuditLogReplacement.key(oldValue) : undefined,
47
- value: typeof value === 'string' ? AuditLogReplacement.key(value) : undefined,
48
- }).autoType(),
49
- ];
50
- };
51
- }
52
- function createIntegerChangeHandler(key: string) {
53
- return (oldValue: unknown, value: unknown) => {
54
- if ((typeof oldValue !== 'number' && oldValue !== null) || (typeof value !== 'number' && value !== null)) {
55
- return [];
56
- }
57
- if (oldValue === value) {
58
- return [];
59
- }
60
-
61
- return [
62
- AuditLogPatchItem.create({
63
- key: getAutoEncoderKey(key),
64
- oldValue: oldValue !== null ? (getAutoEncoderValue(oldValue, key) || undefined) : undefined,
65
- value: value !== null ? (getAutoEncoderValue(value, key) || undefined) : undefined,
66
- }).autoType(),
67
- ];
68
- };
69
- }
70
-
71
- function createDateChangeHandler(key: string) {
72
- return (oldValue: unknown, value: unknown) => {
73
- if (!(oldValue instanceof Date) && oldValue !== null) {
74
- return [];
75
- }
76
-
77
- if ((!(value instanceof Date)) && value !== null) {
78
- return [];
79
- }
80
-
81
- if (oldValue?.getTime() === value?.getTime()) {
82
- return [];
83
- }
84
- let dno = oldValue ? Formatter.dateNumber(oldValue, true) : undefined;
85
- let dn = value ? Formatter.dateNumber(value, true) : undefined;
86
-
87
- if (dno && dn && (dno === dn || (Formatter.time(oldValue!) !== Formatter.time(value!))) && key !== 'birthDay') {
88
- // Add time
89
- dno += ' ' + Formatter.time(oldValue!);
90
- dn += ' ' + Formatter.time(value!);
91
- }
92
-
93
- return [
94
- AuditLogPatchItem.create({
95
- key: getAutoEncoderKey(key),
96
- oldValue: dno ? AuditLogReplacement.string(dno) : undefined,
97
- value: dn ? AuditLogReplacement.string(dn) : undefined,
98
- }).autoType(),
99
- ];
100
- };
101
- }
102
-
103
- function createBooleanChangeHandler(key: string) {
104
- return (oldValue: unknown, value: unknown) => {
105
- if (typeof oldValue !== 'boolean' && oldValue !== null) {
106
- return [];
107
- }
108
-
109
- if (typeof value !== 'boolean' && value !== null) {
110
- return [];
111
- }
112
-
113
- if (oldValue === value) {
114
- return [];
115
- }
116
-
117
- return [
118
- AuditLogPatchItem.create({
119
- key: getAutoEncoderKey(key),
120
- oldValue: oldValue === true ? AuditLogReplacement.key('on') : (oldValue === false ? AuditLogReplacement.key('off') : undefined),
121
- value: value === true ? AuditLogReplacement.key('on') : (value === false ? AuditLogReplacement.key('off') : undefined),
122
- }).autoType(),
123
- ];
124
- };
125
- }
126
-
127
- function getAutoEncoderKey(autoEncoder: string): AuditLogReplacement;
128
- function getAutoEncoderKey(autoEncoder: unknown): AuditLogReplacement | null;
129
- function getAutoEncoderKey(autoEncoder: unknown): AuditLogReplacement | null {
130
- if (typeof autoEncoder === 'string') {
131
- if (isUuid(autoEncoder)) {
132
- return AuditLogReplacement.uuid(autoEncoder);
133
- }
134
- return AuditLogReplacement.key(autoEncoder);
135
- }
136
- return null;
137
- }
138
-
139
- function getAutoEncoderName(autoEncoder: unknown): AuditLogReplacement | null {
140
- if (typeof autoEncoder === 'string') {
141
- if (isUuid(autoEncoder)) {
142
- return AuditLogReplacement.uuid(autoEncoder);
143
- }
144
- return AuditLogReplacement.string(autoEncoder);
145
- }
146
-
147
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPatchName' in autoEncoder && typeof autoEncoder.getPatchName === 'function') {
148
- const name = autoEncoder.getPatchName();
149
- if (typeof name === 'string') {
150
- return name ? AuditLogReplacement.string(name) : AuditLogReplacement.key('untitled');
151
- }
152
- }
153
-
154
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'name' in autoEncoder && typeof autoEncoder.name === 'string') {
155
- return autoEncoder.name ? AuditLogReplacement.string(autoEncoder.name) : AuditLogReplacement.key('untitled');
156
- }
157
- return null;
158
- }
159
- function getAutoEncoderPutValue(autoEncoder: unknown, key?: string): AuditLogReplacement | null {
160
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPutValue' in autoEncoder && typeof autoEncoder.getPutValue === 'function') {
161
- const name = autoEncoder.getPutValue();
162
- if (typeof name === 'string') {
163
- return AuditLogReplacement.string(name);
164
- }
165
- if (name instanceof AuditLogReplacement) {
166
- return name;
167
- }
168
- }
169
- return getAutoEncoderValue(autoEncoder, key);
170
- }
171
-
172
- function getAutoEncoderValue(autoEncoder: unknown, key?: string): AuditLogReplacement | null {
173
- if (typeof autoEncoder === 'string') {
174
- if (isUuid(autoEncoder)) {
175
- return AuditLogReplacement.uuid(autoEncoder);
176
- }
177
- if (key && key === 'status') {
178
- // Will be an enum
179
- return AuditLogReplacement.key(autoEncoder);
180
- }
181
- return AuditLogReplacement.string(autoEncoder);
182
- }
183
-
184
- if (typeof autoEncoder === 'symbol') {
185
- const name = Symbol.keyFor(autoEncoder);
186
- if (name) {
187
- return AuditLogReplacement.key(name);
188
- }
189
- return AuditLogReplacement.key('unknown');
190
- }
191
-
192
- if (typeof autoEncoder === 'number') {
193
- if (key && (key.toLowerCase().includes('price') || key.toLowerCase().includes('fee'))) {
194
- return AuditLogReplacement.string(Formatter.price(autoEncoder));
195
- }
196
- return AuditLogReplacement.string(Formatter.integer(autoEncoder));
197
- }
198
-
199
- if (autoEncoder instanceof Date) {
200
- return AuditLogReplacement.string(Formatter.dateTime(autoEncoder, true, true));
201
- }
202
-
203
- if (typeof autoEncoder === 'object' && autoEncoder !== null && 'getPatchValue' in autoEncoder && typeof autoEncoder.getPatchValue === 'function') {
204
- const name = autoEncoder.getPatchValue();
205
- if (typeof name === 'string') {
206
- return AuditLogReplacement.string(name);
207
- }
208
- if (name instanceof AuditLogReplacement) {
209
- return name;
210
- }
211
- }
212
-
213
- if (autoEncoder instanceof Image) {
214
- return AuditLogReplacement.create({
215
- id: autoEncoder.getPathForSize(undefined, undefined),
216
- value: autoEncoder.source.name ?? undefined,
217
- type: AuditLogReplacementType.Image,
218
- });
219
- }
220
-
221
- if (autoEncoder instanceof RichText) {
222
- return AuditLogReplacement.string(autoEncoder.text);
223
- }
224
-
225
- if (autoEncoder instanceof PropertyFilter) {
226
- if (autoEncoder.isAlwaysEnabledAndRequired) {
227
- return AuditLogReplacement.key('alwaysEnabledAndRequired');
228
- }
229
- if (autoEncoder.enabledWhen === null && autoEncoder.requiredWhen === null) {
230
- return AuditLogReplacement.key('alwaysEnabledAndOptional');
231
- }
232
- if (autoEncoder.enabledWhen !== null && autoEncoder.requiredWhen === null) {
233
- return AuditLogReplacement.key('sometimesEnabledAndOptional');
234
- }
235
- if (autoEncoder.enabledWhen === null && autoEncoder.requiredWhen !== null) {
236
- return AuditLogReplacement.key('alwaysEnabledAndSometimesRequired');
237
- }
238
- if (autoEncoder.enabledWhen !== null && isEmptyFilter(autoEncoder.requiredWhen)) {
239
- return AuditLogReplacement.key('sometimesEnabledAndRequired');
240
- }
241
- return AuditLogReplacement.key('sometimesEnabledAndSometimesRequired');
242
- }
243
-
244
- return null;
245
- }
246
-
247
- function getKeySingular(key: string) {
248
- return key.replace(/ies$/, 'y').replace(/s$/, '');
249
- }
250
-
251
- function findOriginalById(id: unknown, oldArray: unknown[]): unknown | null {
252
- return id ? oldArray.find(v => getId(v) === id) : null;
253
- }
254
-
255
- function findOriginalIndexById(id: unknown, oldArray: unknown[]): number {
256
- return id ? oldArray.findIndex(v => getId(v) === id) : -1;
257
- }
258
-
259
- function getId(object: unknown): string | number | null {
260
- const id = getOptionalId(object);
261
- if (typeof id !== 'string' && typeof id !== 'number') {
262
- if (object instanceof AutoEncoder) {
263
- const encoded = object.encode({ version: Version });
264
- return JSON.stringify(encoded);
265
- }
266
- return JSON.stringify(object);
267
- }
268
- return id;
269
- }
270
-
271
- function findOriginal(put: unknown, oldArray: unknown[]): unknown | null {
272
- return findOriginalById(getId(put), oldArray);
273
- }
274
-
275
- function findIndex(put: unknown, oldArray: unknown[]): number {
276
- return findOriginalIndexById(getId(put), oldArray);
277
- }
278
-
279
- function processPut(key: string, put: unknown, original: unknown | null, createdIdSet?: Set<string>): AuditLogPatchItem[] {
280
- const items: AuditLogPatchItem[] = [];
281
- const keySingular = getKeySingular(key);
282
- const v = getAutoEncoderPutValue(put);
283
-
284
- // Added a new parent
285
- if (!original) {
286
- const n = getAutoEncoderName(put);
287
- items.push(
288
- AuditLogPatchItem.create({
289
- key: AuditLogReplacement.key(keySingular).append(n),
290
- value: (n?.toString() !== v?.toString()) ? (v || undefined) : undefined,
291
- type: AuditLogPatchItemType.Added,
292
- }),
293
- );
294
- }
295
-
296
- // Little hack: detect PUT/DELETE behaviour:
297
- if (createdIdSet) {
298
- const id = getId(put);
299
- if (id && typeof id === 'string') {
300
- createdIdSet.add(id);
301
- }
302
- }
303
-
304
- if (!original && (v || getAutoEncoderName(put))) {
305
- // Simplify addition: don't show all added properties
306
- return items;
307
- }
308
-
309
- items.push(
310
- ...explainPatch(
311
- original ?? null,
312
- put,
313
- ).map((i) => {
314
- i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(put) || AuditLogReplacement.key('item'));
315
- i.key = i.key.prepend(getAutoEncoderKey(key));
316
- return i;
317
- }),
318
- );
319
- return items;
320
- }
321
-
322
- function processPatch(key: string, patch: unknown, original: unknown | null): AuditLogPatchItem[] {
323
- if (!original) {
324
- // Not supported
325
- return [];
326
- }
327
-
328
- if (patch === original) {
329
- return [];
330
- }
331
-
332
- const items: AuditLogPatchItem[] = [];
333
- const keySingular = getKeySingular(key);
334
-
335
- const l = explainPatch(
336
- original,
337
- patch,
338
- ).map((i) => {
339
- i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(patch) || AuditLogReplacement.key('item'));
340
- i.key = i.key.prepend(getAutoEncoderKey(key));
341
- return i;
342
- });
343
- let ov = getAutoEncoderValue(original);
344
- let v = getAutoEncoderValue(patch);
345
-
346
- if (l.length === 0 && patch instanceof AutoEncoder && patch.isPatch()) {
347
- items.push(
348
- AuditLogPatchItem.create({
349
- key: getAutoEncoderKey(keySingular).append(getAutoEncoderName(original) || getAutoEncoderName(patch) || AuditLogReplacement.key('item')),
350
- oldValue: ov || undefined,
351
- value: v || undefined,
352
- type: AuditLogPatchItemType.Changed,
353
- }),
354
- );
355
- return items;
356
- }
357
-
358
- if (ov && v) {
359
- if (ov.toString() === v.toString()) {
360
- ov = null;
361
- v = null;
362
-
363
- // if (l.length === 0) {
364
- // Probably no change
365
- return [];
366
- // }
367
- }
368
-
369
- // Simplify changes by providing one change instead of for all keys
370
- items.push(
371
- AuditLogPatchItem.create({
372
- key: getAutoEncoderKey(keySingular).append(getAutoEncoderName(original) || getAutoEncoderName(v) || AuditLogReplacement.key('item')),
373
- oldValue: ov || undefined,
374
- value: v || undefined,
375
- type: AuditLogPatchItemType.Changed,
376
- }),
377
- );
378
- return items;
379
- }
380
-
381
- items.push(
382
- ...l,
383
- );
384
-
385
- return items;
386
- }
387
-
388
- function processDelete(key: string, deletedItem: unknown, createdIdSet?: Set<string>): AuditLogPatchItem[] {
389
- if (createdIdSet) {
390
- const id = getId(deletedItem);
391
- if (id && typeof id === 'string' && createdIdSet.has(id)) {
392
- // DELETE + PUT happened
393
- return [];
394
- }
395
- }
396
-
397
- const v = getAutoEncoderPutValue(deletedItem);
398
- const n = getAutoEncoderName(deletedItem);
399
-
400
- const keySingular = getKeySingular(key);
401
- const k = AuditLogReplacement.key(keySingular).append(n);
402
-
403
- return [
404
- AuditLogPatchItem.create({
405
- key: k,
406
- type: AuditLogPatchItemType.Removed,
407
- oldValue: (n?.toString() !== v?.toString()) ? (v || undefined) : undefined,
408
- }),
409
- ];
410
- }
411
-
412
- function createArrayChangeHandler(key: string) {
413
- return (oldValue: unknown, value: unknown) => {
414
- if (!Array.isArray(oldValue)) {
415
- // Not supported
416
- return [];
417
- }
418
- const items: AuditLogPatchItem[] = [];
419
-
420
- if (!isPatchableArray(value)) {
421
- if (Array.isArray(value)) {
422
- // Search for puts
423
- let orderChanged = false;
424
- let added = 0;
425
- for (const [index, newItem] of value.entries()) {
426
- const originalIndex = findIndex(newItem, oldValue);
427
-
428
- if (originalIndex === -1) {
429
- // Has been added
430
- items.push(...processPut(key, newItem, null));
431
- added++;
432
- }
433
- else {
434
- // Has been overwritten
435
- const original = oldValue[originalIndex];
436
- items.push(...processPatch(key, newItem, original));
437
-
438
- if ((index - added) !== originalIndex) {
439
- // Order has changed
440
- orderChanged = true;
441
- }
442
- }
443
- }
444
-
445
- // Search for deletes
446
- for (const original of oldValue) {
447
- const newItem = findOriginal(original, value);
448
- if (!newItem) {
449
- // Has been deleted
450
- items.push(...processDelete(key, original));
451
-
452
- orderChanged = false; // ignore order changed as delete will have set it to true
453
- }
454
- }
455
-
456
- if (orderChanged) {
457
- // Check if order has changed
458
- items.push(
459
- AuditLogPatchItem.create({
460
- key: getAutoEncoderKey(key),
461
- type: AuditLogPatchItemType.Reordered,
462
- }),
463
- );
464
- }
465
- }
466
- // Not supported
467
- return items;
468
- }
469
-
470
- const createdIdSet = new Set<string>();
471
-
472
- for (const { put } of value.getPuts()) {
473
- items.push(...processPut(key, put, findOriginal(put, oldValue), createdIdSet));
474
- }
475
-
476
- for (const patch of value.getPatches()) {
477
- items.push(...processPatch(key, patch, findOriginal(patch, oldValue)));
478
- }
479
-
480
- for (const id of value.getDeletes()) {
481
- items.push(...processDelete(key, findOriginalById(id, oldValue), createdIdSet));
482
- }
483
-
484
- if (value.getMoves().length > 0) {
485
- items.push(
486
- AuditLogPatchItem.create({
487
- key: getAutoEncoderKey(key),
488
- type: AuditLogPatchItemType.Reordered,
489
- }),
490
- );
491
- }
492
- return items;
493
- };
494
- }
495
-
496
- function createMapChangeHandler(key?: string) {
497
- return (oldValue: unknown, value: unknown) => {
498
- if (!(value instanceof Map)) {
499
- // Not supported
500
- return [];
501
- }
502
- if (!(oldValue instanceof Map)) {
503
- // Not supported
504
- return [];
505
- }
506
-
507
- const items: AuditLogPatchItem[] = [];
508
- const keySingular = key ? key.replace(/ies$/, 'y').replace(/s$/, '') : key;
509
- const isPatch = isPatchMap(value);
510
-
511
- for (const [k, v] of value.entries()) {
512
- if (typeof k !== 'string') {
513
- // Not supported
514
- continue;
515
- }
516
- let original = oldValue.get(k);
517
-
518
- if (v instanceof Map && !original) {
519
- original = new Map();
520
- }
521
-
522
- if (v === null && isPatch) {
523
- // Delete
524
- if (original) {
525
- items.push(
526
- AuditLogPatchItem.create({
527
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original)),
528
- oldValue: getAutoEncoderPutValue(original) || undefined,
529
- type: AuditLogPatchItemType.Removed,
530
- }),
531
- );
532
- }
533
- continue;
534
- }
535
-
536
- if (!original) {
537
- // added
538
- items.push(
539
- AuditLogPatchItem.create({
540
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(v)),
541
- value: getAutoEncoderPutValue(v) || undefined,
542
- type: AuditLogPatchItemType.Added,
543
- }),
544
- );
545
- }
546
- else {
547
- let ov = getAutoEncoderValue(original);
548
- let nv = getAutoEncoderValue(v);
549
-
550
- const c = explainPatch(
551
- original,
552
- v,
553
- ).map((i) => {
554
- i.key = i.key.prepend(getAutoEncoderName(original) || getAutoEncoderName(v) || getAutoEncoderKey(k));
555
- i.key = i.key.prepend(AuditLogReplacement.key(keySingular));
556
- return i;
557
- });
558
-
559
- if (ov && nv) {
560
- if (ov.toString() === nv.toString()) {
561
- ov = null;
562
- nv = null;
563
-
564
- // if (c.length === 0) {
565
- // Probably no change
566
- continue;
567
- // }
568
- }
569
-
570
- // Simplify change
571
- items.push(
572
- AuditLogPatchItem.create({
573
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original) || getAutoEncoderName(v)),
574
- oldValue: ov || undefined,
575
- value: nv || undefined,
576
- type: AuditLogPatchItemType.Changed,
577
- }),
578
- );
579
- continue;
580
- }
581
-
582
- if (c.length === 0 && v instanceof AutoEncoder && v.isPatch()) {
583
- // Manual log
584
- items.push(
585
- AuditLogPatchItem.create({
586
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(original) || getAutoEncoderName(v)),
587
- oldValue: getAutoEncoderValue(original) || undefined,
588
- value: getAutoEncoderValue(v) || undefined,
589
- type: AuditLogPatchItemType.Changed,
590
- }),
591
- );
592
- }
593
-
594
- // Changed
595
- items.push(
596
- ...c,
597
- );
598
- }
599
- }
600
-
601
- if (!isPatch) {
602
- // Loop old values
603
- for (const [k, v] of oldValue.entries()) {
604
- if (typeof k !== 'string') {
605
- // Not supported
606
- continue;
607
- }
608
-
609
- if (value.has(k)) {
610
- continue;
611
- }
612
-
613
- items.push(
614
- AuditLogPatchItem.create({
615
- key: AuditLogReplacement.key(keySingular).append(getAutoEncoderKey(k)).append(getAutoEncoderName(v)),
616
- oldValue: getAutoEncoderPutValue(v) || undefined,
617
- type: AuditLogPatchItemType.Removed,
618
- }),
619
- );
620
- }
621
- }
622
-
623
- return items;
624
- };
625
- }
626
-
627
- export function createUnknownChangeHandler(key: string) {
628
- return (oldValue: unknown, value: unknown) => {
629
- if (oldValue === value) {
630
- return [];
631
- }
632
-
633
- if ((Array.isArray(oldValue) || Array.isArray(value)) && (!oldValue || Array.isArray(oldValue)) && (Array.isArray(value) || !value)) {
634
- return createArrayChangeHandler(key)(oldValue, value);
635
- }
636
-
637
- if (!oldValue && oldValue !== 0 && getAutoEncoderValue(value, key)) {
638
- // Simplify addition
639
- return [
640
- AuditLogPatchItem.create({
641
- key: getAutoEncoderKey(key),
642
- value: getAutoEncoderPutValue(value, key) || undefined,
643
- type: AuditLogPatchItemType.Added,
644
- }),
645
- ];
646
- }
647
-
648
- if ((oldValue || oldValue === 0) && (value === null || value === undefined)) {
649
- return [
650
- AuditLogPatchItem.create({
651
- key: getAutoEncoderKey(key),
652
- oldValue: getAutoEncoderPutValue(oldValue, key) || undefined,
653
- type: AuditLogPatchItemType.Removed,
654
- }),
655
- ];
656
- }
657
-
658
- const items = explainPatch(oldValue, value).map((i) => {
659
- i.key = i.key.prepend(getAutoEncoderKey(key));
660
- return i;
661
- });
662
-
663
- let v = getAutoEncoderValue(value, key);
664
- let ov = getAutoEncoderValue(oldValue, key);
665
-
666
- if (oldValue !== undefined && oldValue !== null && value !== undefined && value !== null && getAutoEncoderValue(value, key) && items.length === 0 && value instanceof AutoEncoder && value.isPatch()) {
667
- return [
668
- AuditLogPatchItem.create({
669
- key: getAutoEncoderKey(key),
670
- value: v || undefined,
671
- oldValue: ov || undefined,
672
- type: AuditLogPatchItemType.Changed,
673
- }),
674
- ];
675
- }
676
-
677
- if (v && ov) {
678
- // Simplify change
679
- if (v.toString() === ov.toString()) {
680
- v = null;
681
- ov = null;
682
-
683
- // if (items.length === 0) {
684
- // Probably no change
685
- return [];
686
- // }
687
- }
688
-
689
- return [
690
- AuditLogPatchItem.create({
691
- key: getAutoEncoderKey(key),
692
- value: v || undefined,
693
- oldValue: ov || undefined,
694
- type: AuditLogPatchItemType.Changed,
695
- }),
696
- ];
697
- }
698
- return items;
699
- };
700
- }
701
-
702
- function getExplainerForField(field: Field<any>) {
703
- if (field.decoder === StringDecoder) {
704
- return createStringChangeHandler(field.property);
705
- }
706
-
707
- if (field.decoder instanceof EnumDecoder) {
708
- return createEnumChangeHandler(field.property);
709
- }
710
-
711
- if (field.decoder instanceof SymbolDecoder) {
712
- if (field.decoder.decoder === StringDecoder) {
713
- return createStringChangeHandler(field.property);
714
- }
715
-
716
- if (field.decoder.decoder instanceof EnumDecoder) {
717
- return createEnumChangeHandler(field.property);
718
- }
719
- }
720
-
721
- if (field.decoder === DateDecoder) {
722
- return createDateChangeHandler(field.property);
723
- }
724
-
725
- if (field.decoder === BooleanDecoder) {
726
- return createBooleanChangeHandler(field.property);
727
- }
728
-
729
- if (field.decoder === IntegerDecoder) {
730
- return createIntegerChangeHandler(field.property);
731
- }
732
-
733
- if (field.decoder instanceof ArrayDecoder) {
734
- const handler = createArrayChangeHandler(field.property);
735
-
736
- if (field.decoder.decoder instanceof EnumDecoder) {
737
- // Map values to keys
738
- return (oldValue: unknown, value: unknown) => {
739
- const items = handler(oldValue, value);
740
-
741
- for (const item of items) {
742
- if (item.oldValue && !item.oldValue.type) {
743
- item.oldValue.type = AuditLogReplacementType.Key;
744
- }
745
- if (item.value && !item.value.type) {
746
- item.value.type = AuditLogReplacementType.Key;
747
- }
748
-
749
- // If item.key is an array that ends with a 'value', also change it
750
- if (item.key.type === AuditLogReplacementType.Array) {
751
- const lastKeyItem = item.key.values[item.key.values.length - 1];
752
- if (!lastKeyItem.type) {
753
- lastKeyItem.type = AuditLogReplacementType.Key;
754
- }
755
- }
756
- else {
757
- if (!item.key.type) {
758
- item.key.type = AuditLogReplacementType.Key;
759
- }
760
- }
761
- }
762
- return items;
763
- };
764
- }
765
-
766
- return handler;
767
- }
768
-
769
- if (field.decoder instanceof MapDecoder) {
770
- return createMapChangeHandler(field.property);
771
- }
772
-
773
- if (field.decoder === BooleanStatus) {
774
- return (oldValue: unknown, value: unknown) => {
775
- if (value === undefined) {
776
- return [];
777
- }
778
-
779
- const wasTrueOld = oldValue instanceof BooleanStatus ? oldValue.value : null;
780
- const isTrue = value instanceof BooleanStatus ? value.value : null;
781
-
782
- if (wasTrueOld === isTrue) {
783
- return [];
784
- }
785
-
786
- return [
787
- AuditLogPatchItem.create({
788
- key: getAutoEncoderKey(field.property),
789
- oldValue: wasTrueOld === true ? AuditLogReplacement.key('checked') : (wasTrueOld === false ? AuditLogReplacement.key('unchecked') : undefined),
790
- value: isTrue === true ? AuditLogReplacement.key('checked') : (isTrue === false ? AuditLogReplacement.key('unchecked') : undefined),
791
- }).autoType(),
792
- ];
793
- };
794
- }
795
-
796
- return createUnknownChangeHandler(field.property);
797
- }
798
-
799
- export function explainPatch(original: unknown | null, patch: unknown): AuditLogPatchItem[] {
800
- if (isPatchableArray(patch)) {
801
- const b = createArrayChangeHandler('items');
802
- return b(original, patch);
803
- }
804
-
805
- if (original instanceof Map) {
806
- const b = createMapChangeHandler();
807
- return b(original, patch);
808
- }
809
-
810
- if (typeof patch !== 'object' || patch === null) {
811
- if (patch === null) {
812
- // todo
813
- }
814
- return [];
815
- }
816
- if (original && typeof original !== 'object') {
817
- return [];
818
- }
819
-
820
- const items: AuditLogPatchItem[] = [];
821
-
822
- for (const key in patch) {
823
- const field = original instanceof AutoEncoder
824
- ? original.static.latestFields.find(f => f.property === key)
825
- : (
826
- patch instanceof AutoEncoder
827
- ? patch.static.latestFields.find(f => f.property === key)
828
- : null
829
- );
830
- const oldValue = original?.[key] ?? null;
831
- const value = patch[key];
832
-
833
- if (!(patch instanceof AutoEncoder) || !field) {
834
- // try manual without type information
835
- items.push(...createUnknownChangeHandler(key)(oldValue, value));
836
- continue;
837
- }
838
-
839
- if (patch.isPut() && key === 'id') {
840
- continue;
841
- }
842
-
843
- const handler = getExplainerForField(field);
844
- if (!handler) {
845
- continue;
846
- }
847
-
848
- items.push(...handler(oldValue, value));
849
- }
850
- return items;
851
- }