@striae-org/striae 5.2.1 → 5.3.1

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 (117) hide show
  1. package/.env.example +2 -10
  2. package/README.md +5 -46
  3. package/app/components/actions/case-export/core-export.ts +5 -174
  4. package/app/components/actions/case-export/download-handlers.ts +84 -751
  5. package/app/components/actions/case-export/index.ts +6 -30
  6. package/app/components/actions/case-export/metadata-helpers.ts +0 -78
  7. package/app/components/actions/case-export/types-constants.ts +0 -43
  8. package/app/components/actions/case-import/confirmation-import.ts +75 -36
  9. package/app/components/actions/case-import/confirmation-package.ts +68 -1
  10. package/app/components/actions/case-import/index.ts +1 -1
  11. package/app/components/actions/case-import/orchestrator.ts +78 -53
  12. package/app/components/actions/case-import/zip-processing.ts +160 -330
  13. package/app/components/actions/generate-pdf.ts +3 -2
  14. package/app/components/audit/user-audit-viewer.tsx +0 -19
  15. package/app/components/audit/viewer/audit-viewer-header.tsx +0 -33
  16. package/app/components/navbar/case-modals/archive-case-modal.tsx +1 -1
  17. package/app/components/navbar/case-modals/export-case-modal.module.css +27 -0
  18. package/app/components/navbar/case-modals/export-case-modal.tsx +132 -0
  19. package/app/components/navbar/case-modals/export-confirmations-modal.module.css +24 -0
  20. package/app/components/navbar/case-modals/export-confirmations-modal.tsx +108 -0
  21. package/app/components/navbar/navbar.tsx +1 -1
  22. package/app/components/sidebar/case-import/case-import.module.css +35 -0
  23. package/app/components/sidebar/case-import/components/CasePreviewSection.tsx +51 -3
  24. package/app/components/sidebar/case-import/components/ConfirmationDialog.tsx +2 -4
  25. package/app/components/sidebar/case-import/components/ConfirmationPreviewSection.tsx +36 -5
  26. package/app/components/sidebar/case-import/hooks/useFilePreview.ts +5 -9
  27. package/app/components/sidebar/case-import/index.ts +1 -4
  28. package/app/components/sidebar/notes/class-details-shared.ts +2 -2
  29. package/app/components/toast/toast.module.css +36 -0
  30. package/app/components/toast/toast.tsx +6 -2
  31. package/app/components/user/manage-profile.tsx +4 -3
  32. package/app/config-example/config.json +1 -2
  33. package/app/root.tsx +0 -7
  34. package/app/routes/_index.tsx +1 -1
  35. package/app/routes/auth/login.example.tsx +22 -103
  36. package/app/routes/auth/login.tsx +22 -103
  37. package/app/routes/auth/route.ts +1 -1
  38. package/app/routes/striae/striae.tsx +117 -59
  39. package/app/services/firebase/index.ts +0 -3
  40. package/app/types/case.ts +1 -0
  41. package/app/types/export.ts +2 -2
  42. package/app/types/import.ts +10 -0
  43. package/app/utils/auth/index.ts +0 -1
  44. package/app/utils/data/permissions.ts +3 -2
  45. package/package.json +9 -16
  46. package/public/_headers +0 -4
  47. package/public/_routes.json +0 -1
  48. package/worker-configuration.d.ts +20 -17
  49. package/workers/audit-worker/src/audit-worker.example.ts +9 -806
  50. package/workers/audit-worker/src/config.ts +7 -0
  51. package/workers/audit-worker/src/crypto/data-at-rest.ts +410 -0
  52. package/workers/audit-worker/src/handlers/audit-routes.ts +125 -0
  53. package/workers/audit-worker/src/storage/audit-storage.ts +99 -0
  54. package/workers/audit-worker/src/types.ts +56 -0
  55. package/workers/audit-worker/worker-configuration.d.ts +1 -1
  56. package/workers/audit-worker/wrangler.jsonc.example +1 -1
  57. package/workers/data-worker/src/config.ts +11 -0
  58. package/workers/data-worker/src/data-worker.example.ts +21 -942
  59. package/workers/data-worker/src/handlers/decrypt-export.ts +118 -0
  60. package/workers/data-worker/src/handlers/signing.ts +174 -0
  61. package/workers/data-worker/src/handlers/storage-routes.ts +129 -0
  62. package/workers/data-worker/src/registry/key-registry.ts +368 -0
  63. package/workers/data-worker/src/types.ts +46 -0
  64. package/workers/data-worker/worker-configuration.d.ts +1 -1
  65. package/workers/data-worker/wrangler.jsonc.example +1 -1
  66. package/workers/image-worker/worker-configuration.d.ts +1 -1
  67. package/workers/image-worker/wrangler.jsonc.example +1 -1
  68. package/workers/pdf-worker/worker-configuration.d.ts +2 -3
  69. package/workers/pdf-worker/wrangler.jsonc.example +1 -1
  70. package/workers/user-worker/src/auth.ts +30 -0
  71. package/workers/user-worker/src/cleanup/account-deletion.ts +337 -0
  72. package/workers/user-worker/src/config.ts +4 -0
  73. package/workers/user-worker/src/encryption-utils.ts +25 -0
  74. package/workers/user-worker/src/firebase/admin.ts +152 -0
  75. package/workers/user-worker/src/handlers/user-routes.ts +242 -0
  76. package/workers/user-worker/src/registry/user-kv.ts +172 -0
  77. package/workers/user-worker/src/storage/user-records.ts +34 -0
  78. package/workers/user-worker/src/types.ts +106 -0
  79. package/workers/user-worker/src/user-worker.example.ts +18 -964
  80. package/workers/user-worker/worker-configuration.d.ts +4 -2
  81. package/workers/user-worker/wrangler.jsonc.example +12 -1
  82. package/wrangler.toml.example +1 -1
  83. package/app/components/actions/case-export/data-processing.ts +0 -223
  84. package/app/components/sidebar/case-export/case-export.module.css +0 -418
  85. package/app/components/sidebar/case-export/case-export.tsx +0 -310
  86. package/app/types/exceljs-bare.d.ts +0 -9
  87. package/app/utils/auth/auth.ts +0 -11
  88. package/public/.well-known/security.txt +0 -6
  89. package/public/favicon.ico +0 -0
  90. package/public/icon-256.png +0 -0
  91. package/public/icon-512.png +0 -0
  92. package/public/manifest.json +0 -39
  93. package/public/shortcut.png +0 -0
  94. package/public/social-image.png +0 -0
  95. package/public/vendor/exceljs.LICENSE +0 -22
  96. package/public/vendor/exceljs.bare.min.js +0 -45
  97. package/scripts/deploy-all.sh +0 -166
  98. package/scripts/deploy-config/modules/env-utils.sh +0 -322
  99. package/scripts/deploy-config/modules/keys.sh +0 -404
  100. package/scripts/deploy-config/modules/prompt.sh +0 -372
  101. package/scripts/deploy-config/modules/scaffolding.sh +0 -344
  102. package/scripts/deploy-config/modules/validation.sh +0 -365
  103. package/scripts/deploy-config.sh +0 -236
  104. package/scripts/deploy-pages-secrets.sh +0 -231
  105. package/scripts/deploy-pages.sh +0 -34
  106. package/scripts/deploy-primershear-emails.sh +0 -167
  107. package/scripts/deploy-worker-secrets.sh +0 -374
  108. package/scripts/dev.cjs +0 -23
  109. package/scripts/install-workers.sh +0 -88
  110. package/scripts/run-eslint.cjs +0 -43
  111. package/scripts/update-compatibility-dates.cjs +0 -124
  112. package/scripts/update-markdown-versions.cjs +0 -43
  113. package/workers/keys-worker/package.json +0 -18
  114. package/workers/keys-worker/src/keys.example.ts +0 -67
  115. package/workers/keys-worker/src/keys.ts +0 -67
  116. package/workers/keys-worker/worker-configuration.d.ts +0 -7447
  117. package/workers/keys-worker/wrangler.jsonc.example +0 -15
