@jmruthers/pace-core 0.5.189 → 0.5.190
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
- package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
- package/dist/chunk-4QYC5L4K.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
- package/dist/chunk-73HSNNOQ.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
- package/dist/chunk-DZWK57KZ.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
- package/dist/chunk-HQVPB5MZ.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
- package/dist/chunk-J2XXC7R5.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
- package/dist/chunk-NIU6J6OX.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
- package/dist/chunk-RUYZKXOD.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
- package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
- package/dist/{chunk-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
- package/dist/chunk-VVBAW5A5.js.map +1 -0
- package/dist/chunk-Y4BUBBHD.js +614 -0
- package/dist/chunk-Y4BUBBHD.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +3 -4
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +10 -5
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +13 -11
- package/dist/index.js +79 -69
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +76 -12
- package/dist/rbac/index.js +12 -9
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +4 -4
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +21 -16
- package/docs/api/classes/StorageUtils.md +7 -4
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- 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/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.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/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.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/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.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/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +1 -1
- package/docs/api/interfaces/TextareaProps.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 +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.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/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +151 -92
- package/docs/api-reference/components.md +15 -7
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -3
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +9 -9
- package/src/__tests__/rls-policies.test.ts +197 -61
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +1 -0
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.tsx +5 -5
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.tsx +3 -3
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +2 -2
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +297 -34
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +21 -0
- package/src/hooks/useSecureDataAccess.test.ts +80 -35
- package/src/hooks/useSecureDataAccess.ts +80 -37
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +48 -2
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +37 -2
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/types/file-reference.ts +13 -10
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +61 -33
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
- package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -69,7 +69,7 @@ describe('usePublicEvent', () => {
|
|
|
69
69
|
Object.defineProperty(import.meta, 'env', {
|
|
70
70
|
value: {
|
|
71
71
|
VITE_SUPABASE_URL: 'https://test.supabase.co',
|
|
72
|
-
|
|
72
|
+
VITE_SUPABASE_PUBLISHABLE_KEY: 'test-publishable-key'
|
|
73
73
|
},
|
|
74
74
|
writable: true
|
|
75
75
|
});
|
|
@@ -577,5 +577,32 @@ describe('usePublicEvent', () => {
|
|
|
577
577
|
expect(result.current.event).toBe(null);
|
|
578
578
|
expect(result.current.error).toEqual(new Error('Event not found'));
|
|
579
579
|
});
|
|
580
|
+
|
|
581
|
+
it('warns when Supabase environment variables are missing', async () => {
|
|
582
|
+
vi.mocked(usePublicPageContext).mockReturnValue({
|
|
583
|
+
environment: { supabaseUrl: null, supabaseKey: null }
|
|
584
|
+
} as any);
|
|
585
|
+
|
|
586
|
+
Object.defineProperty(import.meta, 'env', {
|
|
587
|
+
value: {},
|
|
588
|
+
writable: true
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
592
|
+
const { createClient } = await import('@supabase/supabase-js');
|
|
593
|
+
vi.mocked(createClient).mockClear();
|
|
594
|
+
|
|
595
|
+
const { result } = renderHook(() => usePublicEvent('test-event'));
|
|
596
|
+
|
|
597
|
+
await waitFor(() => {
|
|
598
|
+
expect(result.current.isLoading).toBe(false);
|
|
599
|
+
}, { interval: 10 });
|
|
600
|
+
|
|
601
|
+
expect(result.current.error).toBeInstanceOf(Error);
|
|
602
|
+
expect(result.current.error?.message).toContain('Invalid event code or Supabase client not available');
|
|
603
|
+
expect(vi.mocked(createClient)).not.toHaveBeenCalled();
|
|
604
|
+
|
|
605
|
+
warnSpy.mockRestore();
|
|
606
|
+
});
|
|
580
607
|
});
|
|
581
608
|
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { renderHook, act } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
4
|
+
import { useQueryCache, queryCacheHelpers } from '../useQueryCache';
|
|
5
|
+
|
|
6
|
+
vi.mock('../../utils/core/logger', () => ({
|
|
7
|
+
createLogger: () => ({
|
|
8
|
+
debug: vi.fn(),
|
|
9
|
+
error: vi.fn(),
|
|
10
|
+
}),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe('useQueryCache', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
vi.useFakeTimers();
|
|
16
|
+
vi.setSystemTime(0);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
vi.useRealTimers();
|
|
21
|
+
vi.clearAllMocks();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const supabase = {} as SupabaseClient<any>;
|
|
25
|
+
|
|
26
|
+
it('caches query results and returns cached data', async () => {
|
|
27
|
+
const fetchFn = vi.fn().mockResolvedValue({ id: 1 });
|
|
28
|
+
const { result } = renderHook(() => useQueryCache(supabase));
|
|
29
|
+
|
|
30
|
+
const first = await result.current.getCachedQuery('table', 'id', '1', fetchFn, { ttl: 5 });
|
|
31
|
+
const second = await result.current.getCachedQuery('table', 'id', '1', fetchFn, { ttl: 5 });
|
|
32
|
+
|
|
33
|
+
expect(first).toEqual({ id: 1 });
|
|
34
|
+
expect(second).toEqual(first);
|
|
35
|
+
expect(fetchFn).toHaveBeenCalledTimes(1);
|
|
36
|
+
|
|
37
|
+
act(() => {
|
|
38
|
+
result.current.clearCache();
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('invalidates cache entries explicitly', async () => {
|
|
43
|
+
const fetchFn = vi.fn().mockResolvedValue({ id: 2 });
|
|
44
|
+
const { result } = renderHook(() => useQueryCache());
|
|
45
|
+
|
|
46
|
+
await result.current.getCachedQuery('table', 'id', '2', fetchFn);
|
|
47
|
+
act(() => {
|
|
48
|
+
result.current.invalidateQuery('table', 'id', '2');
|
|
49
|
+
});
|
|
50
|
+
await result.current.getCachedQuery('table', 'id', '2', fetchFn);
|
|
51
|
+
|
|
52
|
+
expect(fetchFn).toHaveBeenCalledTimes(2);
|
|
53
|
+
|
|
54
|
+
act(() => {
|
|
55
|
+
result.current.clearCache();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('expires cache entries after ttl', async () => {
|
|
60
|
+
const fetchFn = vi.fn().mockResolvedValue('value');
|
|
61
|
+
const { result } = renderHook(() => useQueryCache());
|
|
62
|
+
|
|
63
|
+
await result.current.getCachedQuery('table', 'key', 'val', fetchFn, { ttl: 1 });
|
|
64
|
+
|
|
65
|
+
vi.setSystemTime(2000); // advance past ttl
|
|
66
|
+
await result.current.getCachedQuery('table', 'key', 'val', fetchFn, { ttl: 1 });
|
|
67
|
+
|
|
68
|
+
expect(fetchFn).toHaveBeenCalledTimes(2);
|
|
69
|
+
|
|
70
|
+
act(() => {
|
|
71
|
+
result.current.clearCache();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('deduplicates in-flight requests', async () => {
|
|
76
|
+
let resolveFn: (value: string) => void;
|
|
77
|
+
const fetchPromise = new Promise<string>(resolve => {
|
|
78
|
+
resolveFn = resolve;
|
|
79
|
+
});
|
|
80
|
+
const fetchFn = vi.fn(() => fetchPromise);
|
|
81
|
+
const { result } = renderHook(() => useQueryCache());
|
|
82
|
+
|
|
83
|
+
const firstPromise = result.current.getCachedQuery('table', 'id', '3', fetchFn);
|
|
84
|
+
const secondPromise = result.current.getCachedQuery('table', 'id', '3', fetchFn);
|
|
85
|
+
|
|
86
|
+
expect(fetchFn).toHaveBeenCalledTimes(1);
|
|
87
|
+
|
|
88
|
+
resolveFn!('done');
|
|
89
|
+
|
|
90
|
+
await expect(firstPromise).resolves.toBe('done');
|
|
91
|
+
await expect(secondPromise).resolves.toBe('done');
|
|
92
|
+
|
|
93
|
+
act(() => {
|
|
94
|
+
result.current.clearCache();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('bypasses cache when disabled', async () => {
|
|
99
|
+
const fetchFn = vi.fn().mockResolvedValue('fresh');
|
|
100
|
+
const { result } = renderHook(() => useQueryCache());
|
|
101
|
+
|
|
102
|
+
await result.current.getCachedQuery('table', 'id', '4', fetchFn, { enabled: false });
|
|
103
|
+
await result.current.getCachedQuery('table', 'id', '4', fetchFn, { enabled: false });
|
|
104
|
+
|
|
105
|
+
expect(fetchFn).toHaveBeenCalledTimes(2);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('queryCacheHelpers', () => {
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
vi.useFakeTimers();
|
|
112
|
+
vi.setSystemTime(0);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
afterEach(() => {
|
|
116
|
+
vi.useRealTimers();
|
|
117
|
+
vi.clearAllMocks();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('caches helper queries and reuses promises for in-flight requests', async () => {
|
|
121
|
+
let resolveFn: (value: string) => void;
|
|
122
|
+
const promise = new Promise<string>(resolve => {
|
|
123
|
+
resolveFn = resolve;
|
|
124
|
+
});
|
|
125
|
+
const fetchFn = vi.fn(() => promise);
|
|
126
|
+
const supabase = {} as SupabaseClient<any>;
|
|
127
|
+
|
|
128
|
+
const firstPromise = queryCacheHelpers.pacePersonByUserId(supabase, 'user', fetchFn);
|
|
129
|
+
const secondPromise = queryCacheHelpers.pacePersonByUserId(supabase, 'user', fetchFn);
|
|
130
|
+
|
|
131
|
+
expect(fetchFn).toHaveBeenCalledTimes(1);
|
|
132
|
+
|
|
133
|
+
resolveFn!('person');
|
|
134
|
+
|
|
135
|
+
await expect(firstPromise).resolves.toBe('person');
|
|
136
|
+
await expect(secondPromise).resolves.toBe('person');
|
|
137
|
+
|
|
138
|
+
// Subsequent call should return cached data immediately
|
|
139
|
+
const cached = await queryCacheHelpers.pacePersonByUserId(supabase, 'user', fetchFn);
|
|
140
|
+
expect(cached).toBe('person');
|
|
141
|
+
expect(fetchFn).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
@@ -4,6 +4,8 @@ import { useSecureDataAccess } from '../useSecureDataAccess';
|
|
|
4
4
|
import { useUnifiedAuth } from '../../providers';
|
|
5
5
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
6
6
|
import { testDataGenerators } from '../../__tests__/helpers/test-utils';
|
|
7
|
+
import { useResolvedScope } from '../../rbac/hooks/useResolvedScope';
|
|
8
|
+
import { useSuperAdminBypass } from '../../rbac/hooks/useSuperAdminBypass';
|
|
7
9
|
|
|
8
10
|
// Mock dependencies
|
|
9
11
|
vi.mock('../../providers', () => ({
|
|
@@ -22,10 +24,20 @@ vi.mock('../../utils/context/organisationContext', () => ({
|
|
|
22
24
|
setOrganisationContext: vi.fn().mockResolvedValue(undefined),
|
|
23
25
|
}));
|
|
24
26
|
|
|
27
|
+
vi.mock('../../rbac/hooks/useResolvedScope', () => ({
|
|
28
|
+
useResolvedScope: vi.fn(),
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
vi.mock('../../rbac/hooks/useSuperAdminBypass', () => ({
|
|
32
|
+
useSuperAdminBypass: vi.fn(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
25
35
|
const mockUseUnifiedAuth = {
|
|
26
36
|
user: null,
|
|
27
37
|
session: null,
|
|
28
|
-
supabase: null
|
|
38
|
+
supabase: null,
|
|
39
|
+
selectedOrganisation: null,
|
|
40
|
+
selectedEvent: null,
|
|
29
41
|
};
|
|
30
42
|
|
|
31
43
|
const mockUseOrganisations = vi.fn(() => ({
|
|
@@ -54,7 +66,8 @@ const createAuthenticatedContext = (supabase = { auth: {} }, org = { id: 'org-12
|
|
|
54
66
|
...mockUseUnifiedAuth,
|
|
55
67
|
user: mockUser,
|
|
56
68
|
session: mockSession,
|
|
57
|
-
supabase
|
|
69
|
+
supabase,
|
|
70
|
+
selectedOrganisation: org,
|
|
58
71
|
} as any);
|
|
59
72
|
|
|
60
73
|
// Create a fresh mock with ensureOrganisationContext that returns the org
|
|
@@ -76,6 +89,17 @@ const createAuthenticatedContext = (supabase = { auth: {} }, org = { id: 'org-12
|
|
|
76
89
|
};
|
|
77
90
|
|
|
78
91
|
vi.mocked(useOrganisations).mockReturnValue(mockOrgs as any);
|
|
92
|
+
|
|
93
|
+
// Mock resolved scope with organisationId
|
|
94
|
+
vi.mocked(useResolvedScope).mockReturnValue({
|
|
95
|
+
resolvedScope: {
|
|
96
|
+
organisationId: org.id,
|
|
97
|
+
eventId: undefined,
|
|
98
|
+
appId: undefined,
|
|
99
|
+
},
|
|
100
|
+
isLoading: false,
|
|
101
|
+
error: null,
|
|
102
|
+
});
|
|
79
103
|
};
|
|
80
104
|
|
|
81
105
|
describe('useSecureDataAccess', () => {
|
|
@@ -84,6 +108,16 @@ describe('useSecureDataAccess', () => {
|
|
|
84
108
|
vi.mocked(useUnifiedAuth).mockReturnValue(mockUseUnifiedAuth as any);
|
|
85
109
|
// Set up useOrganisations to return the default mock (which throws for org context)
|
|
86
110
|
vi.mocked(useOrganisations).mockReturnValue(mockUseOrganisations() as any);
|
|
111
|
+
// Default mock for useResolvedScope - returns null scope (no context)
|
|
112
|
+
vi.mocked(useResolvedScope).mockReturnValue({
|
|
113
|
+
resolvedScope: null,
|
|
114
|
+
isLoading: false,
|
|
115
|
+
error: null,
|
|
116
|
+
});
|
|
117
|
+
// Default mock for useSuperAdminBypass - not super admin
|
|
118
|
+
vi.mocked(useSuperAdminBypass).mockReturnValue({
|
|
119
|
+
isSuperAdmin: false,
|
|
120
|
+
});
|
|
87
121
|
});
|
|
88
122
|
|
|
89
123
|
describe('validateContext', () => {
|
|
@@ -129,21 +163,23 @@ describe('useSecureDataAccess', () => {
|
|
|
129
163
|
user: mockUser,
|
|
130
164
|
session: mockSession,
|
|
131
165
|
supabase: mockSupabase,
|
|
166
|
+
selectedOrganisation: { id: 'org-123' },
|
|
132
167
|
} as any);
|
|
133
168
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
169
|
+
// Mock useResolvedScope to return resolved scope with organisationId
|
|
170
|
+
vi.mocked(useResolvedScope).mockReturnValue({
|
|
171
|
+
resolvedScope: {
|
|
172
|
+
organisationId: 'org-123',
|
|
173
|
+
eventId: undefined,
|
|
174
|
+
appId: undefined,
|
|
175
|
+
},
|
|
176
|
+
isLoading: false,
|
|
177
|
+
error: null,
|
|
178
|
+
});
|
|
142
179
|
|
|
143
180
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
144
181
|
|
|
145
182
|
expect(() => result.current.validateContext()).not.toThrow();
|
|
146
|
-
expect(ensureOrgContextMock).toHaveBeenCalled();
|
|
147
183
|
});
|
|
148
184
|
});
|
|
149
185
|
|
|
@@ -158,13 +194,19 @@ describe('useSecureDataAccess', () => {
|
|
|
158
194
|
user: mockUser,
|
|
159
195
|
session: mockSession,
|
|
160
196
|
supabase: mockSupabase,
|
|
197
|
+
selectedOrganisation: { id: 'org-123' },
|
|
161
198
|
} as any);
|
|
162
199
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
// Mock resolved scope with organisationId
|
|
201
|
+
vi.mocked(useResolvedScope).mockReturnValue({
|
|
202
|
+
resolvedScope: {
|
|
203
|
+
organisationId: 'org-123',
|
|
204
|
+
eventId: undefined,
|
|
205
|
+
appId: undefined,
|
|
206
|
+
},
|
|
207
|
+
isLoading: false,
|
|
208
|
+
error: null,
|
|
209
|
+
});
|
|
168
210
|
|
|
169
211
|
const { result } = renderHook(() => useSecureDataAccess());
|
|
170
212
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -50,7 +50,7 @@ export { useComponentPerformance } from './useComponentPerformance';
|
|
|
50
50
|
export { useAppConfig } from './useAppConfig';
|
|
51
51
|
export type { UseAppConfigReturn } from './useAppConfig';
|
|
52
52
|
export { usePerformanceMonitor } from './usePerformanceMonitor';
|
|
53
|
-
export { useQueryCache, queryCacheHelpers } from './useQueryCache';
|
|
53
|
+
export { useQueryCache, queryCacheHelpers, cleanupQueryCache } from './useQueryCache';
|
|
54
54
|
export type { UseQueryCacheReturn, UseQueryCacheOptions } from './useQueryCache';
|
|
55
55
|
|
|
56
56
|
// DataTable performance hook
|
|
@@ -120,7 +120,7 @@ export function usePublicEvent(
|
|
|
120
120
|
// Fallback to direct environment variable access if not in PublicPageProvider
|
|
121
121
|
environment = {
|
|
122
122
|
supabaseUrl: (import.meta as any).env?.VITE_SUPABASE_URL || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_URL || null,
|
|
123
|
-
supabaseKey: (import.meta as any).env?.
|
|
123
|
+
supabaseKey: (import.meta as any).env?.VITE_SUPABASE_PUBLISHABLE_KEY || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || null
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
@@ -129,7 +129,7 @@ export function usePublicEvent(
|
|
|
129
129
|
if (typeof window === 'undefined') return null;
|
|
130
130
|
|
|
131
131
|
if (!environment.supabaseUrl || !environment.supabaseKey) {
|
|
132
|
-
logger.warn('usePublicEvent', 'Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and
|
|
132
|
+
logger.warn('usePublicEvent', 'Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.');
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
|
|
@@ -109,7 +109,7 @@ export function usePublicFileDisplay(
|
|
|
109
109
|
const [error, setError] = useState<Error | null>(null);
|
|
110
110
|
|
|
111
111
|
const fetchFiles = useCallback(async (): Promise<void> => {
|
|
112
|
-
if (!table_name || !record_id || !
|
|
112
|
+
if (!table_name || !record_id || !supabase) {
|
|
113
113
|
setFileUrl(null);
|
|
114
114
|
setFileReference(null);
|
|
115
115
|
setFileReferences([]);
|
|
@@ -119,14 +119,17 @@ export function usePublicFileDisplay(
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
// Validate UUID format for organisationId to prevent database errors
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
122
|
+
// Validate UUID format for organisationId to prevent database errors (only if provided)
|
|
123
|
+
if (organisation_id) {
|
|
124
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
125
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
126
|
+
logger.warn('usePublicFileDisplay', 'Invalid organisationId format (not a valid UUID)', { organisation_id });
|
|
127
|
+
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
130
|
// Check cache first
|
|
129
|
-
|
|
131
|
+
// When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null
|
|
132
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;
|
|
130
133
|
if (enableCache) {
|
|
131
134
|
const cached = publicFileCache.get(cacheKey);
|
|
132
135
|
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
@@ -148,103 +151,186 @@ export function usePublicFileDisplay(
|
|
|
148
151
|
|
|
149
152
|
let files: any[] = [];
|
|
150
153
|
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
p_organisation_id: organisation_id
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
const { data, error: rpcError } = await (supabase as any)
|
|
163
|
-
.rpc('data_file_reference_by_category_list', rpcParams);
|
|
164
|
-
|
|
165
|
-
if (rpcError) {
|
|
166
|
-
logger.error('usePublicFileDisplay', 'RPC function error', {
|
|
167
|
-
function: 'data_file_reference_by_category_list',
|
|
168
|
-
table_name,
|
|
169
|
-
record_id,
|
|
170
|
-
category,
|
|
171
|
-
organisation_id,
|
|
172
|
-
error: rpcError.message,
|
|
173
|
-
errorCode: rpcError.code,
|
|
174
|
-
errorDetails: rpcError.details,
|
|
175
|
-
errorHint: rpcError.hint
|
|
176
|
-
});
|
|
177
|
-
throw new Error(rpcError.message || 'Failed to fetch file reference');
|
|
178
|
-
}
|
|
154
|
+
// When organisation_id is undefined, search both user-scoped (null) and organisation-scoped files
|
|
155
|
+
// This allows FileDisplay to work without requiring the organisation_id prop
|
|
156
|
+
if (organisation_id === undefined) {
|
|
157
|
+
logger.debug('usePublicFileDisplay', 'organisation_id is undefined, searching both user-scoped and organisation-scoped files:', {
|
|
158
|
+
table_name,
|
|
159
|
+
record_id,
|
|
160
|
+
category
|
|
161
|
+
});
|
|
179
162
|
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
})
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
163
|
+
// First, try user-scoped files (organisation_id = null)
|
|
164
|
+
let userScopedFiles: any[] = [];
|
|
165
|
+
let orgScopedFiles: any[] = [];
|
|
166
|
+
|
|
167
|
+
// Query user-scoped files
|
|
168
|
+
if (category) {
|
|
169
|
+
const { data: userData, error: userRpcError } = await (supabase as any)
|
|
170
|
+
.rpc('data_file_reference_by_category_list', {
|
|
171
|
+
p_table_name: table_name,
|
|
172
|
+
p_record_id: record_id,
|
|
173
|
+
p_category: category,
|
|
174
|
+
p_organisation_id: null
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (!userRpcError && userData) {
|
|
178
|
+
userScopedFiles = userData
|
|
179
|
+
.filter((item: any) => item.is_public === true && item.id && item.file_path && item.file_metadata)
|
|
180
|
+
.map((item: any) => ({
|
|
196
181
|
id: item.id,
|
|
197
182
|
table_name: table_name,
|
|
198
183
|
record_id: record_id,
|
|
199
184
|
file_path: item.file_path,
|
|
200
185
|
file_metadata: item.file_metadata || {},
|
|
201
|
-
organisation_id:
|
|
186
|
+
organisation_id: null,
|
|
202
187
|
app_id: item.file_metadata?.app_id || null,
|
|
203
|
-
is_public: true,
|
|
188
|
+
is_public: true,
|
|
204
189
|
created_at: item.created_at || new Date().toISOString(),
|
|
205
190
|
updated_at: item.created_at || new Date().toISOString()
|
|
206
|
-
};
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
const { data: userData, error: userRpcError } = await (supabase as any)
|
|
195
|
+
.rpc('data_file_reference_list', {
|
|
196
|
+
p_table_name: table_name,
|
|
197
|
+
p_record_id: record_id,
|
|
198
|
+
p_organisation_id: null
|
|
207
199
|
});
|
|
200
|
+
|
|
201
|
+
if (!userRpcError && userData) {
|
|
202
|
+
const ids = userData.map((item: any) => item.id);
|
|
203
|
+
if (ids.length > 0) {
|
|
204
|
+
const { data: fullData } = await supabase
|
|
205
|
+
.from('file_references')
|
|
206
|
+
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
207
|
+
.in('id', ids)
|
|
208
|
+
.eq('is_public', true);
|
|
209
|
+
userScopedFiles = fullData || [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
208
212
|
}
|
|
213
|
+
|
|
214
|
+
// For public pages, we can't query user's organisations (user is anonymous)
|
|
215
|
+
// But we can try to find organisation-scoped files by querying the record's context
|
|
216
|
+
// For now, we'll just use user-scoped files. If needed, the page context can provide organisation_id
|
|
217
|
+
// Note: This is a limitation of public pages - they should provide organisation_id if needed
|
|
218
|
+
|
|
219
|
+
// Merge results
|
|
220
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
221
|
+
allFiles.sort((a, b) => {
|
|
222
|
+
const aTime = new Date(a.created_at).getTime();
|
|
223
|
+
const bTime = new Date(b.created_at).getTime();
|
|
224
|
+
return bTime - aTime;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
files = allFiles;
|
|
228
|
+
|
|
229
|
+
logger.debug('usePublicFileDisplay', 'Found files with undefined organisation_id:', {
|
|
230
|
+
userScopedCount: userScopedFiles.length,
|
|
231
|
+
orgScopedCount: orgScopedFiles.length,
|
|
232
|
+
totalCount: files.length
|
|
233
|
+
});
|
|
209
234
|
} else {
|
|
210
|
-
//
|
|
211
|
-
|
|
212
|
-
|
|
235
|
+
// organisation_id is provided (or explicitly null) - use normal query
|
|
236
|
+
// CRITICAL: When category is provided, MUST use RPC function, not direct queries
|
|
237
|
+
// Category is stored in file_metadata JSONB field, not a direct column
|
|
238
|
+
if (category) {
|
|
239
|
+
// Single file mode - use RPC to get files by category
|
|
240
|
+
const rpcParams = {
|
|
213
241
|
p_table_name: table_name,
|
|
214
242
|
p_record_id: record_id,
|
|
215
|
-
|
|
216
|
-
|
|
243
|
+
p_category: category,
|
|
244
|
+
p_organisation_id: organisation_id ?? null
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const { data, error: rpcError } = await (supabase as any)
|
|
248
|
+
.rpc('data_file_reference_by_category_list', rpcParams);
|
|
217
249
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
250
|
+
if (rpcError) {
|
|
251
|
+
logger.error('usePublicFileDisplay', 'RPC function error', {
|
|
252
|
+
function: 'data_file_reference_by_category_list',
|
|
253
|
+
table_name,
|
|
254
|
+
record_id,
|
|
255
|
+
category,
|
|
256
|
+
organisation_id,
|
|
257
|
+
error: rpcError.message,
|
|
258
|
+
errorCode: rpcError.code,
|
|
259
|
+
errorDetails: rpcError.details,
|
|
260
|
+
errorHint: rpcError.hint
|
|
261
|
+
});
|
|
262
|
+
throw new Error(rpcError.message || 'Failed to fetch file reference');
|
|
263
|
+
}
|
|
231
264
|
|
|
232
|
-
|
|
233
|
-
|
|
265
|
+
// RPC returns partial data with: id, file_path, file_metadata, is_public, created_at
|
|
266
|
+
// We have table_name, record_id, organisation_id from function parameters
|
|
267
|
+
// We can construct FileReference objects directly without another query (avoiding RLS issues)
|
|
268
|
+
if (!data || data.length === 0) {
|
|
269
|
+
files = [];
|
|
270
|
+
} else {
|
|
271
|
+
// Construct file reference objects from RPC response
|
|
272
|
+
// This avoids RLS issues - the RPC already validated permissions and filtered public files
|
|
273
|
+
files = data
|
|
274
|
+
.filter((item: any) => {
|
|
275
|
+
// RPC should only return public files for public context, but verify
|
|
276
|
+
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
277
|
+
})
|
|
278
|
+
.map((item: any) => {
|
|
279
|
+
// Construct complete file reference from RPC response + function parameters
|
|
280
|
+
return {
|
|
281
|
+
id: item.id,
|
|
282
|
+
table_name: table_name,
|
|
283
|
+
record_id: record_id,
|
|
284
|
+
file_path: item.file_path,
|
|
285
|
+
file_metadata: item.file_metadata || {},
|
|
286
|
+
organisation_id: organisation_id ?? null,
|
|
287
|
+
app_id: item.file_metadata?.app_id || null,
|
|
288
|
+
is_public: true, // RPC already filtered for public files
|
|
289
|
+
created_at: item.created_at || new Date().toISOString(),
|
|
290
|
+
updated_at: item.created_at || new Date().toISOString()
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
}
|
|
234
294
|
} else {
|
|
235
|
-
//
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if (
|
|
244
|
-
|
|
295
|
+
// Multiple files mode - use RPC to get all files
|
|
296
|
+
const { data: fileIds, error: rpcError } = await (supabase as any)
|
|
297
|
+
.rpc('data_file_reference_list', {
|
|
298
|
+
p_table_name: table_name,
|
|
299
|
+
p_record_id: record_id,
|
|
300
|
+
p_organisation_id: organisation_id ?? null
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (rpcError) {
|
|
304
|
+
logger.error('usePublicFileDisplay', 'RPC function error', {
|
|
305
|
+
function: 'data_file_reference_list',
|
|
306
|
+
table_name,
|
|
307
|
+
record_id,
|
|
308
|
+
organisation_id,
|
|
309
|
+
error: rpcError.message,
|
|
310
|
+
errorCode: rpcError.code,
|
|
311
|
+
errorDetails: rpcError.details,
|
|
312
|
+
errorHint: rpcError.hint
|
|
313
|
+
});
|
|
314
|
+
throw new Error(rpcError.message || 'Failed to fetch file references');
|
|
245
315
|
}
|
|
246
316
|
|
|
247
|
-
|
|
317
|
+
if (!fileIds || fileIds.length === 0) {
|
|
318
|
+
files = [];
|
|
319
|
+
} else {
|
|
320
|
+
// Fetch full file reference data for each ID, but only public files
|
|
321
|
+
const ids = fileIds.map((item: any) => item.id);
|
|
322
|
+
const { data: fullData, error: fetchError } = await supabase
|
|
323
|
+
.from('file_references')
|
|
324
|
+
.select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')
|
|
325
|
+
.in('id', ids)
|
|
326
|
+
.eq('is_public', true); // Only public files in public context
|
|
327
|
+
|
|
328
|
+
if (fetchError) {
|
|
329
|
+
throw new Error(fetchError.message || 'Failed to fetch file references');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
files = fullData || [];
|
|
333
|
+
}
|
|
248
334
|
}
|
|
249
335
|
}
|
|
250
336
|
|
|
@@ -353,7 +439,7 @@ export function usePublicFileDisplay(
|
|
|
353
439
|
|
|
354
440
|
// Fetch files when parameters change
|
|
355
441
|
useEffect(() => {
|
|
356
|
-
if (table_name && record_id
|
|
442
|
+
if (table_name && record_id) {
|
|
357
443
|
fetchFiles();
|
|
358
444
|
} else {
|
|
359
445
|
setFileUrl(null);
|
|
@@ -367,11 +453,11 @@ export function usePublicFileDisplay(
|
|
|
367
453
|
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
368
454
|
|
|
369
455
|
const refetch = useCallback(async (): Promise<void> => {
|
|
370
|
-
if (!table_name || !record_id
|
|
456
|
+
if (!table_name || !record_id) return;
|
|
371
457
|
|
|
372
458
|
// Clear cache for this file
|
|
373
459
|
if (enableCache) {
|
|
374
|
-
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || 'all'}`;
|
|
460
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;
|
|
375
461
|
publicFileCache.delete(cacheKey);
|
|
376
462
|
}
|
|
377
463
|
await fetchFiles();
|