@jmruthers/pace-core 0.5.190 → 0.5.193
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/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
- package/dist/{DataTable-ON3IXISJ.js → DataTable-5FU7IESH.js} +7 -6
- package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +4 -2
- package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
- package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
- package/dist/{api-I6UCQ5S6.js → api-N774RPUA.js} +2 -2
- package/dist/chunk-6C4YBBJM 5.js +628 -0
- package/dist/chunk-7D4SUZUM.js 2.map +1 -0
- package/dist/{chunk-73HSNNOQ.js → chunk-7EQTDTTJ.js} +47 -74
- package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
- package/dist/chunk-7EQTDTTJ.js.map +1 -0
- package/dist/{chunk-J2XXC7R5.js → chunk-7FLMSG37.js} +409 -244
- package/dist/chunk-7FLMSG37.js 2.map +1 -0
- package/dist/chunk-7FLMSG37.js.map +1 -0
- package/dist/{chunk-NIU6J6OX.js → chunk-BC4IJKSL.js} +23 -32
- package/dist/chunk-BC4IJKSL.js.map +1 -0
- package/dist/{chunk-SDMHPX3X.js → chunk-E3SPN4VZ 5.js } +198 -53
- package/dist/chunk-E3SPN4VZ.js +12917 -0
- package/dist/{chunk-SDMHPX3X.js.map → chunk-E3SPN4VZ.js.map} +1 -1
- package/dist/chunk-E66EQZE6 5.js +37 -0
- package/dist/chunk-E66EQZE6.js 2.map +1 -0
- package/dist/{chunk-DZWK57KZ.js → chunk-G37KK66H.js} +1 -1
- package/dist/{chunk-DZWK57KZ.js.map → chunk-G37KK66H.js.map} +1 -1
- package/dist/{chunk-STYK4OH2.js → chunk-HWIIPPNI.js} +44 -225
- package/dist/chunk-HWIIPPNI.js.map +1 -0
- package/dist/chunk-I7PSE6JW 5.js +191 -0
- package/dist/chunk-I7PSE6JW.js 2.map +1 -0
- package/dist/{chunk-Y4BUBBHD.js → chunk-IIELH4DL.js} +211 -136
- package/dist/chunk-IIELH4DL.js.map +1 -0
- package/dist/{chunk-RUYZKXOD.js → chunk-KNC55RTG.js} +17 -5
- package/dist/chunk-KNC55RTG.js 5.map +1 -0
- package/dist/chunk-KNC55RTG.js.map +1 -0
- package/dist/chunk-KQCRWDSA.js 5.map +1 -0
- package/dist/{chunk-4QYC5L4K.js → chunk-LFNCN2SP.js} +26 -30
- package/dist/chunk-LFNCN2SP.js 2.map +1 -0
- package/dist/chunk-LFNCN2SP.js.map +1 -0
- package/dist/chunk-LMC26NLJ 2.js +84 -0
- package/dist/{chunk-VVBAW5A5.js → chunk-NOAYCWCX 5.js } +118 -110
- package/dist/chunk-NOAYCWCX.js +4993 -0
- package/dist/chunk-NOAYCWCX.js.map +1 -0
- package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
- package/dist/chunk-QXHPKYJV 3.js +113 -0
- package/dist/chunk-R77UEZ4E 3.js +68 -0
- package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
- package/dist/{chunk-HQVPB5MZ.js → chunk-XNXXZ43G.js} +77 -33
- package/dist/chunk-XNXXZ43G.js.map +1 -0
- package/dist/chunk-ZSAAAMVR 6.js +25 -0
- package/dist/components.d.ts +4 -4
- package/dist/components.js +8 -8
- package/dist/components.js 5.map +1 -0
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/hooks.d.ts +12 -12
- package/dist/hooks.js +9 -9
- package/dist/index.d.ts +11 -11
- package/dist/index.js +20 -27
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -20
- package/dist/rbac/index.js +7 -9
- package/dist/styles/index 2.js +12 -0
- package/dist/styles/index.js 5.map +1 -0
- package/dist/theming/runtime 5.js +19 -0
- package/dist/theming/runtime.js 5.map +1 -0
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-DxIDS4bC.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +1 -1
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +2 -2
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +10 -10
- package/docs/api/classes/StorageUtils.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- 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 +24 -11
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- 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 +2 -2
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- package/docs/api/interfaces/ProgressProps.md +1 -1
- 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 +2 -2
- 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 +2 -2
- 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 +2 -2
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +2 -2
- 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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/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 +60 -38
- 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 +2 -2
- 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 +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +202 -217
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +768 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/event-based-apps.md +124 -6
- package/package.json +1 -1
- package/scripts/check-pace-core-compliance.cjs +292 -57
- package/src/__tests__/public-recipe-view.test.ts +10 -10
- package/src/__tests__/rls-policies.test.ts +16 -14
- package/src/components/AddressField/README.md +6 -6
- package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
- package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
- package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
- package/src/components/FileDisplay/FileDisplay.tsx +16 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
- package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -16
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
- package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +60 -15
- package/src/hooks/__tests__/usePermissionCache.simple.test.ts +192 -0
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +741 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +703 -0
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +581 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +23 -15
- package/src/hooks/public/usePublicEvent.ts +8 -8
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/services/useAuthService.ts +21 -3
- package/src/hooks/services/useEventService.ts +21 -3
- package/src/hooks/services/useInactivityService.ts +21 -3
- package/src/hooks/services/useOrganisationService.ts +21 -3
- package/src/hooks/useFileDisplay.ts +18 -26
- package/src/hooks/useQueryCache.ts +6 -6
- package/src/hooks/useSecureDataAccess.test.ts +24 -17
- package/src/hooks/useSecureDataAccess.ts +18 -13
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/providers/services/EventServiceProvider.tsx +0 -8
- package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
- package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
- package/src/rbac/adapters.tsx +3 -22
- package/src/rbac/api.test.ts +2 -2
- package/src/rbac/api.ts +7 -1
- package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
- package/src/rbac/components/NavigationGuard.tsx +1 -10
- package/src/rbac/components/NavigationProvider.tsx +0 -1
- package/src/rbac/components/PermissionEnforcer.tsx +45 -12
- package/src/rbac/components/SecureDataProvider.tsx +0 -1
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
- package/src/rbac/engine.ts +14 -2
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/usePermissions.ts +51 -11
- package/src/rbac/hooks/useRBAC.simple.test.ts +95 -0
- package/src/rbac/hooks/useRBAC.ts +3 -13
- package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
- package/src/rbac/hooks/useResolvedScope.ts +58 -33
- package/src/rbac/hooks/useSecureSupabase.ts +4 -9
- package/src/rbac/secureClient.ts +31 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +2 -2
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +490 -0
- package/src/rbac/utils/eventContext.ts +5 -2
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +4 -57
- package/src/services/InactivityService.ts +127 -34
- package/src/services/OrganisationService.ts +160 -149
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/file-reference/index.ts +4 -4
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/dist/chunk-4QYC5L4K.js.map +0 -1
- package/dist/chunk-73HSNNOQ.js.map +0 -1
- package/dist/chunk-HQVPB5MZ.js.map +0 -1
- package/dist/chunk-J2XXC7R5.js.map +0 -1
- package/dist/chunk-NIU6J6OX.js.map +0 -1
- package/dist/chunk-RUYZKXOD.js.map +0 -1
- package/dist/chunk-STYK4OH2.js.map +0 -1
- package/dist/chunk-VVBAW5A5.js.map +0 -1
- package/dist/chunk-Y4BUBBHD.js.map +0 -1
- package/scripts/check-pace-core-compliance.js +0 -512
- package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
- package/src/utils/context/superAdminOverride.ts +0 -58
- /package/dist/{DataTable-ON3IXISJ.js.map → DataTable-5FU7IESH.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
- /package/dist/{api-I6UCQ5S6.js.map → api-N774RPUA.js.map} +0 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
# Person-Scoped Profiles Migration Guide
|
|
2
|
+
|
|
3
|
+
**Version:** 0.5.190+
|
|
4
|
+
**Migration Date:** 2025-12-05
|
|
5
|
+
**Breaking Changes:** Yes
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Profiles (membership, medical, and contact) have been migrated from **organisation-scoped** to **person-scoped**. Each person now has:
|
|
10
|
+
|
|
11
|
+
- **One membership profile** (`pace_member`) - regardless of organisation
|
|
12
|
+
- **One medical profile** (`medi_profile`) - regardless of organisation
|
|
13
|
+
- **Multiple contact profiles** (`pace_contact`) - person-scoped, multiple per type allowed
|
|
14
|
+
|
|
15
|
+
This change means profiles are no longer tied to specific organisations and are shared across all organisations a person belongs to.
|
|
16
|
+
|
|
17
|
+
## What Changed
|
|
18
|
+
|
|
19
|
+
### Tables Modified
|
|
20
|
+
|
|
21
|
+
#### 1. `pace_member`
|
|
22
|
+
- **Removed:** `organisation_id` column
|
|
23
|
+
- **Added:** Unique constraint on `person_id` (one membership per person)
|
|
24
|
+
- **Impact:** A person can now only have ONE membership record, regardless of how many organisations they belong to
|
|
25
|
+
|
|
26
|
+
#### 2. `medi_profile`
|
|
27
|
+
- **Removed:** `organisation_id` column
|
|
28
|
+
- **Impact:** One medical profile per person (via member relationship)
|
|
29
|
+
|
|
30
|
+
#### 3. `pace_contact`
|
|
31
|
+
- **Removed:** `organisation_id` and `member_id` columns
|
|
32
|
+
- **Added:** `person_id` column (NOT NULL, FK to `pace_person`)
|
|
33
|
+
- **Impact:** Contacts are now directly linked to persons, not members
|
|
34
|
+
|
|
35
|
+
#### 4. Related Tables (organisation_id removed)
|
|
36
|
+
- `medi_condition`
|
|
37
|
+
- `medi_diet`
|
|
38
|
+
- `medi_action_plan`
|
|
39
|
+
- `medi_profile_versions`
|
|
40
|
+
- `pace_consent`
|
|
41
|
+
- `pace_identification`
|
|
42
|
+
- `pace_qualification`
|
|
43
|
+
|
|
44
|
+
### RLS Policy Changes
|
|
45
|
+
|
|
46
|
+
All RLS policies for these tables have been updated to:
|
|
47
|
+
- Remove organisation-based access checks
|
|
48
|
+
- Use person-scoped access control instead
|
|
49
|
+
- Allow access based on person ownership, not organisation membership
|
|
50
|
+
|
|
51
|
+
## Breaking Changes
|
|
52
|
+
|
|
53
|
+
### 1. Queries Filtering by `organisation_id`
|
|
54
|
+
|
|
55
|
+
**Before:**
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ This will fail - organisation_id column no longer exists
|
|
58
|
+
const { data } = await supabase
|
|
59
|
+
.from('pace_member')
|
|
60
|
+
.select('*')
|
|
61
|
+
.eq('organisation_id', orgId);
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**After:**
|
|
65
|
+
```typescript
|
|
66
|
+
// ✅ Filter by person_id instead
|
|
67
|
+
const { data } = await supabase
|
|
68
|
+
.from('pace_member')
|
|
69
|
+
.select('*')
|
|
70
|
+
.eq('person_id', personId);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 2. Joins Using `organisation_id`
|
|
74
|
+
|
|
75
|
+
**Before:**
|
|
76
|
+
```typescript
|
|
77
|
+
// ❌ This will fail
|
|
78
|
+
const { data } = await supabase
|
|
79
|
+
.from('pace_member')
|
|
80
|
+
.select('*, organisations(*)')
|
|
81
|
+
.eq('organisation_id', orgId);
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**After:**
|
|
85
|
+
```typescript
|
|
86
|
+
// ✅ Join via person → user → organisation memberships
|
|
87
|
+
const { data } = await supabase
|
|
88
|
+
.from('pace_member')
|
|
89
|
+
.select('*, pace_person(*, user_profiles(*))')
|
|
90
|
+
.eq('person_id', personId);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 3. Contact Queries Using `member_id`
|
|
94
|
+
|
|
95
|
+
**Before:**
|
|
96
|
+
```typescript
|
|
97
|
+
// ❌ This will fail - member_id column no longer exists
|
|
98
|
+
const { data } = await supabase
|
|
99
|
+
.from('pace_contact')
|
|
100
|
+
.select('*')
|
|
101
|
+
.eq('member_id', memberId);
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**After:**
|
|
105
|
+
```typescript
|
|
106
|
+
// ✅ Use person_id instead
|
|
107
|
+
const { data } = await supabase
|
|
108
|
+
.from('pace_contact')
|
|
109
|
+
.select('*')
|
|
110
|
+
.eq('person_id', personId);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4. Insert/Update Operations
|
|
114
|
+
|
|
115
|
+
**Before:**
|
|
116
|
+
```typescript
|
|
117
|
+
// ❌ This will fail - organisation_id required
|
|
118
|
+
await supabase
|
|
119
|
+
.from('pace_member')
|
|
120
|
+
.insert({
|
|
121
|
+
person_id: personId,
|
|
122
|
+
organisation_id: orgId, // ❌ Column doesn't exist
|
|
123
|
+
membership_number: '12345'
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**After:**
|
|
128
|
+
```typescript
|
|
129
|
+
// ✅ No organisation_id needed
|
|
130
|
+
await supabase
|
|
131
|
+
.from('pace_member')
|
|
132
|
+
.insert({
|
|
133
|
+
person_id: personId,
|
|
134
|
+
membership_number: '12345'
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Impact Assessment Checklist
|
|
139
|
+
|
|
140
|
+
Use this checklist to identify areas in your app that need updates:
|
|
141
|
+
|
|
142
|
+
### Database Queries
|
|
143
|
+
|
|
144
|
+
- [ ] Search codebase for `pace_member` queries filtering by `organisation_id`
|
|
145
|
+
- [ ] Search codebase for `medi_profile` queries filtering by `organisation_id`
|
|
146
|
+
- [ ] Search codebase for `pace_contact` queries using `member_id`
|
|
147
|
+
- [ ] Search codebase for `pace_consent` queries filtering by `organisation_id`
|
|
148
|
+
- [ ] Search codebase for `pace_identification` queries filtering by `organisation_id`
|
|
149
|
+
- [ ] Search codebase for `pace_qualification` queries filtering by `organisation_id`
|
|
150
|
+
- [ ] Search codebase for `medi_condition`, `medi_diet`, `medi_action_plan` queries filtering by `organisation_id`
|
|
151
|
+
|
|
152
|
+
### TypeScript Types
|
|
153
|
+
|
|
154
|
+
- [ ] Update TypeScript types (regenerate from database schema)
|
|
155
|
+
- [ ] Remove `organisation_id` from type definitions for profile tables
|
|
156
|
+
- [ ] Update `pace_contact` types to use `person_id` instead of `member_id`
|
|
157
|
+
|
|
158
|
+
### UI Components
|
|
159
|
+
|
|
160
|
+
- [ ] Review profile display components - remove organisation context assumptions
|
|
161
|
+
- [ ] Update forms that create/edit profiles - remove organisation_id fields
|
|
162
|
+
- [ ] Update contact management - use person_id instead of member_id
|
|
163
|
+
- [ ] Review profile selection/filtering UI - remove organisation filters
|
|
164
|
+
|
|
165
|
+
### Business Logic
|
|
166
|
+
|
|
167
|
+
- [ ] Review code that assumes multiple memberships per person
|
|
168
|
+
- [ ] Update logic that creates profiles per organisation
|
|
169
|
+
- [ ] Review permission checks that rely on organisation_id for profiles
|
|
170
|
+
- [ ] Update any code that merges/duplicates profiles across organisations
|
|
171
|
+
|
|
172
|
+
### Data Access Patterns
|
|
173
|
+
|
|
174
|
+
- [ ] Review use of `useSecureDataAccess` hook with profile tables
|
|
175
|
+
- [ ] Update any custom data access utilities
|
|
176
|
+
- [ ] Review RPC functions that query profile tables
|
|
177
|
+
|
|
178
|
+
## Code Migration Examples
|
|
179
|
+
|
|
180
|
+
### Example 1: Fetching Member Profile
|
|
181
|
+
|
|
182
|
+
**Before:**
|
|
183
|
+
```typescript
|
|
184
|
+
async function getMemberProfile(personId: string, orgId: string) {
|
|
185
|
+
const { data } = await supabase
|
|
186
|
+
.from('pace_member')
|
|
187
|
+
.select('*')
|
|
188
|
+
.eq('person_id', personId)
|
|
189
|
+
.eq('organisation_id', orgId) // ❌
|
|
190
|
+
.single();
|
|
191
|
+
return data;
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
**After:**
|
|
196
|
+
```typescript
|
|
197
|
+
async function getMemberProfile(personId: string) {
|
|
198
|
+
// ✅ No organisation_id needed - one profile per person
|
|
199
|
+
const { data } = await supabase
|
|
200
|
+
.from('pace_member')
|
|
201
|
+
.select('*')
|
|
202
|
+
.eq('person_id', personId)
|
|
203
|
+
.single();
|
|
204
|
+
return data;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Example 2: Fetching Contacts
|
|
209
|
+
|
|
210
|
+
**Before:**
|
|
211
|
+
```typescript
|
|
212
|
+
async function getContacts(memberId: string) {
|
|
213
|
+
const { data } = await supabase
|
|
214
|
+
.from('pace_contact')
|
|
215
|
+
.select('*')
|
|
216
|
+
.eq('member_id', memberId) // ❌
|
|
217
|
+
.eq('organisation_id', orgId); // ❌
|
|
218
|
+
return data;
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**After:**
|
|
223
|
+
```typescript
|
|
224
|
+
async function getContacts(personId: string) {
|
|
225
|
+
// ✅ Use person_id directly
|
|
226
|
+
const { data } = await supabase
|
|
227
|
+
.from('pace_contact')
|
|
228
|
+
.select('*')
|
|
229
|
+
.eq('person_id', personId);
|
|
230
|
+
return data;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Example 3: Creating Medical Profile
|
|
235
|
+
|
|
236
|
+
**Before:**
|
|
237
|
+
```typescript
|
|
238
|
+
async function createMedicalProfile(memberId: string, orgId: string, data: MedicalData) {
|
|
239
|
+
const { data: profile } = await supabase
|
|
240
|
+
.from('medi_profile')
|
|
241
|
+
.insert({
|
|
242
|
+
member_id: memberId,
|
|
243
|
+
organisation_id: orgId, // ❌
|
|
244
|
+
...data
|
|
245
|
+
})
|
|
246
|
+
.select()
|
|
247
|
+
.single();
|
|
248
|
+
return profile;
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**After:**
|
|
253
|
+
```typescript
|
|
254
|
+
async function createMedicalProfile(memberId: string, data: MedicalData) {
|
|
255
|
+
// ✅ No organisation_id needed
|
|
256
|
+
const { data: profile } = await supabase
|
|
257
|
+
.from('medi_profile')
|
|
258
|
+
.insert({
|
|
259
|
+
member_id: memberId,
|
|
260
|
+
...data
|
|
261
|
+
})
|
|
262
|
+
.select()
|
|
263
|
+
.single();
|
|
264
|
+
return profile;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Example 4: Getting Person's Member in Organisation Context
|
|
269
|
+
|
|
270
|
+
**Before:**
|
|
271
|
+
```typescript
|
|
272
|
+
// ❌ This pattern no longer works
|
|
273
|
+
const member = await getMemberForPersonInOrg(personId, orgId);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**After:**
|
|
277
|
+
```typescript
|
|
278
|
+
// ✅ Get the person's single membership
|
|
279
|
+
const { data: member } = await supabase
|
|
280
|
+
.from('pace_member')
|
|
281
|
+
.select('*')
|
|
282
|
+
.eq('person_id', personId)
|
|
283
|
+
.single();
|
|
284
|
+
|
|
285
|
+
// If you need to check organisation access, check via rbac_organisation_roles
|
|
286
|
+
const { data: orgRoles } = await supabase
|
|
287
|
+
.from('rbac_organisation_roles')
|
|
288
|
+
.select('organisation_id')
|
|
289
|
+
.eq('user_id', userId)
|
|
290
|
+
.eq('organisation_id', orgId)
|
|
291
|
+
.eq('status', 'active');
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Testing Recommendations
|
|
295
|
+
|
|
296
|
+
### 1. Unit Tests
|
|
297
|
+
|
|
298
|
+
- [ ] Update test fixtures to remove `organisation_id` from profile records
|
|
299
|
+
- [ ] Update mock data generators
|
|
300
|
+
- [ ] Fix tests that assert organisation_id values
|
|
301
|
+
|
|
302
|
+
### 2. Integration Tests
|
|
303
|
+
|
|
304
|
+
- [ ] Test profile creation without organisation_id
|
|
305
|
+
- [ ] Test profile queries by person_id
|
|
306
|
+
- [ ] Test contact management with person_id
|
|
307
|
+
- [ ] Verify RLS policies work correctly
|
|
308
|
+
|
|
309
|
+
### 3. E2E Tests
|
|
310
|
+
|
|
311
|
+
- [ ] Test profile display across different organisations
|
|
312
|
+
- [ ] Verify profiles are shared correctly
|
|
313
|
+
- [ ] Test contact management flows
|
|
314
|
+
- [ ] Verify permission checks still work
|
|
315
|
+
|
|
316
|
+
### 4. Data Validation
|
|
317
|
+
|
|
318
|
+
- [ ] Verify no duplicate memberships exist per person
|
|
319
|
+
- [ ] Verify medical profiles are correctly linked
|
|
320
|
+
- [ ] Verify contacts are correctly migrated to person_id
|
|
321
|
+
|
|
322
|
+
## Common Patterns to Update
|
|
323
|
+
|
|
324
|
+
### Pattern 1: Organisation-Scoped Profile Lists
|
|
325
|
+
|
|
326
|
+
**Before:**
|
|
327
|
+
```typescript
|
|
328
|
+
// Get all members in an organisation
|
|
329
|
+
const { data } = await supabase
|
|
330
|
+
.from('pace_member')
|
|
331
|
+
.select('*')
|
|
332
|
+
.eq('organisation_id', orgId);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**After:**
|
|
336
|
+
```typescript
|
|
337
|
+
// Get all members in an organisation via person → user → org roles
|
|
338
|
+
const { data: orgMembers } = await supabase
|
|
339
|
+
.from('rbac_organisation_roles')
|
|
340
|
+
.select(`
|
|
341
|
+
organisation_id,
|
|
342
|
+
user_id,
|
|
343
|
+
pace_person!inner(
|
|
344
|
+
id,
|
|
345
|
+
pace_member(*)
|
|
346
|
+
)
|
|
347
|
+
`)
|
|
348
|
+
.eq('organisation_id', orgId)
|
|
349
|
+
.eq('status', 'active');
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Pattern 2: Profile Existence Checks
|
|
353
|
+
|
|
354
|
+
**Before:**
|
|
355
|
+
```typescript
|
|
356
|
+
// Check if member exists in organisation
|
|
357
|
+
const { data } = await supabase
|
|
358
|
+
.from('pace_member')
|
|
359
|
+
.select('id')
|
|
360
|
+
.eq('person_id', personId)
|
|
361
|
+
.eq('organisation_id', orgId)
|
|
362
|
+
.single();
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**After:**
|
|
366
|
+
```typescript
|
|
367
|
+
// Check if person has a membership (regardless of org)
|
|
368
|
+
const { data } = await supabase
|
|
369
|
+
.from('pace_member')
|
|
370
|
+
.select('id')
|
|
371
|
+
.eq('person_id', personId)
|
|
372
|
+
.single();
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Pattern 3: Profile Creation with Organisation Context
|
|
376
|
+
|
|
377
|
+
**Before:**
|
|
378
|
+
```typescript
|
|
379
|
+
// Create profile for person in organisation
|
|
380
|
+
await supabase
|
|
381
|
+
.from('pace_member')
|
|
382
|
+
.insert({
|
|
383
|
+
person_id: personId,
|
|
384
|
+
organisation_id: orgId,
|
|
385
|
+
membership_number: generateNumber()
|
|
386
|
+
});
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
**After:**
|
|
390
|
+
```typescript
|
|
391
|
+
// Create profile for person (one per person, not per org)
|
|
392
|
+
// Check if already exists first
|
|
393
|
+
const { data: existing } = await supabase
|
|
394
|
+
.from('pace_member')
|
|
395
|
+
.select('id')
|
|
396
|
+
.eq('person_id', personId)
|
|
397
|
+
.single();
|
|
398
|
+
|
|
399
|
+
if (!existing) {
|
|
400
|
+
await supabase
|
|
401
|
+
.from('pace_member')
|
|
402
|
+
.insert({
|
|
403
|
+
person_id: personId,
|
|
404
|
+
membership_number: generateNumber()
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## TypeScript Type Updates
|
|
410
|
+
|
|
411
|
+
After applying migrations, regenerate your database types:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
npx supabase gen types typescript --project-id <your-project-id> > src/types/database.generated.ts
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The updated types will reflect:
|
|
418
|
+
- Removed `organisation_id` from profile tables
|
|
419
|
+
- `pace_contact` using `person_id` instead of `member_id`
|
|
420
|
+
- Updated relationships
|
|
421
|
+
|
|
422
|
+
## RLS Policy Behavior
|
|
423
|
+
|
|
424
|
+
RLS policies now work as follows:
|
|
425
|
+
|
|
426
|
+
1. **Person Ownership**: Users can access profiles for persons they own (via `pace_person.user_id`)
|
|
427
|
+
2. **Contact Permissions**: Users can access contacts where they are the contact person or own the person
|
|
428
|
+
3. **No Organisation Filtering**: Profiles are accessible regardless of organisation context
|
|
429
|
+
4. **Super Admin Override**: Super admins can access all profiles
|
|
430
|
+
|
|
431
|
+
## Migration Support
|
|
432
|
+
|
|
433
|
+
If you encounter issues:
|
|
434
|
+
|
|
435
|
+
1. **Check Migration Status**: Verify all migrations are applied
|
|
436
|
+
2. **Verify Types**: Regenerate TypeScript types from current schema
|
|
437
|
+
3. **Review RLS**: Ensure RLS policies are correctly applied
|
|
438
|
+
4. **Check Logs**: Review Supabase logs for RLS policy violations
|
|
439
|
+
|
|
440
|
+
## RPC Functions Updated
|
|
441
|
+
|
|
442
|
+
The following RPC functions have been updated to work with person-scoped profiles:
|
|
443
|
+
|
|
444
|
+
- **`data_pace_linked_profiles_list`** - Now uses `person_id` from `pace_contact` instead of `member_id`
|
|
445
|
+
- **`data_pace_contacts_list`** - Now uses `person_id` from `pace_contact` instead of `member_id`
|
|
446
|
+
- **`data_pace_contact_get`** - Now uses `person_id` from `pace_contact` instead of `member_id`
|
|
447
|
+
|
|
448
|
+
These functions now:
|
|
449
|
+
- Join `pace_contact` via `person_id` instead of `member_id`
|
|
450
|
+
- Get `member_id` from `pace_member` via `person_id` join
|
|
451
|
+
- Get `organisation_id` from user's organisation roles (since profiles are no longer organisation-scoped)
|
|
452
|
+
|
|
453
|
+
If you have custom RPC functions that reference `pace_contact.member_id` or profile table `organisation_id` columns, you'll need to update them similarly.
|
|
454
|
+
|
|
455
|
+
## Questions?
|
|
456
|
+
|
|
457
|
+
For questions or issues related to this migration:
|
|
458
|
+
- Review the migration files in `supabase/migrations/`
|
|
459
|
+
- Check RLS policy definitions in the migration files
|
|
460
|
+
- Review the pace-core documentation for updated API patterns
|
|
461
|
+
- Check for custom RPC functions that may need updates
|
|
462
|
+
|
|
463
|
+
## Summary
|
|
464
|
+
|
|
465
|
+
**Key Takeaways:**
|
|
466
|
+
- Profiles are now person-scoped, not organisation-scoped
|
|
467
|
+
- Remove all `organisation_id` filters/queries for profile tables
|
|
468
|
+
- Use `person_id` instead of `member_id` for contacts
|
|
469
|
+
- One membership per person (enforced by unique constraint)
|
|
470
|
+
- Update all TypeScript types after migration
|
|
471
|
+
- Test thoroughly, especially cross-organisation scenarios
|
|
472
|
+
|
|
@@ -29,7 +29,9 @@ A simple event management app that demonstrates:
|
|
|
29
29
|
4. **ALWAYS set up providers correctly** in the exact order shown below
|
|
30
30
|
5. **Use the exact app name** from your environment variable (must match database exactly, case-sensitive)
|
|
31
31
|
6. **Ensure database setup is complete** before starting the app - App must be registered with `requires_event = true`
|
|
32
|
-
7. **User must have organisation role** - Users need
|
|
32
|
+
7. **User must have organisation role OR event access** - Users need either:
|
|
33
|
+
- Explicit roles in `rbac_organisation_roles` table, OR
|
|
34
|
+
- Event access via `rbac_event_app_roles` (organisation access is automatically inferred)
|
|
33
35
|
8. **Event must be selected** - `selectedEventId` must be set in UnifiedAuth context before permission checks
|
|
34
36
|
9. **Event must belong to organisation** - The event's `organisation_id` must match the user's organisation
|
|
35
37
|
|
|
@@ -200,6 +202,8 @@ RETURNING event_id, event_name, event_code, organisation_id;
|
|
|
200
202
|
|
|
201
203
|
#### 5.5 Assign User Roles
|
|
202
204
|
|
|
205
|
+
**Option 1: Explicit Organisation Membership (Traditional)**
|
|
206
|
+
|
|
203
207
|
```sql
|
|
204
208
|
-- Grant a user the org_admin role (replace with actual user and organisation IDs)
|
|
205
209
|
INSERT INTO rbac_organisation_roles (user_id, organisation_id, role, status, granted_at)
|
|
@@ -216,6 +220,42 @@ ON CONFLICT (user_id, organisation_id) DO UPDATE SET
|
|
|
216
220
|
updated_at = NOW();
|
|
217
221
|
```
|
|
218
222
|
|
|
223
|
+
**Option 2: Event-Based Access (Automatic Organisation Inference)**
|
|
224
|
+
|
|
225
|
+
For event-based apps, you can grant access via event roles. The system will automatically infer organisation access from event access:
|
|
226
|
+
|
|
227
|
+
```sql
|
|
228
|
+
-- Grant a user event access (organisation access is automatically inferred)
|
|
229
|
+
-- Replace with actual user, event, app, and organisation IDs
|
|
230
|
+
INSERT INTO rbac_event_app_roles (
|
|
231
|
+
user_id,
|
|
232
|
+
event_id,
|
|
233
|
+
app_id,
|
|
234
|
+
organisation_id,
|
|
235
|
+
role,
|
|
236
|
+
status,
|
|
237
|
+
granted_at
|
|
238
|
+
)
|
|
239
|
+
VALUES (
|
|
240
|
+
'your-user-id'::uuid,
|
|
241
|
+
'your-event-id'::text,
|
|
242
|
+
(SELECT id FROM rbac_apps WHERE name = 'pace-trac'),
|
|
243
|
+
'your-organisation-id'::uuid, -- Must match event's organisation_id
|
|
244
|
+
'planner', -- or 'event_admin', 'participant', etc.
|
|
245
|
+
'active',
|
|
246
|
+
NOW()
|
|
247
|
+
)
|
|
248
|
+
ON CONFLICT (user_id, event_id, app_id, role) DO UPDATE SET
|
|
249
|
+
status = EXCLUDED.status,
|
|
250
|
+
updated_at = NOW();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Important Notes:**
|
|
254
|
+
- When a user has event access via `rbac_event_app_roles`, they automatically get implicit organisation access
|
|
255
|
+
- The system grants an implicit 'member' role for permission checks
|
|
256
|
+
- Explicit organisation memberships take precedence over inferred access
|
|
257
|
+
- This eliminates the need to maintain duplicate organisation memberships for event-only users
|
|
258
|
+
|
|
219
259
|
### 6. Create Supabase Client
|
|
220
260
|
|
|
221
261
|
Create `src/lib/supabase.ts`:
|
|
@@ -640,7 +680,9 @@ Your event-based RBAC setup is working correctly if:
|
|
|
640
680
|
- [ ] App is registered in `rbac_apps` table with `requires_event = true` and `is_active = true`
|
|
641
681
|
- [ ] Pages exist in `rbac_app_pages` for your app
|
|
642
682
|
- [ ] Page permissions exist in `rbac_page_permissions` for your pages, roles, and organisation
|
|
643
|
-
- [ ] User has
|
|
683
|
+
- [ ] User has either:
|
|
684
|
+
- An active role in `rbac_organisation_roles` for the organisation, OR
|
|
685
|
+
- Event access via `rbac_event_app_roles` (organisation access is automatically inferred)
|
|
644
686
|
- [ ] At least one event exists in the `event` table for your organisation
|
|
645
687
|
- [ ] `VITE_APP_NAME` environment variable matches database `rbac_apps.name` exactly
|
|
646
688
|
- [ ] `EventProvider` wraps your app content
|
|
@@ -762,15 +804,40 @@ WHERE event_id = 'your-event-id'::uuid;
|
|
|
762
804
|
```
|
|
763
805
|
- Must have pages for `dashboard`, `participants`, etc.
|
|
764
806
|
|
|
765
|
-
5. **Verify user has organisation role**:
|
|
807
|
+
5. **Verify user has organisation role OR event access**:
|
|
766
808
|
```sql
|
|
767
|
-
|
|
809
|
+
-- Check explicit organisation roles
|
|
810
|
+
SELECT ror.*, u.email, 'explicit' as access_type
|
|
768
811
|
FROM rbac_organisation_roles ror
|
|
769
812
|
JOIN auth.users u ON ror.user_id = u.id
|
|
770
813
|
WHERE ror.user_id = 'your-user-id'::uuid
|
|
771
|
-
AND ror.status = 'active'
|
|
814
|
+
AND ror.status = 'active'
|
|
815
|
+
|
|
816
|
+
UNION ALL
|
|
817
|
+
|
|
818
|
+
-- Check event-based access (which infers organisation access)
|
|
819
|
+
SELECT
|
|
820
|
+
rear.id,
|
|
821
|
+
rear.user_id,
|
|
822
|
+
e.organisation_id as organisation_id,
|
|
823
|
+
'member' as role, -- Implicit role granted from event access
|
|
824
|
+
rear.status,
|
|
825
|
+
rear.granted_at,
|
|
826
|
+
rear.granted_by,
|
|
827
|
+
rear.revoked_at,
|
|
828
|
+
rear.revoked_by,
|
|
829
|
+
u.email,
|
|
830
|
+
'inferred' as access_type
|
|
831
|
+
FROM rbac_event_app_roles rear
|
|
832
|
+
JOIN event e ON e.event_id = rear.event_id
|
|
833
|
+
JOIN auth.users u ON rear.user_id = u.id
|
|
834
|
+
WHERE rear.user_id = 'your-user-id'::uuid
|
|
835
|
+
AND rear.status = 'active'
|
|
836
|
+
AND e.organisation_id = 'your-organisation-id'::uuid;
|
|
772
837
|
```
|
|
773
|
-
- User must have
|
|
838
|
+
- User must have either:
|
|
839
|
+
- At least one active explicit organisation role, OR
|
|
840
|
+
- Event access via `rbac_event_app_roles` (organisation access is automatically inferred)
|
|
774
841
|
- Must be for the organisation that owns the event
|
|
775
842
|
|
|
776
843
|
6. **Verify event belongs to user's organisation**:
|
|
@@ -847,8 +914,59 @@ WHERE event_id = 'your-event-id'::uuid;
|
|
|
847
914
|
| **Context Required** | `organisationId` | `eventId` (org auto-resolved) |
|
|
848
915
|
| **PagePermissionGuard** | Needs `organisationId` | Needs `eventId` (org auto-resolved) |
|
|
849
916
|
| **Scope Resolution** | Direct from context | Organisation resolved from event |
|
|
917
|
+
| **User Access** | Requires explicit `rbac_organisation_roles` | Can use `rbac_event_app_roles` (org access inferred) |
|
|
850
918
|
| **Use Case** | User management, org settings | Event registration, event management |
|
|
851
919
|
|
|
920
|
+
## 🎯 Organisation Access Inference from Events
|
|
921
|
+
|
|
922
|
+
**New Feature:** The RBAC system can now automatically infer organisation access from event access, eliminating the need for explicit organisation memberships when users have event access.
|
|
923
|
+
|
|
924
|
+
### How It Works
|
|
925
|
+
|
|
926
|
+
1. **User has event access** via `rbac_event_app_roles` table
|
|
927
|
+
2. **System derives organisation** from the event's `organisation_id`
|
|
928
|
+
3. **System grants implicit 'member' role** for permission checks
|
|
929
|
+
4. **Permission checks proceed** as if user had explicit organisation membership
|
|
930
|
+
|
|
931
|
+
### Benefits
|
|
932
|
+
|
|
933
|
+
- **Eliminates data duplication** - No need to maintain both event and organisation memberships
|
|
934
|
+
- **Simplifies onboarding** - Grant event access, organisation access is automatic
|
|
935
|
+
- **Maintains security** - Still validates that user has valid event access
|
|
936
|
+
- **Backward compatible** - Explicit organisation memberships still work and take precedence
|
|
937
|
+
|
|
938
|
+
### Example
|
|
939
|
+
|
|
940
|
+
```sql
|
|
941
|
+
-- User has event access but no explicit organisation membership
|
|
942
|
+
INSERT INTO rbac_event_app_roles (
|
|
943
|
+
user_id,
|
|
944
|
+
event_id,
|
|
945
|
+
app_id,
|
|
946
|
+
organisation_id,
|
|
947
|
+
role,
|
|
948
|
+
status
|
|
949
|
+
)
|
|
950
|
+
VALUES (
|
|
951
|
+
'user-123'::uuid,
|
|
952
|
+
'event-456'::text,
|
|
953
|
+
(SELECT id FROM rbac_apps WHERE name = 'pace-trac'),
|
|
954
|
+
'org-789'::uuid,
|
|
955
|
+
'planner',
|
|
956
|
+
'active'
|
|
957
|
+
);
|
|
958
|
+
|
|
959
|
+
-- System automatically allows organisation access for permission checks
|
|
960
|
+
-- User can now access pages that require organisation context
|
|
961
|
+
-- Implicit role: 'member' (for permission matching)
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### When Explicit Membership is Still Required
|
|
965
|
+
|
|
966
|
+
- Users who need organisation-level access without event context
|
|
967
|
+
- Users who need specific organisation roles (org_admin, leader) that aren't granted via events
|
|
968
|
+
- Organisation management features that don't require event context
|
|
969
|
+
|
|
852
970
|
## 🚀 Next Steps
|
|
853
971
|
|
|
854
972
|
- **[RBAC Overview](./README.md)** - Complete RBAC system documentation
|