@@ -5,13 +5,15 @@ import { Navbar } from '~/components/navbar/navbar';
5
5
  import { RenameCaseModal } from '~/components/navbar/case-modals/rename-case-modal';
6
6
  import { ArchiveCaseModal } from '~/components/navbar/case-modals/archive-case-modal';
7
7
  import { OpenCaseModal } from '~/components/navbar/case-modals/open-case-modal';
8
+ import { ExportCaseModal } from '~/components/navbar/case-modals/export-case-modal';
9
+ import { ExportConfirmationsModal } from '~/components/navbar/case-modals/export-confirmations-modal';
8
10
  import { Toolbar } from '~/components/toolbar/toolbar';
9
11
  import { Canvas } from '~/components/canvas/canvas';
10
- import { Toast } from '~/components/toast/toast';
12
+ import { Toast, type ToastType } from '~/components/toast/toast';
11
13
  import { getImageUrl, fetchFiles, deleteFile } from '~/components/actions/image-manage';
12
14
  import { getNotes, saveNotes } from '~/components/actions/notes-manage';
13
15
  import { generatePDF } from '~/components/actions/generate-pdf';
14
- import { CaseExport, type ExportFormat } from '~/components/sidebar/case-export/case-export';
16
+ import { exportConfirmationData } from '~/components/actions/confirm-export';
15
17
  import { CasesModal } from '~/components/sidebar/cases/cases-modal';
16
18
  import { FilesModal } from '~/components/sidebar/files/files-modal';
