@jmruthers/pace-core 0.5.188 → 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-DrLDztHt.d.ts → PublicPageProvider-C4uxosp6.d.ts} +129 -40
- 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-UNOTYLQF.js → chunk-NIU6J6OX.js} +772 -725
- 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-IPCH26AG.js → chunk-STYK4OH2.js} +11 -11
- package/dist/chunk-STYK4OH2.js.map +1 -0
- package/dist/{chunk-EFCLXK7F.js → chunk-VVBAW5A5.js} +4201 -3809
- 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 -5
- package/dist/components.js +19 -23
- 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 -12
- package/dist/index.js +79 -73
- 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 +128 -0
- 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 +155 -135
- package/docs/api-reference/components.md +72 -29
- 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 -4
- 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 +252 -226
- package/src/components/Avatar/Avatar.tsx +179 -53
- package/src/components/Avatar/index.ts +1 -1
- 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.test.tsx +7 -9
- package/src/components/UserMenu/UserMenu.tsx +10 -8
- package/src/components/index.ts +2 -1
- 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/index.ts +2 -1
- 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-EFCLXK7F.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-IPCH26AG.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UNOTYLQF.js.map +0 -1
- package/dist/chunk-VGZZXKBR.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
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context Validator for RBAC
|
|
3
|
+
* @package @jmruthers/pace-core
|
|
4
|
+
* @module RBAC/ContextValidator
|
|
5
|
+
* @since 1.0.0
|
|
6
|
+
*
|
|
7
|
+
* Centralized validation for RBAC context requirements based on app configuration.
|
|
8
|
+
* Enforces app-specific context rules with single primary context:
|
|
9
|
+
* - requires_event = TRUE: Event is PRIMARY context, org derived from event (org not required in input)
|
|
10
|
+
* - requires_event = FALSE: Organisation is PRIMARY context, event optional
|
|
11
|
+
* - PORTAL/ADMIN apps: Both contexts optional (allows users to view/edit own profiles, super admin access)
|
|
12
|
+
*
|
|
13
|
+
* Key principle: Only one primary context is required based on app config. The other is derived or optional.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
17
|
+
import { Database } from '../../types/database';
|
|
18
|
+
import { UUID, Scope } from '../types';
|
|
19
|
+
import { EventContextRequiredError, OrganisationContextRequiredError } from '../types';
|
|
20
|
+
import { getOrganisationFromEvent } from './eventContext';
|
|
21
|
+
import { createLogger } from '../../utils/core/logger';
|
|
22
|
+
|
|
23
|
+
const log = createLogger('ContextValidator');
|
|
24
|
+
|
|
25
|
+
export interface AppConfig {
|
|
26
|
+
requires_event: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if an app allows optional contexts (both organisation and event optional)
|
|
31
|
+
* @param appName - App name to check
|
|
32
|
+
* @returns True if app allows optional contexts
|
|
33
|
+
*/
|
|
34
|
+
function allowsOptionalContexts(appName?: string): boolean {
|
|
35
|
+
return appName === 'PORTAL' || appName === 'ADMIN';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ContextValidationResult {
|
|
39
|
+
isValid: boolean;
|
|
40
|
+
resolvedScope: Scope | null;
|
|
41
|
+
error: Error | null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Context Validator class
|
|
46
|
+
*
|
|
47
|
+
* Validates and resolves RBAC scope based on app configuration requirements.
|
|
48
|
+
*/
|
|
49
|
+
export class ContextValidator {
|
|
50
|
+
/**
|
|
51
|
+
* Validate scope against app requirements
|
|
52
|
+
*
|
|
53
|
+
* @param scope - Current scope
|
|
54
|
+
* @param appConfig - App configuration (requires_event flag)
|
|
55
|
+
* @param appName - App name (for PORTAL/ADMIN special case)
|
|
56
|
+
* @returns Validation result with resolved scope
|
|
57
|
+
*/
|
|
58
|
+
static async validateScope(
|
|
59
|
+
scope: Scope,
|
|
60
|
+
appConfig: AppConfig | null,
|
|
61
|
+
appName?: string
|
|
62
|
+
): Promise<ContextValidationResult> {
|
|
63
|
+
// PORTAL/ADMIN special case: both contexts optional
|
|
64
|
+
if (allowsOptionalContexts(appName)) {
|
|
65
|
+
return {
|
|
66
|
+
isValid: true,
|
|
67
|
+
resolvedScope: {
|
|
68
|
+
organisationId: scope.organisationId,
|
|
69
|
+
eventId: scope.eventId,
|
|
70
|
+
appId: scope.appId
|
|
71
|
+
},
|
|
72
|
+
error: null
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// If no app config, default to requiring org context
|
|
77
|
+
if (!appConfig) {
|
|
78
|
+
if (!scope.organisationId) {
|
|
79
|
+
return {
|
|
80
|
+
isValid: false,
|
|
81
|
+
resolvedScope: null,
|
|
82
|
+
error: new OrganisationContextRequiredError()
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
isValid: true,
|
|
87
|
+
resolvedScope: scope,
|
|
88
|
+
error: null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Event-required apps: must have eventId, derive org from event
|
|
93
|
+
if (appConfig.requires_event) {
|
|
94
|
+
if (!scope.eventId) {
|
|
95
|
+
return {
|
|
96
|
+
isValid: false,
|
|
97
|
+
resolvedScope: null,
|
|
98
|
+
error: new EventContextRequiredError()
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// If org is not provided, we'll need to derive it from event
|
|
103
|
+
// But for validation, we just check that eventId exists
|
|
104
|
+
// The actual derivation happens in resolveRequiredContext
|
|
105
|
+
return {
|
|
106
|
+
isValid: true,
|
|
107
|
+
resolvedScope: scope,
|
|
108
|
+
error: null
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Org-required apps: must have organisationId, eventId optional
|
|
113
|
+
if (!scope.organisationId) {
|
|
114
|
+
return {
|
|
115
|
+
isValid: false,
|
|
116
|
+
resolvedScope: null,
|
|
117
|
+
error: new OrganisationContextRequiredError()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
isValid: true,
|
|
123
|
+
resolvedScope: scope,
|
|
124
|
+
error: null
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Resolve required context and derive missing values
|
|
130
|
+
*
|
|
131
|
+
* @param scope - Current scope
|
|
132
|
+
* @param appConfig - App configuration
|
|
133
|
+
* @param appName - App name (for PORTAL/ADMIN special case)
|
|
134
|
+
* @param supabase - Supabase client (for deriving org from event)
|
|
135
|
+
* @returns Resolved scope with all required context
|
|
136
|
+
*/
|
|
137
|
+
static async resolveRequiredContext(
|
|
138
|
+
scope: Scope,
|
|
139
|
+
appConfig: AppConfig | null,
|
|
140
|
+
appName?: string,
|
|
141
|
+
supabase?: SupabaseClient<Database> | null
|
|
142
|
+
): Promise<ContextValidationResult> {
|
|
143
|
+
// PORTAL/ADMIN special case: both contexts optional
|
|
144
|
+
if (allowsOptionalContexts(appName)) {
|
|
145
|
+
return {
|
|
146
|
+
isValid: true,
|
|
147
|
+
resolvedScope: {
|
|
148
|
+
organisationId: scope.organisationId,
|
|
149
|
+
eventId: scope.eventId,
|
|
150
|
+
appId: scope.appId
|
|
151
|
+
},
|
|
152
|
+
error: null
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If no app config, default to requiring org context
|
|
157
|
+
if (!appConfig) {
|
|
158
|
+
if (!scope.organisationId) {
|
|
159
|
+
return {
|
|
160
|
+
isValid: false,
|
|
161
|
+
resolvedScope: null,
|
|
162
|
+
error: new OrganisationContextRequiredError()
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
isValid: true,
|
|
167
|
+
resolvedScope: scope,
|
|
168
|
+
error: null
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Event-required apps: must have eventId, derive org from event
|
|
173
|
+
if (appConfig.requires_event) {
|
|
174
|
+
if (!scope.eventId) {
|
|
175
|
+
return {
|
|
176
|
+
isValid: false,
|
|
177
|
+
resolvedScope: null,
|
|
178
|
+
error: new EventContextRequiredError()
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Derive organisationId from event if not provided
|
|
183
|
+
let organisationId: UUID | undefined = scope.organisationId;
|
|
184
|
+
if (!organisationId && supabase && scope.eventId) {
|
|
185
|
+
try {
|
|
186
|
+
const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);
|
|
187
|
+
organisationId = derivedOrgId || undefined;
|
|
188
|
+
if (!organisationId) {
|
|
189
|
+
return {
|
|
190
|
+
isValid: false,
|
|
191
|
+
resolvedScope: null,
|
|
192
|
+
error: new Error('Could not resolve organisation from event context')
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
} catch (error) {
|
|
196
|
+
log.error('Failed to derive org from event:', error);
|
|
197
|
+
return {
|
|
198
|
+
isValid: false,
|
|
199
|
+
resolvedScope: null,
|
|
200
|
+
error: error instanceof Error ? error : new Error('Failed to derive organisation from event')
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
} else if (!organisationId) {
|
|
204
|
+
return {
|
|
205
|
+
isValid: false,
|
|
206
|
+
resolvedScope: null,
|
|
207
|
+
error: new Error('Event context requires organisationId but it could not be derived (supabase client not available)')
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
isValid: true,
|
|
213
|
+
resolvedScope: {
|
|
214
|
+
organisationId,
|
|
215
|
+
eventId: scope.eventId,
|
|
216
|
+
appId: scope.appId
|
|
217
|
+
},
|
|
218
|
+
error: null
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Org-required apps: must have organisationId, eventId optional
|
|
223
|
+
if (!scope.organisationId) {
|
|
224
|
+
return {
|
|
225
|
+
isValid: false,
|
|
226
|
+
resolvedScope: null,
|
|
227
|
+
error: new OrganisationContextRequiredError()
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
isValid: true,
|
|
233
|
+
resolvedScope: scope,
|
|
234
|
+
error: null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Derive organisation ID from event ID
|
|
240
|
+
*
|
|
241
|
+
* @param supabase - Supabase client
|
|
242
|
+
* @param eventId - Event ID
|
|
243
|
+
* @returns Organisation ID or null
|
|
244
|
+
*/
|
|
245
|
+
static async deriveOrgFromEvent(
|
|
246
|
+
supabase: SupabaseClient<Database>,
|
|
247
|
+
eventId: string
|
|
248
|
+
): Promise<UUID | null> {
|
|
249
|
+
return getOrganisationFromEvent(supabase, eventId);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Check if context is ready for permission checks
|
|
254
|
+
*
|
|
255
|
+
* @param scope - Current scope
|
|
256
|
+
* @param appConfig - App configuration
|
|
257
|
+
* @param appName - App name
|
|
258
|
+
* @param hasSelectedEvent - Whether event is selected
|
|
259
|
+
* @param hasSelectedOrganisation - Whether organisation is selected
|
|
260
|
+
* @returns True if context is ready
|
|
261
|
+
*/
|
|
262
|
+
static isContextReady(
|
|
263
|
+
scope: Scope,
|
|
264
|
+
appConfig: AppConfig | null,
|
|
265
|
+
appName?: string,
|
|
266
|
+
hasSelectedEvent?: boolean,
|
|
267
|
+
hasSelectedOrganisation?: boolean
|
|
268
|
+
): boolean {
|
|
269
|
+
// PORTAL/ADMIN special case: context is always ready
|
|
270
|
+
if (allowsOptionalContexts(appName)) {
|
|
271
|
+
return true;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// If no app config, default to requiring org context
|
|
275
|
+
if (!appConfig) {
|
|
276
|
+
return !!hasSelectedOrganisation || !!scope.organisationId;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Event-required apps: need event context
|
|
280
|
+
if (appConfig.requires_event) {
|
|
281
|
+
return !!hasSelectedEvent || !!scope.eventId;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Org-required apps: need org context
|
|
285
|
+
return !!hasSelectedOrganisation || !!scope.organisationId;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
@@ -12,9 +12,35 @@ import { SupabaseClient } from '@supabase/supabase-js';
|
|
|
12
12
|
import { Database } from '../../types/database';
|
|
13
13
|
import { UUID, Scope } from '../types';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Cache for organisation derivation from event
|
|
17
|
+
* Key: eventId, Value: organisationId | null
|
|
18
|
+
* Maximum cache size to prevent memory leaks
|
|
19
|
+
*/
|
|
20
|
+
const orgDerivationCache = new Map<string, UUID | null>();
|
|
21
|
+
const MAX_CACHE_SIZE = 100; // Limit cache to 100 entries
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Clear cache entry for a specific event (useful if event's org changes)
|
|
25
|
+
* @param eventId - Event ID to clear from cache
|
|
26
|
+
*/
|
|
27
|
+
export function clearOrgDerivationCache(eventId: string): void {
|
|
28
|
+
orgDerivationCache.delete(eventId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Clear all cached organisation derivations
|
|
33
|
+
*/
|
|
34
|
+
export function clearAllOrgDerivationCache(): void {
|
|
35
|
+
orgDerivationCache.clear();
|
|
36
|
+
}
|
|
37
|
+
|
|
15
38
|
/**
|
|
16
39
|
* Get organization ID from event ID
|
|
17
40
|
*
|
|
41
|
+
* Uses caching to avoid repeated database queries for the same event.
|
|
42
|
+
* Cache is limited to prevent memory leaks.
|
|
43
|
+
*
|
|
18
44
|
* @param supabase - Supabase client
|
|
19
45
|
* @param eventId - Event ID
|
|
20
46
|
* @returns Promise resolving to organization ID or null
|
|
@@ -23,17 +49,37 @@ export async function getOrganisationFromEvent(
|
|
|
23
49
|
supabase: SupabaseClient<Database>,
|
|
24
50
|
eventId: string
|
|
25
51
|
): Promise<UUID | null> {
|
|
52
|
+
// Check cache first
|
|
53
|
+
if (orgDerivationCache.has(eventId)) {
|
|
54
|
+
return orgDerivationCache.get(eventId) ?? null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Query database
|
|
26
58
|
const { data, error } = await supabase
|
|
27
59
|
.from('event')
|
|
28
60
|
.select('organisation_id')
|
|
29
61
|
.eq('event_id', eventId)
|
|
30
62
|
.single() as { data: { organisation_id: string } | null; error: any };
|
|
31
63
|
|
|
64
|
+
let organisationId: UUID | null = null;
|
|
65
|
+
|
|
32
66
|
if (error || !data) {
|
|
33
|
-
|
|
67
|
+
organisationId = null;
|
|
68
|
+
} else {
|
|
69
|
+
organisationId = data.organisation_id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Cache the result (with size limit to prevent memory leaks)
|
|
73
|
+
if (orgDerivationCache.size >= MAX_CACHE_SIZE) {
|
|
74
|
+
// Remove oldest entry (first key in Map)
|
|
75
|
+
const firstKey = orgDerivationCache.keys().next().value;
|
|
76
|
+
if (firstKey) {
|
|
77
|
+
orgDerivationCache.delete(firstKey);
|
|
78
|
+
}
|
|
34
79
|
}
|
|
80
|
+
orgDerivationCache.set(eventId, organisationId);
|
|
35
81
|
|
|
36
|
-
return
|
|
82
|
+
return organisationId;
|
|
37
83
|
}
|
|
38
84
|
|
|
39
85
|
/**
|
|
@@ -16,6 +16,9 @@ import { Organisation } from '../types/organisation';
|
|
|
16
16
|
import { assertOrganisationId } from '../types/core';
|
|
17
17
|
import { logger } from '../utils/core/logger';
|
|
18
18
|
import { secureStorage } from '../utils/security/secureStorage';
|
|
19
|
+
import { isSuperAdmin, getAppConfigByName } from '../rbac/api';
|
|
20
|
+
import type { UUID } from '../rbac/types';
|
|
21
|
+
import type { AppConfig } from '../rbac/utils/contextValidator';
|
|
19
22
|
|
|
20
23
|
export class EventService extends BaseService implements IEventService {
|
|
21
24
|
private events: Event[] = [];
|
|
@@ -30,6 +33,8 @@ export class EventService extends BaseService implements IEventService {
|
|
|
30
33
|
private appName: string = '';
|
|
31
34
|
private selectedOrganisation: Organisation | null = null;
|
|
32
35
|
private setSelectedEventId: ((eventId: string | null) => void) | null = null;
|
|
36
|
+
private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
|
|
37
|
+
private appConfig: AppConfig | null = null; // Cache app config to avoid repeated lookups
|
|
33
38
|
|
|
34
39
|
// Internal state management
|
|
35
40
|
private isInitializedRef = false;
|
|
@@ -76,6 +81,7 @@ export class EventService extends BaseService implements IEventService {
|
|
|
76
81
|
const newOrgId = selectedOrganisation?.id;
|
|
77
82
|
const previousUserId = this.user?.id || null;
|
|
78
83
|
const newUserId = user?.id || null;
|
|
84
|
+
const previousAppName = this.appName;
|
|
79
85
|
|
|
80
86
|
// If user changed, clear previous user's event selection from storage
|
|
81
87
|
if (previousUserId !== newUserId) {
|
|
@@ -87,6 +93,10 @@ export class EventService extends BaseService implements IEventService {
|
|
|
87
93
|
this.selectedEvent = null;
|
|
88
94
|
this.setSelectedEventId?.(null);
|
|
89
95
|
}
|
|
96
|
+
// Reset initialization when user changes
|
|
97
|
+
this.resetInitialization();
|
|
98
|
+
this.isInitializedRef = false;
|
|
99
|
+
this.isFetchingRef = false;
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
this.supabaseClient = supabaseClient;
|
|
@@ -96,8 +106,31 @@ export class EventService extends BaseService implements IEventService {
|
|
|
96
106
|
this.selectedOrganisation = selectedOrganisation;
|
|
97
107
|
this.setSelectedEventId = setSelectedEventId;
|
|
98
108
|
|
|
99
|
-
//
|
|
100
|
-
|
|
109
|
+
// Clear app config cache when app name changes
|
|
110
|
+
if (previousAppName !== appName) {
|
|
111
|
+
this.appConfig = null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Update super admin status when user changes
|
|
115
|
+
// This allows super admins to select events from any organisation
|
|
116
|
+
if (user?.id) {
|
|
117
|
+
try {
|
|
118
|
+
this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
|
|
119
|
+
logger.debug('EventService', 'Updated super admin status', {
|
|
120
|
+
userId: user.id,
|
|
121
|
+
isSuperAdmin: this.isSuperAdmin
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
logger.warn('EventService', 'Failed to check super admin status', { error });
|
|
125
|
+
this.isSuperAdmin = false; // Default to false on error
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
this.isSuperAdmin = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If organisation changed (from null to value, or different org, or value to null), reset initialization
|
|
132
|
+
// This ensures events are re-fetched when organisation context changes
|
|
133
|
+
// For event-required apps, selectedOrganisation will be null, so we need to reset when it changes from undefined/null to null
|
|
101
134
|
if (previousOrgId !== newOrgId) {
|
|
102
135
|
this.resetInitialization(); // Reset BaseService's isInitialized flag
|
|
103
136
|
this.isInitializedRef = false;
|
|
@@ -138,20 +171,9 @@ export class EventService extends BaseService implements IEventService {
|
|
|
138
171
|
// Event methods
|
|
139
172
|
setSelectedEvent(event: Event | null): void {
|
|
140
173
|
if (event) {
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
logger.error('EventService', 'Event organisation_id does not match selected organisation', {
|
|
145
|
-
eventOrganisationId: event.organisation_id,
|
|
146
|
-
selectedOrganisationId: this.selectedOrganisation.id,
|
|
147
|
-
eventName: event.event_name
|
|
148
|
-
});
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
} catch (error) {
|
|
152
|
-
logger.error('EventService', 'Error during event validation:', error);
|
|
153
|
-
}
|
|
154
|
-
|
|
174
|
+
// No validation needed: For event-required apps, org is derived from event
|
|
175
|
+
// For org-required apps, event is optional and doesn't need validation
|
|
176
|
+
// RLS policies handle security at the database level
|
|
155
177
|
this.selectedEvent = event;
|
|
156
178
|
this.setSelectedEventId?.(event.event_id);
|
|
157
179
|
// Persist asynchronously (don't await to avoid blocking)
|
|
@@ -286,6 +308,12 @@ export class EventService extends BaseService implements IEventService {
|
|
|
286
308
|
async initialize(): Promise<void> {
|
|
287
309
|
// Only call super.initialize() which will call doInitialize() and fetchEvents()
|
|
288
310
|
// Don't call fetchEvents() again here to avoid double-fetching
|
|
311
|
+
logger.debug('EventService', 'initialize() called', {
|
|
312
|
+
isInitializedRef: this.isInitializedRef,
|
|
313
|
+
hasUser: !!this.user,
|
|
314
|
+
hasSession: !!this.session,
|
|
315
|
+
appName: this.appName
|
|
316
|
+
});
|
|
289
317
|
await super.initialize();
|
|
290
318
|
}
|
|
291
319
|
|
|
@@ -294,13 +322,23 @@ export class EventService extends BaseService implements IEventService {
|
|
|
294
322
|
}
|
|
295
323
|
|
|
296
324
|
protected async doInitialize(): Promise<void> {
|
|
325
|
+
logger.debug('EventService', 'doInitialize() called', {
|
|
326
|
+
isInitializedRef: this.isInitializedRef,
|
|
327
|
+
isFetchingRef: this.isFetchingRef,
|
|
328
|
+
hasUser: !!this.user,
|
|
329
|
+
hasSession: !!this.session,
|
|
330
|
+
appName: this.appName
|
|
331
|
+
});
|
|
332
|
+
|
|
297
333
|
// Skip if already initialized
|
|
298
334
|
if (this.isInitializedRef) {
|
|
335
|
+
logger.debug('EventService', 'Skipping initialization - already initialized');
|
|
299
336
|
return;
|
|
300
337
|
}
|
|
301
338
|
|
|
302
339
|
// Skip if already fetching
|
|
303
340
|
if (this.isFetchingRef) {
|
|
341
|
+
logger.debug('EventService', 'Skipping initialization - already fetching');
|
|
304
342
|
return;
|
|
305
343
|
}
|
|
306
344
|
|
|
@@ -313,13 +351,25 @@ export class EventService extends BaseService implements IEventService {
|
|
|
313
351
|
logger.warn('EventService', 'Failed to clean up old storage keys:', error);
|
|
314
352
|
}
|
|
315
353
|
|
|
316
|
-
// Skip if no user
|
|
317
|
-
|
|
354
|
+
// Skip if no user
|
|
355
|
+
// For event-required apps, selectedOrganisation may be null (org derived from event)
|
|
356
|
+
// For org-required apps, selectedOrganisation is required
|
|
357
|
+
if (!this.user) {
|
|
358
|
+
logger.debug('EventService', 'Skipping initialization - missing user');
|
|
318
359
|
return;
|
|
319
360
|
}
|
|
320
361
|
|
|
362
|
+
logger.debug('EventService', 'Initializing - fetching events', {
|
|
363
|
+
userId: this.user.id,
|
|
364
|
+
organisationId: this.selectedOrganisation?.id || 'derived-from-event',
|
|
365
|
+
appName: this.appName
|
|
366
|
+
});
|
|
367
|
+
|
|
321
368
|
// Initial setup - fetch events on initialization
|
|
322
369
|
await this.fetchEvents(false);
|
|
370
|
+
|
|
371
|
+
// Mark as initialized after successful fetch
|
|
372
|
+
this.isInitializedRef = true;
|
|
323
373
|
}
|
|
324
374
|
|
|
325
375
|
protected doCleanup(): void {
|
|
@@ -327,7 +377,15 @@ export class EventService extends BaseService implements IEventService {
|
|
|
327
377
|
}
|
|
328
378
|
|
|
329
379
|
private async fetchEvents(skipLoadPersisted: boolean = false): Promise<void> {
|
|
330
|
-
|
|
380
|
+
// For event-required apps, selectedOrganisation may be null (org derived from event)
|
|
381
|
+
// For org-required apps, selectedOrganisation is required
|
|
382
|
+
if (!this.user || !this.session || !this.supabaseClient || !this.appName) {
|
|
383
|
+
logger.debug('EventService', 'Skipping fetchEvents - missing dependencies', {
|
|
384
|
+
hasUser: !!this.user,
|
|
385
|
+
hasSession: !!this.session,
|
|
386
|
+
hasSupabaseClient: !!this.supabaseClient,
|
|
387
|
+
appName: this.appName
|
|
388
|
+
});
|
|
331
389
|
// Already false from initialization, just notify
|
|
332
390
|
this.notify();
|
|
333
391
|
return;
|
|
@@ -339,6 +397,7 @@ export class EventService extends BaseService implements IEventService {
|
|
|
339
397
|
|
|
340
398
|
// Prevent multiple simultaneous fetches
|
|
341
399
|
if (this.isFetchingRef) {
|
|
400
|
+
logger.debug('EventService', 'Skipping fetchEvents - already fetching');
|
|
342
401
|
return;
|
|
343
402
|
}
|
|
344
403
|
|
|
@@ -346,12 +405,97 @@ export class EventService extends BaseService implements IEventService {
|
|
|
346
405
|
let isMounted = true;
|
|
347
406
|
|
|
348
407
|
try {
|
|
408
|
+
// Load app config if not already cached (only once per app)
|
|
409
|
+
if (!this.appConfig && this.appName) {
|
|
410
|
+
try {
|
|
411
|
+
this.appConfig = await getAppConfigByName(this.appName);
|
|
412
|
+
} catch (configError) {
|
|
413
|
+
logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
|
|
414
|
+
error: configError
|
|
415
|
+
});
|
|
416
|
+
// Default to event-required for safety
|
|
417
|
+
this.appConfig = { requires_event: true };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Determine organisationId for RPC call
|
|
422
|
+
// For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
|
|
423
|
+
// For org-required apps: org comes from selectedOrganisation
|
|
424
|
+
// Super admins: pass null to see all events
|
|
425
|
+
let organisationIdForRpc: string | null = null;
|
|
426
|
+
|
|
427
|
+
// Check if user is super admin first
|
|
428
|
+
let userIsSuperAdmin = false;
|
|
429
|
+
try {
|
|
430
|
+
userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
|
|
431
|
+
if (userIsSuperAdmin) {
|
|
432
|
+
// Super admin: Pass null to see all events across all organisations
|
|
433
|
+
organisationIdForRpc = null;
|
|
434
|
+
logger.debug('EventService', 'Super admin detected - fetching all events', {
|
|
435
|
+
userId: this.user.id
|
|
436
|
+
});
|
|
437
|
+
} else {
|
|
438
|
+
// Not super admin: determine org from context based on app type
|
|
439
|
+
if (this.selectedEvent) {
|
|
440
|
+
// If event is already selected, use its organisation
|
|
441
|
+
organisationIdForRpc = this.selectedEvent.organisation_id;
|
|
442
|
+
} else if (this.appConfig?.requires_event === true) {
|
|
443
|
+
// Event-required app with no selected event yet: pass null to get all accessible events
|
|
444
|
+
// The RPC will filter by event app roles, returning all events the user has access to
|
|
445
|
+
organisationIdForRpc = null;
|
|
446
|
+
logger.debug('EventService', 'Event-required app: fetching all accessible events (no event selected yet)', {
|
|
447
|
+
userId: this.user.id,
|
|
448
|
+
appName: this.appName
|
|
449
|
+
});
|
|
450
|
+
} else if (this.selectedOrganisation) {
|
|
451
|
+
// Org-required app: use selected organisation
|
|
452
|
+
organisationIdForRpc = this.selectedOrganisation.id;
|
|
453
|
+
} else {
|
|
454
|
+
// No context available - this shouldn't happen for authenticated users
|
|
455
|
+
logger.warn('EventService', 'No organisation context available for event fetch', {
|
|
456
|
+
hasSelectedEvent: !!this.selectedEvent,
|
|
457
|
+
hasSelectedOrganisation: !!this.selectedOrganisation,
|
|
458
|
+
appRequiresEvent: this.appConfig?.requires_event
|
|
459
|
+
});
|
|
460
|
+
organisationIdForRpc = null; // Will return empty list
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
} catch (superAdminCheckError) {
|
|
464
|
+
// If super admin check fails, fall back to organisation-scoped query
|
|
465
|
+
logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
|
|
466
|
+
error: superAdminCheckError
|
|
467
|
+
});
|
|
468
|
+
// Fallback: use available context
|
|
469
|
+
if (this.selectedEvent) {
|
|
470
|
+
organisationIdForRpc = this.selectedEvent.organisation_id;
|
|
471
|
+
} else if (this.appConfig?.requires_event === true) {
|
|
472
|
+
// Event-required app: pass null to get all accessible events
|
|
473
|
+
organisationIdForRpc = null;
|
|
474
|
+
} else if (this.selectedOrganisation) {
|
|
475
|
+
organisationIdForRpc = this.selectedOrganisation.id;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
logger.debug('EventService', 'Fetching events via RPC', {
|
|
480
|
+
userId: this.user.id,
|
|
481
|
+
organisationId: organisationIdForRpc,
|
|
482
|
+
appName: this.appName
|
|
483
|
+
});
|
|
484
|
+
|
|
349
485
|
// Call the RPC function following the established pattern
|
|
350
|
-
|
|
486
|
+
// For super admins, pass null for p_organisation_id to see all events
|
|
487
|
+
let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
|
|
351
488
|
p_user_id: this.user.id,
|
|
352
|
-
p_organisation_id:
|
|
489
|
+
p_organisation_id: organisationIdForRpc,
|
|
353
490
|
p_app_name: this.appName
|
|
354
491
|
});
|
|
492
|
+
|
|
493
|
+
logger.debug('EventService', 'RPC response received', {
|
|
494
|
+
hasData: !!data,
|
|
495
|
+
dataLength: Array.isArray(data) ? data.length : 'not array',
|
|
496
|
+
hasError: !!rpcError,
|
|
497
|
+
error: rpcError
|
|
498
|
+
});
|
|
355
499
|
|
|
356
500
|
if (rpcError) {
|
|
357
501
|
logger.error('EventService', 'RPC error fetching events:', rpcError);
|