@jmruthers/pace-core 0.5.190 → 0.5.191
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/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{DataTable-ON3IXISJ.js → DataTable-WKRZD47S.js} +6 -6
- package/dist/{PublicPageProvider-C4uxosp6.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +1 -1
- package/dist/{UnifiedAuthProvider-X5NXANVI.js → UnifiedAuthProvider-FTSG5XH7.js} +3 -3
- package/dist/{api-I6UCQ5S6.js → api-IHKALJZD.js} +2 -2
- package/dist/{chunk-J2XXC7R5.js → chunk-6LTQQAT6.js} +77 -111
- package/dist/chunk-6LTQQAT6.js.map +1 -0
- package/dist/{chunk-STYK4OH2.js → chunk-6TQDD426.js} +10 -10
- package/dist/chunk-6TQDD426.js.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-73HSNNOQ.js → chunk-LOMZXPSN.js} +13 -13
- package/dist/{chunk-Y4BUBBHD.js → chunk-OETXORNB.js} +3 -3
- package/dist/{chunk-RUYZKXOD.js → chunk-ROXMHMY2.js} +5 -3
- package/dist/chunk-ROXMHMY2.js.map +1 -0
- package/dist/{chunk-SDMHPX3X.js → chunk-ULHIJK66.js} +56 -21
- package/dist/{chunk-SDMHPX3X.js.map → chunk-ULHIJK66.js.map} +1 -1
- package/dist/{chunk-VVBAW5A5.js → chunk-VKB2CO4Z.js} +46 -35
- package/dist/chunk-VKB2CO4Z.js.map +1 -0
- package/dist/{chunk-HQVPB5MZ.js → chunk-VRGWKHDB.js} +6 -6
- package/dist/{chunk-NIU6J6OX.js → chunk-XNYQOL3Z.js} +16 -16
- package/dist/chunk-XNYQOL3Z.js.map +1 -0
- package/dist/{chunk-4QYC5L4K.js → chunk-XYXSXPUK.js} +22 -27
- package/dist/chunk-XYXSXPUK.js.map +1 -0
- package/dist/components.d.ts +3 -3
- package/dist/components.js +8 -8
- 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 +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +18 -23
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +1 -1
- package/dist/rbac/index.js +6 -6
- 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 +1 -1
- 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 +5 -5
- 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 +1 -1
- 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 +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- 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 +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 +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- 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 +16 -16
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +767 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/package.json +1 -1
- package/src/__tests__/public-recipe-view.test.ts +10 -10
- package/src/__tests__/rls-policies.test.ts +13 -13
- package/src/components/AddressField/README.md +6 -6
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +35 -15
- 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 +9 -8
- package/src/hooks/public/usePublicEvent.ts +8 -8
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/useFileDisplay.ts +8 -9
- package/src/hooks/useQueryCache.ts +6 -6
- package/src/hooks/useSecureDataAccess.test.ts +8 -8
- package/src/hooks/useSecureDataAccess.ts +15 -11
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/rbac/hooks/useRBAC.simple.test.ts +95 -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/OrganisationService.ts +92 -139
- 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-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/{DataTable-ON3IXISJ.js.map → DataTable-WKRZD47S.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-X5NXANVI.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
- /package/dist/{api-I6UCQ5S6.js.map → api-IHKALJZD.js.map} +0 -0
- /package/dist/{chunk-73HSNNOQ.js.map → chunk-LOMZXPSN.js.map} +0 -0
- /package/dist/{chunk-Y4BUBBHD.js.map → chunk-OETXORNB.js.map} +0 -0
- /package/dist/{chunk-HQVPB5MZ.js.map → chunk-VRGWKHDB.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
|
+
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
58
58
|
it('should allow anonymous access when event.public_readable = true', async () => {
|
|
59
59
|
const start = Date.now();
|
|
60
60
|
const { data, error } = await anonClient
|
|
61
|
-
.from('
|
|
61
|
+
.from('cake_public_recipe_details')
|
|
62
62
|
.select('*')
|
|
63
63
|
.eq('event_id', publicEvent.event_id);
|
|
64
64
|
const duration = Date.now() - start;
|
|
@@ -70,7 +70,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
70
70
|
|
|
71
71
|
it('should block anonymous access when event.public_readable = false', async () => {
|
|
72
72
|
const { data, error } = await anonClient
|
|
73
|
-
.from('
|
|
73
|
+
.from('cake_public_recipe_details')
|
|
74
74
|
.select('*')
|
|
75
75
|
.eq('event_id', privateEvent.event_id);
|
|
76
76
|
|
|
@@ -80,7 +80,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
80
80
|
|
|
81
81
|
it('should block anonymous access when event.is_visible = false', async () => {
|
|
82
82
|
const { data, error } = await anonClient
|
|
83
|
-
.from('
|
|
83
|
+
.from('cake_public_recipe_details')
|
|
84
84
|
.select('*')
|
|
85
85
|
.eq('event_id', 'hidden-event-1');
|
|
86
86
|
|
|
@@ -92,7 +92,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
92
92
|
describe('View Column Exposure', () => {
|
|
93
93
|
it('should expose only expected columns', async () => {
|
|
94
94
|
const { data, error } = await anonClient
|
|
95
|
-
.from('
|
|
95
|
+
.from('cake_public_recipe_details')
|
|
96
96
|
.select('*')
|
|
97
97
|
.eq('event_id', publicEvent.event_id)
|
|
98
98
|
.limit(1)
|
|
@@ -119,7 +119,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
119
119
|
it('should filter by meal type', async () => {
|
|
120
120
|
const start = Date.now();
|
|
121
121
|
const { data, error } = await anonClient
|
|
122
|
-
.from('
|
|
122
|
+
.from('cake_public_recipe_details')
|
|
123
123
|
.select('*')
|
|
124
124
|
.eq('event_id', publicEvent.event_id)
|
|
125
125
|
.eq('mealtype_name', 'Breakfast');
|
|
@@ -134,7 +134,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
134
134
|
// This test would verify that diet filters work correctly
|
|
135
135
|
// Adjust based on actual view structure
|
|
136
136
|
const { data, error } = await anonClient
|
|
137
|
-
.from('
|
|
137
|
+
.from('cake_public_recipe_details')
|
|
138
138
|
.select('*')
|
|
139
139
|
.eq('event_id', publicEvent.event_id)
|
|
140
140
|
.eq('dish_dietnote', 'Vegetarian');
|
|
@@ -148,7 +148,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
148
148
|
it('should complete view queries in < 500ms', async () => {
|
|
149
149
|
const start = Date.now();
|
|
150
150
|
await anonClient
|
|
151
|
-
.from('
|
|
151
|
+
.from('cake_public_recipe_details')
|
|
152
152
|
.select('*')
|
|
153
153
|
.eq('event_id', publicEvent.event_id)
|
|
154
154
|
.limit(100);
|
|
@@ -160,7 +160,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
160
160
|
it('should handle filtered queries efficiently', async () => {
|
|
161
161
|
const start = Date.now();
|
|
162
162
|
await anonClient
|
|
163
|
-
.from('
|
|
163
|
+
.from('cake_public_recipe_details')
|
|
164
164
|
.select('*')
|
|
165
165
|
.eq('event_id', publicEvent.event_id)
|
|
166
166
|
.eq('mealtype_name', 'Breakfast')
|
|
@@ -175,7 +175,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
175
175
|
describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE_KEY)('Public Recipe View - Authenticated Access', () => {
|
|
176
176
|
it('should allow authenticated users to view public recipes', async () => {
|
|
177
177
|
const { data, error } = await authenticatedClient
|
|
178
|
-
.from('
|
|
178
|
+
.from('cake_public_recipe_details')
|
|
179
179
|
.select('*')
|
|
180
180
|
.eq('event_id', publicEvent.event_id);
|
|
181
181
|
|
|
@@ -188,7 +188,7 @@ describe.skipIf(!USE_REAL_DB || !TEST_SUPABASE_URL || !TEST_SUPABASE_PUBLISHABLE
|
|
|
188
188
|
// recipes from events in their organisation, even if not public_readable
|
|
189
189
|
// (This depends on the view definition and RLS policies)
|
|
190
190
|
const { data, error } = await authenticatedClient
|
|
191
|
-
.from('
|
|
191
|
+
.from('cake_public_recipe_details')
|
|
192
192
|
.select('*')
|
|
193
193
|
.eq('event_id', privateEvent.event_id);
|
|
194
194
|
|
|
@@ -179,7 +179,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
179
179
|
it('should allow super admin to view all organisations', async () => {
|
|
180
180
|
const start = Date.now();
|
|
181
181
|
const { data, error } = await superAdminClient
|
|
182
|
-
.from('
|
|
182
|
+
.from('core_organisations')
|
|
183
183
|
.select('*')
|
|
184
184
|
.limit(10);
|
|
185
185
|
const duration = Date.now() - start;
|
|
@@ -192,7 +192,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
192
192
|
|
|
193
193
|
it('should allow super admin to update any organisation', async () => {
|
|
194
194
|
const { data, error } = await superAdminClient
|
|
195
|
-
.from('
|
|
195
|
+
.from('core_organisations')
|
|
196
196
|
.update({ name: 'Updated Name' })
|
|
197
197
|
.eq('id', testOrganisation1.id)
|
|
198
198
|
.select()
|
|
@@ -214,7 +214,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
214
214
|
it('should allow org admin to view their organisation', async () => {
|
|
215
215
|
const start = Date.now();
|
|
216
216
|
const { data, error } = await orgAdminClient
|
|
217
|
-
.from('
|
|
217
|
+
.from('core_organisations')
|
|
218
218
|
.select('*')
|
|
219
219
|
.eq('id', testOrganisation1.id)
|
|
220
220
|
.single();
|
|
@@ -232,7 +232,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
232
232
|
|
|
233
233
|
it('should block org admin from viewing other organisations', async () => {
|
|
234
234
|
const { data, error } = await orgAdminClient
|
|
235
|
-
.from('
|
|
235
|
+
.from('core_organisations')
|
|
236
236
|
.select('*')
|
|
237
237
|
.eq('id', testOrganisation2.id)
|
|
238
238
|
.single();
|
|
@@ -246,7 +246,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
246
246
|
it('should allow member to view their organisation', async () => {
|
|
247
247
|
const start = Date.now();
|
|
248
248
|
const { data, error } = await regularMemberClient
|
|
249
|
-
.from('
|
|
249
|
+
.from('core_organisations')
|
|
250
250
|
.select('*')
|
|
251
251
|
.eq('id', testOrganisation1.id)
|
|
252
252
|
.single();
|
|
@@ -264,7 +264,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
264
264
|
|
|
265
265
|
it('should block member from updating organisation', async () => {
|
|
266
266
|
const { data, error } = await regularMemberClient
|
|
267
|
-
.from('
|
|
267
|
+
.from('core_organisations')
|
|
268
268
|
.update({ name: 'Unauthorized Update' })
|
|
269
269
|
.eq('id', testOrganisation1.id);
|
|
270
270
|
|
|
@@ -288,7 +288,7 @@ describe('RLS Policies - Organisations', () => {
|
|
|
288
288
|
describe('Anonymous Access', () => {
|
|
289
289
|
it('should block anonymous users from viewing organisations', async () => {
|
|
290
290
|
const { data, error } = await anonClient
|
|
291
|
-
.from('
|
|
291
|
+
.from('core_organisations')
|
|
292
292
|
.select('*');
|
|
293
293
|
|
|
294
294
|
// Should return empty (RLS blocks anonymous access)
|
|
@@ -302,7 +302,7 @@ describe('RLS Policies - Events', () => {
|
|
|
302
302
|
it('should allow anonymous access to public events', async () => {
|
|
303
303
|
const start = Date.now();
|
|
304
304
|
const { data, error } = await anonClient
|
|
305
|
-
.from('
|
|
305
|
+
.from('core_events')
|
|
306
306
|
.select('*')
|
|
307
307
|
.eq('event_id', testEvent.event_id)
|
|
308
308
|
.eq('public_readable', true)
|
|
@@ -321,7 +321,7 @@ describe('RLS Policies - Events', () => {
|
|
|
321
321
|
|
|
322
322
|
it('should block anonymous access to non-public events', async () => {
|
|
323
323
|
const { data, error } = await anonClient
|
|
324
|
-
.from('
|
|
324
|
+
.from('core_events')
|
|
325
325
|
.select('*')
|
|
326
326
|
.eq('event_id', testEvent.event_id)
|
|
327
327
|
.eq('public_readable', false)
|
|
@@ -336,7 +336,7 @@ describe('RLS Policies - Events', () => {
|
|
|
336
336
|
it('should allow org member to view events in their organisation', async () => {
|
|
337
337
|
const start = Date.now();
|
|
338
338
|
const { data, error } = await regularMemberClient
|
|
339
|
-
.from('
|
|
339
|
+
.from('core_events')
|
|
340
340
|
.select('*')
|
|
341
341
|
.eq('organisation_id', testOrganisation1.id)
|
|
342
342
|
.limit(10);
|
|
@@ -411,7 +411,7 @@ describe('RLS Policies - Performance', () => {
|
|
|
411
411
|
it('should complete organisation queries in < 1 second', async () => {
|
|
412
412
|
const start = Date.now();
|
|
413
413
|
await superAdminClient
|
|
414
|
-
.from('
|
|
414
|
+
.from('core_organisations')
|
|
415
415
|
.select('*')
|
|
416
416
|
.limit(100);
|
|
417
417
|
const duration = Date.now() - start;
|
|
@@ -422,7 +422,7 @@ describe('RLS Policies - Performance', () => {
|
|
|
422
422
|
it('should complete event queries in < 1 second', async () => {
|
|
423
423
|
const start = Date.now();
|
|
424
424
|
await superAdminClient
|
|
425
|
-
.from('
|
|
425
|
+
.from('core_events')
|
|
426
426
|
.select('*')
|
|
427
427
|
.eq('is_visible', true)
|
|
428
428
|
.limit(100);
|
|
@@ -452,7 +452,7 @@ describe('RLS Policies - Helper Functions', () => {
|
|
|
452
452
|
|
|
453
453
|
const { data, error } = await superAdminClient
|
|
454
454
|
.rpc('check_query_performance', {
|
|
455
|
-
p_query: 'SELECT * FROM
|
|
455
|
+
p_query: 'SELECT * FROM core_organisations LIMIT 1'
|
|
456
456
|
});
|
|
457
457
|
|
|
458
458
|
// Note: If the RPC function doesn't exist or uses EXPLAIN incorrectly, we'll get an error
|