17
19
  import { NotesEditorModal } from '~/components/sidebar/notes/notes-editor-modal';
@@ -20,7 +22,7 @@ import { fetchUserApi } from '~/utils/api';
20
22
  import { type AnnotationData, type FileData } from '~/types';
21
23
  import { validateCaseNumber, renameCase, deleteCase, checkExistingCase, createNewCase, archiveCase, getCaseArchiveDetails } from '~/components/actions/case-manage';
22
24
  import { checkReadOnlyCaseExists, deleteReadOnlyCase } from '~/components/actions/case-review';
23
- import { canCreateCase } from '~/utils/data';
25
+ import { canCreateCase, getCaseConfirmationSummary } from '~/utils/data';
24
26
  import {
25
27
  resolveEarliestAnnotationTimestamp,
26
28
  CREATE_READ_ONLY_CASE_EXISTS_ERROR,
@@ -77,8 +79,8 @@ export const Striae = ({ user }: StriaePage) => {
77
79
  const [isGeneratingPDF, setIsGeneratingPDF] = useState(false);
78
80
  const [showToast, setShowToast] = useState(false);
79
81
  const [toastMessage, setToastMessage] = useState('');
80
- const [toastType, setToastType] = useState<'success' | 'error' | 'warning'>('success');
81
- const [isCaseExportModalOpen, setIsCaseExportModalOpen] = useState(false);
82
+ const [toastType, setToastType] = useState<ToastType>('success');
83
+ const [toastDuration, setToastDuration] = useState(4000);
82
84
  const [isAuditTrailOpen, setIsAuditTrailOpen] = useState(false);
83
85
  const [isRenameCaseModalOpen, setIsRenameCaseModalOpen] = useState(false);
84
86
  const [isOpenCaseModalOpen, setIsOpenCaseModalOpen] = useState(false);
@@ -91,6 +93,14 @@ export const Striae = ({ user }: StriaePage) => {
91
93
  const [isOpeningCase, setIsOpeningCase] = useState(false);
92
94
  const [openCaseHelperText, setOpenCaseHelperText] = useState('');
93
95
  const [isArchiveCaseModalOpen, setIsArchiveCaseModalOpen] = useState(false);
96
+ const [isExportCaseModalOpen, setIsExportCaseModalOpen] = useState(false);
97
+ const [isExportingCase, setIsExportingCase] = useState(false);
98
+ const [isExportConfirmationsModalOpen, setIsExportConfirmationsModalOpen] = useState(false);
99
+ const [isExportingConfirmations, setIsExportingConfirmations] = useState(false);
100
+ const [exportConfirmationStats, setExportConfirmationStats] = useState<{
101
+ confirmedCount: number;
102
+ unconfirmedCount: number;
103
+ } | null>(null);
94
104
  const [archiveDetails, setArchiveDetails] = useState<{
95
105
  archived: boolean;
96
106
  archivedAt?: string;
@@ -253,13 +263,19 @@ export const Striae = ({ user }: StriaePage) => {
253
263
  setIsGeneratingPDF,
254
264
  setToastType,
255
265
  setToastMessage,
256
- setShowToast
266
+ setShowToast,
267
+ setToastDuration
257
268
  });
258
269
  };
259
270
 
260
- const showNotification = (message: string, type: 'success' | 'error' | 'warning' = 'success') => {
271
+ const showNotification = (
272
+ message: string,
273
+ type: ToastType = 'success',
274
+ duration = 4000
275
+ ) => {
261
276
  setToastType(type);
262
277
  setToastMessage(message);
278
+ setToastDuration(duration);
263
279
  setShowToast(true);
264
280
  };
265
281
 
@@ -270,60 +286,82 @@ export const Striae = ({ user }: StriaePage) => {
270
286
 
271
287
  const handleExport = async (
272
288
  exportCaseNumber: string,
273
- format: ExportFormat,
274
- includeImages?: boolean,
289
+ designatedReviewerEmail?: string,
275
290
  onProgress?: (progress: number, label: string) => void
276
291
  ) => {
277
- const caseExportActions = await loadCaseExportActions();
278
-
279
- if (includeImages) {
280
- await caseExportActions.downloadCaseAsZip(user, exportCaseNumber, format, (progress) => {
281
- const label = getExportProgressLabel(progress);
282
- onProgress?.(Math.round(progress), label);
283
- });
284
- showNotification(`Case ${exportCaseNumber} exported successfully.`, 'success');
292
+ if (!exportCaseNumber) {
293
+ showNotification('Select a case before exporting.', 'error');
285
294
  return;
286
295
  }
287
296
 
288
- onProgress?.(5, 'Loading case data');
289
- const exportData = await caseExportActions.exportCaseData(
290
- user,
291
- exportCaseNumber,
292
- { includeMetadata: true },
293
- (current, total, label) => {
294
- const progress = total > 0 ? Math.round(10 + (current / total) * 60) : 10;
295
- onProgress?.(progress, label);
296
- }
297
- );
297
+ showNotification(`Exporting case ${exportCaseNumber}...`, 'loading', 0);
298
+
299
+ try {
300
+ const caseExportActions = await loadCaseExportActions();
301
+
302
+ await caseExportActions.downloadCaseAsZip(
303
+ user,
304
+ exportCaseNumber,
305
+ (progress) => {
306
+ const roundedProgress = Math.round(progress);
307
+ const label = getExportProgressLabel(progress);
308
+ setToastType('loading');
309
+ setToastMessage(`Exporting case ${exportCaseNumber}... ${label} (${roundedProgress}%)`);
310
+ setToastDuration(0);
311
+ setShowToast(true);
312
+ onProgress?.(roundedProgress, label);
313
+ },
314
+ { designatedReviewerEmail }
315
+ );
298
316
 
299
- onProgress?.(75, 'Preparing download');
300
- if (format === 'json') {
301
- await caseExportActions.downloadCaseAsJSON(user, exportData);
302
- } else {
303
- await caseExportActions.downloadCaseAsCSV(user, exportData);
317
+ showNotification(`Case ${exportCaseNumber} exported successfully.`, 'success');
318
+ } catch (error) {
319
+ showNotification(error instanceof Error ? error.message : 'Export failed. Please try again.', 'error');
304
320
  }
305
- onProgress?.(100, 'Complete');
306
- showNotification(`Case ${exportCaseNumber} exported successfully.`, 'success');
307
321
  };
308
322
 
309
- const handleExportAll = async (
310
- onProgress: (current: number, total: number, caseName: string) => void,
311
- format: ExportFormat
312
- ) => {
313
- const caseExportActions = await loadCaseExportActions();
314
- const exportData = await caseExportActions.exportAllCases(
315
- user,
316
- { includeMetadata: true },
317
- onProgress
318
- );
323
+ const handleExportCaseModalSubmit = async (designatedReviewerEmail: string | undefined) => {
324
+ setIsExportingCase(true);
325
+ setIsExportCaseModalOpen(false);
326
+ try {
327
+ await handleExport(currentCase || '', designatedReviewerEmail);
328
+ } finally {
329
+ setIsExportingCase(false);
330
+ }
331
+ };
332
+
333
+ const handleOpenExportConfirmationsModal = async () => {
334
+ if (!currentCase || !user) return;
319
335
 
320
- if (format === 'json') {
321
- await caseExportActions.downloadAllCasesAsJSON(user, exportData);
322
- } else {
323
- await caseExportActions.downloadAllCasesAsCSV(user, exportData);
336
+ try {
337
+ const summary = await getCaseConfirmationSummary(user, currentCase);
338
+ const filesById = summary?.filesById ?? {};
339
+ const values = Object.values(filesById);
340
+ const confirmedCount = values.filter((f) => f.includeConfirmation && f.isConfirmed).length;
341
+ const unconfirmedCount = values.filter((f) => f.includeConfirmation && !f.isConfirmed).length;
342
+ setExportConfirmationStats({ confirmedCount, unconfirmedCount });
343
+ } catch {
344
+ setExportConfirmationStats({ confirmedCount: 0, unconfirmedCount: 0 });
324
345
  }
325
346
 
326
- showNotification('All cases exported successfully.', 'success');
347
+ setIsExportConfirmationsModalOpen(true);
348
+ };
349
+
350
+ const handleExportConfirmations = async () => {
351
+ if (!currentCase || !user) return;
352
+
353
+ setIsExportingConfirmations(true);
354
+ showNotification(`Exporting confirmations for case ${currentCase}...`, 'loading', 0);
355
+
356
+ try {
357
+ await exportConfirmationData(user, currentCase);
358
+ setIsExportConfirmationsModalOpen(false);
359
+ showNotification(`Confirmations for case ${currentCase} exported successfully.`, 'success');
360
+ } catch (e) {
361
+ showNotification(e instanceof Error ? e.message : 'Confirmation export failed. Please try again.', 'error');
362
+ } finally {
363
+ setIsExportingConfirmations(false);
364
+ }
327
365
  };
328
366
 
329
367
  const handleRenameCaseSubmit = async (newCaseName: string) => {
@@ -370,6 +408,8 @@ export const Striae = ({ user }: StriaePage) => {
370
408
  }
371
409
 
372
410
  setIsDeletingCase(true);
411
+ showNotification(`Deleting case ${currentCase}...`, 'loading', 0);
412
+
373
413
  try {
374
414
  const deleteResult = await deleteCase(user, currentCase);
375
415
  clearLoadedCaseState();
@@ -474,6 +514,8 @@ export const Striae = ({ user }: StriaePage) => {
474
514
  }
475
515
 
476
516
  setIsArchivingCase(true);
517
+ showNotification(`Archiving case ${currentCase}... Preparing archive package.`, 'loading', 0);
518
+
477
519
  try {
478
520
  await archiveCase(user, currentCase, archiveReason);
479
521
  setIsReadOnlyCase(true);
@@ -767,7 +809,13 @@ export const Striae = ({ user }: StriaePage) => {
767
809
  void handleOpenCaseModal();
768
810
  }}
769
811
  onOpenListAllCases={() => setIsListCasesModalOpen(true)}
770
- onOpenCaseExport={() => setIsCaseExportModalOpen(true)}
812
+ onOpenCaseExport={() => {
813
+ if (isReadOnlyCase) {
814
+ void handleOpenExportConfirmationsModal();
815
+ } else {
816
+ setIsExportCaseModalOpen(true);
817
+ }
818
+ }}
771
819
  onOpenAuditTrail={() => setIsAuditTrailOpen(true)}
772
820
  onOpenRenameCase={() => setIsRenameCaseModalOpen(true)}
773
821
  onDeleteCase={() => {
@@ -790,7 +838,7 @@ export const Striae = ({ user }: StriaePage) => {
790
838
  onOpenCase={() => {
791
839
  void handleOpenCaseModal();
792
840
  }}
793
- onOpenCaseExport={() => setIsCaseExportModalOpen(true)}
841
+ onOpenCaseExport={() => void handleOpenExportConfirmationsModal()}
794
842
  imageId={imageId}
795
843
  currentCase={currentCase}
796
844
  imageLoaded={imageLoaded}
@@ -883,14 +931,6 @@ export const Striae = ({ user }: StriaePage) => {
883
931
  isUploading={isUploading}
884
932
  showNotification={showNotification}
885
933
  />
886
- <CaseExport
887
- isOpen={isCaseExportModalOpen}
888
- onClose={() => setIsCaseExportModalOpen(false)}
889
- onExport={handleExport}
890
- onExportAll={handleExportAll}
891
- currentCaseNumber={currentCase}
892
- isReadOnly={isReadOnlyCase}
893
- />
894
934
  <UserAuditViewer
895
935
  caseNumber={currentCase || ''}
896
936
  isOpen={isAuditTrailOpen}
@@ -911,11 +951,29 @@ export const Striae = ({ user }: StriaePage) => {
911
951
  onClose={() => setIsArchiveCaseModalOpen(false)}
912
952
  onSubmit={handleArchiveCaseSubmit}
913
953
  />
954
+ <ExportCaseModal
955
+ isOpen={isExportCaseModalOpen}
956
+ caseNumber={currentCase || ''}
957
+ currentUserEmail={user.email ?? undefined}
958
+ isSubmitting={isExportingCase}
959
+ onClose={() => setIsExportCaseModalOpen(false)}
960
+ onSubmit={handleExportCaseModalSubmit}
961
+ />
962
+ <ExportConfirmationsModal
963
+ isOpen={isExportConfirmationsModalOpen}
964
+ caseNumber={currentCase || ''}
965
+ confirmedCount={exportConfirmationStats?.confirmedCount ?? 0}
966
+ unconfirmedCount={exportConfirmationStats?.unconfirmedCount ?? 0}
967
+ isSubmitting={isExportingConfirmations}
968
+ onClose={() => setIsExportConfirmationsModalOpen(false)}
969
+ onConfirm={() => void handleExportConfirmations()}
970
+ />
914
971
  <Toast
915
972
  message={toastMessage}
916
973
  type={toastType}
917
974
  isVisible={showToast}
918
975
  onClose={closeToast}
976
+ duration={toastDuration}
919
977
  />
920
978
  </div>
921
979
  );
@@ -6,14 +6,11 @@ import {
6
6
  //connectAuthEmulator,
7
7
  } from 'firebase/auth';
8
8
  import firebaseConfig from '~/config/firebase';
9
- import { getAppVersion } from '~/utils/common';
10
9
 
11
10
  export const app = initializeApp(firebaseConfig, "Striae");
12
11
  export const auth = getAuth(app);
13
12
 
14
13
  setPersistence(auth, browserSessionPersistence);
15
14
 
16
- console.log(`Welcome to ${app.name} v${getAppVersion()}`);
17
-
18
15
  //Connect to the Firebase Auth emulator if running locally
19
16
  //connectAuthEmulator(auth, 'http://127.0.0.1:9099');
package/app/types/case.ts CHANGED
@@ -50,6 +50,7 @@ export interface CaseExportData {
50
50
  exportedByName: string;
51
51
  exportedByCompany: string;
52
52
  exportedByBadgeId?: string;
53
+ designatedReviewerEmail?: string;
53
54
  striaeExportSchemaVersion: string;
54
55
  totalFiles: number;
55
56
  };
@@ -1,8 +1,8 @@
1
1
  // Export-related types and interfaces
2
2
 
3
3
  export interface ExportOptions {
4
- format?: 'json' | 'csv';
5
4
  includeMetadata?: boolean;
6
5
  includeUserInfo?: boolean;
7
- protectForensicData?: boolean; // Enable read-only protection
6
+ protectForensicData?: boolean;
7
+ designatedReviewerEmail?: string;
8
8
  }
@@ -78,6 +78,16 @@ export interface ConfirmationImportData {
78
78
  };
79
79
  }
80
80
 
81
+ export interface ConfirmationImportPreview {
82
+ caseNumber: string;
83
+ exportedBy: string;
84
+ exportedByName: string;
85
+ exportedByCompany: string;
86
+ exportedByBadgeId?: string;
87
+ exportDate: string;
88
+ totalConfirmations: number;
89
+ }
90
+
81
91
  export interface CaseImportPreview {
82
92
  caseNumber: string;
83
93
  archived?: boolean;
@@ -1,4 +1,3 @@
1
- export * from './auth';
2
1
  export * from './auth-action-settings';
3
2
  export * from './mfa';
4
3
  export * from './mfa-phone';
@@ -103,7 +103,8 @@ export const createUser = async (
103
103
  firstName: string,
104
104
  lastName: string,
105
105
  company: string,
106
- permitted: boolean = false
106
+ permitted: boolean = false,
107
+ badgeId: string = ''
107
108
  ): Promise<UserData> => {
108
109
  try {
109
110
  const userData: UserData = {
@@ -112,7 +113,7 @@ export const createUser = async (
112
113
  firstName,
113
114
  lastName,
114
115
  company,
115
- badgeId: '',
116
+ badgeId,
116
117
  permitted,
117
118
  cases: [],
118
119
  readOnlyCases: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@striae-org/striae",
3
- "version": "5.2.1",
3
+ "version": "5.3.1",
4
4
  "private": false,
5
5
  "description": "Striae is a specialized, cloud-native platform designed to streamline forensic firearms identification by providing an intuitive environment for digital comparison image annotation, authenticated confirmations, and automated report generation.",
6
6
  "license": "Apache-2.0",
@@ -49,14 +49,15 @@
49
49
  "load-context.ts",
50
50
  "functions/",
51
51
  "public/",
52
- "scripts/",
53
52
  "workers/*/package.json",
54
- "workers/*/src/*.example.ts",
55
- "workers/*/src/*.example.js",
56
- "workers/*/src/*.ts",
53
+ "workers/*/src/**/*.example.ts",
54
+ "workers/*/src/**/*.example.js",
55
+ "workers/*/src/**/*.ts",
57
56
  "workers/pdf-worker/scripts/*.js",
58
- "!workers/*/src/*worker.ts",
57
+ "!workers/*/src/**/*worker.ts",
58
+ "!workers/pdf-worker/src/assets/**/*",
59
59
  "workers/pdf-worker/src/assets/generated-assets.example.ts",
60
+ "!workers/pdf-worker/src/formats/**/*",
60
61
  "workers/pdf-worker/src/formats/format-striae.ts",
61
62
  "workers/pdf-worker/src/report-types.ts",
62
63
  "workers/*/wrangler.jsonc.example",
@@ -96,7 +97,7 @@
96
97
  "deploy-config": "bash ./scripts/deploy-config.sh",
97
98
  "update-env": "bash ./scripts/deploy-config.sh --update-env",
98
99
  "install-workers": "bash ./scripts/install-workers.sh",
99
- "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:keys && npm run deploy-workers:pdf && npm run deploy-workers:user",
100
+ "deploy-workers": "npm run deploy-workers:audit && npm run deploy-workers:data && npm run deploy-workers:image && npm run deploy-workers:pdf && npm run deploy-workers:user",
100
101
  "deploy-workers:secrets": "bash ./scripts/deploy-worker-secrets.sh",
101
102
  "deploy-pages:secrets": "bash ./scripts/deploy-pages-secrets.sh --production-only",
102
103
  "deploy-pages": "bash ./scripts/deploy-pages.sh",
@@ -104,13 +105,11 @@
104
105
  "deploy-workers:audit": "cd workers/audit-worker && npm run deploy",
105
106
  "deploy-workers:data": "cd workers/data-worker && npm run deploy",
106
107
  "deploy-workers:image": "cd workers/image-worker && npm run deploy",
107
- "deploy-workers:keys": "cd workers/keys-worker && npm run deploy",
108
108
  "deploy-workers:pdf": "cd workers/pdf-worker && npm run deploy",
109
109
  "deploy-workers:user": "cd workers/user-worker && npm run deploy"
110
110
  },
111
111
  "dependencies": {
112
112
  "@react-router/cloudflare": "^7.13.2",
113
- "exceljs": "^4.4.0",
114
113
  "firebase": "^12.10.0",
115
114
  "isbot": "^5.1.36",
116
115
  "jszip": "^3.10.1",
@@ -139,13 +138,7 @@
139
138
  },
140
139
  "overrides": {
141
140
  "tar": "7.5.11",
142
- "undici": "7.24.1",
143
- "exceljs": {
144
- "archiver": "7.0.1",
145
- "fast-csv": "5.0.5",
146
- "unzipper": "0.12.3",
147
- "glob": "13.0.6"
148
- }
141
+ "undici": "7.24.1"
149
142
  },
150
143
  "engines": {
151
144
  "node": ">=20.0.0"
package/public/_headers CHANGED
@@ -1,7 +1,3 @@
1
- /favicon.ico
2
- Cache-Control: public, max-age=3600, s-maxage=3600
3
- /favicon.svg
4
- Cache-Control: public, max-age=3600, s-maxage=3600
5
1
  /*.css
6
2
  Cache-Control: public, max-age=31536000, immutable
7
3
  /*.js
@@ -2,7 +2,6 @@
2
2
  "version": 1,
3
3
  "include": ["/*"],
4
4
  "exclude": [
5
- "/favicon.ico",
6
5
  "/assets/*",
7
6
  "/build/*",
8
7
  "/*.css",
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable */
2
- // Generated by Wrangler by running `wrangler types` (hash: 3fce96aefe167606678b821d5dbcc109)
3
- // Runtime types generated with workerd@1.20250823.0 2026-03-25 nodejs_compat
2
+ // Generated by Wrangler by running `wrangler types` (hash: d8f8f87d89a635e81e94aa31fb52008f)
3
+ // Runtime types generated with workerd@1.20250823.0 2026-03-26 nodejs_compat
4
4
  declare namespace Cloudflare {
5
5
  interface Env {
6
6
  ACCOUNT_ID: string;
@@ -16,31 +16,35 @@ declare namespace Cloudflare {
16
16
  MEASUREMENT_ID: string;
17
17
  FIREBASE_SERVICE_ACCOUNT_EMAIL: string;
18
18
  FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY: string;
19
- PAGES_PROJECT_NAME: string;
20
- PAGES_CUSTOM_DOMAIN: string;
21
- KEYS_WORKER_NAME: string;
22
- KEYS_WORKER_DOMAIN: string;
23
- KEYS_AUTH: string;
24
- ACCOUNT_HASH: string;
25
- USER_WORKER_NAME: string;
26
- USER_WORKER_DOMAIN: string;
27
- KV_STORE_ID: string;
28
19
  USER_KV_ENCRYPTION_PRIVATE_KEY: string;
29
20
  USER_KV_ENCRYPTION_KEY_ID: string;
30
21
  USER_KV_ENCRYPTION_PUBLIC_KEY: string;
31
- DATA_WORKER_NAME: string;
32
- DATA_BUCKET_NAME: string;
33
- FILES_BUCKET_NAME: string;
34
- DATA_WORKER_DOMAIN: string;
22
+ USER_KV_WRITE_ENDPOINTS_ENABLED: string;
23
+ USER_KV_ENCRYPTION_KEYS_JSON: string;
24
+ USER_KV_ENCRYPTION_ACTIVE_KEY_ID: string;
35
25
  MANIFEST_SIGNING_PRIVATE_KEY: string;
36
26
  MANIFEST_SIGNING_KEY_ID: string;
37
27
  MANIFEST_SIGNING_PUBLIC_KEY: string;
38
28
  EXPORT_ENCRYPTION_PRIVATE_KEY: string;
39
29
  EXPORT_ENCRYPTION_KEY_ID: string;
40
30
  EXPORT_ENCRYPTION_PUBLIC_KEY: string;
31
+ EXPORT_ENCRYPTION_KEYS_JSON: string;
32
+ EXPORT_ENCRYPTION_ACTIVE_KEY_ID: string;
33
+ DATA_AT_REST_ENCRYPTION_ENABLED: string;
41
34
  DATA_AT_REST_ENCRYPTION_PRIVATE_KEY: string;
42
35
  DATA_AT_REST_ENCRYPTION_KEY_ID: string;
43
36
  DATA_AT_REST_ENCRYPTION_PUBLIC_KEY: string;
37
+ DATA_AT_REST_ENCRYPTION_KEYS_JSON: string;
38
+ DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID: string;
39
+ PAGES_PROJECT_NAME: string;
40
+ PAGES_CUSTOM_DOMAIN: string;
41
+ USER_WORKER_NAME: string;
42
+ USER_WORKER_DOMAIN: string;
43
+ KV_STORE_ID: string;
44
+ DATA_WORKER_NAME: string;
45
+ DATA_BUCKET_NAME: string;
46
+ FILES_BUCKET_NAME: string;
47
+ DATA_WORKER_DOMAIN: string;
44
48
  AUDIT_WORKER_NAME: string;
45
49
  AUDIT_BUCKET_NAME: string;
46
50
  AUDIT_WORKER_DOMAIN: string;
@@ -53,7 +57,6 @@ declare namespace Cloudflare {
53
57
  PDF_WORKER_AUTH: string;
54
58
  BROWSER_API_TOKEN: string;
55
59
  PRIMERSHEAR_EMAILS: string;
56
- DATA_AT_REST_ENCRYPTION_ENABLED: string;
57
60
  }
58
61
  }
59
62
  interface Env extends Cloudflare.Env {}
@@ -61,7 +64,7 @@ type StringifyValues<EnvType extends Record<string, unknown>> = {
61
64
  [Binding in keyof EnvType]: EnvType[Binding] extends string ? EnvType[Binding] : string;
62
65
  };
63
66
  declare namespace NodeJS {
64
- interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "KEYS_WORKER_NAME" | "KEYS_WORKER_DOMAIN" | "KEYS_AUTH" | "ACCOUNT_HASH" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "USER_KV_ENCRYPTION_PRIVATE_KEY" | "USER_KV_ENCRYPTION_KEY_ID" | "USER_KV_ENCRYPTION_PUBLIC_KEY" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "FILES_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "EXPORT_ENCRYPTION_PRIVATE_KEY" | "EXPORT_ENCRYPTION_KEY_ID" | "EXPORT_ENCRYPTION_PUBLIC_KEY" | "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" | "DATA_AT_REST_ENCRYPTION_KEY_ID" | "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "IMAGE_SIGNED_URL_SECRET" | "IMAGE_SIGNED_URL_TTL_SECONDS" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS" | "DATA_AT_REST_ENCRYPTION_ENABLED">> {}
67
+ interface ProcessEnv extends StringifyValues<Pick<Cloudflare.Env, "ACCOUNT_ID" | "USER_DB_AUTH" | "R2_KEY_SECRET" | "IMAGES_API_TOKEN" | "API_KEY" | "AUTH_DOMAIN" | "PROJECT_ID" | "STORAGE_BUCKET" | "MESSAGING_SENDER_ID" | "APP_ID" | "MEASUREMENT_ID" | "FIREBASE_SERVICE_ACCOUNT_EMAIL" | "FIREBASE_SERVICE_ACCOUNT_PRIVATE_KEY" | "USER_KV_ENCRYPTION_PRIVATE_KEY" | "USER_KV_ENCRYPTION_KEY_ID" | "USER_KV_ENCRYPTION_PUBLIC_KEY" | "USER_KV_WRITE_ENDPOINTS_ENABLED" | "USER_KV_ENCRYPTION_KEYS_JSON" | "USER_KV_ENCRYPTION_ACTIVE_KEY_ID" | "MANIFEST_SIGNING_PRIVATE_KEY" | "MANIFEST_SIGNING_KEY_ID" | "MANIFEST_SIGNING_PUBLIC_KEY" | "EXPORT_ENCRYPTION_PRIVATE_KEY" | "EXPORT_ENCRYPTION_KEY_ID" | "EXPORT_ENCRYPTION_PUBLIC_KEY" | "EXPORT_ENCRYPTION_KEYS_JSON" | "EXPORT_ENCRYPTION_ACTIVE_KEY_ID" | "DATA_AT_REST_ENCRYPTION_ENABLED" | "DATA_AT_REST_ENCRYPTION_PRIVATE_KEY" | "DATA_AT_REST_ENCRYPTION_KEY_ID" | "DATA_AT_REST_ENCRYPTION_PUBLIC_KEY" | "DATA_AT_REST_ENCRYPTION_KEYS_JSON" | "DATA_AT_REST_ENCRYPTION_ACTIVE_KEY_ID" | "PAGES_PROJECT_NAME" | "PAGES_CUSTOM_DOMAIN" | "USER_WORKER_NAME" | "USER_WORKER_DOMAIN" | "KV_STORE_ID" | "DATA_WORKER_NAME" | "DATA_BUCKET_NAME" | "FILES_BUCKET_NAME" | "DATA_WORKER_DOMAIN" | "AUDIT_WORKER_NAME" | "AUDIT_BUCKET_NAME" | "AUDIT_WORKER_DOMAIN" | "IMAGES_WORKER_NAME" | "IMAGES_WORKER_DOMAIN" | "IMAGE_SIGNED_URL_SECRET" | "IMAGE_SIGNED_URL_TTL_SECONDS" | "PDF_WORKER_NAME" | "PDF_WORKER_DOMAIN" | "PDF_WORKER_AUTH" | "BROWSER_API_TOKEN" | "PRIMERSHEAR_EMAILS">> {}
65
68
  }
66
69
 
67
70
  // Begin runtime types