@striae-org/striae 4.3.2 → 4.3.4

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/app/components/actions/case-import/orchestrator.ts +1 -1
  2. package/app/components/actions/case-manage.ts +50 -14
  3. package/app/components/audit/user-audit.module.css +49 -0
  4. package/app/components/audit/viewer/audit-entries-list.tsx +130 -48
  5. package/app/components/navbar/navbar.tsx +25 -12
  6. package/app/components/sidebar/case-import/case-import.tsx +56 -14
  7. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +7 -6
  8. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +9 -5
  9. package/app/components/sidebar/cases/cases-modal.module.css +19 -0
  10. package/app/components/sidebar/cases/cases-modal.tsx +23 -8
  11. package/app/routes/striae/hooks/use-striae-reset-helpers.ts +102 -0
  12. package/app/routes/striae/striae.tsx +72 -74
  13. package/app/routes/striae/utils/case-export.ts +37 -0
  14. package/app/routes/striae/utils/open-case-helper.ts +18 -0
  15. package/app/services/audit/audit-console-logger.ts +1 -1
  16. package/app/services/audit/audit-export-csv.ts +1 -1
  17. package/app/services/audit/audit-export-signing.ts +2 -2
  18. package/app/services/audit/audit-export.service.ts +1 -1
  19. package/app/services/audit/audit-worker-client.ts +1 -1
  20. package/app/services/audit/audit.service.ts +5 -75
  21. package/app/services/audit/builders/audit-event-builders-case-file.ts +3 -0
  22. package/app/services/audit/index.ts +2 -2
  23. package/app/types/audit.ts +8 -7
  24. package/app/utils/data/case-filters.ts +1 -1
  25. package/app/utils/ui/case-messages.ts +69 -0
  26. package/app/utils/ui/index.ts +1 -0
  27. package/package.json +5 -5
  28. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  29. package/workers/data-worker/wrangler.jsonc.example +1 -1
  30. package/workers/image-worker/wrangler.jsonc.example +1 -1
  31. package/workers/keys-worker/wrangler.jsonc.example +1 -1
  32. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  33. package/workers/user-worker/wrangler.jsonc.example +1 -1
  34. package/wrangler.toml.example +1 -1
@@ -17,30 +17,29 @@ import { FilesModal } from '~/components/sidebar/files/files-modal';
17
17
  import { NotesEditorModal } from '~/components/sidebar/notes/notes-editor-modal';
18
18
  import { UserAuditViewer } from '~/components/audit/user-audit-viewer';
19
19
  import { fetchUserApi } from '~/utils/api';
20
- import { resolveEarliestAnnotationTimestamp } from '~/utils/ui';
21
20
  import { type AnnotationData, type FileData } from '~/types';
22
- import type * as CaseExportActions from '~/components/actions/case-export';
23
- import { checkCaseIsReadOnly, validateCaseNumber, renameCase, deleteCase, checkExistingCase, createNewCase, archiveCase, getCaseArchiveDetails } from '~/components/actions/case-manage';
21
+ import { validateCaseNumber, renameCase, deleteCase, checkExistingCase, createNewCase, archiveCase, getCaseArchiveDetails } from '~/components/actions/case-manage';
24
22
  import { checkReadOnlyCaseExists, deleteReadOnlyCase } from '~/components/actions/case-review';
25
- import { canCreateCase, getLimitsDescription, getUserData } from '~/utils/data';
23
+ import { canCreateCase } from '~/utils/data';
24
+ import {
25
+ resolveEarliestAnnotationTimestamp,
26
+ CREATE_READ_ONLY_CASE_EXISTS_ERROR,
27
+ CLEAR_READ_ONLY_CASE_PARTIAL_FAILURE,
28
+ DELETE_CASE_CONFIRMATION,
29
+ DELETE_FILE_CONFIRMATION,
30
+ DELETE_CASE_FAILED,
31
+ DELETE_FILE_FAILED,
32
+ RENAME_CASE_FAILED
33
+ } from '~/utils/ui';
34
+ import { useStriaeResetHelpers } from './hooks/use-striae-reset-helpers';
35
+ import { getExportProgressLabel, loadCaseExportActions } from './utils/case-export';
36
+ import { resolveOpenCaseHelperText } from './utils/open-case-helper';
26
37
  import styles from './striae.module.css';
