@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
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useEvents
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import {
|
|
5
|
-
useUnifiedAuth
|
|
6
|
-
} from "./chunk-VGZZXKBR.js";
|
|
3
|
+
} from "./chunk-Y4BUBBHD.js";
|
|
7
4
|
import {
|
|
8
5
|
assertAppId
|
|
9
6
|
} from "./chunk-QXHPKYJV.js";
|
|
@@ -11,9 +8,8 @@ import {
|
|
|
11
8
|
createAddressFromPlaceResult,
|
|
12
9
|
fetchPlaceAutocomplete,
|
|
13
10
|
fetchPlaceDetails,
|
|
14
|
-
getAddressByPlaceId
|
|
15
|
-
|
|
16
|
-
} from "./chunk-YHCN776L.js";
|
|
11
|
+
getAddressByPlaceId
|
|
12
|
+
} from "./chunk-DZWK57KZ.js";
|
|
17
13
|
import {
|
|
18
14
|
setOrganisationContext
|
|
19
15
|
} from "./chunk-VBXEHIUJ.js";
|
|
@@ -64,6 +60,20 @@ function runCacheCleanup() {
|
|
|
64
60
|
if (typeof window !== "undefined" && !cleanupTimer) {
|
|
65
61
|
cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);
|
|
66
62
|
log.debug("Query cache cleanup initialized.");
|
|
63
|
+
window.addEventListener("beforeunload", () => {
|
|
64
|
+
if (cleanupTimer) {
|
|
65
|
+
clearInterval(cleanupTimer);
|
|
66
|
+
cleanupTimer = null;
|
|
67
|
+
log.debug("Query cache cleanup timer cleared on page unload.");
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function cleanupQueryCache() {
|
|
72
|
+
if (cleanupTimer) {
|
|
73
|
+
clearInterval(cleanupTimer);
|
|
74
|
+
cleanupTimer = null;
|
|
75
|
+
log.debug("Query cache cleanup timer cleared.");
|
|
76
|
+
}
|
|
67
77
|
}
|
|
68
78
|
function useQueryCache(supabase) {
|
|
69
79
|
const getCachedQuery = useCallback(async (table, filterKey, filterValue, fetchFn, options = {}) => {
|
|
@@ -443,21 +453,22 @@ function validateFileSize(file) {
|
|
|
443
453
|
// src/utils/storage/helpers.ts
|
|
444
454
|
var log2 = createLogger("StorageHelpers");
|
|
445
455
|
function generateFilePath(options, fileName) {
|
|
446
|
-
const { orgId, isPublic = false, customPath } = options;
|
|
447
|
-
if (!orgId) {
|
|
448
|
-
throw new Error("orgId is required for file path generation");
|
|
456
|
+
const { orgId, userId, isPublic = false, customPath } = options;
|
|
457
|
+
if (!orgId && !userId) {
|
|
458
|
+
throw new Error("Either orgId or userId is required for file path generation");
|
|
449
459
|
}
|
|
460
|
+
const basePath = orgId ? orgId : `users/${userId}`;
|
|
450
461
|
if (isPublic) {
|
|
451
462
|
if (customPath) {
|
|
452
|
-
return `${
|
|
463
|
+
return `${basePath}/${customPath}/${fileName}`;
|
|
453
464
|
}
|
|
454
|
-
return `${
|
|
465
|
+
return `${basePath}/public/${fileName}`;
|
|
455
466
|
}
|
|
456
467
|
if (customPath) {
|
|
457
|
-
return `${
|
|
468
|
+
return `${basePath}/${customPath}/${fileName}`;
|
|
458
469
|
}
|
|
459
470
|
const pathFolder = customPath || "files";
|
|
460
|
-
return `${
|
|
471
|
+
return `${basePath}/${pathFolder}/${fileName}`;
|
|
461
472
|
}
|
|
462
473
|
function generateUniqueFileName(originalName) {
|
|
463
474
|
const timestamp = Date.now();
|
|
@@ -474,7 +485,8 @@ async function extractFileMetadata(file, options, uploadedBy) {
|
|
|
474
485
|
const metadata = {
|
|
475
486
|
mimeType: file.type,
|
|
476
487
|
size: file.size,
|
|
477
|
-
orgId: options.orgId,
|
|
488
|
+
...options.orgId && { orgId: options.orgId },
|
|
489
|
+
...options.userId && { userId: options.userId },
|
|
478
490
|
appName: options.appName || "pace-core",
|
|
479
491
|
uploadedBy,
|
|
480
492
|
uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -776,7 +788,11 @@ async function deleteFile(supabase, path, isPublic = false) {
|
|
|
776
788
|
async function listFiles(supabase, options) {
|
|
777
789
|
try {
|
|
778
790
|
const bucketName = getBucketName(options.isPublic || false);
|
|
779
|
-
|
|
791
|
+
if (!options.orgId && !options.userId) {
|
|
792
|
+
throw new Error("Either orgId or userId is required for listing files");
|
|
793
|
+
}
|
|
794
|
+
const basePath = options.orgId ? options.orgId : `users/${options.userId}`;
|
|
795
|
+
const pathPrefix = `${basePath}/`;
|
|
780
796
|
const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;
|
|
781
797
|
const { data, error } = await supabase.storage.from(bucketName).list(searchPath, {
|
|
782
798
|
limit: options.limit || 100,
|
|
@@ -796,7 +812,8 @@ async function listFiles(supabase, options) {
|
|
|
796
812
|
metadata: {
|
|
797
813
|
mimeType: item.metadata?.mimetype || "application/octet-stream",
|
|
798
814
|
size: item.metadata?.size || 0,
|
|
799
|
-
orgId: options.orgId,
|
|
815
|
+
...options.orgId && { orgId: options.orgId },
|
|
816
|
+
...options.userId && { userId: options.userId },
|
|
800
817
|
appName: options.appName,
|
|
801
818
|
uploadedBy: "unknown",
|
|
802
819
|
uploadedAt: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -845,7 +862,14 @@ async function downloadFile(supabase, path, isPublic = false) {
|
|
|
845
862
|
async function archiveFile(supabase, path, options) {
|
|
846
863
|
try {
|
|
847
864
|
const bucketName = getBucketName(options.isPublic || false);
|
|
848
|
-
|
|
865
|
+
let archivedPath;
|
|
866
|
+
if (options.orgId) {
|
|
867
|
+
archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);
|
|
868
|
+
} else if (options.userId) {
|
|
869
|
+
archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);
|
|
870
|
+
} else {
|
|
871
|
+
throw new Error("Either orgId or userId is required for archiving files");
|
|
872
|
+
}
|
|
849
873
|
const { error: copyError } = await supabase.storage.from(bucketName).copy(path, archivedPath);
|
|
850
874
|
if (copyError) {
|
|
851
875
|
return {
|
|
@@ -884,7 +908,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
884
908
|
const [isLoading, setIsLoading] = useState3(false);
|
|
885
909
|
const [error, setError] = useState3(null);
|
|
886
910
|
const fetchFiles = useCallback3(async () => {
|
|
887
|
-
if (!table_name || !record_id || !
|
|
911
|
+
if (!table_name || !record_id || !supabase) {
|
|
888
912
|
setFileUrl(null);
|
|
889
913
|
setFileReference(null);
|
|
890
914
|
setFileReferences([]);
|
|
@@ -893,11 +917,13 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
893
917
|
setIsLoading(false);
|
|
894
918
|
return;
|
|
895
919
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
920
|
+
if (organisation_id) {
|
|
921
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
922
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
923
|
+
logger.warn("usePublicFileDisplay", "Invalid organisationId format (not a valid UUID)", { organisation_id });
|
|
924
|
+
}
|
|
899
925
|
}
|
|
900
|
-
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
926
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
901
927
|
if (enableCache) {
|
|
902
928
|
const cached = publicFileCache.get(cacheKey);
|
|
903
929
|
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
@@ -916,77 +942,134 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
916
942
|
setIsLoading(true);
|
|
917
943
|
setError(null);
|
|
918
944
|
let files = [];
|
|
919
|
-
if (
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
if (
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
organisation_id,
|
|
934
|
-
error: rpcError.message,
|
|
935
|
-
errorCode: rpcError.code,
|
|
936
|
-
errorDetails: rpcError.details,
|
|
937
|
-
errorHint: rpcError.hint
|
|
945
|
+
if (organisation_id === void 0) {
|
|
946
|
+
logger.debug("usePublicFileDisplay", "organisation_id is undefined, searching both user-scoped and organisation-scoped files:", {
|
|
947
|
+
table_name,
|
|
948
|
+
record_id,
|
|
949
|
+
category
|
|
950
|
+
});
|
|
951
|
+
let userScopedFiles = [];
|
|
952
|
+
let orgScopedFiles = [];
|
|
953
|
+
if (category) {
|
|
954
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_by_category_list", {
|
|
955
|
+
p_table_name: table_name,
|
|
956
|
+
p_record_id: record_id,
|
|
957
|
+
p_category: category,
|
|
958
|
+
p_organisation_id: null
|
|
938
959
|
});
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if (!data || data.length === 0) {
|
|
942
|
-
files = [];
|
|
943
|
-
} else {
|
|
944
|
-
files = data.filter((item) => {
|
|
945
|
-
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
946
|
-
}).map((item) => {
|
|
947
|
-
return {
|
|
960
|
+
if (!userRpcError && userData) {
|
|
961
|
+
userScopedFiles = userData.filter((item) => item.is_public === true && item.id && item.file_path && item.file_metadata).map((item) => ({
|
|
948
962
|
id: item.id,
|
|
949
963
|
table_name,
|
|
950
964
|
record_id,
|
|
951
965
|
file_path: item.file_path,
|
|
952
966
|
file_metadata: item.file_metadata || {},
|
|
953
|
-
organisation_id,
|
|
967
|
+
organisation_id: null,
|
|
954
968
|
app_id: item.file_metadata?.app_id || null,
|
|
955
969
|
is_public: true,
|
|
956
|
-
// RPC already filtered for public files
|
|
957
970
|
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
958
971
|
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
959
|
-
};
|
|
972
|
+
}));
|
|
973
|
+
}
|
|
974
|
+
} else {
|
|
975
|
+
const { data: userData, error: userRpcError } = await supabase.rpc("data_file_reference_list", {
|
|
976
|
+
p_table_name: table_name,
|
|
977
|
+
p_record_id: record_id,
|
|
978
|
+
p_organisation_id: null
|
|
960
979
|
});
|
|
980
|
+
if (!userRpcError && userData) {
|
|
981
|
+
const ids = userData.map((item) => item.id);
|
|
982
|
+
if (ids.length > 0) {
|
|
983
|
+
const { data: fullData } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
984
|
+
userScopedFiles = fullData || [];
|
|
985
|
+
}
|
|
986
|
+
}
|
|
961
987
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
988
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
989
|
+
allFiles.sort((a, b) => {
|
|
990
|
+
const aTime = new Date(a.created_at).getTime();
|
|
991
|
+
const bTime = new Date(b.created_at).getTime();
|
|
992
|
+
return bTime - aTime;
|
|
967
993
|
});
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
994
|
+
files = allFiles;
|
|
995
|
+
logger.debug("usePublicFileDisplay", "Found files with undefined organisation_id:", {
|
|
996
|
+
userScopedCount: userScopedFiles.length,
|
|
997
|
+
orgScopedCount: orgScopedFiles.length,
|
|
998
|
+
totalCount: files.length
|
|
999
|
+
});
|
|
1000
|
+
} else {
|
|
1001
|
+
if (category) {
|
|
1002
|
+
const rpcParams = {
|
|
1003
|
+
p_table_name: table_name,
|
|
1004
|
+
p_record_id: record_id,
|
|
1005
|
+
p_category: category,
|
|
1006
|
+
p_organisation_id: organisation_id ?? null
|
|
1007
|
+
};
|
|
1008
|
+
const { data, error: rpcError } = await supabase.rpc("data_file_reference_by_category_list", rpcParams);
|
|
1009
|
+
if (rpcError) {
|
|
1010
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1011
|
+
function: "data_file_reference_by_category_list",
|
|
1012
|
+
table_name,
|
|
1013
|
+
record_id,
|
|
1014
|
+
category,
|
|
1015
|
+
organisation_id,
|
|
1016
|
+
error: rpcError.message,
|
|
1017
|
+
errorCode: rpcError.code,
|
|
1018
|
+
errorDetails: rpcError.details,
|
|
1019
|
+
errorHint: rpcError.hint
|
|
1020
|
+
});
|
|
1021
|
+
throw new Error(rpcError.message || "Failed to fetch file reference");
|
|
1022
|
+
}
|
|
1023
|
+
if (!data || data.length === 0) {
|
|
1024
|
+
files = [];
|
|
1025
|
+
} else {
|
|
1026
|
+
files = data.filter((item) => {
|
|
1027
|
+
return item.is_public === true && item.id && item.file_path && item.file_metadata;
|
|
1028
|
+
}).map((item) => {
|
|
1029
|
+
return {
|
|
1030
|
+
id: item.id,
|
|
1031
|
+
table_name,
|
|
1032
|
+
record_id,
|
|
1033
|
+
file_path: item.file_path,
|
|
1034
|
+
file_metadata: item.file_metadata || {},
|
|
1035
|
+
organisation_id: organisation_id ?? null,
|
|
1036
|
+
app_id: item.file_metadata?.app_id || null,
|
|
1037
|
+
is_public: true,
|
|
1038
|
+
// RPC already filtered for public files
|
|
1039
|
+
created_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1040
|
+
updated_at: item.created_at || (/* @__PURE__ */ new Date()).toISOString()
|
|
1041
|
+
};
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
983
1044
|
} else {
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1045
|
+
const { data: fileIds, error: rpcError } = await supabase.rpc("data_file_reference_list", {
|
|
1046
|
+
p_table_name: table_name,
|
|
1047
|
+
p_record_id: record_id,
|
|
1048
|
+
p_organisation_id: organisation_id ?? null
|
|
1049
|
+
});
|
|
1050
|
+
if (rpcError) {
|
|
1051
|
+
logger.error("usePublicFileDisplay", "RPC function error", {
|
|
1052
|
+
function: "data_file_reference_list",
|
|
1053
|
+
table_name,
|
|
1054
|
+
record_id,
|
|
1055
|
+
organisation_id,
|
|
1056
|
+
error: rpcError.message,
|
|
1057
|
+
errorCode: rpcError.code,
|
|
1058
|
+
errorDetails: rpcError.details,
|
|
1059
|
+
errorHint: rpcError.hint
|
|
1060
|
+
});
|
|
1061
|
+
throw new Error(rpcError.message || "Failed to fetch file references");
|
|
1062
|
+
}
|
|
1063
|
+
if (!fileIds || fileIds.length === 0) {
|
|
1064
|
+
files = [];
|
|
1065
|
+
} else {
|
|
1066
|
+
const ids = fileIds.map((item) => item.id);
|
|
1067
|
+
const { data: fullData, error: fetchError } = await supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").in("id", ids).eq("is_public", true);
|
|
1068
|
+
if (fetchError) {
|
|
1069
|
+
throw new Error(fetchError.message || "Failed to fetch file references");
|
|
1070
|
+
}
|
|
1071
|
+
files = fullData || [];
|
|
988
1072
|
}
|
|
989
|
-
files = fullData || [];
|
|
990
1073
|
}
|
|
991
1074
|
}
|
|
992
1075
|
const publicFiles = files.filter((f) => f.is_public === true);
|
|
@@ -1077,7 +1160,7 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1077
1160
|
}
|
|
1078
1161
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1079
1162
|
useEffect4(() => {
|
|
1080
|
-
if (table_name && record_id
|
|
1163
|
+
if (table_name && record_id) {
|
|
1081
1164
|
fetchFiles();
|
|
1082
1165
|
} else {
|
|
1083
1166
|
setFileUrl(null);
|
|
@@ -1090,9 +1173,9 @@ function usePublicFileDisplay(table_name, record_id, organisation_id, category,
|
|
|
1090
1173
|
}
|
|
1091
1174
|
}, [fetchFiles, table_name, record_id, organisation_id]);
|
|
1092
1175
|
const refetch = useCallback3(async () => {
|
|
1093
|
-
if (!table_name || !record_id
|
|
1176
|
+
if (!table_name || !record_id) return;
|
|
1094
1177
|
if (enableCache) {
|
|
1095
|
-
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1178
|
+
const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1096
1179
|
publicFileCache.delete(cacheKey);
|
|
1097
1180
|
}
|
|
1098
1181
|
await fetchFiles();
|
|
@@ -1148,8 +1231,9 @@ var FileReferenceServiceImpl = class {
|
|
|
1148
1231
|
*/
|
|
1149
1232
|
async createFileReference(options, file) {
|
|
1150
1233
|
try {
|
|
1151
|
-
|
|
1152
|
-
|
|
1234
|
+
const isUserScoped = !options.organisation_id && options.userId;
|
|
1235
|
+
if (!isUserScoped && !options.organisation_id) {
|
|
1236
|
+
throw new Error("organisation_id is required for file upload, or userId must be provided for user-scoped files");
|
|
1153
1237
|
}
|
|
1154
1238
|
if (!options.table_name) {
|
|
1155
1239
|
throw new Error("table_name is required for file upload");
|
|
@@ -1160,9 +1244,20 @@ var FileReferenceServiceImpl = class {
|
|
|
1160
1244
|
if (!options.folder) {
|
|
1161
1245
|
throw new Error("folder is required for file upload. The folder prop determines the storage path.");
|
|
1162
1246
|
}
|
|
1247
|
+
let authenticatedUserId = void 0;
|
|
1248
|
+
if (isUserScoped) {
|
|
1249
|
+
const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();
|
|
1250
|
+
if (authError || !authUser) {
|
|
1251
|
+
throw new Error("User must be authenticated to upload user-scoped files");
|
|
1252
|
+
}
|
|
1253
|
+
authenticatedUserId = authUser.id;
|
|
1254
|
+
log3.debug("Using authenticated user ID for user-scoped file upload", { userId: authenticatedUserId });
|
|
1255
|
+
}
|
|
1163
1256
|
const uploadResult = await uploadFile(this.supabase, file, {
|
|
1164
1257
|
appName: "file-reference",
|
|
1165
|
-
orgId: options.organisation_id,
|
|
1258
|
+
orgId: options.organisation_id || void 0,
|
|
1259
|
+
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1260
|
+
// Use auth.uid() for user-scoped files
|
|
1166
1261
|
isPublic: options.is_public || false,
|
|
1167
1262
|
customPath: options.folder
|
|
1168
1263
|
// Use folder prop as the custom path segment
|
|
@@ -1176,16 +1271,20 @@ var FileReferenceServiceImpl = class {
|
|
|
1176
1271
|
const filePath = uploadResult.path;
|
|
1177
1272
|
const metadata = await extractFileMetadata(file, {
|
|
1178
1273
|
appName: "file-reference",
|
|
1179
|
-
orgId: options.organisation_id,
|
|
1274
|
+
orgId: options.organisation_id || void 0,
|
|
1275
|
+
userId: authenticatedUserId || (isUserScoped ? void 0 : options.userId),
|
|
1276
|
+
// Use auth.uid() for user-scoped files
|
|
1180
1277
|
isPublic: options.is_public || false
|
|
1181
1278
|
}, "system");
|
|
1182
|
-
|
|
1279
|
+
if (!isUserScoped && options.organisation_id) {
|
|
1280
|
+
await setOrganisationContext(this.supabase, options.organisation_id);
|
|
1281
|
+
}
|
|
1183
1282
|
const { data, error } = await this.supabase.rpc("data_file_reference_create", {
|
|
1184
1283
|
p_table_name: options.table_name,
|
|
1185
1284
|
p_record_id: options.record_id,
|
|
1186
1285
|
p_file_path: filePath,
|
|
1187
1286
|
// Storage path from step 1
|
|
1188
|
-
p_organisation_id: options.organisation_id,
|
|
1287
|
+
p_organisation_id: options.organisation_id ?? null,
|
|
1189
1288
|
p_app_id: options.app_id,
|
|
1190
1289
|
p_page_context: options.pageContext,
|
|
1191
1290
|
p_event_id: options.event_id || null,
|
|
@@ -1198,7 +1297,9 @@ var FileReferenceServiceImpl = class {
|
|
|
1198
1297
|
...metadata,
|
|
1199
1298
|
...options.custom_metadata
|
|
1200
1299
|
},
|
|
1201
|
-
p_is_public: options.is_public || false
|
|
1300
|
+
p_is_public: options.is_public || false,
|
|
1301
|
+
p_user_id: authenticatedUserId || options.userId || null
|
|
1302
|
+
// Pass authenticated user ID for user-scoped files
|
|
1202
1303
|
});
|
|
1203
1304
|
if (error) {
|
|
1204
1305
|
await deleteFile(this.supabase, filePath, options.is_public || false);
|
|
@@ -1216,7 +1317,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1216
1317
|
invalidateFileDisplayCache(
|
|
1217
1318
|
options.table_name,
|
|
1218
1319
|
options.record_id,
|
|
1219
|
-
options.organisation_id,
|
|
1320
|
+
options.organisation_id || null,
|
|
1220
1321
|
options.category
|
|
1221
1322
|
);
|
|
1222
1323
|
return fileRef;
|
|
@@ -1227,7 +1328,13 @@ var FileReferenceServiceImpl = class {
|
|
|
1227
1328
|
}
|
|
1228
1329
|
async getFileReference(table_name, record_id, organisation_id) {
|
|
1229
1330
|
try {
|
|
1230
|
-
|
|
1331
|
+
let query = this.supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id);
|
|
1332
|
+
if (organisation_id === null || organisation_id === void 0) {
|
|
1333
|
+
query = query.is("organisation_id", null);
|
|
1334
|
+
} else {
|
|
1335
|
+
query = query.eq("organisation_id", organisation_id);
|
|
1336
|
+
}
|
|
1337
|
+
const { data, error } = await query.single();
|
|
1231
1338
|
if (error) {
|
|
1232
1339
|
if (error.code === "PGRST116") {
|
|
1233
1340
|
return null;
|
|
@@ -1269,7 +1376,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1269
1376
|
const { data: filePath, error } = await this.supabase.rpc("data_file_reference_signed_url_get", {
|
|
1270
1377
|
p_table_name: table_name,
|
|
1271
1378
|
p_record_id: record_id,
|
|
1272
|
-
p_organisation_id: organisation_id,
|
|
1379
|
+
p_organisation_id: organisation_id ?? null,
|
|
1273
1380
|
p_expires_in: expires_in
|
|
1274
1381
|
});
|
|
1275
1382
|
if (error) {
|
|
@@ -1281,6 +1388,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1281
1388
|
const signedUrlResult = await getSignedUrl(this.supabase, filePath, {
|
|
1282
1389
|
appName: "file-reference",
|
|
1283
1390
|
orgId: organisation_id,
|
|
1391
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1284
1392
|
expiresIn: expires_in
|
|
1285
1393
|
});
|
|
1286
1394
|
return signedUrlResult?.url || null;
|
|
@@ -1307,7 +1415,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1307
1415
|
const { error } = await this.supabase.rpc("data_file_reference_delete", {
|
|
1308
1416
|
p_table_name: table_name,
|
|
1309
1417
|
p_record_id: record_id,
|
|
1310
|
-
p_organisation_id: organisation_id,
|
|
1418
|
+
p_organisation_id: organisation_id ?? null,
|
|
1311
1419
|
p_delete_file: delete_file
|
|
1312
1420
|
});
|
|
1313
1421
|
if (error) {
|
|
@@ -1327,7 +1435,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1327
1435
|
const { data, error } = await this.supabase.rpc("data_file_reference_list", {
|
|
1328
1436
|
p_table_name: table_name,
|
|
1329
1437
|
p_record_id: record_id,
|
|
1330
|
-
p_organisation_id: organisation_id
|
|
1438
|
+
p_organisation_id: organisation_id ?? null
|
|
1331
1439
|
});
|
|
1332
1440
|
if (error) {
|
|
1333
1441
|
throw new Error(`Failed to list file references: ${error.message}`);
|
|
@@ -1348,7 +1456,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1348
1456
|
fileType,
|
|
1349
1457
|
...item.file_metadata || {}
|
|
1350
1458
|
},
|
|
1351
|
-
organisation_id,
|
|
1459
|
+
organisation_id: organisation_id ?? null,
|
|
1352
1460
|
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1353
1461
|
// May not be in metadata, use empty string
|
|
1354
1462
|
is_public: item.is_public ?? false,
|
|
@@ -1369,7 +1477,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1369
1477
|
const { data, error } = await this.supabase.rpc("data_file_reference_count_get", {
|
|
1370
1478
|
p_table_name: table_name,
|
|
1371
1479
|
p_record_id: record_id,
|
|
1372
|
-
p_organisation_id: organisation_id
|
|
1480
|
+
p_organisation_id: organisation_id ?? null
|
|
1373
1481
|
});
|
|
1374
1482
|
if (error) {
|
|
1375
1483
|
throw new Error(`Failed to get file count: ${error.message}`);
|
|
@@ -1384,7 +1492,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1384
1492
|
try {
|
|
1385
1493
|
const { data, error } = await this.supabase.rpc("data_file_reference_get", {
|
|
1386
1494
|
p_file_reference_id: id,
|
|
1387
|
-
p_organisation_id: organisation_id
|
|
1495
|
+
p_organisation_id: organisation_id ?? null
|
|
1388
1496
|
});
|
|
1389
1497
|
if (error) {
|
|
1390
1498
|
throw new Error(`Failed to get file reference by ID: ${error.message}`);
|
|
@@ -1404,7 +1512,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1404
1512
|
p_table_name: table_name,
|
|
1405
1513
|
p_record_id: record_id,
|
|
1406
1514
|
p_category: category,
|
|
1407
|
-
p_organisation_id: organisation_id
|
|
1515
|
+
p_organisation_id: organisation_id ?? null
|
|
1408
1516
|
});
|
|
1409
1517
|
if (error) {
|
|
1410
1518
|
log3.error("RPC ERROR getting files by category:", {
|
|
@@ -1448,7 +1556,7 @@ var FileReferenceServiceImpl = class {
|
|
|
1448
1556
|
category: item.file_metadata?.category || "general_documents" /* GENERAL_DOCUMENTS */,
|
|
1449
1557
|
...item.file_metadata || {}
|
|
1450
1558
|
},
|
|
1451
|
-
organisation_id,
|
|
1559
|
+
organisation_id: organisation_id ?? null,
|
|
1452
1560
|
app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(""),
|
|
1453
1561
|
// May not be in metadata, use empty string
|
|
1454
1562
|
is_public: item.is_public ?? false,
|
|
@@ -1502,7 +1610,8 @@ async function uploadFileWithReference(supabase, options, file) {
|
|
|
1502
1610
|
const fileReference = await service.createFileReference(options, file);
|
|
1503
1611
|
const fileUrl = options.is_public ? getPublicUrl(supabase, fileReference.file_path, true) : await getSignedUrl(supabase, fileReference.file_path, {
|
|
1504
1612
|
appName: "file-reference",
|
|
1505
|
-
orgId: options.organisation_id,
|
|
1613
|
+
orgId: options.organisation_id || void 0,
|
|
1614
|
+
userId: options.userId || void 0,
|
|
1506
1615
|
expiresIn: 3600
|
|
1507
1616
|
});
|
|
1508
1617
|
const urlString = typeof fileUrl === "string" ? fileUrl : fileUrl?.url || "";
|
|
@@ -1547,7 +1656,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1547
1656
|
const [isLoading, setIsLoading] = useState4(false);
|
|
1548
1657
|
const [error, setError] = useState4(null);
|
|
1549
1658
|
const fetchFiles = useCallback4(async () => {
|
|
1550
|
-
if (!table_name || !record_id || !
|
|
1659
|
+
if (!table_name || !record_id || !supabase) {
|
|
1551
1660
|
setFileUrl(null);
|
|
1552
1661
|
setFileReference(null);
|
|
1553
1662
|
setFileReferences([]);
|
|
@@ -1556,11 +1665,13 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1556
1665
|
setIsLoading(false);
|
|
1557
1666
|
return;
|
|
1558
1667
|
}
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1668
|
+
if (organisation_id) {
|
|
1669
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1670
|
+
if (!uuidRegex.test(organisation_id)) {
|
|
1671
|
+
logger.warn("useFileDisplay", "Invalid organisationId format (not a valid UUID):", organisation_id);
|
|
1672
|
+
}
|
|
1562
1673
|
}
|
|
1563
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
1674
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1564
1675
|
if (enableCache) {
|
|
1565
1676
|
const cached = authenticatedFileCache.get(cacheKey);
|
|
1566
1677
|
if (cached && Date.now() - cached.timestamp < cached.ttl) {
|
|
@@ -1570,6 +1681,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1570
1681
|
const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {
|
|
1571
1682
|
appName: "pace-core",
|
|
1572
1683
|
orgId: organisation_id,
|
|
1684
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1573
1685
|
expiresIn: 3600
|
|
1574
1686
|
});
|
|
1575
1687
|
const regeneratedUrl = signedUrlResult?.url || null;
|
|
@@ -1600,25 +1712,205 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1600
1712
|
setError(null);
|
|
1601
1713
|
const service = createFileReferenceService(supabase);
|
|
1602
1714
|
let files = [];
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1715
|
+
const shouldSearchBothScopes = organisation_id === void 0;
|
|
1716
|
+
if (shouldSearchBothScopes) {
|
|
1717
|
+
let userScopedFiles = [];
|
|
1718
|
+
let orgScopedFiles = [];
|
|
1719
|
+
try {
|
|
1720
|
+
if (category) {
|
|
1721
|
+
userScopedFiles = await service.getFilesByCategory(
|
|
1722
|
+
table_name,
|
|
1723
|
+
record_id,
|
|
1724
|
+
category,
|
|
1725
|
+
void 0
|
|
1726
|
+
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1727
|
+
);
|
|
1728
|
+
} else {
|
|
1729
|
+
userScopedFiles = await service.listFileReferences(
|
|
1730
|
+
table_name,
|
|
1731
|
+
record_id,
|
|
1732
|
+
void 0
|
|
1733
|
+
// Explicitly pass undefined for user-scoped files (service converts to null for RPC)
|
|
1734
|
+
);
|
|
1735
|
+
}
|
|
1736
|
+
} catch (err) {
|
|
1737
|
+
logger.warn("useFileDisplay", "Error querying user-scoped files:", err);
|
|
1738
|
+
userScopedFiles = [];
|
|
1739
|
+
}
|
|
1740
|
+
try {
|
|
1741
|
+
const { data: { user }, error: userError } = await supabase.auth.getUser();
|
|
1742
|
+
if (userError) {
|
|
1743
|
+
logger.warn("useFileDisplay", "Error getting user:", userError);
|
|
1744
|
+
}
|
|
1745
|
+
if (user) {
|
|
1746
|
+
const { data: memberships, error: membershipError } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
|
|
1747
|
+
if (membershipError) {
|
|
1748
|
+
logger.warn("useFileDisplay", "Error querying organisation memberships:", membershipError);
|
|
1749
|
+
}
|
|
1750
|
+
if (memberships && memberships.length > 0) {
|
|
1751
|
+
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1752
|
+
const orgQueries = orgIds.map(async (orgId) => {
|
|
1753
|
+
try {
|
|
1754
|
+
if (category) {
|
|
1755
|
+
return await service.getFilesByCategory(
|
|
1756
|
+
table_name,
|
|
1757
|
+
record_id,
|
|
1758
|
+
category,
|
|
1759
|
+
orgId
|
|
1760
|
+
);
|
|
1761
|
+
} else {
|
|
1762
|
+
return await service.listFileReferences(
|
|
1763
|
+
table_name,
|
|
1764
|
+
record_id,
|
|
1765
|
+
orgId
|
|
1766
|
+
);
|
|
1767
|
+
}
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
return [];
|
|
1770
|
+
}
|
|
1771
|
+
});
|
|
1772
|
+
const orgResults = await Promise.all(orgQueries);
|
|
1773
|
+
orgScopedFiles = orgResults.flat();
|
|
1774
|
+
} else {
|
|
1775
|
+
try {
|
|
1776
|
+
let fallbackQuery = supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1777
|
+
if (category) {
|
|
1778
|
+
fallbackQuery = fallbackQuery.eq("file_metadata->>category", category);
|
|
1779
|
+
}
|
|
1780
|
+
const { data: fallbackFiles } = await fallbackQuery;
|
|
1781
|
+
if (fallbackFiles && fallbackFiles.length > 0) {
|
|
1782
|
+
orgScopedFiles = fallbackFiles.map((f) => ({
|
|
1783
|
+
id: f.id,
|
|
1784
|
+
table_name: f.table_name,
|
|
1785
|
+
record_id: f.record_id,
|
|
1786
|
+
file_path: f.file_path,
|
|
1787
|
+
file_metadata: f.file_metadata || {},
|
|
1788
|
+
organisation_id: f.organisation_id,
|
|
1789
|
+
app_id: f.app_id,
|
|
1790
|
+
is_public: f.is_public ?? false,
|
|
1791
|
+
created_at: f.created_at,
|
|
1792
|
+
updated_at: f.updated_at
|
|
1793
|
+
}));
|
|
1794
|
+
}
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
logger.warn("useFileDisplay", "Error querying organisation-scoped files:", err);
|
|
1801
|
+
orgScopedFiles = [];
|
|
1802
|
+
}
|
|
1803
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1804
|
+
allFiles.sort((a, b) => {
|
|
1805
|
+
const aTime = new Date(a.created_at).getTime();
|
|
1806
|
+
const bTime = new Date(b.created_at).getTime();
|
|
1807
|
+
return bTime - aTime;
|
|
1609
1808
|
});
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
)
|
|
1809
|
+
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1810
|
+
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1811
|
+
} else {
|
|
1812
|
+
files = allFiles;
|
|
1813
|
+
}
|
|
1814
|
+
if (files.length === 0) {
|
|
1815
|
+
try {
|
|
1816
|
+
let directQuery = supabase.from("file_references").select("id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at").eq("table_name", table_name).eq("record_id", record_id).order("created_at", { ascending: false });
|
|
1817
|
+
if (category) {
|
|
1818
|
+
directQuery = directQuery.eq("file_metadata->>category", category);
|
|
1819
|
+
}
|
|
1820
|
+
const { data: directFiles } = await directQuery;
|
|
1821
|
+
if (directFiles && directFiles.length > 0) {
|
|
1822
|
+
files = directFiles.map((f) => ({
|
|
1823
|
+
id: f.id,
|
|
1824
|
+
table_name: f.table_name,
|
|
1825
|
+
record_id: f.record_id,
|
|
1826
|
+
file_path: f.file_path,
|
|
1827
|
+
file_metadata: f.file_metadata || {},
|
|
1828
|
+
organisation_id: f.organisation_id,
|
|
1829
|
+
app_id: f.app_id,
|
|
1830
|
+
is_public: f.is_public ?? false,
|
|
1831
|
+
created_at: f.created_at,
|
|
1832
|
+
updated_at: f.updated_at
|
|
1833
|
+
}));
|
|
1834
|
+
}
|
|
1835
|
+
} catch (err) {
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1616
1838
|
} else {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1839
|
+
if (category) {
|
|
1840
|
+
logger.debug("useFileDisplay", "Using RPC function for category filtering:", {
|
|
1841
|
+
table_name,
|
|
1842
|
+
record_id,
|
|
1843
|
+
category,
|
|
1844
|
+
organisation_id
|
|
1845
|
+
});
|
|
1846
|
+
files = await service.getFilesByCategory(
|
|
1847
|
+
table_name,
|
|
1848
|
+
record_id,
|
|
1849
|
+
category,
|
|
1850
|
+
organisation_id
|
|
1851
|
+
);
|
|
1852
|
+
} else {
|
|
1853
|
+
files = await service.listFileReferences(
|
|
1854
|
+
table_name,
|
|
1855
|
+
record_id,
|
|
1856
|
+
organisation_id
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
if (files.length === 0 && (!organisation_id || organisation_id === "")) {
|
|
1860
|
+
let userScopedFiles = [];
|
|
1861
|
+
let orgScopedFiles = [];
|
|
1862
|
+
try {
|
|
1863
|
+
if (category) {
|
|
1864
|
+
userScopedFiles = await service.getFilesByCategory(
|
|
1865
|
+
table_name,
|
|
1866
|
+
record_id,
|
|
1867
|
+
category,
|
|
1868
|
+
void 0
|
|
1869
|
+
);
|
|
1870
|
+
} else {
|
|
1871
|
+
userScopedFiles = await service.listFileReferences(
|
|
1872
|
+
table_name,
|
|
1873
|
+
record_id,
|
|
1874
|
+
void 0
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
}
|
|
1879
|
+
try {
|
|
1880
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
1881
|
+
if (user) {
|
|
1882
|
+
const { data: memberships } = await supabase.from("organisation_memberships").select("organisation_id").eq("user_id", user.id).or("status.is.null,status.eq.active");
|
|
1883
|
+
if (memberships && memberships.length > 0) {
|
|
1884
|
+
const orgIds = memberships.map((m) => m.organisation_id).filter(Boolean);
|
|
1885
|
+
const orgQueries = orgIds.map(async (orgId) => {
|
|
1886
|
+
try {
|
|
1887
|
+
if (category) {
|
|
1888
|
+
return await service.getFilesByCategory(table_name, record_id, category, orgId);
|
|
1889
|
+
} else {
|
|
1890
|
+
return await service.listFileReferences(table_name, record_id, orgId);
|
|
1891
|
+
}
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
return [];
|
|
1894
|
+
}
|
|
1895
|
+
});
|
|
1896
|
+
const orgResults = await Promise.all(orgQueries);
|
|
1897
|
+
orgScopedFiles = orgResults.flat();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
} catch (err) {
|
|
1901
|
+
}
|
|
1902
|
+
const allFiles = [...userScopedFiles, ...orgScopedFiles];
|
|
1903
|
+
allFiles.sort((a, b) => {
|
|
1904
|
+
const aTime = new Date(a.created_at).getTime();
|
|
1905
|
+
const bTime = new Date(b.created_at).getTime();
|
|
1906
|
+
return bTime - aTime;
|
|
1907
|
+
});
|
|
1908
|
+
if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {
|
|
1909
|
+
files = allFiles.filter((f) => f.organisation_id !== null);
|
|
1910
|
+
} else {
|
|
1911
|
+
files = allFiles;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1622
1914
|
}
|
|
1623
1915
|
if (files.length === 0) {
|
|
1624
1916
|
setFileUrl(null);
|
|
@@ -1656,6 +1948,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1656
1948
|
const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {
|
|
1657
1949
|
appName: "pace-core",
|
|
1658
1950
|
orgId: organisation_id,
|
|
1951
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1659
1952
|
expiresIn: 3600
|
|
1660
1953
|
});
|
|
1661
1954
|
url = signedUrlResult?.url || null;
|
|
@@ -1667,6 +1960,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1667
1960
|
const urlMap = await generateFileUrlsBatch(supabase, files, {
|
|
1668
1961
|
appName: "pace-core",
|
|
1669
1962
|
orgId: organisation_id,
|
|
1963
|
+
userId: organisation_id ? void 0 : record_id,
|
|
1670
1964
|
expiresIn: 3600
|
|
1671
1965
|
});
|
|
1672
1966
|
setFileUrls(urlMap);
|
|
@@ -1720,7 +2014,7 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1720
2014
|
}
|
|
1721
2015
|
}, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);
|
|
1722
2016
|
useEffect5(() => {
|
|
1723
|
-
if (table_name && record_id &&
|
|
2017
|
+
if (table_name && record_id && supabase) {
|
|
1724
2018
|
fetchFiles();
|
|
1725
2019
|
} else {
|
|
1726
2020
|
setFileUrl(null);
|
|
@@ -1731,11 +2025,11 @@ function useFileDisplay(table_name, record_id, organisation_id, category, option
|
|
|
1731
2025
|
setIsLoading(false);
|
|
1732
2026
|
setError(null);
|
|
1733
2027
|
}
|
|
1734
|
-
}, [
|
|
2028
|
+
}, [table_name, record_id, organisation_id, supabase]);
|
|
1735
2029
|
const refetch = useCallback4(async () => {
|
|
1736
|
-
if (!table_name || !record_id || !
|
|
2030
|
+
if (!table_name || !record_id || !supabase) return;
|
|
1737
2031
|
if (enableCache) {
|
|
1738
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
2032
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1739
2033
|
authenticatedFileCache.delete(cacheKey);
|
|
1740
2034
|
}
|
|
1741
2035
|
await fetchFiles();
|
|
@@ -1766,210 +2060,14 @@ function getFileDisplayCacheStats() {
|
|
|
1766
2060
|
};
|
|
1767
2061
|
}
|
|
1768
2062
|
function invalidateFileDisplayCache(table_name, record_id, organisation_id, category) {
|
|
1769
|
-
const cacheKey = `file_${table_name}_${record_id}_${organisation_id}_${category || "all"}`;
|
|
2063
|
+
const cacheKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_${category || "all"}`;
|
|
1770
2064
|
authenticatedFileCache.delete(cacheKey);
|
|
1771
2065
|
if (category) {
|
|
1772
|
-
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id}_all`;
|
|
2066
|
+
const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === void 0 ? "undefined" : organisation_id ?? "null"}_all`;
|
|
1773
2067
|
authenticatedFileCache.delete(allCategoryKey);
|
|
1774
2068
|
}
|
|
1775
2069
|
}
|
|
1776
2070
|
|
|
1777
|
-
// src/components/ErrorBoundary/ErrorBoundary.tsx
|
|
1778
|
-
import { Component } from "react";
|
|
1779
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
1780
|
-
var ErrorBoundary = class extends Component {
|
|
1781
|
-
constructor(props) {
|
|
1782
|
-
super(props);
|
|
1783
|
-
this.retryTimeoutId = null;
|
|
1784
|
-
this.reportError = (errorId, componentName) => {
|
|
1785
|
-
if (import.meta.env.MODE === "production") {
|
|
1786
|
-
logger.warn("ErrorBoundary", "Error reporting would be triggered in production:", { errorId, componentName });
|
|
1787
|
-
}
|
|
1788
|
-
};
|
|
1789
|
-
this.handleRetry = () => {
|
|
1790
|
-
const { maxRetries = 3 } = this.props;
|
|
1791
|
-
const { retryCount } = this.state;
|
|
1792
|
-
if (retryCount < maxRetries) {
|
|
1793
|
-
logger.debug("ErrorBoundary", `Retrying component render (attempt ${retryCount + 1}/${maxRetries})`);
|
|
1794
|
-
this.setState((prevState) => ({
|
|
1795
|
-
hasError: false,
|
|
1796
|
-
error: void 0,
|
|
1797
|
-
errorInfo: void 0,
|
|
1798
|
-
errorId: void 0,
|
|
1799
|
-
retryCount: prevState.retryCount + 1
|
|
1800
|
-
}));
|
|
1801
|
-
}
|
|
1802
|
-
};
|
|
1803
|
-
this.state = {
|
|
1804
|
-
hasError: false,
|
|
1805
|
-
retryCount: 0
|
|
1806
|
-
};
|
|
1807
|
-
}
|
|
1808
|
-
static getDerivedStateFromError(error) {
|
|
1809
|
-
const errorId = `error_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
1810
|
-
return {
|
|
1811
|
-
hasError: true,
|
|
1812
|
-
error,
|
|
1813
|
-
errorId
|
|
1814
|
-
};
|
|
1815
|
-
}
|
|
1816
|
-
componentDidCatch(error, errorInfo) {
|
|
1817
|
-
const { componentName = "Unknown Component", onError, enableReporting = true } = this.props;
|
|
1818
|
-
const errorId = this.state.errorId;
|
|
1819
|
-
this.setState({ errorInfo });
|
|
1820
|
-
logger.error("ErrorBoundary", `[${componentName}] Caught error ${errorId}:`, error, errorInfo);
|
|
1821
|
-
performanceBudgetMonitor.measure("ERROR_BOUNDARY_TRIGGER", 1, {
|
|
1822
|
-
componentName,
|
|
1823
|
-
errorId,
|
|
1824
|
-
errorMessage: error.message,
|
|
1825
|
-
stack: error.stack?.substring(0, 200)
|
|
1826
|
-
// Truncated stack trace
|
|
1827
|
-
});
|
|
1828
|
-
if (enableReporting) {
|
|
1829
|
-
this.reportError(errorId, componentName);
|
|
1830
|
-
}
|
|
1831
|
-
if (onError) {
|
|
1832
|
-
onError(error, errorInfo, errorId);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
componentWillUnmount() {
|
|
1836
|
-
if (this.retryTimeoutId) {
|
|
1837
|
-
clearTimeout(this.retryTimeoutId);
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
render() {
|
|
1841
|
-
if (this.state.hasError) {
|
|
1842
|
-
const {
|
|
1843
|
-
componentName = "Component",
|
|
1844
|
-
fallback,
|
|
1845
|
-
enableRetry = true,
|
|
1846
|
-
maxRetries = 3
|
|
1847
|
-
} = this.props;
|
|
1848
|
-
const { retryCount, errorId } = this.state;
|
|
1849
|
-
if (fallback) {
|
|
1850
|
-
return fallback;
|
|
1851
|
-
}
|
|
1852
|
-
return /* @__PURE__ */ jsx(
|
|
1853
|
-
"div",
|
|
1854
|
-
{
|
|
1855
|
-
role: "alert",
|
|
1856
|
-
className: "p-6 bg-destructive/10 border border-destructive/20 rounded-lg",
|
|
1857
|
-
"data-error-boundary": errorId,
|
|
1858
|
-
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
1859
|
-
/* @__PURE__ */ jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5 text-destructive", viewBox: "0 0 20 20", fill: "currentColor", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) }) }),
|
|
1860
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
1861
|
-
/* @__PURE__ */ jsxs("h3", { className: "text-destructive", children: [
|
|
1862
|
-
"Error in ",
|
|
1863
|
-
componentName
|
|
1864
|
-
] }),
|
|
1865
|
-
/* @__PURE__ */ jsx("p", { className: "text-destructive/80", children: this.state.error?.message || "An unexpected error occurred." }),
|
|
1866
|
-
enableRetry && retryCount < maxRetries && /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mb-4", children: [
|
|
1867
|
-
/* @__PURE__ */ jsxs(
|
|
1868
|
-
"button",
|
|
1869
|
-
{
|
|
1870
|
-
onClick: this.handleRetry,
|
|
1871
|
-
className: "px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90 transition-colors text-sm font-medium",
|
|
1872
|
-
children: [
|
|
1873
|
-
"Retry (",
|
|
1874
|
-
retryCount + 1,
|
|
1875
|
-
"/",
|
|
1876
|
-
maxRetries,
|
|
1877
|
-
")"
|
|
1878
|
-
]
|
|
1879
|
-
}
|
|
1880
|
-
),
|
|
1881
|
-
/* @__PURE__ */ jsx(
|
|
1882
|
-
"button",
|
|
1883
|
-
{
|
|
1884
|
-
onClick: () => window.location.reload(),
|
|
1885
|
-
className: "px-4 py-2 bg-sec-600 text-main-50 rounded-md hover:bg-sec-700 transition-colors text-sm font-medium",
|
|
1886
|
-
children: "Reload Page"
|
|
1887
|
-
}
|
|
1888
|
-
)
|
|
1889
|
-
] }),
|
|
1890
|
-
retryCount >= maxRetries && /* @__PURE__ */ jsxs("div", { className: "mb-4 p-3 bg-acc-50 border border-acc-200 rounded-md", children: [
|
|
1891
|
-
/* @__PURE__ */ jsx("p", { className: "text-acc-800", children: "Maximum retry attempts reached. Please reload the page or contact support." }),
|
|
1892
|
-
/* @__PURE__ */ jsx(
|
|
1893
|
-
"button",
|
|
1894
|
-
{
|
|
1895
|
-
onClick: () => window.location.reload(),
|
|
1896
|
-
className: "mt-2 px-3 py-1 bg-acc-600 text-main-50 rounded text-sm hover:bg-acc-700",
|
|
1897
|
-
children: "Reload Page"
|
|
1898
|
-
}
|
|
1899
|
-
)
|
|
1900
|
-
] }),
|
|
1901
|
-
import.meta.env.MODE === "development" && this.state.error && /* @__PURE__ */ jsxs("details", { className: "text-sm text-destructive/70", children: [
|
|
1902
|
-
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium mb-2", children: "Error Details (Development)" }),
|
|
1903
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-destructive/5 p-3 rounded border", children: [
|
|
1904
|
-
/* @__PURE__ */ jsxs("p", { className: "font-mono", children: [
|
|
1905
|
-
"Error ID: ",
|
|
1906
|
-
errorId
|
|
1907
|
-
] }),
|
|
1908
|
-
/* @__PURE__ */ jsxs("pre", { className: "whitespace-pre-wrap text-xs overflow-auto max-h-32", children: [
|
|
1909
|
-
this.state.error.toString(),
|
|
1910
|
-
this.state.errorInfo?.componentStack
|
|
1911
|
-
] })
|
|
1912
|
-
] })
|
|
1913
|
-
] })
|
|
1914
|
-
] })
|
|
1915
|
-
] })
|
|
1916
|
-
}
|
|
1917
|
-
);
|
|
1918
|
-
}
|
|
1919
|
-
return this.props.children;
|
|
1920
|
-
}
|
|
1921
|
-
};
|
|
1922
|
-
|
|
1923
|
-
// src/components/PublicLayout/PublicPageProvider.tsx
|
|
1924
|
-
import { createContext, useContext, useMemo as useMemo2 } from "react";
|
|
1925
|
-
import { createClient } from "@supabase/supabase-js";
|
|
1926
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
1927
|
-
var PublicPageContext = createContext(void 0);
|
|
1928
|
-
function PublicPageProvider({ children, appName }) {
|
|
1929
|
-
const getEnvVar = (key) => {
|
|
1930
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
1931
|
-
const env = import.meta.env;
|
|
1932
|
-
return env[key];
|
|
1933
|
-
}
|
|
1934
|
-
if (typeof process !== "undefined" && process.env) {
|
|
1935
|
-
return process.env[key];
|
|
1936
|
-
}
|
|
1937
|
-
return void 0;
|
|
1938
|
-
};
|
|
1939
|
-
const supabaseUrl = getEnvVar("VITE_SUPABASE_URL") || getEnvVar("NEXT_PUBLIC_SUPABASE_URL") || null;
|
|
1940
|
-
const supabaseKey = getEnvVar("VITE_SUPABASE_ANON_KEY") || getEnvVar("NEXT_PUBLIC_SUPABASE_ANON_KEY") || null;
|
|
1941
|
-
const supabase = useMemo2(() => {
|
|
1942
|
-
if (!supabaseUrl || !supabaseKey) {
|
|
1943
|
-
logger.warn("PublicPageProvider", "Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY are set in your environment. Use publishable key if anon key is disabled.");
|
|
1944
|
-
return null;
|
|
1945
|
-
}
|
|
1946
|
-
const client = createClient(supabaseUrl, supabaseKey);
|
|
1947
|
-
logger.info("PublicPageProvider", "Supabase client created successfully for public pages");
|
|
1948
|
-
return client;
|
|
1949
|
-
}, [supabaseUrl, supabaseKey]);
|
|
1950
|
-
const contextValue = {
|
|
1951
|
-
isPublicPage: true,
|
|
1952
|
-
supabase,
|
|
1953
|
-
appName: appName || null,
|
|
1954
|
-
environment: {
|
|
1955
|
-
supabaseUrl,
|
|
1956
|
-
supabaseKey
|
|
1957
|
-
}
|
|
1958
|
-
};
|
|
1959
|
-
return /* @__PURE__ */ jsx2(PublicPageContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx2(ErrorBoundary, { componentName: "PublicPageProvider", children }) });
|
|
1960
|
-
}
|
|
1961
|
-
function usePublicPageContext() {
|
|
1962
|
-
const context = useContext(PublicPageContext);
|
|
1963
|
-
if (!context) {
|
|
1964
|
-
throw new Error("usePublicPageContext must be used within a PublicPageProvider");
|
|
1965
|
-
}
|
|
1966
|
-
return context;
|
|
1967
|
-
}
|
|
1968
|
-
function useIsPublicPage() {
|
|
1969
|
-
const context = useContext(PublicPageContext);
|
|
1970
|
-
return context !== void 0;
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
2071
|
// src/hooks/useEventTheme.ts
|
|
1974
2072
|
import { useEffect as useEffect6 } from "react";
|
|
1975
2073
|
import { useLocation } from "react-router-dom";
|
|
@@ -2059,54 +2157,9 @@ function usePreventTabReload(options = {}) {
|
|
|
2059
2157
|
}, [enabled, gracePeriodMs]);
|
|
2060
2158
|
}
|
|
2061
2159
|
|
|
2062
|
-
// src/hooks/useAppConfig.ts
|
|
2063
|
-
import { useMemo as useMemo3, useContext as useContext2 } from "react";
|
|
2064
|
-
function useAppConfig() {
|
|
2065
|
-
const isPublicPage = useIsPublicPage();
|
|
2066
|
-
const publicPageContext = useContext2(PublicPageContext);
|
|
2067
|
-
const contextAppName = publicPageContext?.appName || null;
|
|
2068
|
-
if (isPublicPage) {
|
|
2069
|
-
const getAppName = () => {
|
|
2070
|
-
if (contextAppName) {
|
|
2071
|
-
return contextAppName;
|
|
2072
|
-
}
|
|
2073
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
2074
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
2075
|
-
}
|
|
2076
|
-
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
2077
|
-
return import.meta.env.VITE_APP_NAME || import.meta.env.NEXT_PUBLIC_APP_NAME || "PACE";
|
|
2078
|
-
}
|
|
2079
|
-
return "PACE";
|
|
2080
|
-
};
|
|
2081
|
-
return useMemo3(() => ({
|
|
2082
|
-
supportsDirectAccess: false,
|
|
2083
|
-
// Public pages don't support direct access
|
|
2084
|
-
requiresEvent: true,
|
|
2085
|
-
// Public pages always require an event
|
|
2086
|
-
isLoading: false,
|
|
2087
|
-
appName: getAppName()
|
|
2088
|
-
}), [contextAppName]);
|
|
2089
|
-
}
|
|
2090
|
-
try {
|
|
2091
|
-
const { appConfig, appName } = useUnifiedAuth();
|
|
2092
|
-
return useMemo3(() => ({
|
|
2093
|
-
supportsDirectAccess: !(appConfig?.requires_event ?? true),
|
|
2094
|
-
requiresEvent: appConfig?.requires_event ?? true,
|
|
2095
|
-
isLoading: appConfig === null,
|
|
2096
|
-
appName
|
|
2097
|
-
}), [appConfig?.requires_event, appName]);
|
|
2098
|
-
} catch (error) {
|
|
2099
|
-
return useMemo3(() => ({
|
|
2100
|
-
supportsDirectAccess: false,
|
|
2101
|
-
requiresEvent: true,
|
|
2102
|
-
isLoading: false,
|
|
2103
|
-
appName: "PACE"
|
|
2104
|
-
}), []);
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
2160
|
export {
|
|
2109
2161
|
useDebounce,
|
|
2162
|
+
cleanupQueryCache,
|
|
2110
2163
|
useQueryCache,
|
|
2111
2164
|
queryCacheHelpers,
|
|
2112
2165
|
useAddressAutocomplete,
|
|
@@ -2137,13 +2190,7 @@ export {
|
|
|
2137
2190
|
clearFileDisplayCache,
|
|
2138
2191
|
getFileDisplayCacheStats,
|
|
2139
2192
|
invalidateFileDisplayCache,
|
|
2140
|
-
ErrorBoundary,
|
|
2141
|
-
PublicPageContext,
|
|
2142
|
-
PublicPageProvider,
|
|
2143
|
-
usePublicPageContext,
|
|
2144
|
-
useIsPublicPage,
|
|
2145
2193
|
useEventTheme,
|
|
2146
|
-
usePreventTabReload
|
|
2147
|
-
useAppConfig
|
|
2194
|
+
usePreventTabReload
|
|
2148
2195
|
};
|
|
2149
|
-
//# sourceMappingURL=chunk-
|
|
2196
|
+
//# sourceMappingURL=chunk-NIU6J6OX.js.map
|