@striae-org/striae 4.0.3 → 4.2.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 (118) hide show
  1. package/.env.example +8 -0
  2. package/app/components/actions/case-export/core-export.ts +14 -8
  3. package/app/components/actions/case-export/data-processing.ts +1 -0
  4. package/app/components/actions/case-export/download-handlers.ts +7 -0
  5. package/app/components/actions/case-export/metadata-helpers.ts +2 -1
  6. package/app/components/actions/case-import/confirmation-import.ts +12 -2
  7. package/app/components/actions/case-import/orchestrator.ts +78 -32
  8. package/app/components/actions/case-import/storage-operations.ts +97 -8
  9. package/app/components/actions/case-import/zip-processing.ts +159 -86
  10. package/app/components/actions/case-manage.ts +430 -8
  11. package/app/components/actions/confirm-export.ts +13 -4
  12. package/app/components/actions/generate-pdf.ts +10 -2
  13. package/app/components/actions/image-manage.ts +77 -44
  14. package/app/components/audit/user-audit-viewer.tsx +137 -945
  15. package/app/components/audit/user-audit.module.css +41 -0
  16. package/app/components/audit/viewer/audit-activity-summary.tsx +52 -0
  17. package/app/components/audit/viewer/audit-entries-list.tsx +207 -0
  18. package/app/components/audit/viewer/audit-filters-panel.tsx +307 -0
  19. package/app/components/audit/viewer/audit-user-info-card.tsx +44 -0
  20. package/app/components/audit/viewer/audit-viewer-header.tsx +55 -0
  21. package/app/components/audit/viewer/audit-viewer-utils.ts +123 -0
  22. package/app/components/audit/viewer/types.ts +1 -0
  23. package/app/components/audit/viewer/use-audit-viewer-data.ts +186 -0
  24. package/app/components/audit/viewer/use-audit-viewer-export.ts +176 -0
  25. package/app/components/audit/viewer/use-audit-viewer-filters.ts +141 -0
  26. package/app/components/auth/mfa-enrollment.module.css +13 -5
  27. package/app/components/auth/mfa-verification.module.css +13 -5
  28. package/app/components/canvas/box-annotations/box-annotations.module.css +22 -18
  29. package/app/components/canvas/box-annotations/box-annotations.tsx +15 -0
  30. package/app/components/canvas/canvas.module.css +64 -54
  31. package/app/components/canvas/canvas.tsx +17 -16
  32. package/app/components/canvas/confirmation/confirmation.module.css +1 -0
  33. package/app/components/canvas/confirmation/confirmation.tsx +17 -47
  34. package/app/components/navbar/case-modals/archive-case-modal.module.css +110 -0
  35. package/app/components/navbar/case-modals/archive-case-modal.tsx +129 -0
  36. package/app/components/navbar/case-modals/open-case-modal.module.css +81 -0
  37. package/app/components/navbar/case-modals/open-case-modal.tsx +120 -0
  38. package/app/components/navbar/case-modals/rename-case-modal.module.css +81 -0
  39. package/app/components/navbar/case-modals/rename-case-modal.tsx +107 -0
  40. package/app/components/navbar/navbar.module.css +447 -0
  41. package/app/components/navbar/navbar.tsx +377 -0
  42. package/app/components/public-signing-key-modal/public-signing-key-modal.module.css +2 -0
  43. package/app/components/public-signing-key-modal/public-signing-key-modal.tsx +21 -51
  44. package/app/components/sidebar/case-export/case-export.module.css +1 -0
  45. package/app/components/sidebar/case-export/case-export.tsx +14 -77
  46. package/app/components/sidebar/case-import/case-import.module.css +25 -0
  47. package/app/components/sidebar/case-import/case-import.tsx +64 -40
  48. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +20 -1
  49. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +15 -0
  50. package/app/components/sidebar/cases/case-sidebar.tsx +25 -519
  51. package/app/components/sidebar/cases/cases-modal.module.css +45 -9
  52. package/app/components/sidebar/cases/cases-modal.tsx +16 -16
  53. package/app/components/sidebar/cases/cases.module.css +62 -21
  54. package/app/components/sidebar/files/files-modal.module.css +46 -10
  55. package/app/components/sidebar/files/files-modal.tsx +22 -23
  56. package/app/components/sidebar/notes/notes-editor-modal.module.css +49 -0
  57. package/app/components/sidebar/notes/notes-editor-modal.tsx +66 -0
  58. package/app/components/sidebar/notes/notes-modal.tsx +18 -17
  59. package/app/components/sidebar/notes/notes-sidebar.tsx +199 -113
  60. package/app/components/sidebar/notes/notes.module.css +155 -0
  61. package/app/components/sidebar/sidebar-container.tsx +15 -28
  62. package/app/components/sidebar/sidebar.module.css +7 -71
  63. package/app/components/sidebar/sidebar.tsx +24 -125
  64. package/app/components/sidebar/upload/image-upload-zone.module.css +13 -13
  65. package/app/components/toast/toast.module.css +2 -1
  66. package/app/components/toast/toast.tsx +16 -11
  67. package/app/components/user/delete-account.tsx +10 -31
  68. package/app/components/user/inactivity-warning.module.css +9 -6
  69. package/app/components/user/inactivity-warning.tsx +15 -2
  70. package/app/components/user/manage-profile.module.css +2 -0
  71. package/app/components/user/manage-profile.tsx +108 -40
  72. package/app/hooks/useOverlayDismiss.ts +116 -0
  73. package/app/routes/auth/login.example.tsx +19 -8
  74. package/app/routes/auth/login.tsx +785 -774
  75. package/app/routes/auth/passwordReset.module.css +23 -13
  76. package/app/routes/striae/striae.module.css +10 -3
  77. package/app/routes/striae/striae.tsx +477 -31
  78. package/app/routes.ts +7 -0
  79. package/app/services/audit/audit-export-csv.ts +2 -0
  80. package/app/services/audit/audit.service.ts +202 -32
  81. package/app/services/audit/builders/audit-entry-builder.ts +2 -1
  82. package/app/services/audit/builders/audit-event-builders-case-file.ts +43 -0
  83. package/app/services/audit/builders/audit-event-builders-user-security.ts +4 -2
  84. package/app/services/audit/builders/audit-event-builders-workflow.ts +8 -0
  85. package/app/services/audit/builders/index.ts +1 -0
  86. package/app/types/audit.ts +5 -2
  87. package/app/types/case.ts +29 -0
  88. package/app/types/import.ts +3 -0
  89. package/app/types/user.ts +1 -0
  90. package/app/utils/data/permissions.ts +17 -1
  91. package/app/utils/forensics/audit-export-signature.ts +5 -1
  92. package/app/utils/forensics/confirmation-signature.ts +3 -0
  93. package/app/utils/forensics/export-verification.ts +497 -22
  94. package/functions/api/pdf/[[path]].ts +32 -1
  95. package/load-context.ts +9 -0
  96. package/package.json +6 -2
  97. package/primershear.emails.example +6 -0
  98. package/scripts/deploy-pages-secrets.sh +6 -0
  99. package/scripts/deploy-primershear-emails.sh +167 -0
  100. package/worker-configuration.d.ts +7493 -7491
  101. package/workers/audit-worker/worker-configuration.d.ts +7448 -11323
  102. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  103. package/workers/data-worker/worker-configuration.d.ts +7448 -11323
  104. package/workers/data-worker/wrangler.jsonc.example +1 -1
  105. package/workers/image-worker/worker-configuration.d.ts +7447 -11322
  106. package/workers/image-worker/wrangler.jsonc.example +1 -1
  107. package/workers/keys-worker/worker-configuration.d.ts +7447 -11322
  108. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  109. package/workers/pdf-worker/src/formats/format-striae.ts +8 -7
  110. package/workers/pdf-worker/src/pdf-worker.example.ts +3 -0
  111. package/workers/pdf-worker/src/report-types.ts +3 -0
  112. package/workers/pdf-worker/worker-configuration.d.ts +7448 -11323
  113. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  114. package/workers/user-worker/src/user-worker.example.ts +6 -1
  115. package/workers/user-worker/worker-configuration.d.ts +7448 -11323
  116. package/workers/user-worker/wrangler.jsonc.example +1 -1
  117. package/wrangler.toml.example +1 -1
  118. package/public/.well-known/keybase.txt +0 -56