27
38
 
28
39
  interface StriaePage {
29
40
  user: User;
30
41
  }
31
42
 
32
- type CaseExportActionsModule = typeof CaseExportActions;
33
-
34
- let caseExportActionsPromise: Promise<CaseExportActionsModule> | null = null;
35
-
36
- const loadCaseExportActions = (): Promise<CaseExportActionsModule> => {
37
- if (!caseExportActionsPromise) {
38
- caseExportActionsPromise = import('~/components/actions/case-export');
39
- }
40
-
41
- return caseExportActionsPromise;
42
- };
43
-
44
43
  export const Striae = ({ user }: StriaePage) => {
45
44
  // Image and error states
46
45
  const [selectedImage, setSelectedImage] = useState<string>();
@@ -61,6 +60,7 @@ export const Striae = ({ user }: StriaePage) => {
61
60
  const [showNotes, setShowNotes] = useState(false);
62
61
  const [isUploading, setIsUploading] = useState(false);
63
62
  const [isReadOnlyCase, setIsReadOnlyCase] = useState(false);
63
+ const [isReviewOnlyCase, setIsReviewOnlyCase] = useState(false);
64
64
 
65
65
  // Annotation states
66
66
  const [activeAnnotations, setActiveAnnotations] = useState<Set<string>>(new Set());
@@ -98,24 +98,39 @@ export const Striae = ({ user }: StriaePage) => {
98
98
  archiveReason?: string;
99
99
  }>({ archived: false });
100
100
 
101
+ const {
102
+ clearSelectedImageState,
103
+ clearCaseContextState,
104
+ clearLoadedCaseState,
105
+ } = useStriaeResetHelpers({
106
+ setSelectedImage,
107
+ setSelectedFilename,
108
+ setImageId,
109
+ setAnnotationData,
110
+ setError,
111
+ setImageLoaded,
112
+ setCurrentCase,
113
+ setFiles,
114
+ setActiveAnnotations,
115
+ setIsBoxAnnotationMode,
116
+ setIsReadOnlyCase,
117
+ setIsReviewOnlyCase,
118
+ setArchiveDetails,
119
+ setShowNotes,
120
+ setIsAuditTrailOpen,
121
+ setIsRenameCaseModalOpen,
122
+ });
123
+
101
124
 
102
125
  useEffect(() => {
103
126
  // Set clear.jpg when case changes or is cleared
104
- setSelectedImage('/clear.jpg');
105
- setSelectedFilename(undefined);
106
- setImageId(undefined);
107
- setAnnotationData(null);
108
- setError(undefined);
109
- setImageLoaded(false);
127
+ clearSelectedImageState();
110
128
 
111
129
  // Reset annotation and UI states when case is cleared
112
130
  if (!currentCase) {
113
- setActiveAnnotations(new Set());
114
- setIsBoxAnnotationMode(false);
115
- setIsReadOnlyCase(false);
116
- setArchiveDetails({ archived: false });
131
+ clearCaseContextState();
117
132
  }
118
- }, [currentCase]);
133
+ }, [currentCase, clearSelectedImageState, clearCaseContextState]);
119
134
 
120
135
  // Fetch user company data when component mounts
