@jmruthers/pace-core 0.5.54 → 0.5.55
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.
- package/README.md +0 -4
- package/dist/{DataTable-7FMFXA7A.js → DataTable-4T627QFJ.js} +11 -11
- package/dist/{PublicLoadingSpinner-Bq_-BeK-.d.ts → PublicLoadingSpinner-SL8WaQN7.d.ts} +2 -21
- package/dist/{api-H5A3H4IR.js → api-LUNF5O6M.js} +3 -3
- package/dist/{appConfig-BVGyuvI7.d.ts → appConfig-DjpeG6P-.d.ts} +9 -1
- package/dist/{appNameResolver-7GHF5ED2.js → appNameResolver-UURKN7NF.js} +2 -2
- package/dist/{audit-BUW3LMJB.js → audit-6TOCAMKO.js} +2 -2
- package/dist/{chunk-MZBUOP4P.js → chunk-5BSLGBYI.js} +4 -3
- package/dist/chunk-5BSLGBYI.js.map +1 -0
- package/dist/{chunk-I5Z3QH5X.js → chunk-66C4BSAY.js} +2 -2
- package/dist/{chunk-I5Z3QH5X.js.map → chunk-66C4BSAY.js.map} +1 -1
- package/dist/{chunk-MYP2EGHX.js → chunk-AJ2KMES7.js} +21 -14
- package/dist/chunk-AJ2KMES7.js.map +1 -0
- package/dist/{chunk-EL2O4IUX.js → chunk-AQFRLC7K.js} +16 -24
- package/dist/{chunk-EL2O4IUX.js.map → chunk-AQFRLC7K.js.map} +1 -1
- package/dist/{chunk-7BNPOCLL.js → chunk-B2WTCLCV.js} +6 -2
- package/dist/chunk-B2WTCLCV.js.map +1 -0
- package/dist/{chunk-WJARTBCT.js → chunk-D7ARGIA3.js} +16 -7
- package/dist/chunk-D7ARGIA3.js.map +1 -0
- package/dist/{chunk-NRK4AIHQ.js → chunk-KBRACSJI.js} +3 -3
- package/dist/{chunk-NYUJ4FJR.js → chunk-KJDPSM64.js} +7 -7
- package/dist/chunk-KJDPSM64.js.map +1 -0
- package/dist/{chunk-GWSBHC4J.js → chunk-KLPVOPRI.js} +261 -38
- package/dist/chunk-KLPVOPRI.js.map +1 -0
- package/dist/{chunk-TRIZ7IB7.js → chunk-MPQDF75X.js} +148 -288
- package/dist/chunk-MPQDF75X.js.map +1 -0
- package/dist/{chunk-MSFACPQQ.js → chunk-PAEM3OWN.js} +11 -11
- package/dist/{chunk-MSFACPQQ.js.map → chunk-PAEM3OWN.js.map} +1 -1
- package/dist/{chunk-GIO7BFE7.js → chunk-RQD3D2CO.js} +66 -169
- package/dist/{chunk-GIO7BFE7.js.map → chunk-RQD3D2CO.js.map} +1 -1
- package/dist/{chunk-YDJW5XTN.js → chunk-STT7INZR.js} +25 -1
- package/dist/chunk-STT7INZR.js.map +1 -0
- package/dist/{chunk-6MTY77WU.js → chunk-TNMXZLDR.js} +3 -3
- package/dist/{chunk-BC3S53OZ.js → chunk-UQE2Y64H.js} +30 -14
- package/dist/chunk-UQE2Y64H.js.map +1 -0
- package/dist/{chunk-22KLBHPS.js → chunk-W66AZIOH.js} +2 -2
- package/dist/chunk-W66AZIOH.js.map +1 -0
- package/dist/{chunk-SS3E6QLB.js → chunk-YNUBMSMV.js} +2 -2
- package/dist/chunk-YNUBMSMV.js.map +1 -0
- package/dist/{chunk-NZ655MWE.js → chunk-ZOD2ZY6X.js} +5 -4
- package/dist/chunk-ZOD2ZY6X.js.map +1 -0
- package/dist/{chunk-74C6SNEC.js → chunk-ZPK5656W.js} +3 -3
- package/dist/{chunk-74C6SNEC.js.map → chunk-ZPK5656W.js.map} +1 -1
- package/dist/components.d.ts +22 -899
- package/dist/components.js +436 -3118
- package/dist/components.js.map +1 -1
- package/dist/file-reference-9xUOnwyt.d.ts +70 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +10 -10
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +49 -9
- package/dist/index.js +190 -25
- package/dist/index.js.map +1 -1
- package/dist/{organisation-CO3Sh3_D.d.ts → organisation-t-vvQC3g.d.ts} +1 -8
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +5 -5
- package/dist/rbac/index.d.ts +65 -46
- package/dist/rbac/index.js +10 -12
- package/dist/styles/core.css +0 -125
- package/dist/types.d.ts +2 -1
- package/dist/types.js +3 -1
- package/dist/types.js.map +1 -1
- package/dist/{usePublicRouteParams-B2OcAsur.d.ts → usePublicRouteParams-CdoFxnJK.d.ts} +1 -1
- package/dist/utils.d.ts +3 -4
- package/dist/utils.js +44 -13
- package/dist/utils.js.map +1 -1
- package/docs/FILE_REFERENCE_SYSTEM.md +440 -0
- package/docs/INDEX.md +7 -5
- package/docs/README.md +0 -1
- package/docs/api/README.md +0 -4
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +12 -12
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +6 -6
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +281 -0
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContextType.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACProviderProps.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +204 -200
- package/docs/api-reference/components.md +141 -163
- package/docs/api-reference/hooks.md +347 -0
- package/docs/core-concepts/rbac-system.md +69 -16
- package/docs/getting-started/examples/basic-auth-app.md +0 -1
- package/docs/implementation-guides/datatable-rbac-usage.md +12 -11
- package/docs/implementation-guides/file-upload-storage.md +733 -0
- package/docs/implementation-guides/inactivity-tracking.md +779 -0
- package/docs/implementation-guides/organisation-security.md +748 -0
- package/docs/implementation-guides/public-pages-advanced.md +1022 -0
- package/docs/migration/MIGRATION_GUIDE.md +684 -0
- package/docs/migration/README.md +13 -2
- package/docs/migration/rbac-migration.md +73 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +11 -13
- package/docs/style-guide.md +269 -1
- package/package.json +1 -1
- package/src/__tests__/TESTING_GUIDELINES.md +331 -18
- package/src/__tests__/helpers/supabaseMock.ts +99 -0
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +10 -7
- package/src/__tests__/shared.ts +6 -0
- package/src/components/DataTable/components/ActionButtons.tsx +2 -2
- package/src/components/DataTable/components/DataTableCore.tsx +2 -2
- package/src/components/DataTable/components/UnifiedTableBody.tsx +1 -1
- package/src/components/DataTable/utils/debugTools.ts +2 -2
- package/src/components/Dialog/Dialog.test.tsx +12 -2
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +6 -6
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +2 -2
- package/src/components/FileDisplay.tsx +233 -0
- package/src/components/FileUpload.tsx +176 -0
- package/src/components/Footer/Footer.test.tsx +7 -7
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +13 -6
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +30 -3
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +1 -1
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +558 -0
- package/src/components/PublicLayout/PublicErrorBoundary.tsx +1 -1
- package/src/components/PublicLayout/PublicPageDebugger.tsx +2 -2
- package/src/components/PublicLayout/PublicPageDiagnostic.tsx +2 -2
- package/src/components/PublicLayout/PublicPageProvider.tsx +2 -2
- package/src/components/Select/Select.test.tsx +50 -15
- package/src/components/SuperAdminGuard.tsx +2 -2
- package/src/components/__tests__/SuperAdminGuard.test.tsx +559 -0
- package/src/components/index.ts +0 -183
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +2 -2
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +1 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +191 -138
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/useAppConfig.ts +3 -3
- package/src/hooks/useComponentPerformance.ts +1 -1
- package/src/hooks/useDataTablePerformance.ts +1 -1
- package/src/hooks/useFileReference.ts +232 -0
- package/src/hooks/useOrganisationPermissions.test.ts +254 -344
- package/src/hooks/useOrganisationPermissions.ts +15 -7
- package/src/hooks/useOrganisationSecurity.test.ts +390 -402
- package/src/hooks/usePerformanceMonitor.ts +1 -1
- package/src/hooks/usePermissionCache.test.ts +264 -395
- package/src/hooks/usePermissionCache.ts +34 -4
- package/src/hooks/useSecureDataAccess.test.ts +486 -0
- package/src/hooks/useSecureDataAccess.ts +4 -1
- package/src/providers/InactivityProvider.tsx +2 -2
- package/src/providers/OrganisationProvider.test.simple.tsx +168 -0
- package/src/providers/OrganisationProvider.test.tsx +168 -0
- package/src/providers/OrganisationProvider.tsx +18 -31
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +205 -0
- package/src/providers/UnifiedAuthProvider.test.tsx +128 -0
- package/src/providers/__tests__/InactivityProvider.test.tsx +3 -4
- package/src/providers/__tests__/OrganisationProvider.test.tsx +19 -14
- package/src/rbac/__tests__/integration.authflow.test.tsx +123 -0
- package/src/rbac/__tests__/integration.navigation.test.tsx +72 -0
- package/src/rbac/__tests__/integration.securedata.test.tsx +92 -0
- package/src/rbac/__tests__/integration.smoke.test.tsx +73 -0
- package/src/rbac/__tests__/rbac-core.test.tsx +26 -22
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +411 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +285 -0
- package/src/rbac/__tests__/rbac-functions.test.ts +655 -0
- package/src/rbac/__tests__/rbac-integration.test.ts +532 -0
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +196 -0
- package/src/rbac/api.test.ts +6 -6
- package/src/rbac/api.ts +2 -2
- package/src/rbac/audit.test.ts +485 -0
- package/src/rbac/audit.ts +7 -1
- package/src/rbac/cache-invalidation.ts +318 -0
- package/src/rbac/cache.test.ts +286 -0
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +559 -0
- package/src/rbac/components/EnhancedNavigationMenu.tsx +29 -23
- package/src/rbac/components/NavigationProvider.test.tsx +449 -0
- package/src/rbac/components/PagePermissionGuard.tsx +4 -4
- package/src/rbac/components/PagePermissionProvider.test.tsx +479 -0
- package/src/rbac/components/SecureDataProvider.test.tsx +511 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +159 -430
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +4 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +112 -118
- package/src/rbac/config.test.ts +410 -0
- package/src/rbac/engine.test.simple.ts +237 -0
- package/src/rbac/engine.test.ts +233 -0
- package/src/rbac/engine.ts +37 -41
- package/src/rbac/examples/CompleteRBACExample.tsx +3 -3
- package/src/rbac/examples/EventBasedApp.tsx +4 -4
- package/src/rbac/hooks/useRBAC.simple.test.ts +16 -0
- package/src/rbac/hooks/useRBAC.test.ts +207 -455
- package/src/rbac/hooks/useRBAC.ts +30 -22
- package/src/rbac/permissions.test.ts +128 -0
- package/src/rbac/permissions.ts +56 -141
- package/src/rbac/providers/RBACProvider.tsx +1 -1
- package/src/rbac/secureClient.test.ts +444 -0
- package/src/rbac/security.test.ts +390 -0
- package/src/rbac/security.ts +1 -1
- package/src/rbac/types.test.ts +382 -0
- package/src/rbac/types.ts +2 -2
- package/src/styles/core.css +0 -125
- package/src/types/file-reference.ts +77 -0
- package/src/types/rbac-functions.ts +290 -0
- package/src/types/supabase.ts +10 -28
- package/src/types/unified.ts +4 -1
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +81 -55
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +21 -12
- package/src/utils/__tests__/organisationContext.unit.test.ts +13 -7
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +3 -3
- package/src/utils/__tests__/sessionTracking.unit.test.ts +32 -12
- package/src/utils/appConfig.ts +1 -1
- package/src/utils/appIdResolver.test.ts +503 -0
- package/src/utils/appIdResolver.ts +1 -1
- package/src/utils/appNameResolver.test.ts +494 -0
- package/src/utils/appNameResolver.ts +3 -2
- package/src/utils/bundleAnalysis.ts +3 -3
- package/src/utils/debugLogger.ts +1 -1
- package/src/utils/file-reference.ts +263 -0
- package/src/utils/formatDate.test.ts +2 -2
- package/src/utils/organisationContext.test.ts +340 -0
- package/src/utils/organisationContext.ts +19 -6
- package/src/utils/performanceBudgets.ts +2 -2
- package/src/utils/permissionUtils.test.ts +393 -0
- package/src/utils/permissionUtils.ts +5 -2
- package/src/utils/secureDataAccess.test.ts +715 -0
- package/src/utils/secureDataAccess.ts +21 -5
- package/src/utils/sessionTracking.ts +34 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +328 -0
- package/src/utils/storage/__tests__/index.unit.test.ts +16 -0
- package/src/utils/storage/helpers.ts +20 -25
- package/src/utils/storage/index.ts +29 -1
- package/src/vite-env.d.ts +17 -0
- package/dist/chunk-22KLBHPS.js.map +0 -1
- package/dist/chunk-7BNPOCLL.js.map +0 -1
- package/dist/chunk-BC3S53OZ.js.map +0 -1
- package/dist/chunk-GWSBHC4J.js.map +0 -1
- package/dist/chunk-MYP2EGHX.js.map +0 -1
- package/dist/chunk-MZBUOP4P.js.map +0 -1
- package/dist/chunk-NYUJ4FJR.js.map +0 -1
- package/dist/chunk-NZ655MWE.js.map +0 -1
- package/dist/chunk-SS3E6QLB.js.map +0 -1
- package/dist/chunk-TRIZ7IB7.js.map +0 -1
- package/dist/chunk-WJARTBCT.js.map +0 -1
- package/dist/chunk-YDJW5XTN.js.map +0 -1
- package/docs/print-components/README.md +0 -258
- package/docs/print-components/api-reference.md +0 -636
- package/docs/print-components/examples/README.md +0 -204
- package/docs/print-components/examples/basic-report.tsx +0 -92
- package/docs/print-components/examples/card-catalog.tsx +0 -149
- package/docs/print-components/examples/cover-page-report.tsx +0 -163
- package/docs/print-components/quick-start.md +0 -363
- package/src/components/PrintButton/PrintButton.tsx +0 -321
- package/src/components/PrintButton/PrintButtonGroup.tsx +0 -84
- package/src/components/PrintButton/PrintToolbar.tsx +0 -94
- package/src/components/PrintButton/__tests__/PrintButton.test.tsx +0 -271
- package/src/components/PrintButton/examples/PrintButtonShowcase.tsx +0 -438
- package/src/components/PrintButton/index.ts +0 -33
- package/src/components/PrintButton/types.ts +0 -173
- package/src/components/PrintCard/PrintCard.tsx +0 -154
- package/src/components/PrintCard/PrintCardContent.tsx +0 -57
- package/src/components/PrintCard/PrintCardFooter.tsx +0 -60
- package/src/components/PrintCard/PrintCardGrid.tsx +0 -91
- package/src/components/PrintCard/PrintCardHeader.tsx +0 -78
- package/src/components/PrintCard/PrintCardImage.tsx +0 -81
- package/src/components/PrintCard/examples/PrintCardShowcase.tsx +0 -239
- package/src/components/PrintCard/index.ts +0 -34
- package/src/components/PrintCard/types.ts +0 -171
- package/src/components/PrintDataTable/PrintDataTable.tsx +0 -215
- package/src/components/PrintDataTable/PrintTableGroup.tsx +0 -90
- package/src/components/PrintDataTable/PrintTableRow.tsx +0 -76
- package/src/components/PrintDataTable/index.ts +0 -25
- package/src/components/PrintDataTable/types.ts +0 -67
- package/src/components/PrintFooter/PrintFooter.tsx +0 -183
- package/src/components/PrintFooter/PrintFooterContent.tsx +0 -71
- package/src/components/PrintFooter/PrintFooterInfo.tsx +0 -86
- package/src/components/PrintFooter/PrintPageNumber.tsx +0 -90
- package/src/components/PrintFooter/examples/PrintFooterShowcase.tsx +0 -390
- package/src/components/PrintFooter/index.ts +0 -30
- package/src/components/PrintFooter/types.ts +0 -149
- package/src/components/PrintGrid/PrintGrid.tsx +0 -180
- package/src/components/PrintGrid/PrintGridBreakpoint.tsx +0 -109
- package/src/components/PrintGrid/PrintGridContainer.tsx +0 -128
- package/src/components/PrintGrid/PrintGridItem.tsx +0 -220
- package/src/components/PrintGrid/examples/PrintGridShowcase.tsx +0 -359
- package/src/components/PrintGrid/index.ts +0 -31
- package/src/components/PrintGrid/types.ts +0 -159
- package/src/components/PrintHeader/PrintCoverHeader.tsx +0 -230
- package/src/components/PrintHeader/PrintHeader.tsx +0 -150
- package/src/components/PrintHeader/index.ts +0 -17
- package/src/components/PrintHeader/types.ts +0 -42
- package/src/components/PrintLayout/PrintLayout.tsx +0 -122
- package/src/components/PrintLayout/PrintLayoutContext.tsx +0 -66
- package/src/components/PrintLayout/PrintPageBreak.tsx +0 -52
- package/src/components/PrintLayout/examples/PrintShowcase.tsx +0 -230
- package/src/components/PrintLayout/index.ts +0 -19
- package/src/components/PrintLayout/types.ts +0 -37
- package/src/components/PrintPageBreak/PrintPageBreak.tsx +0 -120
- package/src/components/PrintPageBreak/PrintPageBreakGroup.tsx +0 -90
- package/src/components/PrintPageBreak/PrintPageBreakIndicator.tsx +0 -112
- package/src/components/PrintPageBreak/examples/PrintPageBreakShowcase.tsx +0 -279
- package/src/components/PrintPageBreak/index.ts +0 -23
- package/src/components/PrintPageBreak/types.ts +0 -94
- package/src/components/PrintSection/PrintColumn.tsx +0 -104
- package/src/components/PrintSection/PrintDivider.tsx +0 -101
- package/src/components/PrintSection/PrintSection.tsx +0 -129
- package/src/components/PrintSection/PrintSectionContent.tsx +0 -75
- package/src/components/PrintSection/PrintSectionHeader.tsx +0 -97
- package/src/components/PrintSection/examples/PrintSectionShowcase.tsx +0 -258
- package/src/components/PrintSection/index.ts +0 -33
- package/src/components/PrintSection/types.ts +0 -155
- package/src/components/PrintText/PrintText.tsx +0 -116
- package/src/components/PrintText/index.ts +0 -16
- package/src/components/PrintText/types.ts +0 -24
- package/src/rbac/__tests__/integration.test.tsx +0 -218
- package/src/utils/print/PrintDataProcessor.ts +0 -390
- package/src/utils/print/examples/PrintUtilitiesShowcase.tsx +0 -397
- package/src/utils/print/index.ts +0 -29
- package/src/utils/print/types.ts +0 -196
- package/src/utils/print/usePrintOptimization.ts +0 -272
- /package/dist/{DataTable-7FMFXA7A.js.map → DataTable-4T627QFJ.js.map} +0 -0
- /package/dist/{api-H5A3H4IR.js.map → api-LUNF5O6M.js.map} +0 -0
- /package/dist/{appNameResolver-7GHF5ED2.js.map → appNameResolver-UURKN7NF.js.map} +0 -0
- /package/dist/{audit-BUW3LMJB.js.map → audit-6TOCAMKO.js.map} +0 -0
- /package/dist/{chunk-NRK4AIHQ.js.map → chunk-KBRACSJI.js.map} +0 -0
- /package/dist/{chunk-6MTY77WU.js.map → chunk-TNMXZLDR.js.map} +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
// File Reference Service
|
|
2
|
+
// Provides CRUD operations for the centralized file reference system
|
|
3
|
+
|
|
4
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
5
|
+
import {
|
|
6
|
+
FileReference,
|
|
7
|
+
FileUploadOptions,
|
|
8
|
+
FileReferenceService,
|
|
9
|
+
FileUploadResult,
|
|
10
|
+
FileCategory
|
|
11
|
+
} from '../types/file-reference';
|
|
12
|
+
import { generateFilePath, uploadFile, getPublicUrl, getSignedUrl, deleteFile, extractFileMetadata } from './storage/helpers';
|
|
13
|
+
|
|
14
|
+
export class FileReferenceServiceImpl implements FileReferenceService {
|
|
15
|
+
constructor(private supabase: SupabaseClient) {}
|
|
16
|
+
|
|
17
|
+
async createFileReference(options: FileUploadOptions, file: File): Promise<FileReference> {
|
|
18
|
+
try {
|
|
19
|
+
// Generate file path
|
|
20
|
+
const filePath = generateFilePath({
|
|
21
|
+
appName: 'file-reference',
|
|
22
|
+
orgId: options.organisation_id,
|
|
23
|
+
isPublic: options.is_public || false
|
|
24
|
+
}, file.name);
|
|
25
|
+
|
|
26
|
+
// Upload file to storage
|
|
27
|
+
const uploadResult = await uploadFile(this.supabase, file, {
|
|
28
|
+
appName: 'file-reference',
|
|
29
|
+
orgId: options.organisation_id,
|
|
30
|
+
isPublic: options.is_public || false,
|
|
31
|
+
customPath: filePath
|
|
32
|
+
});
|
|
33
|
+
if (!uploadResult.success) {
|
|
34
|
+
throw new Error(`Failed to upload file: ${uploadResult.error}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extract file metadata
|
|
38
|
+
const metadata = await extractFileMetadata(file, {
|
|
39
|
+
appName: 'file-reference',
|
|
40
|
+
orgId: options.organisation_id,
|
|
41
|
+
isPublic: options.is_public || false
|
|
42
|
+
}, 'system');
|
|
43
|
+
|
|
44
|
+
// Create file reference in database
|
|
45
|
+
const { data, error } = await this.supabase
|
|
46
|
+
.rpc('insert_file_reference', {
|
|
47
|
+
p_table_name: options.table_name,
|
|
48
|
+
p_record_id: options.record_id,
|
|
49
|
+
p_file_path: filePath,
|
|
50
|
+
p_organisation_id: options.organisation_id,
|
|
51
|
+
p_app_id: options.app_id,
|
|
52
|
+
p_file_metadata: {
|
|
53
|
+
fileName: file.name,
|
|
54
|
+
fileType: file.type,
|
|
55
|
+
fileSize: file.size,
|
|
56
|
+
category: options.category,
|
|
57
|
+
...metadata,
|
|
58
|
+
...options.custom_metadata
|
|
59
|
+
},
|
|
60
|
+
p_is_public: options.is_public || false
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (error) {
|
|
64
|
+
// Clean up uploaded file if database insert fails
|
|
65
|
+
await deleteFile(this.supabase, filePath);
|
|
66
|
+
throw new Error(`Failed to create file reference: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Get the created file reference
|
|
70
|
+
const { data: fileRef, error: fetchError } = await this.supabase
|
|
71
|
+
.from('file_references')
|
|
72
|
+
.select('*')
|
|
73
|
+
.eq('id', data)
|
|
74
|
+
.single();
|
|
75
|
+
|
|
76
|
+
if (fetchError || !fileRef) {
|
|
77
|
+
throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return fileRef as FileReference;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Error creating file reference:', error);
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getFileReference(table_name: string, record_id: string, organisation_id: string): Promise<FileReference | null> {
|
|
88
|
+
try {
|
|
89
|
+
const { data, error } = await this.supabase
|
|
90
|
+
.from('file_references')
|
|
91
|
+
.select('*')
|
|
92
|
+
.eq('table_name', table_name)
|
|
93
|
+
.eq('record_id', record_id)
|
|
94
|
+
.eq('organisation_id', organisation_id)
|
|
95
|
+
.single();
|
|
96
|
+
|
|
97
|
+
if (error) {
|
|
98
|
+
if (error.code === 'PGRST116') {
|
|
99
|
+
return null; // No rows found
|
|
100
|
+
}
|
|
101
|
+
throw new Error(`Failed to get file reference: ${error.message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return data as FileReference;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error('Error getting file reference:', error);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async getFileUrl(table_name: string, record_id: string, organisation_id: string): Promise<string | null> {
|
|
112
|
+
try {
|
|
113
|
+
const { data, error } = await this.supabase
|
|
114
|
+
.rpc('get_file_url', {
|
|
115
|
+
p_table_name: table_name,
|
|
116
|
+
p_record_id: record_id,
|
|
117
|
+
p_organisation_id: organisation_id
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (error) {
|
|
121
|
+
throw new Error(`Failed to get file URL: ${error.message}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return data;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error('Error getting file URL:', error);
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async getSignedUrl(table_name: string, record_id: string, organisation_id: string, expires_in: number = 3600): Promise<string | null> {
|
|
132
|
+
try {
|
|
133
|
+
const { data, error } = await this.supabase
|
|
134
|
+
.rpc('get_file_signed_url', {
|
|
135
|
+
p_table_name: table_name,
|
|
136
|
+
p_record_id: record_id,
|
|
137
|
+
p_organisation_id: organisation_id,
|
|
138
|
+
p_expires_in: expires_in
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (error) {
|
|
142
|
+
throw new Error(`Failed to get signed URL: ${error.message}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return data;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('Error getting signed URL:', error);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async updateFileReference(id: string, updates: Partial<FileReference>): Promise<FileReference> {
|
|
153
|
+
try {
|
|
154
|
+
const { data, error } = await this.supabase
|
|
155
|
+
.from('file_references')
|
|
156
|
+
.update(updates)
|
|
157
|
+
.eq('id', id)
|
|
158
|
+
.select()
|
|
159
|
+
.single();
|
|
160
|
+
|
|
161
|
+
if (error) {
|
|
162
|
+
throw new Error(`Failed to update file reference: ${error.message}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return data as FileReference;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error('Error updating file reference:', error);
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async deleteFileReference(table_name: string, record_id: string, organisation_id: string, delete_file: boolean = false): Promise<boolean> {
|
|
173
|
+
try {
|
|
174
|
+
const { error } = await this.supabase
|
|
175
|
+
.rpc('delete_file_reference', {
|
|
176
|
+
p_table_name: table_name,
|
|
177
|
+
p_record_id: record_id,
|
|
178
|
+
p_organisation_id: organisation_id,
|
|
179
|
+
p_delete_file: delete_file
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (error) {
|
|
183
|
+
throw new Error(`Failed to delete file reference: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return true;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error deleting file reference:', error);
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async listFileReferences(table_name: string, record_id: string, organisation_id: string): Promise<FileReference[]> {
|
|
194
|
+
try {
|
|
195
|
+
const { data, error } = await this.supabase
|
|
196
|
+
.rpc('get_record_files', {
|
|
197
|
+
p_table_name: table_name,
|
|
198
|
+
p_record_id: record_id,
|
|
199
|
+
p_organisation_id: organisation_id
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (error) {
|
|
203
|
+
throw new Error(`Failed to list file references: ${error.message}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return data as FileReference[];
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error('Error listing file references:', error);
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async getFileCount(table_name: string, record_id: string, organisation_id: string): Promise<number> {
|
|
214
|
+
try {
|
|
215
|
+
const { data, error } = await this.supabase
|
|
216
|
+
.rpc('get_record_file_count', {
|
|
217
|
+
p_table_name: table_name,
|
|
218
|
+
p_record_id: record_id,
|
|
219
|
+
p_organisation_id: organisation_id
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
if (error) {
|
|
223
|
+
throw new Error(`Failed to get file count: ${error.message}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return data || 0;
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.error('Error getting file count:', error);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Factory function to create file reference service
|
|
235
|
+
export function createFileReferenceService(supabase: SupabaseClient): FileReferenceService {
|
|
236
|
+
return new FileReferenceServiceImpl(supabase);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Helper function to upload file and create reference in one operation
|
|
240
|
+
export async function uploadFileWithReference(
|
|
241
|
+
supabase: SupabaseClient,
|
|
242
|
+
options: FileUploadOptions,
|
|
243
|
+
file: File
|
|
244
|
+
): Promise<FileUploadResult> {
|
|
245
|
+
const service = createFileReferenceService(supabase);
|
|
246
|
+
const fileReference = await service.createFileReference(options, file);
|
|
247
|
+
|
|
248
|
+
const fileUrl = options.is_public
|
|
249
|
+
? getPublicUrl(supabase, fileReference.file_path)
|
|
250
|
+
: await getSignedUrl(supabase, fileReference.file_path, {
|
|
251
|
+
appName: 'file-reference',
|
|
252
|
+
orgId: options.organisation_id,
|
|
253
|
+
expiresIn: 3600
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const urlString = typeof fileUrl === 'string' ? fileUrl : fileUrl?.url || '';
|
|
257
|
+
|
|
258
|
+
return {
|
|
259
|
+
file_reference: fileReference,
|
|
260
|
+
file_url: urlString,
|
|
261
|
+
signed_url: options.is_public ? undefined : urlString || undefined
|
|
262
|
+
};
|
|
263
|
+
}
|
|
@@ -192,8 +192,8 @@ describe('formatDate Utility', () => {
|
|
|
192
192
|
const endTime = performance.now();
|
|
193
193
|
const duration = endTime - startTime;
|
|
194
194
|
|
|
195
|
-
// Should complete in reasonable time (less than
|
|
196
|
-
expect(duration).toBeLessThan(
|
|
195
|
+
// Should complete in reasonable time (less than 200ms for 1000 calls)
|
|
196
|
+
expect(duration).toBeLessThan(200);
|
|
197
197
|
});
|
|
198
198
|
});
|
|
199
199
|
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Organisation Context Tests
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module Utils/OrganisationContext
|
|
5
|
+
* @since 0.4.0
|
|
6
|
+
*
|
|
7
|
+
* Comprehensive tests for organisation context utility functions covering all critical functionality.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
11
|
+
import {
|
|
12
|
+
setOrganisationContext,
|
|
13
|
+
clearOrganisationContext,
|
|
14
|
+
getOrganisationContext
|
|
15
|
+
} from './organisationContext';
|
|
16
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
17
|
+
|
|
18
|
+
// Mock Supabase client
|
|
19
|
+
const createMockSupabaseClient = () => ({
|
|
20
|
+
rpc: vi.fn().mockResolvedValue({ data: null, error: null })
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('Organisation Context', () => {
|
|
24
|
+
let mockSupabase: any;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
vi.clearAllMocks();
|
|
28
|
+
mockSupabase = createMockSupabaseClient();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
afterEach(() => {
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('setOrganisationContext', () => {
|
|
36
|
+
it('sets organisation context successfully', async () => {
|
|
37
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
38
|
+
data: null,
|
|
39
|
+
error: null
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
await setOrganisationContext(mockSupabase, 'org-123');
|
|
43
|
+
|
|
44
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_audit_log', {
|
|
45
|
+
p_event_type: 'organisation_switched',
|
|
46
|
+
p_organisation_id: 'org-123',
|
|
47
|
+
p_metadata: { action: 'set_context' }
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('handles missing supabase client gracefully', async () => {
|
|
52
|
+
await expect(setOrganisationContext(null as any, 'org-123')).resolves.not.toThrow();
|
|
53
|
+
await expect(setOrganisationContext(undefined as any, 'org-123')).resolves.not.toThrow();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('handles missing organisation ID gracefully', async () => {
|
|
57
|
+
await expect(setOrganisationContext(mockSupabase, null as any)).resolves.not.toThrow();
|
|
58
|
+
await expect(setOrganisationContext(mockSupabase, undefined as any)).resolves.not.toThrow();
|
|
59
|
+
await expect(setOrganisationContext(mockSupabase, '')).resolves.not.toThrow();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('handles database function errors gracefully', async () => {
|
|
63
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
64
|
+
data: null,
|
|
65
|
+
error: { message: 'Function not found' }
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Should not throw error
|
|
69
|
+
await expect(setOrganisationContext(mockSupabase, 'org-123')).resolves.not.toThrow();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('handles RPC exceptions gracefully', async () => {
|
|
73
|
+
mockSupabase.rpc.mockRejectedValue(new Error('Network error'));
|
|
74
|
+
|
|
75
|
+
// Should not throw error
|
|
76
|
+
await expect(setOrganisationContext(mockSupabase, 'org-123')).resolves.not.toThrow();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('handles invalid organisation ID format', async () => {
|
|
80
|
+
await expect(setOrganisationContext(mockSupabase, 'invalid-id')).resolves.not.toThrow();
|
|
81
|
+
await expect(setOrganisationContext(mockSupabase, '123')).resolves.not.toThrow();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('handles very long organisation ID', async () => {
|
|
85
|
+
const longOrgId = 'a'.repeat(1000);
|
|
86
|
+
await expect(setOrganisationContext(mockSupabase, longOrgId)).resolves.not.toThrow();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('clearOrganisationContext', () => {
|
|
91
|
+
it('clears organisation context successfully', async () => {
|
|
92
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
93
|
+
data: null,
|
|
94
|
+
error: null
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await clearOrganisationContext(mockSupabase);
|
|
98
|
+
|
|
99
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_audit_log', {
|
|
100
|
+
p_event_type: 'organisation_switched',
|
|
101
|
+
p_metadata: { action: 'clear_context' }
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('handles missing supabase client gracefully', async () => {
|
|
106
|
+
await expect(clearOrganisationContext(null as any)).resolves.not.toThrow();
|
|
107
|
+
await expect(clearOrganisationContext(undefined as any)).resolves.not.toThrow();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('handles database function errors gracefully', async () => {
|
|
111
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
112
|
+
data: null,
|
|
113
|
+
error: { message: 'Function not found' }
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Should not throw error
|
|
117
|
+
await expect(clearOrganisationContext(mockSupabase)).resolves.not.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('handles RPC exceptions gracefully', async () => {
|
|
121
|
+
mockSupabase.rpc.mockRejectedValue(new Error('Network error'));
|
|
122
|
+
|
|
123
|
+
// Should not throw error
|
|
124
|
+
await expect(clearOrganisationContext(mockSupabase)).resolves.not.toThrow();
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('getOrganisationContext', () => {
|
|
129
|
+
it('gets organisation context successfully', async () => {
|
|
130
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
131
|
+
|
|
132
|
+
expect(result).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('returns null when no context is set', async () => {
|
|
136
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
137
|
+
|
|
138
|
+
expect(result).toBeNull();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('handles missing supabase client gracefully', async () => {
|
|
142
|
+
const result1 = await getOrganisationContext(null as any);
|
|
143
|
+
const result2 = await getOrganisationContext(undefined as any);
|
|
144
|
+
|
|
145
|
+
expect(result1).toBeNull();
|
|
146
|
+
expect(result2).toBeNull();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('handles database function errors gracefully', async () => {
|
|
150
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
151
|
+
|
|
152
|
+
expect(result).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('handles RPC exceptions gracefully', async () => {
|
|
156
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
157
|
+
|
|
158
|
+
expect(result).toBeNull();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('handles invalid data format gracefully', async () => {
|
|
162
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
163
|
+
|
|
164
|
+
expect(result).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('handles empty string data', async () => {
|
|
168
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
169
|
+
|
|
170
|
+
expect(result).toBeNull();
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
describe('Integration Tests', () => {
|
|
175
|
+
it('works with real Supabase client structure', async () => {
|
|
176
|
+
const mockClient = {
|
|
177
|
+
rpc: vi.fn().mockResolvedValue({
|
|
178
|
+
data: 'org-123',
|
|
179
|
+
error: null
|
|
180
|
+
})
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const result = await getOrganisationContext(mockClient as any);
|
|
184
|
+
|
|
185
|
+
expect(result).toBeNull();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('handles complete workflow', async () => {
|
|
189
|
+
// Set context
|
|
190
|
+
mockSupabase.rpc.mockResolvedValueOnce({
|
|
191
|
+
data: null,
|
|
192
|
+
error: null
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await setOrganisationContext(mockSupabase, 'org-123');
|
|
196
|
+
|
|
197
|
+
// Get context
|
|
198
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
199
|
+
|
|
200
|
+
expect(result).toBeNull();
|
|
201
|
+
|
|
202
|
+
// Clear context
|
|
203
|
+
mockSupabase.rpc.mockResolvedValueOnce({
|
|
204
|
+
data: null,
|
|
205
|
+
error: null
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
await clearOrganisationContext(mockSupabase);
|
|
209
|
+
|
|
210
|
+
expect(mockSupabase.rpc).toHaveBeenCalledTimes(2);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it('handles multiple organisation switches', async () => {
|
|
214
|
+
const organisations = ['org-123', 'org-456', 'org-789'];
|
|
215
|
+
|
|
216
|
+
for (const orgId of organisations) {
|
|
217
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
218
|
+
data: null,
|
|
219
|
+
error: null
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await setOrganisationContext(mockSupabase, orgId);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
expect(mockSupabase.rpc).toHaveBeenCalledTimes(3);
|
|
226
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_audit_log', {
|
|
227
|
+
p_event_type: 'organisation_switched',
|
|
228
|
+
p_organisation_id: 'org-123',
|
|
229
|
+
p_metadata: { action: 'set_context' }
|
|
230
|
+
});
|
|
231
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_audit_log', {
|
|
232
|
+
p_event_type: 'organisation_switched',
|
|
233
|
+
p_organisation_id: 'org-456',
|
|
234
|
+
p_metadata: { action: 'set_context' }
|
|
235
|
+
});
|
|
236
|
+
expect(mockSupabase.rpc).toHaveBeenCalledWith('rbac_audit_log', {
|
|
237
|
+
p_event_type: 'organisation_switched',
|
|
238
|
+
p_organisation_id: 'org-789',
|
|
239
|
+
p_metadata: { action: 'set_context' }
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe('Error Handling', () => {
|
|
245
|
+
it('handles network timeouts gracefully', async () => {
|
|
246
|
+
mockSupabase.rpc.mockRejectedValue(new Error('Request timeout'));
|
|
247
|
+
|
|
248
|
+
await expect(setOrganisationContext(mockSupabase, 'org-123')).resolves.not.toThrow();
|
|
249
|
+
await expect(clearOrganisationContext(mockSupabase)).resolves.not.toThrow();
|
|
250
|
+
await expect(getOrganisationContext(mockSupabase)).resolves.toBeNull();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('handles malformed responses gracefully', async () => {
|
|
254
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
255
|
+
|
|
256
|
+
expect(result).toBeNull();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('handles null responses gracefully', async () => {
|
|
260
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
261
|
+
|
|
262
|
+
expect(result).toBeNull();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('handles undefined responses gracefully', async () => {
|
|
266
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
267
|
+
|
|
268
|
+
expect(result).toBeNull();
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Performance', () => {
|
|
273
|
+
it('handles rapid context changes efficiently', async () => {
|
|
274
|
+
const startTime = Date.now();
|
|
275
|
+
|
|
276
|
+
// Rapidly switch contexts
|
|
277
|
+
for (let i = 0; i < 100; i++) {
|
|
278
|
+
mockSupabase.rpc.mockResolvedValue({
|
|
279
|
+
data: null,
|
|
280
|
+
error: null
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await setOrganisationContext(mockSupabase, `org-${i}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const endTime = Date.now();
|
|
287
|
+
|
|
288
|
+
expect(endTime - startTime).toBeLessThan(1000); // Should complete within 1 second
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('handles concurrent operations gracefully', async () => {
|
|
292
|
+
const operations = [
|
|
293
|
+
setOrganisationContext(mockSupabase, 'org-1'),
|
|
294
|
+
setOrganisationContext(mockSupabase, 'org-2'),
|
|
295
|
+
clearOrganisationContext(mockSupabase),
|
|
296
|
+
getOrganisationContext(mockSupabase)
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
await expect(Promise.all(operations)).resolves.not.toThrow();
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
describe('Edge Cases', () => {
|
|
304
|
+
it('handles special characters in organisation ID', async () => {
|
|
305
|
+
const specialOrgId = 'org-123@domain.com';
|
|
306
|
+
await expect(setOrganisationContext(mockSupabase, specialOrgId)).resolves.not.toThrow();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('handles unicode characters in organisation ID', async () => {
|
|
310
|
+
const unicodeOrgId = 'org-用户-123';
|
|
311
|
+
await expect(setOrganisationContext(mockSupabase, unicodeOrgId)).resolves.not.toThrow();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('handles very long organisation ID', async () => {
|
|
315
|
+
const longOrgId = 'a'.repeat(10000);
|
|
316
|
+
await expect(setOrganisationContext(mockSupabase, longOrgId)).resolves.not.toThrow();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('handles numeric organisation ID', async () => {
|
|
320
|
+
await expect(setOrganisationContext(mockSupabase, '123')).resolves.not.toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('handles boolean organisation ID', async () => {
|
|
324
|
+
await expect(setOrganisationContext(mockSupabase, 'true' as any)).resolves.not.toThrow();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe('Type Safety', () => {
|
|
329
|
+
it('maintains type safety with TypeScript', async () => {
|
|
330
|
+
const result = await getOrganisationContext(mockSupabase);
|
|
331
|
+
expect(result).toBeNull();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('handles mixed input types gracefully', async () => {
|
|
335
|
+
await expect(setOrganisationContext(mockSupabase, 123 as any)).resolves.not.toThrow();
|
|
336
|
+
await expect(setOrganisationContext(mockSupabase, true as any)).resolves.not.toThrow();
|
|
337
|
+
await expect(setOrganisationContext(mockSupabase, {} as any)).resolves.not.toThrow();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -31,8 +31,10 @@ export async function setOrganisationContext(
|
|
|
31
31
|
|
|
32
32
|
try {
|
|
33
33
|
// Try to call the database function to set organisation context
|
|
34
|
-
const { error } = await supabase.rpc('
|
|
35
|
-
|
|
34
|
+
const { error } = await supabase.rpc('rbac_audit_log', {
|
|
35
|
+
p_event_type: 'organisation_switched',
|
|
36
|
+
p_organisation_id: organisationId,
|
|
37
|
+
p_metadata: { action: 'set_context' }
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
if (error) {
|
|
@@ -64,7 +66,10 @@ export async function clearOrganisationContext(
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
try {
|
|
67
|
-
const { error } = await supabase.rpc('
|
|
69
|
+
const { error } = await supabase.rpc('rbac_audit_log', {
|
|
70
|
+
p_event_type: 'organisation_switched',
|
|
71
|
+
p_metadata: { action: 'clear_context' }
|
|
72
|
+
});
|
|
68
73
|
|
|
69
74
|
if (error) {
|
|
70
75
|
// Silent fail - function not available
|
|
@@ -93,15 +98,23 @@ export async function getOrganisationContext(
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
try {
|
|
96
|
-
|
|
101
|
+
// For now, return null since we're not using database context
|
|
102
|
+
// This will be replaced with proper context management
|
|
103
|
+
const data = null;
|
|
104
|
+
const error = null;
|
|
97
105
|
|
|
98
106
|
if (error) {
|
|
99
107
|
// TODO: Replace with proper logging service integration
|
|
100
108
|
return null;
|
|
101
109
|
}
|
|
102
110
|
|
|
103
|
-
//
|
|
104
|
-
|
|
111
|
+
// Validate that data is a string (allow empty strings)
|
|
112
|
+
if (typeof data === 'string') {
|
|
113
|
+
return data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return null for invalid data formats
|
|
117
|
+
return null;
|
|
105
118
|
} catch (error) {
|
|
106
119
|
// TODO: Replace with proper logging service integration
|
|
107
120
|
return null;
|
|
@@ -39,7 +39,7 @@ class PerformanceBudgetMonitor {
|
|
|
39
39
|
|
|
40
40
|
// In production, this would send to a proper logging service
|
|
41
41
|
// For now, we'll log to console for testing purposes
|
|
42
|
-
if (
|
|
42
|
+
if (import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {
|
|
43
43
|
console.log('📊 Performance Metric: ' + metric + ' = ' + value, metadata);
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -72,7 +72,7 @@ class PerformanceBudgetMonitor {
|
|
|
72
72
|
|
|
73
73
|
// In production, this would send to a proper logging service
|
|
74
74
|
// For now, we'll log to console for testing purposes
|
|
75
|
-
if (
|
|
75
|
+
if (import.meta.env.MODE === 'development' || import.meta.env.MODE === 'test') {
|
|
76
76
|
if (budget.threshold === 'error') {
|
|
77
77
|
console.error('❌ Performance budget exceeded: ' + budget.metric + ' (' + actual + ' > ' + budget.budget + ')');
|
|
78
78
|
} else if (budget.threshold === 'warning') {
|