@@ -9,6 +9,7 @@ import type {
9
9
  AuditResult,
10
10
  PerformanceMetrics
11
11
  } from '~/types';
12
+ import { getCaseData, getUserData } from '~/utils/data';
12
13
  import { generateWorkflowId } from '~/utils/common';
13
14
  import {
14
15
  fetchAuditEntriesForUser,
@@ -26,6 +27,7 @@ import {
26
27
  buildAnnotationCreateAuditParams,
27
28
  buildAnnotationDeleteAuditParams,
28
29
  buildAnnotationEditAuditParams,
30
+ buildCaseArchiveAuditParams,
29
31
  buildCaseCreationAuditParams,
30
32
  buildCaseDeletionAuditParams,
31
33
  buildCaseExportAuditParams,
@@ -60,6 +62,7 @@ export class AuditService {
60
62
  private static instance: AuditService;
61
63
  private auditBuffer: ValidationAuditEntry[] = [];
62
64
  private workflowId: string | null = null;
65
+ private userBadgeIdByUserId = new Map<string, string>();
63
66
 
64
67
  private constructor() {}
65
68
 
@@ -97,7 +100,26 @@ export class AuditService {
97
100
  const startTime = Date.now();
98
101
 
99
102
  try {
100
- const auditEntry = buildValidationAuditEntry(params);
103
+ const providedBadgeId = params.userProfileDetails?.badgeId?.trim();
104
+ if (providedBadgeId && params.userId) {
105
+ this.userBadgeIdByUserId.set(params.userId, providedBadgeId);
106
+ }
107
+
108
+ const resolvedBadgeId =
109
+ providedBadgeId ||
110
+ (params.userId ? this.userBadgeIdByUserId.get(params.userId) : undefined);
111
+
112
+ const paramsWithBadgeId: CreateAuditEntryParams = resolvedBadgeId
113
+ ? {
114
+ ...params,
115
+ userProfileDetails: {
116
+ ...(params.userProfileDetails || {}),
117
+ badgeId: resolvedBadgeId
118
+ }
119
+ }
120
+ : params;
121
+
122
+ const auditEntry = buildValidationAuditEntry(paramsWithBadgeId);
101
123
 
102
124
  // Add to buffer for batch processing
103
125
  this.auditBuffer.push(auditEntry);
@@ -117,6 +139,67 @@ export class AuditService {
117
139
  }
118
140
  }
119
141
 
142
+ private normalizeBadgeId(badgeId?: string): string | undefined {
143
+ const normalized = badgeId?.trim();
144
+ return normalized ? normalized : undefined;
145
+ }
146
+
147
+ private applyBadgeIdToParams(
148
+ params: CreateAuditEntryParams,
149
+ badgeId?: string
150
+ ): CreateAuditEntryParams {
151
+ const normalizedBadgeId = this.normalizeBadgeId(badgeId);
152
+ if (!normalizedBadgeId) {
153
+ return params;
154
+ }
155
+
156
+ return {
157
+ ...params,
158
+ userProfileDetails: {
159
+ ...(params.userProfileDetails || {}),
160
+ badgeId: normalizedBadgeId
161
+ }
162
+ };
163
+ }
164
+
165
+ private async resolveBadgeIdForUser(user: User): Promise<string | undefined> {
166
+ const cachedBadgeId = this.userBadgeIdByUserId.get(user.uid);
167
+ if (cachedBadgeId) {
168
+ return cachedBadgeId;
169
+ }
170
+
171
+ try {
172
+ const userData = await getUserData(user);
173
+ const resolvedBadgeId = this.normalizeBadgeId(userData?.badgeId);
174
+
175
+ if (resolvedBadgeId) {
176
+ this.userBadgeIdByUserId.set(user.uid, resolvedBadgeId);
177
+ }
178
+
179
+ return resolvedBadgeId;
180
+ } catch (error) {
181
+ console.error('🚨 Audit: Failed to resolve badge ID for user:', error);
182
+ return undefined;
183
+ }
184
+ }
185
+
186
+ private async logEventForUser(user: User, params: CreateAuditEntryParams): Promise<void> {
187
+ const resolvedBadgeId = await this.resolveBadgeIdForUser(user);
188
+ await this.logEvent(this.applyBadgeIdToParams(params, resolvedBadgeId));
189
+ }
190
+
191
+ private async logEventForOptionalUser(
192
+ user: User | null,
193
+ params: CreateAuditEntryParams
194
+ ): Promise<void> {
195
+ if (!user) {
196
+ await this.logEvent(params);
197
+ return;
198
+ }
199
+
200
+ await this.logEventForUser(user, params);
201
+ }
202
+
120
203
  /**
121
204
  * Log case export event
122
205
  */
@@ -135,7 +218,7 @@ export class AuditService {
135
218
  keyId?: string;
136
219
  }
137
220
  ): Promise<void> {
138
- await this.logEvent(
221
+ await this.logEventForUser(user,
139
222
  buildCaseExportAuditParams({
140
223
  user,
141
224
  caseNumber,
@@ -168,7 +251,7 @@ export class AuditService {
168
251
  keyId?: string;
169
252
  }
170
253
  ): Promise<void> {
171
- await this.logEvent(
254
+ await this.logEventForUser(user,
172
255
  buildCaseImportAuditParams({
173
256
  user,
174
257
  caseNumber,
@@ -196,13 +279,15 @@ export class AuditService {
196
279
  originalExaminerUid?: string,
197
280
  performanceMetrics?: PerformanceMetrics,
198
281
  imageFileId?: string,
199
- originalImageFileName?: string
282
+ originalImageFileName?: string,
283
+ badgeId?: string
200
284
  ): Promise<void> {
201
- await this.logEvent(
285
+ await this.logEventForUser(user,
202
286
  buildConfirmationCreationAuditParams({
203
287
  user,
204
288
  caseNumber,
205
289
  confirmationId,
290
+ badgeId,
206
291
  result,
207
292
  errors,
208
293
  originalExaminerUid,
@@ -231,7 +316,7 @@ export class AuditService {
231
316
  keyId?: string;
232
317
  }
233
318
  ): Promise<void> {
234
- await this.logEvent(
319
+ await this.logEventForUser(user,
235
320
  buildConfirmationExportAuditParams({
236
321
  user,
237
322
  caseNumber,
@@ -264,9 +349,10 @@ export class AuditService {
264
349
  present: boolean;
265
350
  valid: boolean;
266
351
  keyId?: string;
267
- }
352
+ },
353
+ reviewerBadgeId?: string // Badge/ID number of the reviewing examiner who exported the file
268
354
  ): Promise<void> {
269
- await this.logEvent(
355
+ await this.logEventForUser(user,
270
356
  buildConfirmationImportAuditParams({
271
357
  user,
272
358
  caseNumber,
@@ -276,6 +362,7 @@ export class AuditService {
276
362
  confirmationsImported,
277
363
  errors,
278
364
  reviewingExaminerUid,
365
+ reviewerBadgeId,
279
366
  performanceMetrics,
280
367
  exporterUidValidated,
281
368
  totalConfirmationsInFile,
@@ -296,7 +383,7 @@ export class AuditService {
296
383
  caseNumber: string,
297
384
  caseName: string
298
385
  ): Promise<void> {
299
- await this.logEvent(
386
+ await this.logEventForUser(user,
300
387
  buildCaseCreationAuditParams({
301
388
  user,
302
389
  caseNumber,
@@ -314,7 +401,7 @@ export class AuditService {
314
401
  oldName: string,
315
402
  newName: string
316
403
  ): Promise<void> {
317
- await this.logEvent(
404
+ await this.logEventForUser(user,
318
405
  buildCaseRenameAuditParams({
319
406
  user,
320
407
  caseNumber,
@@ -334,7 +421,7 @@ export class AuditService {
334
421
  deleteReason: string,
335
422
  backupCreated: boolean = false
336
423
  ): Promise<void> {
337
- await this.logEvent(
424
+ await this.logEventForUser(user,
338
425
  buildCaseDeletionAuditParams({
339
426
  user,
340
427
  caseNumber,
@@ -345,6 +432,35 @@ export class AuditService {
345
432
  );
346
433
  }
347
434
 
435
+ /**
436
+ * Log case archive event
437
+ */
438
+ public async logCaseArchive(
439
+ user: User,
440
+ caseNumber: string,
441
+ caseName: string,
442
+ archiveReason: string,
443
+ result: AuditResult = 'success',
444
+ errors: string[] = [],
445
+ totalFiles?: number,
446
+ archivedAt?: string,
447
+ processingTimeMs?: number
448
+ ): Promise<void> {
449
+ await this.logEventForUser(user,
450
+ buildCaseArchiveAuditParams({
451
+ user,
452
+ caseNumber,
453
+ caseName,
454
+ archiveReason,
455
+ result,
456
+ errors,
457
+ totalFiles,
458
+ archivedAt,
459
+ processingTimeMs
460
+ })
461
+ );
462
+ }
463
+
348
464
  /**
349
465
  * Log file upload event
350
466
  */
@@ -359,7 +475,7 @@ export class AuditService {
359
475
  processingTime?: number,
360
476
  fileId?: string
361
477
  ): Promise<void> {
362
- await this.logEvent(
478
+ await this.logEventForUser(user,
363
479
  buildFileUploadAuditParams({
364
480
  user,
365
481
  fileName,
@@ -386,7 +502,7 @@ export class AuditService {
386
502
  fileId?: string,
387
503
  originalFileName?: string
388
504
  ): Promise<void> {
389
- await this.logEvent(
505
+ await this.logEventForUser(user,
390
506
  buildFileDeletionAuditParams({
391
507
  user,
392
508
  fileName,
@@ -413,7 +529,7 @@ export class AuditService {
413
529
  accessReason?: string,
414
530
  originalFileName?: string
415
531
  ): Promise<void> {
416
- await this.logEvent(
532
+ await this.logEventForUser(user,
417
533
  buildFileAccessAuditParams({
418
534
  user,
419
535
  fileName,
@@ -441,7 +557,7 @@ export class AuditService {
441
557
  imageFileId?: string,
442
558
  originalImageFileName?: string
443
559
  ): Promise<void> {
444
- await this.logEvent(
560
+ await this.logEventForUser(user,
445
561
  buildAnnotationCreateAuditParams({
446
562
  user,
447
563
  annotationId,
@@ -468,7 +584,7 @@ export class AuditService {
468
584
  imageFileId?: string,
469
585
  originalImageFileName?: string
470
586
  ): Promise<void> {
471
- await this.logEvent(
587
+ await this.logEventForUser(user,
472
588
  buildAnnotationEditAuditParams({
473
589
  user,
474
590
  annotationId,
@@ -494,7 +610,7 @@ export class AuditService {
494
610
  imageFileId?: string,
495
611
  originalImageFileName?: string
496
612
  ): Promise<void> {
497
- await this.logEvent(
613
+ await this.logEventForUser(user,
498
614
  buildAnnotationDeleteAuditParams({
499
615
  user,
500
616
  annotationId,
@@ -516,7 +632,7 @@ export class AuditService {
516
632
  loginMethod: 'firebase' | 'sso' | 'api-key' | 'manual',
517
633
  userAgent?: string
518
634
  ): Promise<void> {
519
- await this.logEvent(
635
+ await this.logEventForUser(user,
520
636
  buildUserLoginAuditParams({
521
637
  user,
522
638
  sessionId,
@@ -535,7 +651,7 @@ export class AuditService {
535
651
  sessionDuration: number,
536
652
  logoutReason: 'user-initiated' | 'timeout' | 'security' | 'error'
537
653
  ): Promise<void> {
538
- await this.logEvent(
654
+ await this.logEventForUser(user,
539
655
  buildUserLogoutAuditParams({
540
656
  user,
541
657
  sessionId,
@@ -550,14 +666,15 @@ export class AuditService {
550
666
  */
551
667
  public async logUserProfileUpdate(
552
668
  user: User,
553
- profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar',
669
+ profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId',
554
670
  oldValue: string,
555
671
  newValue: string,
556
672
  result: AuditResult,
557
673
  sessionId?: string,
558
- errors: string[] = []
674
+ errors: string[] = [],
675
+ badgeId?: string
559
676
  ): Promise<void> {
560
- await this.logEvent(
677
+ await this.logEventForUser(user,
561
678
  buildUserProfileUpdateAuditParams({
562
679
  user,
563
680
  profileField,
@@ -565,7 +682,8 @@ export class AuditService {
565
682
  newValue,
566
683
  result,
567
684
  sessionId,
568
- errors
685
+ errors,
686
+ badgeId
569
687
  })
570
688
  );
571
689
  }
@@ -677,7 +795,7 @@ export class AuditService {
677
795
  userAgent?: string,
678
796
  sessionId?: string
679
797
  ): Promise<void> {
680
- await this.logEvent(
798
+ await this.logEventForUser(user,
681
799
  buildUserRegistrationAuditParams({
682
800
  user,
683
801
  firstName,
@@ -703,7 +821,7 @@ export class AuditService {
703
821
  userAgent?: string,
704
822
  errors: string[] = []
705
823
  ): Promise<void> {
706
- await this.logEvent(
824
+ await this.logEventForUser(user,
707
825
  buildMfaEnrollmentAuditParams({
708
826
  user,
709
827
  phoneNumber,
@@ -729,7 +847,7 @@ export class AuditService {
729
847
  userAgent?: string,
730
848
  errors: string[] = []
731
849
  ): Promise<void> {
732
- await this.logEvent(
850
+ await this.logEventForUser(user,
733
851
  buildMfaAuthenticationAuditParams({
734
852
  user,
735
853
  mfaMethod,
@@ -754,7 +872,7 @@ export class AuditService {
754
872
  userAgent?: string,
755
873
  errors: string[] = []
756
874
  ): Promise<void> {
757
- await this.logEvent(
875
+ await this.logEventForUser(user,
758
876
  buildEmailVerificationAuditParams({
759
877
  user,
760
878
  result,
@@ -804,7 +922,7 @@ export class AuditService {
804
922
  sessionId?: string,
805
923
  userAgent?: string
806
924
  ): Promise<void> {
807
- await this.logEvent(
925
+ await this.logEventForUser(user,
808
926
  buildMarkEmailVerificationSuccessfulAuditParams({
809
927
  user,
810
928
  reason,
@@ -828,7 +946,7 @@ export class AuditService {
828
946
  sourceFileId?: string,
829
947
  sourceFileName?: string
830
948
  ): Promise<void> {
831
- await this.logEvent(
949
+ await this.logEventForUser(user,
832
950
  buildPDFGenerationAuditParams({
833
951
  user,
834
952
  fileName,
@@ -854,7 +972,7 @@ export class AuditService {
854
972
  targetResource?: string,
855
973
  blockedBySystem: boolean = true
856
974
  ): Promise<void> {
857
- await this.logEvent(
975
+ await this.logEventForOptionalUser(user,
858
976
  buildSecurityViolationAuditParams({
859
977
  user,
860
978
  incidentType,
@@ -874,6 +992,7 @@ export class AuditService {
874
992
  * Get audit entries for display (public method for components)
875
993
  */
876
994
  public async getAuditEntriesForUser(userId: string, params?: {
995
+ requestingUser?: User;
877
996
  startDate?: string;
878
997
  endDate?: string;
879
998
  caseNumber?: string;
@@ -887,7 +1006,7 @@ export class AuditService {
887
1006
  userId,
888
1007
  ...params
889
1008
  };
890
- return await this.getAuditEntries(queryParams);
1009
+ return await this.getAuditEntries(queryParams, params?.requestingUser);
891
1010
  }
892
1011
 
893
1012
  /**
@@ -919,8 +1038,59 @@ export class AuditService {
919
1038
  /**
920
1039
  * Get audit entries based on query parameters
921
1040
  */
922
- private async getAuditEntries(params: AuditQueryParams): Promise<ValidationAuditEntry[]> {
1041
+ private applyDateRangeFilter(
1042
+ entries: ValidationAuditEntry[],
1043
+ startDate?: string,
1044
+ endDate?: string
1045
+ ): ValidationAuditEntry[] {
1046
+ const startMs = startDate ? new Date(startDate).getTime() : Number.NEGATIVE_INFINITY;
1047
+ const endMs = endDate ? new Date(endDate).getTime() : Number.POSITIVE_INFINITY;
1048
+
1049
+ return entries.filter((entry) => {
1050
+ const timestampMs = new Date(entry.timestamp).getTime();
1051
+ return timestampMs >= startMs && timestampMs <= endMs;
1052
+ });
1053
+ }
1054
+
1055
+ private async getBundledArchivedCaseAuditEntries(
1056
+ params: AuditQueryParams,
1057
+ requestingUser?: User
1058
+ ): Promise<ValidationAuditEntry[] | null> {
1059
+ if (!requestingUser || !params.caseNumber) {
1060
+ return null;
1061
+ }
1062
+
1063
+ const caseData = await getCaseData(requestingUser, params.caseNumber);
1064
+ if (!caseData?.isReadOnly || caseData.archived !== true) {
1065
+ return null;
1066
+ }
1067
+
1068
+ const bundledEntries = caseData.bundledAuditTrail?.entries;
1069
+ if (!Array.isArray(bundledEntries)) {
1070
+ return [];
1071
+ }
1072
+
1073
+ const sortedEntries = sortAuditEntriesNewestFirst(bundledEntries);
1074
+ const dateFilteredEntries = this.applyDateRangeFilter(
1075
+ sortedEntries,
1076
+ params.startDate,
1077
+ params.endDate
1078
+ );
1079
+ const filteredEntries = applyAuditEntryFilters(dateFilteredEntries, {
1080
+ ...params,
1081
+ userId: undefined
1082
+ });
1083
+
1084
+ return applyAuditPagination(filteredEntries, params);
1085
+ }
1086
+
1087
+ private async getAuditEntries(params: AuditQueryParams, requestingUser?: User): Promise<ValidationAuditEntry[]> {
923
1088
  try {
1089
+ const bundledArchivedEntries = await this.getBundledArchivedCaseAuditEntries(params, requestingUser);
1090
+ if (bundledArchivedEntries) {
1091
+ return bundledArchivedEntries;
1092
+ }
1093
+
924
1094
  // If userId is provided, fetch from server
925
1095
  if (params.userId) {
926
1096
  const serverEntries = await fetchAuditEntriesForUser({
@@ -26,7 +26,8 @@ export const buildValidationAuditEntry = (
26
26
  fileDetails: params.fileDetails,
27
27
  annotationDetails: params.annotationDetails,
28
28
  sessionDetails: params.sessionDetails,
29
- securityDetails: params.securityDetails
29
+ securityDetails: params.securityDetails,
30
+ userProfileDetails: params.userProfileDetails
30
31
  }
31
32
  };
32
33
  };
@@ -88,6 +88,49 @@ export const buildCaseDeletionAuditParams = (
88
88
  };
89
89
  };
90
90
 
91
+ interface BuildCaseArchiveAuditParamsInput {
92
+ user: User;
93
+ caseNumber: string;
94
+ caseName: string;
95
+ archiveReason: string;
96
+ result?: AuditResult;
97
+ errors?: string[];
98
+ totalFiles?: number;
99
+ archivedAt?: string;
100
+ processingTimeMs?: number;
101
+ }
102
+
103
+ export const buildCaseArchiveAuditParams = (
104
+ input: BuildCaseArchiveAuditParamsInput
105
+ ): CreateAuditEntryParams => {
106
+ const result = input.result || 'success';
107
+ const archivedAt = input.archivedAt || new Date().toISOString();
108
+
109
+ return {
110
+ userId: input.user.uid,
111
+ userEmail: input.user.email || '',
112
+ action: 'case-archive',
113
+ result,
114
+ fileName: `${input.caseNumber}.case`,
115
+ fileType: 'case-package',
116
+ validationErrors: input.errors || [],
117
+ caseNumber: input.caseNumber,
118
+ workflowPhase: 'casework',
119
+ caseDetails: {
120
+ newCaseName: input.caseName,
121
+ deleteReason: input.archiveReason,
122
+ totalFiles: input.totalFiles,
123
+ lastModified: archivedAt,
124
+ },
125
+ performanceMetrics: input.processingTimeMs
126
+ ? {
127
+ processingTimeMs: input.processingTimeMs,
128
+ fileSizeBytes: 0,
129
+ }
130
+ : undefined,
131
+ };
132
+ };
133
+
91
134
  interface BuildFileUploadAuditParamsInput {
92
135
  user: User;
93
136
  fileName: string;
@@ -57,9 +57,10 @@ export const buildUserLogoutAuditParams = (
57
57
 
58
58
  interface BuildUserProfileUpdateAuditParamsInput {
59
59
  user: User;
60
- profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar';
60
+ profileField: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId';
61
61
  oldValue: string;
62
62
  newValue: string;
63
+ badgeId?: string;
63
64
  result: AuditResult;
64
65
  sessionId?: string;
65
66
  errors?: string[];
@@ -85,7 +86,8 @@ export const buildUserProfileUpdateAuditParams = (
85
86
  userProfileDetails: {
86
87
  profileField: input.profileField,
87
88
  oldValue: input.oldValue,
88
- newValue: input.newValue
89
+ newValue: input.newValue,
90
+ badgeId: input.badgeId
89
91
  }
90
92
  };
91
93
  };
@@ -125,6 +125,7 @@ interface BuildConfirmationCreationAuditParamsInput {
125
125
  user: User;
126
126
  caseNumber: string;
127
127
  confirmationId: string;
128
+ badgeId?: string;
128
129
  result: AuditResult;
129
130
  errors?: string[];
130
131
  originalExaminerUid?: string;
@@ -156,6 +157,11 @@ export const buildConfirmationCreationAuditParams = (
156
157
  performanceMetrics: input.performanceMetrics,
157
158
  originalExaminerUid: input.originalExaminerUid,
158
159
  reviewingExaminerUid: input.user.uid,
160
+ userProfileDetails: input.badgeId
161
+ ? {
162
+ badgeId: input.badgeId
163
+ }
164
+ : undefined,
159
165
  fileDetails: input.imageFileId && input.originalImageFileName
160
166
  ? {
161
167
  fileId: input.imageFileId,
@@ -214,6 +220,7 @@ interface BuildConfirmationImportAuditParamsInput {
214
220
  confirmationsImported: number;
215
221
  errors?: string[];
216
222
  reviewingExaminerUid?: string;
223
+ reviewerBadgeId?: string;
217
224
  performanceMetrics?: PerformanceMetrics;
218
225
  exporterUidValidated?: boolean;
219
226
  totalConfirmationsInFile?: number;
@@ -263,6 +270,7 @@ export const buildConfirmationImportAuditParams = (
263
270
  },
264
271
  originalExaminerUid: input.user.uid,
265
272
  reviewingExaminerUid: input.reviewingExaminerUid,
273
+ reviewerBadgeId: input.reviewerBadgeId,
266
274
  caseDetails: input.totalConfirmationsInFile !== undefined
267
275
  ? {
268
276
  totalAnnotations: input.totalConfirmationsInFile
@@ -9,6 +9,7 @@ export {
9
9
  } from './audit-event-builders-workflow';
10
10
 
11
11
  export {
12
+ buildCaseArchiveAuditParams,
12
13
  buildCaseCreationAuditParams,
13
14
  buildCaseDeletionAuditParams,
14
15
  buildCaseRenameAuditParams,
@@ -3,7 +3,7 @@
3
3
 
4
4
  export type AuditAction =
5
5
  // Case Management Actions
6
- | 'case-create' | 'case-rename' | 'case-delete'
6
+ | 'case-create' | 'case-rename' | 'case-delete' | 'case-archive'
7
7
  // Confirmation Workflow Actions
8
8
  | 'case-export' | 'case-import' | 'confirmation-create' | 'confirmation-export' | 'confirmation-import'
9
9
  // File Operations
@@ -59,6 +59,7 @@ export interface AuditDetails {
59
59
  // Context & Workflow
60
60
  originalExaminerUid?: string;
61
61
  reviewingExaminerUid?: string;
62
+ reviewerBadgeId?: string;
62
63
  workflowPhase?: WorkflowPhase;
63
64
 
64
65
  // Performance & Metrics
@@ -160,6 +161,7 @@ export interface CreateAuditEntryParams {
160
161
  performanceMetrics?: PerformanceMetrics;
161
162
  originalExaminerUid?: string;
162
163
  reviewingExaminerUid?: string;
164
+ reviewerBadgeId?: string;
163
165
  // Extended detail fields
164
166
  caseDetails?: CaseAuditDetails;
165
167
  fileDetails?: FileAuditDetails;
@@ -269,9 +271,10 @@ export interface SecurityAuditDetails {
269
271
  * User profile and authentication specific audit details
270
272
  */
271
273
  export interface UserProfileAuditDetails {
272
- profileField?: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar';
274
+ profileField?: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId';
273
275
  oldValue?: string;
274
276
  newValue?: string;
277
+ badgeId?: string;
275
278
  resetMethod?: 'email' | 'sms' | 'security-questions' | 'admin-reset';
276
279
  resetToken?: string; // Partial token for tracking (last 4 chars)
277
280
  verificationMethod?: 'email-link' | 'sms-code' | 'totp' | 'backup-codes' | 'admin-verification';