121
136
  useEffect(() => {
@@ -154,18 +169,23 @@ export const Striae = ({ user }: StriaePage) => {
154
169
  const checkReadOnlyStatus = async () => {
155
170
  if (!currentCase || !user?.uid) {
156
171
  setIsReadOnlyCase(false);
172
+ setIsReviewOnlyCase(false);
157
173
  return;
158
174
  }
159
175
 
160
176
  try {
161
- // Check if the case data itself has isReadOnly: true
162
- const isReadOnly = await checkCaseIsReadOnly(user, currentCase);
163
- setIsReadOnlyCase(isReadOnly);
177
+ // Imported review cases are tracked in the user's read-only case list.
178
+ // This includes archived ZIP imports and distinguishes them from manually archived regular cases.
179
+ const readOnlyCaseEntry = await checkReadOnlyCaseExists(user, currentCase);
164
180
  const details = await getCaseArchiveDetails(user, currentCase);
181
+ const reviewOnly = Boolean(readOnlyCaseEntry);
182
+ setIsReviewOnlyCase(reviewOnly);
183
+ setIsReadOnlyCase(reviewOnly || details.archived);
165
184
  setArchiveDetails(details);
166
185
  } catch (error) {
167
186
  console.error('Error checking read-only status:', error);
168
187
  setIsReadOnlyCase(false);
188
+ setIsReviewOnlyCase(false);
169
189
  setArchiveDetails({ archived: false });
170
190
  }
171
191
  };
@@ -250,11 +270,7 @@ export const Striae = ({ user }: StriaePage) => {
250
270
 
251
271
  if (includeImages) {
252
272
  await caseExportActions.downloadCaseAsZip(user, exportCaseNumber, format, (progress) => {
253
- const label = progress < 30 ? 'Loading case data'
254
- : progress < 50 ? 'Preparing archive'
255
- : progress < 80 ? 'Adding images'
256
- : progress < 96 ? 'Finalizing'
257
- : 'Downloading';
273
+ const label = getExportProgressLabel(progress);
258
274
  onProgress?.(Math.round(progress), label);
259
275
  });
260
276
  showNotification(`Case ${exportCaseNumber} exported successfully.`, 'success');
@@ -317,7 +333,7 @@ export const Striae = ({ user }: StriaePage) => {
317
333
  try {
318
334
  const existingReadOnlyCase = await checkReadOnlyCaseExists(user, newCaseName);
319
335
  if (existingReadOnlyCase) {
320
- showNotification(`Case "${newCaseName}" already exists as a read-only review case.`, 'error');
336
+ showNotification(CREATE_READ_ONLY_CASE_EXISTS_ERROR(newCaseName), 'error');
321
337
  return;
322
338
  }
323
339
 
@@ -327,7 +343,7 @@ export const Striae = ({ user }: StriaePage) => {
327
343
  setIsRenameCaseModalOpen(false);
328
344
  showNotification(`Case renamed to ${newCaseName}.`, 'success');
329
345
  } catch (renameError) {
330
- showNotification(renameError instanceof Error ? renameError.message : 'Failed to rename case.', 'error');
346
+ showNotification(renameError instanceof Error ? renameError.message : RENAME_CASE_FAILED, 'error');
331
347
  } finally {
332
348
  setIsRenamingCase(false);
333
349
  }
@@ -339,9 +355,7 @@ export const Striae = ({ user }: StriaePage) => {
339
355
  return;
340
356
  }
341
357
 
342
- const confirmed = window.confirm(
343
- `Are you sure you want to delete case ${currentCase}? This will permanently delete all associated files and cannot be undone. If any image assets are already missing (404), they will be skipped and the case deletion will continue.`
344
- );
358
+ const confirmed = window.confirm(DELETE_CASE_CONFIRMATION(currentCase));
345
359
 
346
360
  if (!confirmed) {
347
361
  return;
@@ -350,11 +364,7 @@ export const Striae = ({ user }: StriaePage) => {
350
364
  setIsDeletingCase(true);
351
365
  try {
352
366
  const deleteResult = await deleteCase(user, currentCase);
353
- setCurrentCase('');
354
- setFiles([]);
355
- setShowNotes(false);
356
- setIsAuditTrailOpen(false);
357
- setIsRenameCaseModalOpen(false);
367
+ clearLoadedCaseState();
358
368
  if (deleteResult.missingImages.length > 0) {
359
369
  showNotification(
360
370
  `Case deleted. ${deleteResult.missingImages.length} image(s) were not found and were skipped during deletion.`,
@@ -364,7 +374,7 @@ export const Striae = ({ user }: StriaePage) => {
364
374
  showNotification('Case deleted successfully.', 'success');
365
375
  }
366
376
  } catch (deleteError) {
367
- showNotification(deleteError instanceof Error ? deleteError.message : 'Failed to delete case.', 'error');
377
+ showNotification(deleteError instanceof Error ? deleteError.message : DELETE_CASE_FAILED, 'error');
368
378
  } finally {
369
379
  setIsDeletingCase(false);
370
380
  }
@@ -383,9 +393,7 @@ export const Striae = ({ user }: StriaePage) => {
383
393
 
384
394
  const selectedFile = files.find((file) => file.id === imageId);
385
395
  const selectedFileName = selectedFile?.originalFilename || imageId;
386
- const confirmed = window.confirm(
387
- `Are you sure you want to delete ${selectedFileName}? This action cannot be undone.`
388
- );
396
+ const confirmed = window.confirm(DELETE_FILE_CONFIRMATION(selectedFileName));
389
397
 
390
398
  if (!confirmed) {
391
399
  return;
@@ -396,7 +404,7 @@ export const Striae = ({ user }: StriaePage) => {
396
404
  const deleteResult = await deleteFile(user, currentCase, imageId, 'User-requested deletion via navbar file management');
397
405
  const updatedFiles = files.filter((file) => file.id !== imageId);
398
406
  setFiles(updatedFiles);
399
- handleImageSelect({ id: 'clear', originalFilename: '/clear.jpg', uploadedAt: '' });
407
+ clearSelectedImageState();
400
408
  setShowNotes(false);
401
409
  if (deleteResult.imageMissing) {
402
410
  showNotification(
@@ -407,13 +415,18 @@ export const Striae = ({ user }: StriaePage) => {
407
415
  showNotification('File deleted successfully.', 'success');
408
416
  }
409
417
  } catch (deleteError) {
410
- showNotification(deleteError instanceof Error ? deleteError.message : 'Failed to delete file.', 'error');
418
+ showNotification(deleteError instanceof Error ? deleteError.message : DELETE_FILE_FAILED, 'error');
411
419
  } finally {
412
420
  setIsDeletingFile(false);
413
421
  }
414
422
  };
415
423
 
416
424
  const handleClearROCase = async () => {
425
+ if (!isReviewOnlyCase) {
426
+ showNotification('Only imported review cases can be cleared from workspace.', 'error');
427
+ return;
428
+ }
429
+
417
430
  if (!currentCase) {
418
431
  showNotification('No read-only case is currently loaded.', 'error');
419
432
  return;
@@ -431,15 +444,10 @@ export const Striae = ({ user }: StriaePage) => {
431
444
  try {
432
445
  const success = await deleteReadOnlyCase(user, caseToRemove);
433
446
  if (!success) {
434
- showNotification(`Failed to fully clear read-only case "${caseToRemove}". Please try again.`, 'error');
447
+ showNotification(CLEAR_READ_ONLY_CASE_PARTIAL_FAILURE(caseToRemove), 'error');
435
448
  return;
436
449
  }
437
- setCurrentCase('');
438
- setFiles([]);
439
- handleImageSelect({ id: 'clear', originalFilename: '/clear.jpg', uploadedAt: '' });
440
- setShowNotes(false);
441
- setIsAuditTrailOpen(false);
442
- setIsRenameCaseModalOpen(false);
450
+ clearLoadedCaseState();
443
451
  showNotification(`Read-only case "${caseToRemove}" cleared.`, 'success');
444
452
  } catch (clearError) {
445
453
  showNotification(clearError instanceof Error ? clearError.message : 'Failed to clear read-only case.', 'error');
@@ -461,6 +469,7 @@ export const Striae = ({ user }: StriaePage) => {
461
469
  try {
462
470
  await archiveCase(user, currentCase, archiveReason);
463
471
  setIsReadOnlyCase(true);
472
+ setIsReviewOnlyCase(false);
464
473
  setArchiveDetails({
465
474
  archived: true,
466
475
  archivedAt: new Date().toISOString(),
@@ -506,7 +515,7 @@ export const Striae = ({ user }: StriaePage) => {
506
515
 
507
516
  const existingReadOnlyCase = await checkReadOnlyCaseExists(user, nextCaseNumber);
508
517
  if (existingReadOnlyCase) {
509
- showNotification(`Case "${nextCaseNumber}" already exists as a read-only review case.`, 'error');
518
+ showNotification(CREATE_READ_ONLY_CASE_EXISTS_ERROR(nextCaseNumber), 'error');
510
519
  return;
511
520
  }
512
521
 
@@ -531,17 +540,8 @@ export const Striae = ({ user }: StriaePage) => {
531
540
 
532
541
  const handleOpenCaseModal = async () => {
533
542
  setIsOpenCaseModalOpen(true);
534
- try {
535
- const userData = await getUserData(user);
536
- if (userData && !userData.permitted) {
537
- const limitsDescription = await getLimitsDescription(user);
538
- setOpenCaseHelperText(limitsDescription || 'Load an existing case or create a new one.');
539
- } else {
540
- setOpenCaseHelperText('Load an existing case or create a new one.');
541
- }
542
- } catch {
543
- setOpenCaseHelperText('Load an existing case or create a new one.');
544
- }
543
+ const helperText = await resolveOpenCaseHelperText(user);
544
+ setOpenCaseHelperText(helperText);
545
545
  };
546
546
 
547
547
  // Function to refresh annotation data (called when notes are saved)
@@ -566,10 +566,7 @@ export const Striae = ({ user }: StriaePage) => {
566
566
  }
567
567
  } else if (!result.caseNumber && !result.isReadOnly) {
568
568
  // Read-only case cleared - reset all UI state
569
- setCurrentCase('');
570
- setFiles([]);
571
- handleImageSelect({ id: 'clear', originalFilename: '/clear.jpg', uploadedAt: '' });
572
- setShowNotes(false);
569
+ clearLoadedCaseState();
573
570
  }
574
571
  }
575
572
  };
@@ -746,6 +743,7 @@ export const Striae = ({ user }: StriaePage) => {
746
743
  isUploading={isUploading}
747
744
  company={userCompany}
748
745
  isReadOnly={isReadOnlyCase}
746
+ isReviewOnlyCase={isReviewOnlyCase}
749
747
  currentCase={currentCase}
750
748
  currentFileName={selectedFilename}
751
749
  isCurrentImageConfirmed={isCurrentImageConfirmed}
@@ -0,0 +1,37 @@
1
+ import type * as CaseExportActions from '~/components/actions/case-export';
2
+
3
+ export type CaseExportActionsModule = typeof CaseExportActions;
4
+
5
+ let caseExportActionsPromise: Promise<CaseExportActionsModule> | null = null;
6
+
7
+ export const loadCaseExportActions = (): Promise<CaseExportActionsModule> => {
8
+ if (!caseExportActionsPromise) {
9
+ caseExportActionsPromise = import('~/components/actions/case-export').catch((error: unknown) => {
10
+ // Clear cached failures so transient chunk/network errors can recover on retry.
11
+ caseExportActionsPromise = null;
12
+ throw error;
13
+ });
14
+ }
15
+
16
+ return caseExportActionsPromise;
17
+ };
18
+
19
+ export const getExportProgressLabel = (progress: number): string => {
20
+ if (progress < 30) {
21
+ return 'Loading case data';
22
+ }
23
+
24
+ if (progress < 50) {
25
+ return 'Preparing archive';
26
+ }
27
+
28
+ if (progress < 80) {
29
+ return 'Adding images';
30
+ }
31
+
32
+ if (progress < 96) {
33
+ return 'Finalizing';
34
+ }
35
+
36
+ return 'Downloading';
37
+ };
@@ -0,0 +1,18 @@
1
+ import type { User } from 'firebase/auth';
2
+ import { getLimitsDescription, getUserData } from '~/utils/data';
3
+
4
+ export const DEFAULT_OPEN_CASE_HELPER_TEXT = 'Load an existing case or create a new one.';
5
+
6
+ export const resolveOpenCaseHelperText = async (user: User): Promise<string> => {
7
+ try {
8
+ const userData = await getUserData(user);
9
+ if (userData && !userData.permitted) {
10
+ const limitsDescription = await getLimitsDescription(user);
11
+ return limitsDescription || DEFAULT_OPEN_CASE_HELPER_TEXT;
12
+ }
13
+
14
+ return DEFAULT_OPEN_CASE_HELPER_TEXT;
15
+ } catch {
16
+ return DEFAULT_OPEN_CASE_HELPER_TEXT;
17
+ }
18
+ };
@@ -1,6 +1,6 @@
1
1
  import { type ValidationAuditEntry } from '~/types';
2
2
 
3
- export const getAuditSecurityIssuesForConsole = (
3
+ const getAuditSecurityIssuesForConsole = (
4
4
  entry: ValidationAuditEntry
5
5
  ): string[] => {
6
6
  const checks = entry.details.securityChecks;
@@ -42,7 +42,7 @@ export const AUDIT_CSV_ENTRY_HEADERS = [
42
42
  'Confirmed Files'
43
43
  ];
44
44
 
45
- export const formatForCSV = (value?: string | number | null): string => {
45
+ const formatForCSV = (value?: string | number | null): string => {
46
46
  if (value === undefined || value === null) return '';
47
47
  const str = String(value);
48
48
  if (str.includes(',') || str.includes('"') || str.includes('\n')) {
@@ -22,14 +22,14 @@ interface SignAuditExportInput {
22
22
  hash: string;
23
23
  }
24
24
 
25
- export interface AuditExportSignature {
25
+ interface AuditExportSignature {
26
26
  algorithm: string;
27
27
  keyId: string;
28
28
  signedAt: string;
29
29
  value: string;
30
30
  }
31
31
 
32
- export interface SignedAuditExportPayload {
32
+ interface SignedAuditExportPayload {
33
33
  signatureMetadata: AuditExportSigningPayload;
34
34
  signature: AuditExportSignature;
35
35
  }
@@ -8,7 +8,7 @@ import { type AuditExportContext, signAuditExport } from './audit-export-signing
8
8
  * Audit Export Service
9
9
  * Handles exporting audit trails to various formats for compliance and forensic analysis
10
10
  */
11
- export class AuditExportService {
11
+ class AuditExportService {
12
12
  private static instance: AuditExportService;
13
13
 
14
14
  private constructor() {}
@@ -18,7 +18,7 @@ interface PersistAuditEntryResponse {
18
18
  filename: string;
19
19
  }
20
20
 
21
- export type PersistAuditEntryResult =
21
+ type PersistAuditEntryResult =
22
22
  | {
23
23
  ok: true;
24
24
  entryCount: number;
@@ -2,7 +2,6 @@ import type { User } from 'firebase/auth';
2
2
  import type {
3
3
  ValidationAuditEntry,
4
4
  CreateAuditEntryParams,
5
- AuditTrail,
6
5
  AuditQueryParams,
7
6
  WorkflowPhase,
8
7
  AuditAction,
@@ -18,7 +17,6 @@ import {
18
17
  import {
19
18
  applyAuditEntryFilters,
20
19
  applyAuditPagination,
21
- generateAuditSummary,
22
20
  sortAuditEntriesNewestFirst
23
21
  } from './audit-query-helpers';
24
22
  import { logAuditEntryToConsole } from './audit-console-logger';
@@ -58,7 +56,7 @@ import {
58
56
  * Audit Service for ValidationAuditEntry system
59
57
  * Provides comprehensive audit logging throughout the confirmation workflow
60
58
  */
61
- export class AuditService {
59
+ class AuditService {
62
60
  private static instance: AuditService;
63
61
  private auditBuffer: ValidationAuditEntry[] = [];
64
62
  private workflowId: string | null = null;
@@ -383,13 +381,15 @@ export class AuditService {
383
381
  public async logCaseCreation(
384
382
  user: User,
385
383
  caseNumber: string,
386
- caseName: string
384
+ caseName: string,
385
+ renamedFromCaseNumber?: string
387
386
  ): Promise<void> {
388
387
  await this.logEventForUser(user,
389
388
  buildCaseCreationAuditParams({
390
389
  user,
391
390
  caseNumber,
392
- caseName
391
+ caseName,
392
+ renamedFromCaseNumber
393
393
  })
394
394
  );
395
395
  }
@@ -721,37 +721,6 @@ export class AuditService {
721
721
  );
722
722
  }
723
723
 
724
- /**
725
- * Log user account deletion event
726
- */
727
- public async logAccountDeletion(
728
- user: User,
729
- result: AuditResult,
730
- deletionReason: 'user-requested' | 'admin-initiated' | 'policy-violation' | 'inactive-account' = 'user-requested',
731
- confirmationMethod: 'uid-email' | 'password' | 'admin-override' = 'uid-email',
732
- casesCount?: number,
733
- filesCount?: number,
734
- dataRetentionPeriod?: number,
735
- emailNotificationSent?: boolean,
736
- sessionId?: string,
737
- errors: string[] = []
738
- ): Promise<void> {
739
- // Wrapper that extracts user data and calls the simplified version
740
- return this.logAccountDeletionSimple(
741
- user.uid,
742
- user.email || '',
743
- result,
744
- deletionReason,
745
- confirmationMethod,
746
- casesCount,
747
- filesCount,
748
- dataRetentionPeriod,
749
- emailNotificationSent,
750
- sessionId,
751
- errors
752
- );
753
- }
754
-
755
724
  /**
756
725
  * Log user account deletion event with simplified user data
757
726
  */
@@ -1011,32 +980,6 @@ export class AuditService {
1011
980
  return await this.getAuditEntries(queryParams, params?.requestingUser);
1012
981
  }
1013
982
 
1014
- /**
1015
- * Get audit trail for a case
1016
- */
1017
- public async getAuditTrail(caseNumber: string): Promise<AuditTrail | null> {
1018
- try {
1019
- // Implement retrieval from storage
1020
- const entries = await this.getAuditEntries({ caseNumber });
1021
- if (!entries || entries.length === 0) {
1022
- return null;
1023
- }
1024
-
1025
- const summary = generateAuditSummary(entries);
1026
- const workflowId = this.workflowId || `${caseNumber}-archived`;
1027
-
1028
- return {
1029
- caseNumber,
1030
- workflowId,
1031
- entries,
1032
- summary
1033
- };
1034
- } catch (error) {
1035
- console.error('🚨 Audit: Failed to get audit trail:', error);
1036
- return null;
1037
- }
1038
- }
1039
-
1040
983
  /**
1041
984
  * Get audit entries based on query parameters
1042
985
  */
@@ -1143,19 +1086,6 @@ export class AuditService {
1143
1086
  }
1144
1087
  }
1145
1088
 
1146
- /**
1147
- * Clear audit buffer (for testing)
1148
- */
1149
- public clearBuffer(): void {
1150
- this.auditBuffer = [];
1151
- }
1152
-
1153
- /**
1154
- * Get current buffer size (for monitoring)
1155
- */
1156
- public getBufferSize(): number {
1157
- return this.auditBuffer.length;
1158
- }
1159
1089
  }
1160
1090
 
1161
1091
  // Export singleton instance
@@ -6,6 +6,7 @@ interface BuildCaseCreationAuditParamsInput {
6
6
  user: User;
7
7
  caseNumber: string;
8
8
  caseName: string;
9
+ renamedFromCaseNumber?: string;
9
10
  }
10
11
 
11
12
  export const buildCaseCreationAuditParams = (
@@ -22,7 +23,9 @@ export const buildCaseCreationAuditParams = (
22
23
  caseNumber: input.caseNumber,
23
24
  workflowPhase: 'casework',
24
25
  caseDetails: {
26
+ oldCaseName: input.renamedFromCaseNumber,
25
27
  newCaseName: input.caseName,
28
+ createdByRename: Boolean(input.renamedFromCaseNumber),
26
29
  createdDate: new Date().toISOString(),
27
30
  totalFiles: 0,
28
31
  totalAnnotations: 0
@@ -1,2 +1,2 @@
1
- export { AuditService, auditService } from './audit.service';
2
- export { AuditExportService, auditExportService } from './audit-export.service';
1
+ export { auditService } from './audit.service';
2
+ export { auditExportService } from './audit-export.service';
@@ -44,7 +44,7 @@ export interface ValidationAuditEntry {
44
44
  * Detailed information for each audit entry
45
45
  * Contains action-specific data and metadata
46
46
  */
47
- export interface AuditDetails {
47
+ interface AuditDetails {
48
48
  // Core identification
49
49
  fileName?: string;
50
50
  fileType?: AuditFileType;
@@ -194,9 +194,10 @@ export interface AuditQueryParams {
194
194
  /**
195
195
  * Case management specific audit details
196
196
  */
197
- export interface CaseAuditDetails {
197
+ interface CaseAuditDetails {
198
198
  oldCaseName?: string;
199
199
  newCaseName?: string;
200
+ createdByRename?: boolean;
200
201
  totalFiles?: number;
201
202
  totalAnnotations?: number;
202
203
  confirmedFileNames?: string[];
@@ -210,7 +211,7 @@ export interface CaseAuditDetails {
210
211
  /**
211
212
  * File operation specific audit details
212
213
  */
213
- export interface FileAuditDetails {
214
+ interface FileAuditDetails {
214
215
  fileId?: string;
215
216
  originalFileName?: string;
216
217
  fileSize: number;
@@ -225,7 +226,7 @@ export interface FileAuditDetails {
225
226
  /**
226
227
  * Annotation operation specific audit details
227
228
  */
228
- export interface AnnotationAuditDetails {
229
+ interface AnnotationAuditDetails {
229
230
  annotationId?: string;
230
231
  annotationType?: 'measurement' | 'identification' | 'comparison' | 'note' | 'region';
231
232
  annotationData?: unknown; // The actual annotation data structure
@@ -238,7 +239,7 @@ export interface AnnotationAuditDetails {
238
239
  /**
239
240
  * User session specific audit details
240
241
  */
241
- export interface SessionAuditDetails {
242
+ interface SessionAuditDetails {
242
243
  sessionId?: string;
243
244
  userAgent?: string;
244
245
  sessionDuration?: number;
@@ -249,7 +250,7 @@ export interface SessionAuditDetails {
249
250
  /**
250
251
  * Security incident specific audit details
251
252
  */
252
- export interface SecurityAuditDetails {
253
+ interface SecurityAuditDetails {
253
254
  incidentType?: 'unauthorized-access' | 'data-breach' | 'malware' | 'injection' | 'brute-force' | 'privilege-escalation';
254
255
  severity?: 'low' | 'medium' | 'high' | 'critical';
255
256
  targetResource?: string;
@@ -272,7 +273,7 @@ export interface SecurityAuditDetails {
272
273
  /**
273
274
  * User profile and authentication specific audit details
274
275
  */
275
- export interface UserProfileAuditDetails {
276
+ interface UserProfileAuditDetails {
276
277
  profileField?: 'displayName' | 'email' | 'organization' | 'role' | 'preferences' | 'avatar' | 'badgeId';
277
278
  oldValue?: string;
278
279
  newValue?: string;
@@ -86,7 +86,7 @@ export function filterCasesForModal(
86
86
  ): CasesModalCaseItem[] {
87
87
  const archiveFilteredCases = preferences.showArchivedOnly
88
88
  ? cases.filter((entry) => entry.archived && !entry.isReadOnly)
89
- : cases.filter((entry) => !entry.archived && !entry.isReadOnly);
89
+ : cases.filter((entry) => !entry.isReadOnly);
90
90
 
91
91
  return archiveFilteredCases.filter((entry) =>
92
92
  matchesConfirmationFilter(entry.caseNumber, preferences.confirmationFilter, caseConfirmationStatus)