@jmruthers/pace-core 0.5.87 → 0.5.89
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-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
- package/dist/{DataTable-FA6EUX5M.js → DataTable-PWBMKMOG.js} +7 -7
- package/dist/{PublicLoadingSpinner-DecuJBX0.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +160 -130
- package/dist/{UnifiedAuthProvider-K2IZAY5F.js → UnifiedAuthProvider-5D3HEQND.js} +4 -4
- package/dist/{UnifiedAuthProvider-B391Aqum.d.ts → UnifiedAuthProvider-BVKmQd9u.d.ts} +4 -0
- package/dist/auth-DReDSLq9.d.ts +16 -0
- package/dist/{chunk-CBSD3BZ3.js → chunk-3RZBKQ5Y.js} +2 -6
- package/dist/{chunk-CBSD3BZ3.js.map → chunk-3RZBKQ5Y.js.map} +1 -1
- package/dist/{chunk-NTW3KGS4.js → chunk-6UHXQH7P.js} +5 -5
- package/dist/{chunk-ZFLOV3OM.js → chunk-7VJDS5QD.js} +401 -16
- package/dist/chunk-7VJDS5QD.js.map +1 -0
- package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
- package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
- package/dist/chunk-BDZUMRBD.js.map +1 -0
- package/dist/{chunk-KAY3K5TP.js → chunk-BNXBJOGL.js} +4 -4
- package/dist/{chunk-S3JKDMD5.js → chunk-CXKMRKRF.js} +4 -4
- package/dist/{chunk-5BN3YGNK.js → chunk-DP5X5ORK.js} +217 -27
- package/dist/chunk-DP5X5ORK.js.map +1 -0
- package/dist/{chunk-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
- package/dist/{chunk-2FQEQUJT.js → chunk-KWICIQVK.js} +4 -4
- package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
- package/dist/chunk-XJ2HZOBU.js.map +1 -0
- package/dist/{chunk-I7O3RSMN.js → chunk-YWAFPVJA.js} +1298 -769
- package/dist/chunk-YWAFPVJA.js.map +1 -0
- package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
- package/dist/components.d.ts +6 -55
- package/dist/components.js +24 -205
- package/dist/components.js.map +1 -1
- package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
- package/dist/hooks.js +9 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +152 -26
- package/dist/index.js +64 -194
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +5 -3
- package/dist/providers.js +3 -3
- package/dist/rbac/index.js +8 -8
- package/dist/types.d.ts +2 -1
- package/dist/types.js +3 -3
- package/dist/utils.js +2 -2
- package/docs/DOCUMENTATION_AUDIT.md +6 -6
- package/docs/DOCUMENTATION_STANDARD.md +137 -0
- package/docs/README.md +1 -1
- 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/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.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 +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +83 -40
- package/docs/api/enums/FileCategory.md +56 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.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/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/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +11 -11
- package/docs/api/interfaces/FileDisplayProps.md +10 -10
- 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 +8 -8
- package/docs/api/interfaces/FileUploadProps.md +137 -42
- package/docs/api/interfaces/FooterProps.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/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/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.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/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.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/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
- package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.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 +290 -95
- package/docs/api-reference/components.md +1 -18
- package/docs/api-reference/hooks.md +1 -4
- package/docs/best-practices/testing.md +2 -0
- package/docs/documentation-index.md +1 -1
- package/docs/getting-started/faq.md +1 -1
- package/docs/implementation-guides/file-reference-system.md +592 -58
- package/docs/implementation-guides/file-upload-storage.md +137 -73
- package/docs/implementation-guides/public-pages-advanced.md +10 -0
- package/docs/rbac/super-admin-guide.md +18 -70
- package/docs/testing/README.md +2 -0
- package/package.json +1 -1
- package/src/__tests__/TEST_STANDARD.md +674 -0
- package/src/__tests__/helpers/test-utils.tsx +3 -2
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
- package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
- package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
- package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
- package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
- package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
- package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
- package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
- package/src/components/FileDisplay/index.tsx +4 -0
- package/src/components/FileUpload/FileUpload.test.tsx +171 -621
- package/src/components/FileUpload/FileUpload.tsx +512 -168
- package/src/components/FileUpload/index.tsx +4 -0
- package/src/components/Progress/Progress.test.tsx +38 -0
- package/src/components/PublicLayout/EventLogo.tsx +6 -4
- package/src/components/Select/Select.test.tsx +1 -1
- package/src/components/SessionRestorationLoader.tsx +48 -0
- package/src/components/Toast/Toast.tsx +13 -8
- package/src/components/index.ts +16 -16
- package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
- package/src/hooks/public/usePublicEventLogo.ts +16 -20
- package/src/hooks/useEventLogo.ts +316 -0
- package/src/hooks/useEvents.ts +0 -5
- package/src/hooks/useFileReference.test.ts +659 -0
- package/src/hooks/useFileReference.ts +207 -3
- package/src/hooks/useSessionRestoration.ts +64 -0
- package/src/index.ts +17 -5
- package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
- package/src/providers/services/AuthServiceProvider.tsx +27 -3
- package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
- package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
- package/src/services/AuthService.ts +142 -20
- package/src/services/EventService.ts +0 -4
- package/src/types/auth.ts +15 -0
- package/src/types/file-reference.ts +73 -1
- package/src/types/index.ts +1 -0
- package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
- package/src/utils/appNameResolver.simple.test.ts +99 -29
- package/src/utils/file-reference.test.ts +535 -0
- package/src/utils/file-reference.ts +200 -30
- package/src/utils/organisationContext.test.ts +5 -19
- package/src/utils/organisationContext.ts +3 -5
- package/src/utils/storage/README.md +269 -262
- package/src/utils/storage/config.ts +9 -0
- package/src/utils/storage/helpers.test.ts +735 -0
- package/src/utils/storage/helpers.ts +189 -16
- package/src/utils/storage/index.ts +3 -0
- package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
- package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
- package/src/validation/__tests__/user.unit.test.ts +1 -1
- package/dist/chunk-5BN3YGNK.js.map +0 -1
- package/dist/chunk-CVMVPYAL.js.map +0 -1
- package/dist/chunk-I7O3RSMN.js.map +0 -1
- package/dist/chunk-WUXCWRL6.js.map +0 -1
- package/dist/chunk-ZFLOV3OM.js.map +0 -1
- package/docs/CONTENT_AUDIT_REPORT.md +0 -253
- package/docs/STYLE_GUIDE.md +0 -37
- package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
- package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
- package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
- package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
- package/src/components/FileUpload/FileUpload.example.tsx +0 -218
- package/src/components/FileUpload/index.ts +0 -6
- package/src/components/FileUpload.tsx +0 -176
- package/src/components/Progress/index.ts +0 -3
- package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
- package/src/components/SuperAdminGuard.tsx +0 -116
- package/src/components/__tests__/FileDisplay.test.tsx +0 -575
- package/src/components/__tests__/FileUpload.test.tsx +0 -446
- package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
- package/src/components/examples/PermissionExample.tsx +0 -173
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
- package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
- package/src/types/__tests__/file-reference.test.ts +0 -447
- package/src/utils/__tests__/file-reference.test.ts +0 -383
- /package/dist/{DataTable-FA6EUX5M.js.map → DataTable-PWBMKMOG.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
- /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
- /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
- /package/dist/{chunk-KAY3K5TP.js.map → chunk-BNXBJOGL.js.map} +0 -0
- /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
- /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
- /package/dist/{chunk-2FQEQUJT.js.map → chunk-KWICIQVK.js.map} +0 -0
- /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
- /package/src/providers/{OrganisationProvider.test.simple.tsx → OrganisationProvider.context.test.tsx} +0 -0
|
@@ -13,10 +13,12 @@ A centralized file management system for PACE Core that provides consistent file
|
|
|
13
13
|
The File Reference System replaces scattered file URL columns with a centralized `file_references` table that provides:
|
|
14
14
|
|
|
15
15
|
- **Consistent Storage**: All files stored in organization-first structure
|
|
16
|
+
- **Bucket-Aware Storage**: Automatic selection between `files` (private) and `public-files` (public) buckets
|
|
16
17
|
- **Metadata Tracking**: Rich file metadata including size, type, dimensions, etc.
|
|
17
18
|
- **Access Control**: Row Level Security (RLS) based on organization membership
|
|
18
19
|
- **Audit Trail**: Track who uploaded files and when
|
|
19
20
|
- **Easy Cleanup**: Identify and manage orphaned files
|
|
21
|
+
- **URL Management**: Automatic signed URL generation for private files, public URLs for public files
|
|
20
22
|
|
|
21
23
|
## Architecture
|
|
22
24
|
|
|
@@ -44,15 +46,54 @@ CREATE TABLE file_references (
|
|
|
44
46
|
{bucket}/{orgId}/{category}/{filename}
|
|
45
47
|
|
|
46
48
|
Examples:
|
|
47
|
-
- files/org-123/profile_photos/timestamp-uuid-photo.jpg
|
|
48
|
-
- files/org-123/id_documents/timestamp-uuid-passport.pdf
|
|
49
|
-
- public-files/org-123/event_logos/timestamp-uuid-logo.png
|
|
49
|
+
- files/org-123/profile_photos/timestamp-uuid-photo.jpg (private)
|
|
50
|
+
- files/org-123/id_documents/timestamp-uuid-passport.pdf (private)
|
|
51
|
+
- public-files/org-123/event_logos/timestamp-uuid-logo.png (public)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Bucket Selection
|
|
55
|
+
|
|
56
|
+
The system uses two Supabase storage buckets:
|
|
57
|
+
|
|
58
|
+
1. **`files` bucket** (Private)
|
|
59
|
+
- For files that require authentication
|
|
60
|
+
- Accessible via signed URLs with expiration
|
|
61
|
+
- Default bucket for new file references
|
|
62
|
+
- Protected by RLS policies
|
|
63
|
+
|
|
64
|
+
2. **`public-files` bucket** (Public)
|
|
65
|
+
- For files accessible without authentication
|
|
66
|
+
- Accessible via direct public URLs
|
|
67
|
+
- Used for event logos, public assets, etc.
|
|
68
|
+
- Defined by `is_public = true` in `file_references`
|
|
69
|
+
|
|
70
|
+
**Bucket selection is automatic** based on the `is_public` flag when creating file references:
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Private file (files bucket)
|
|
74
|
+
await service.createFileReference({
|
|
75
|
+
table_name: 'pace_person',
|
|
76
|
+
record_id: personId,
|
|
77
|
+
organisation_id: orgId,
|
|
78
|
+
category: FileCategory.PROFILE_PHOTOS,
|
|
79
|
+
is_public: false // Uses 'files' bucket
|
|
80
|
+
}, file);
|
|
81
|
+
|
|
82
|
+
// Public file (public-files bucket)
|
|
83
|
+
await service.createFileReference({
|
|
84
|
+
table_name: 'event',
|
|
85
|
+
record_id: eventId,
|
|
86
|
+
organisation_id: orgId,
|
|
87
|
+
category: FileCategory.EVENT_LOGOS,
|
|
88
|
+
is_public: true // Uses 'public-files' bucket
|
|
89
|
+
}, file);
|
|
50
90
|
```
|
|
51
91
|
|
|
52
92
|
### File Categories
|
|
53
93
|
|
|
54
94
|
```typescript
|
|
55
95
|
enum FileCategory {
|
|
96
|
+
// Core categories
|
|
56
97
|
PROFILE_PHOTOS = 'profile_photos',
|
|
57
98
|
ID_DOCUMENTS = 'id_documents',
|
|
58
99
|
QUALIFICATIONS = 'qualifications',
|
|
@@ -63,7 +104,16 @@ enum FileCategory {
|
|
|
63
104
|
IMAGES = 'images',
|
|
64
105
|
AUDIO = 'audio',
|
|
65
106
|
VIDEO = 'video',
|
|
66
|
-
ARCHIVES = 'archives'
|
|
107
|
+
ARCHIVES = 'archives',
|
|
108
|
+
|
|
109
|
+
// CAKE-specific categories
|
|
110
|
+
CAKE_DISH = 'cake_dish',
|
|
111
|
+
|
|
112
|
+
// TRAC-specific categories
|
|
113
|
+
TRAC_ACCOMMODATION = 'trac_accommodation',
|
|
114
|
+
TRAC_ACTIVITY = 'trac_activity',
|
|
115
|
+
TRAC_JOURNAL = 'trac_journal',
|
|
116
|
+
TRAC_TRANSPORT = 'trac_transport'
|
|
67
117
|
}
|
|
68
118
|
```
|
|
69
119
|
|
|
@@ -73,6 +123,7 @@ enum FileCategory {
|
|
|
73
123
|
|
|
74
124
|
#### FileUpload Component
|
|
75
125
|
|
|
126
|
+
**Basic Usage:**
|
|
76
127
|
```tsx
|
|
77
128
|
import { FileUpload, FileCategory } from '@jmruthers/pace-core';
|
|
78
129
|
|
|
@@ -81,12 +132,82 @@ import { FileUpload, FileCategory } from '@jmruthers/pace-core';
|
|
|
81
132
|
table_name="pace_person"
|
|
82
133
|
record_id={personId}
|
|
83
134
|
organisation_id={orgId}
|
|
84
|
-
app_id="your-app-id"
|
|
135
|
+
app_id="your-app-id" // Optional - auto-resolved from app name if not provided
|
|
85
136
|
category={FileCategory.PROFILE_PHOTOS}
|
|
86
137
|
accept="image/*"
|
|
87
138
|
maxSize={5 * 1024 * 1024}
|
|
88
|
-
onUploadSuccess={(result) =>
|
|
89
|
-
|
|
139
|
+
onUploadSuccess={(result) => {
|
|
140
|
+
console.log('Uploaded:', result.file_reference);
|
|
141
|
+
console.log('URL:', result.file_url);
|
|
142
|
+
}}
|
|
143
|
+
onUploadError={(error, file) => {
|
|
144
|
+
console.error(`Upload failed for ${file?.name}:`, error);
|
|
145
|
+
}}
|
|
146
|
+
/>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**With Progress Tracking:**
|
|
150
|
+
```tsx
|
|
151
|
+
<FileUpload
|
|
152
|
+
supabase={supabase}
|
|
153
|
+
table_name="pace_person"
|
|
154
|
+
record_id={personId}
|
|
155
|
+
organisation_id={orgId}
|
|
156
|
+
// app_id omitted - will be auto-resolved from app name
|
|
157
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
158
|
+
accept="image/*"
|
|
159
|
+
maxSize={5 * 1024 * 1024}
|
|
160
|
+
showProgress={true}
|
|
161
|
+
showPreview={true}
|
|
162
|
+
multiple={true}
|
|
163
|
+
disabled={false}
|
|
164
|
+
className="custom-upload-area"
|
|
165
|
+
onProgress={(progress) => {
|
|
166
|
+
console.log(`Uploading ${progress.fileName}: ${progress.percentage}%`);
|
|
167
|
+
}}
|
|
168
|
+
onUploadSuccess={(result) => {
|
|
169
|
+
console.log('Uploaded:', result.file_reference);
|
|
170
|
+
console.log('URL:', result.file_url);
|
|
171
|
+
}}
|
|
172
|
+
onUploadError={(error, file) => {
|
|
173
|
+
console.error(`Failed to upload ${file?.name}:`, error);
|
|
174
|
+
}}
|
|
175
|
+
>
|
|
176
|
+
{/* Custom upload UI */}
|
|
177
|
+
<div className="border-2 border-dashed border-main-300 rounded-lg p-6 text-center">
|
|
178
|
+
<p>Drop files here or click to browse</p>
|
|
179
|
+
</div>
|
|
180
|
+
</FileUpload>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**Public File Upload (Event Logo):**
|
|
184
|
+
```tsx
|
|
185
|
+
<FileUpload
|
|
186
|
+
supabase={supabase}
|
|
187
|
+
table_name="event"
|
|
188
|
+
record_id={eventId}
|
|
189
|
+
organisation_id={orgId}
|
|
190
|
+
app_id="your-app-id"
|
|
191
|
+
category={FileCategory.EVENT_LOGOS}
|
|
192
|
+
accept="image/*"
|
|
193
|
+
isPublic={true} // Uploads to public-files bucket
|
|
194
|
+
showPreview={true}
|
|
195
|
+
onUploadSuccess={(result) => {
|
|
196
|
+
// File is now accessible via public URL
|
|
197
|
+
console.log('Public URL:', result.file_url);
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
**Custom Upload UI:**
|
|
203
|
+
```tsx
|
|
204
|
+
<FileUpload
|
|
205
|
+
supabase={supabase}
|
|
206
|
+
table_name="pace_person"
|
|
207
|
+
record_id={personId}
|
|
208
|
+
organisation_id={orgId}
|
|
209
|
+
app_id="your-app-id"
|
|
210
|
+
category={FileCategory.PROFILE_PHOTOS}
|
|
90
211
|
>
|
|
91
212
|
<div className="custom-upload-ui">
|
|
92
213
|
<p>Click to upload profile photo</p>
|
|
@@ -123,25 +244,112 @@ import { FileDisplay, FileCategory } from '@jmruthers/pace-core';
|
|
|
123
244
|
#### useFileReference Hook
|
|
124
245
|
|
|
125
246
|
```tsx
|
|
126
|
-
import { useFileReference } from '@jmruthers/pace-core';
|
|
127
|
-
|
|
128
|
-
const {
|
|
129
|
-
|
|
130
|
-
|
|
247
|
+
import { useFileReference, FileCategory } from '@jmruthers/pace-core';
|
|
248
|
+
|
|
249
|
+
const {
|
|
250
|
+
uploadFile,
|
|
251
|
+
getFileUrl,
|
|
252
|
+
getFileReferenceById,
|
|
253
|
+
getFilesByCategory,
|
|
254
|
+
deleteFileReference,
|
|
255
|
+
isLoading,
|
|
256
|
+
error
|
|
257
|
+
} = useFileReference(supabase);
|
|
258
|
+
|
|
259
|
+
// Upload a private file
|
|
131
260
|
const result = await uploadFile({
|
|
132
261
|
table_name: 'pace_person',
|
|
133
262
|
record_id: personId,
|
|
134
263
|
organisation_id: orgId,
|
|
135
264
|
app_id: 'your-app-id',
|
|
136
265
|
category: FileCategory.PROFILE_PHOTOS,
|
|
137
|
-
is_public: false
|
|
266
|
+
is_public: false // Uses 'files' bucket
|
|
138
267
|
}, file);
|
|
139
268
|
|
|
140
|
-
//
|
|
269
|
+
// Upload a public file
|
|
270
|
+
const publicResult = await uploadFile({
|
|
271
|
+
table_name: 'event',
|
|
272
|
+
record_id: eventId,
|
|
273
|
+
organisation_id: orgId,
|
|
274
|
+
app_id: 'your-app-id',
|
|
275
|
+
category: FileCategory.EVENT_LOGOS,
|
|
276
|
+
is_public: true // Uses 'public-files' bucket
|
|
277
|
+
}, logoFile);
|
|
278
|
+
|
|
279
|
+
// Get file URL (automatically handles bucket selection)
|
|
141
280
|
const fileUrl = await getFileUrl('pace_person', personId, orgId);
|
|
281
|
+
// Returns:
|
|
282
|
+
// - Public URL for public files (from public-files bucket)
|
|
283
|
+
// - Signed URL for private files (from files bucket)
|
|
284
|
+
|
|
285
|
+
// Get file reference by ID
|
|
286
|
+
const fileRef = await getFileReferenceById(fileRefId, orgId);
|
|
287
|
+
|
|
288
|
+
// Get files by category
|
|
289
|
+
const eventLogos = await getFilesByCategory(
|
|
290
|
+
'event',
|
|
291
|
+
eventId,
|
|
292
|
+
FileCategory.EVENT_LOGOS,
|
|
293
|
+
orgId
|
|
294
|
+
);
|
|
142
295
|
|
|
143
296
|
// Delete file
|
|
144
|
-
const success = await
|
|
297
|
+
const success = await deleteFileReference('pace_person', personId, orgId, true);
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
#### useFileReferenceById Hook
|
|
301
|
+
|
|
302
|
+
```tsx
|
|
303
|
+
import { useFileReferenceById } from '@jmruthers/pace-core';
|
|
304
|
+
|
|
305
|
+
const { fileReference, fileUrl, isLoading, error } = useFileReferenceById(
|
|
306
|
+
supabase,
|
|
307
|
+
fileReferenceId,
|
|
308
|
+
organisationId
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
// Automatically loads file reference and URL when ID changes
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### useFilesByCategory Hook
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
import { useFilesByCategory, FileCategory } from '@jmruthers/pace-core';
|
|
318
|
+
|
|
319
|
+
const { fileReferences, fileUrls, isLoading, error } = useFilesByCategory(
|
|
320
|
+
supabase,
|
|
321
|
+
'event',
|
|
322
|
+
eventId,
|
|
323
|
+
FileCategory.EVENT_LOGOS,
|
|
324
|
+
organisationId
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// fileUrls is a Map<fileId, url> for easy lookup
|
|
328
|
+
fileReferences.forEach(fileRef => {
|
|
329
|
+
const url = fileUrls.get(fileRef.id);
|
|
330
|
+
// Use url for display
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
#### useEventLogo Hook
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
import { useEventLogo } from '@jmruthers/pace-core';
|
|
338
|
+
|
|
339
|
+
const { logoUrl, fallbackText, isLoading, error, refetch } = useEventLogo(
|
|
340
|
+
supabase,
|
|
341
|
+
eventId,
|
|
342
|
+
eventName,
|
|
343
|
+
organisationId,
|
|
344
|
+
{
|
|
345
|
+
validateImage: true,
|
|
346
|
+
enableCache: true,
|
|
347
|
+
cacheTtl: 30 * 60 * 1000 // 30 minutes
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// Automatically handles both public and private event logos
|
|
352
|
+
// Falls back to event initials if no logo is found
|
|
145
353
|
```
|
|
146
354
|
|
|
147
355
|
#### useFileReferenceForRecord Hook
|
|
@@ -185,36 +393,82 @@ const files = await service.listFileReferences('pace_person', personId, orgId);
|
|
|
185
393
|
|
|
186
394
|
## Database Functions
|
|
187
395
|
|
|
188
|
-
###
|
|
396
|
+
### RPC Functions
|
|
397
|
+
|
|
398
|
+
**All RPCs follow the `<family>_<domain>_<verb>` naming convention:**
|
|
189
399
|
|
|
190
400
|
```sql
|
|
191
|
-
--
|
|
192
|
-
SELECT
|
|
193
|
-
'pace_person',
|
|
194
|
-
'person-uuid',
|
|
195
|
-
'org-123/profile_photos/file.jpg',
|
|
196
|
-
'org-uuid',
|
|
197
|
-
'app-uuid',
|
|
198
|
-
'{"fileName": "photo.jpg"}'::jsonb,
|
|
199
|
-
|
|
401
|
+
-- Create file reference (replaces insert_file_reference)
|
|
402
|
+
SELECT data_file_reference_create(
|
|
403
|
+
p_table_name := 'pace_person',
|
|
404
|
+
p_record_id := 'person-uuid',
|
|
405
|
+
p_file_path := 'org-123/profile_photos/file.jpg',
|
|
406
|
+
p_organisation_id := 'org-uuid',
|
|
407
|
+
p_app_id := 'app-uuid',
|
|
408
|
+
p_file_metadata := '{"fileName": "photo.jpg"}'::jsonb,
|
|
409
|
+
p_category := 'profile_photos',
|
|
410
|
+
p_is_public := false
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
-- Get file URL (returns file_path for public files, NULL for private files)
|
|
414
|
+
SELECT data_file_reference_url_get(
|
|
415
|
+
p_table_name := 'pace_person',
|
|
416
|
+
p_record_id := 'person-uuid',
|
|
417
|
+
p_organisation_id := 'org-uuid'
|
|
418
|
+
);
|
|
419
|
+
-- Note: Client generates public URL from returned file_path
|
|
420
|
+
|
|
421
|
+
-- Get signed URL path for private files (client generates signed URL)
|
|
422
|
+
SELECT data_file_reference_signed_url_get(
|
|
423
|
+
p_table_name := 'pace_person',
|
|
424
|
+
p_record_id := 'person-uuid',
|
|
425
|
+
p_organisation_id := 'org-uuid',
|
|
426
|
+
p_expires_in := 3600
|
|
200
427
|
);
|
|
428
|
+
-- Returns file_path - client uses getSignedUrl() helper to generate URL
|
|
201
429
|
|
|
202
|
-
-- Get file
|
|
203
|
-
SELECT
|
|
430
|
+
-- Get file reference by ID
|
|
431
|
+
SELECT data_file_reference_get(
|
|
432
|
+
p_file_reference_id := 'file-ref-uuid',
|
|
433
|
+
p_organisation_id := 'org-uuid'
|
|
434
|
+
);
|
|
204
435
|
|
|
205
|
-
-- Get
|
|
206
|
-
SELECT
|
|
436
|
+
-- Get files by category
|
|
437
|
+
SELECT data_file_reference_by_category_list(
|
|
438
|
+
p_table_name := 'event',
|
|
439
|
+
p_record_id := 'event-uuid',
|
|
440
|
+
p_category := 'event_logos',
|
|
441
|
+
p_organisation_id := 'org-uuid'
|
|
442
|
+
);
|
|
207
443
|
|
|
208
444
|
-- Delete file reference
|
|
209
|
-
SELECT
|
|
445
|
+
SELECT data_file_reference_delete(
|
|
446
|
+
p_table_name := 'pace_person',
|
|
447
|
+
p_record_id := 'person-uuid',
|
|
448
|
+
p_organisation_id := 'org-uuid',
|
|
449
|
+
p_delete_file := true
|
|
450
|
+
);
|
|
210
451
|
|
|
211
|
-
-- List files for a record
|
|
212
|
-
SELECT * FROM
|
|
452
|
+
-- List files for a record (replaces get_record_files)
|
|
453
|
+
SELECT * FROM data_file_reference_list(
|
|
454
|
+
p_table_name := 'pace_person',
|
|
455
|
+
p_record_id := 'person-uuid',
|
|
456
|
+
p_organisation_id := 'org-uuid'
|
|
457
|
+
);
|
|
213
458
|
|
|
214
|
-
-- Get file count for a record
|
|
215
|
-
SELECT
|
|
459
|
+
-- Get file count for a record (replaces get_record_file_count)
|
|
460
|
+
SELECT data_file_reference_count_get(
|
|
461
|
+
p_table_name := 'pace_person',
|
|
462
|
+
p_record_id := 'person-uuid',
|
|
463
|
+
p_organisation_id := 'org-uuid'
|
|
464
|
+
);
|
|
216
465
|
```
|
|
217
466
|
|
|
467
|
+
**Important Notes:**
|
|
468
|
+
- RPCs return `file_path` and `is_public` status
|
|
469
|
+
- Client-side helpers (`getPublicUrl`, `getSignedUrl`) generate the actual URLs
|
|
470
|
+
- This architecture separates database operations (access control) from URL generation (client-side)
|
|
471
|
+
|
|
218
472
|
## Migration from Old System
|
|
219
473
|
|
|
220
474
|
### Before (Old Pattern)
|
|
@@ -226,10 +480,10 @@ interface Person {
|
|
|
226
480
|
profile_photo_url: string; // Direct URL
|
|
227
481
|
}
|
|
228
482
|
|
|
229
|
-
// Manual file management
|
|
483
|
+
// Manual file management with hardcoded bucket
|
|
230
484
|
const uploadFile = async (file: File) => {
|
|
231
485
|
const { data } = await supabase.storage
|
|
232
|
-
.from('files')
|
|
486
|
+
.from('files') // Hardcoded bucket
|
|
233
487
|
.upload(`org-123/profile_photos/${file.name}`, file);
|
|
234
488
|
|
|
235
489
|
await supabase
|
|
@@ -237,18 +491,25 @@ const uploadFile = async (file: File) => {
|
|
|
237
491
|
.update({ profile_photo_url: data.path })
|
|
238
492
|
.eq('id', personId);
|
|
239
493
|
};
|
|
494
|
+
|
|
495
|
+
// Manual URL generation
|
|
496
|
+
const getPhotoUrl = async () => {
|
|
497
|
+
const { data } = await supabase.storage
|
|
498
|
+
.from('files')
|
|
499
|
+
.getPublicUrl(photoPath); // Doesn't handle signed URLs
|
|
500
|
+
};
|
|
240
501
|
```
|
|
241
502
|
|
|
242
503
|
### After (New Pattern)
|
|
243
504
|
```typescript
|
|
244
|
-
// File reference system
|
|
505
|
+
// File reference system - no URL columns needed
|
|
245
506
|
interface Person {
|
|
246
507
|
id: string;
|
|
247
508
|
name: string;
|
|
248
509
|
// profile_photo_url removed - use file_references table
|
|
249
510
|
}
|
|
250
511
|
|
|
251
|
-
// Centralized file management
|
|
512
|
+
// Centralized file management with bucket selection
|
|
252
513
|
const { uploadFile } = useFileReference(supabase);
|
|
253
514
|
|
|
254
515
|
const handleUpload = async (file: File) => {
|
|
@@ -257,11 +518,32 @@ const handleUpload = async (file: File) => {
|
|
|
257
518
|
record_id: personId,
|
|
258
519
|
organisation_id: orgId,
|
|
259
520
|
app_id: 'your-app-id',
|
|
260
|
-
category: FileCategory.PROFILE_PHOTOS
|
|
521
|
+
category: FileCategory.PROFILE_PHOTOS,
|
|
522
|
+
is_public: false // Automatically uses 'files' bucket
|
|
261
523
|
}, file);
|
|
262
524
|
};
|
|
525
|
+
|
|
526
|
+
// Automatic URL generation with bucket selection
|
|
527
|
+
const { fileUrl } = useFileReferenceForRecord(
|
|
528
|
+
supabase,
|
|
529
|
+
'pace_person',
|
|
530
|
+
personId,
|
|
531
|
+
orgId
|
|
532
|
+
);
|
|
533
|
+
// Automatically uses correct bucket and URL type (public or signed)
|
|
263
534
|
```
|
|
264
535
|
|
|
536
|
+
### Key Migration Benefits
|
|
537
|
+
|
|
538
|
+
1. **Bucket-Aware Storage**: No need to manually select buckets
|
|
539
|
+
2. **Automatic URL Generation**: Client helpers handle public vs signed URLs
|
|
540
|
+
3. **Unified Access Control**: RLS policies apply to all file references
|
|
541
|
+
4. **Consistent Metadata**: All files tracked with rich metadata
|
|
542
|
+
5. **Easy Cleanup**: Identify orphaned files easily
|
|
543
|
+
6. **App ID Auto-Resolution**: No need to hardcode app IDs in components
|
|
544
|
+
7. **Memory Leak Prevention**: Automatic cleanup and cache management
|
|
545
|
+
8. **Enhanced Error Handling**: Comprehensive error messages and recovery
|
|
546
|
+
|
|
265
547
|
## Storage Management
|
|
266
548
|
|
|
267
549
|
### Audit Orphaned Files
|
|
@@ -295,16 +577,36 @@ All file references are protected by RLS policies:
|
|
|
295
577
|
- Service role has full access for administrative operations
|
|
296
578
|
- File paths are validated to prevent directory traversal
|
|
297
579
|
|
|
298
|
-
### Access Control
|
|
580
|
+
### Access Control and URL Generation
|
|
299
581
|
|
|
300
|
-
|
|
301
|
-
// Public files - accessible via public URL
|
|
302
|
-
const publicUrl = getPublicUrl(filePath);
|
|
582
|
+
The system automatically handles URL generation based on file visibility:
|
|
303
583
|
|
|
304
|
-
|
|
305
|
-
|
|
584
|
+
```typescript
|
|
585
|
+
import { getPublicUrl, getSignedUrl, getBucketName } from '@jmruthers/pace-core';
|
|
586
|
+
|
|
587
|
+
// Public files - accessible via direct public URL
|
|
588
|
+
const publicPath = 'org-123/event_logos/logo.png';
|
|
589
|
+
const publicUrl = getPublicUrl(supabase, publicPath, true);
|
|
590
|
+
// Uses 'public-files' bucket automatically
|
|
591
|
+
|
|
592
|
+
// Private files - require signed URL with expiration
|
|
593
|
+
const privatePath = 'org-123/profile_photos/photo.jpg';
|
|
594
|
+
const signedUrlResult = await getSignedUrl(supabase, privatePath, {
|
|
595
|
+
appName: 'pace-core',
|
|
596
|
+
orgId: 'org-123',
|
|
597
|
+
expiresIn: 3600 // 1 hour
|
|
598
|
+
});
|
|
599
|
+
// Uses 'files' bucket automatically and generates signed URL
|
|
600
|
+
|
|
601
|
+
// Bucket selection helper
|
|
602
|
+
const bucket = getBucketName(isPublic); // 'public-files' or 'files'
|
|
306
603
|
```
|
|
307
604
|
|
|
605
|
+
**URL Refresh for Signed URLs:**
|
|
606
|
+
- Signed URLs expire after the specified duration (default: 1 hour)
|
|
607
|
+
- The `useFileReferenceForRecord` hook automatically refreshes signed URLs before expiration
|
|
608
|
+
- Refresh happens 5 minutes before expiration (at 55 minutes into the 1-hour expiry)
|
|
609
|
+
|
|
308
610
|
## Best Practices
|
|
309
611
|
|
|
310
612
|
### 1. Use Appropriate Categories
|
|
@@ -351,14 +653,78 @@ useEffect(() => {
|
|
|
351
653
|
/>
|
|
352
654
|
```
|
|
353
655
|
|
|
656
|
+
## Recent Improvements
|
|
657
|
+
|
|
658
|
+
### Performance & Memory Management
|
|
659
|
+
- **Fixed infinite logging loops**: Removed debug logging that was causing performance issues
|
|
660
|
+
- **Memory leak prevention**: Added automatic cleanup in hooks and components
|
|
661
|
+
- **Cache management**: Implemented cache size limits and TTL for event logos
|
|
662
|
+
- **Optimized re-renders**: Fixed infinite re-render loops in FileDisplay component
|
|
663
|
+
|
|
664
|
+
### Enhanced User Experience
|
|
665
|
+
- **App ID auto-resolution**: FileUpload component now automatically resolves app_id from app name
|
|
666
|
+
- **Better error handling**: More descriptive error messages and graceful failure handling
|
|
667
|
+
- **Progress tracking**: Enhanced upload progress with visual feedback
|
|
668
|
+
- **Event context integration**: Event logos now automatically sync with selected events
|
|
669
|
+
|
|
670
|
+
### Security & Reliability
|
|
671
|
+
- **RLS policy fixes**: Resolved permission issues for file uploads
|
|
672
|
+
- **Organisation context**: Proper database session context setting
|
|
673
|
+
- **Super admin support**: Enhanced permissions for administrative users
|
|
674
|
+
- **Bucket validation**: Improved bucket selection and validation logic
|
|
675
|
+
|
|
354
676
|
## Troubleshooting
|
|
355
677
|
|
|
356
678
|
### Common Issues
|
|
357
679
|
|
|
358
|
-
1. **File not found
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
680
|
+
1. **File not found**
|
|
681
|
+
- Check if file reference exists in `file_references` table
|
|
682
|
+
- Verify `file_path` is correct in the database
|
|
683
|
+
- Check if file exists in the correct bucket (`files` vs `public-files`)
|
|
684
|
+
|
|
685
|
+
2. **Access denied / RLS policy violation**
|
|
686
|
+
- Verify user has access to organisation (check RLS policies)
|
|
687
|
+
- Check if file reference `organisation_id` matches user's organisation
|
|
688
|
+
- Ensure RLS policies are correctly set up
|
|
689
|
+
- For super admins, verify `rbac_global_roles` table entry
|
|
690
|
+
|
|
691
|
+
3. **Upload fails**
|
|
692
|
+
- Check file size limits (default: 10MB, configurable)
|
|
693
|
+
- Verify storage bucket permissions
|
|
694
|
+
- Check file type validation (if `accept` prop is specified)
|
|
695
|
+
- Review browser console for detailed error messages
|
|
696
|
+
- Ensure organisation context is properly set
|
|
697
|
+
|
|
698
|
+
4. **App ID resolution failed**
|
|
699
|
+
- Ensure app name is set via `setRBACAppName()` or provide `app_id` prop
|
|
700
|
+
- Check that app exists in `rbac_apps` table
|
|
701
|
+
- Verify app name matches exactly (case-sensitive)
|
|
702
|
+
|
|
703
|
+
5. **Memory issues / infinite re-renders**
|
|
704
|
+
- Use `clearEventLogoCache()` when unmounting components
|
|
705
|
+
- Check for dependency loops in useEffect hooks
|
|
706
|
+
- Ensure proper cleanup in component unmount effects
|
|
707
|
+
|
|
708
|
+
6. **Signed URL expired**
|
|
709
|
+
- Signed URLs expire after 1 hour by default
|
|
710
|
+
- Use `useFileReferenceForRecord` hook for automatic refresh
|
|
711
|
+
- Or manually refresh using `getSignedUrl()` helper
|
|
712
|
+
|
|
713
|
+
7. **Wrong bucket being used**
|
|
714
|
+
- Check `is_public` flag in file reference
|
|
715
|
+
- Verify `isPublic` prop in `FileUpload` component
|
|
716
|
+
- Use `getBucketName()` helper to verify bucket selection
|
|
717
|
+
|
|
718
|
+
8. **Public files not accessible**
|
|
719
|
+
- Ensure file is in `public-files` bucket
|
|
720
|
+
- Verify `is_public = true` in `file_references` table
|
|
721
|
+
- Check storage bucket public access policies
|
|
722
|
+
|
|
723
|
+
9. **URL generation fails**
|
|
724
|
+
- Verify Supabase client is properly configured
|
|
725
|
+
- Check network connectivity
|
|
726
|
+
- Review browser console for errors
|
|
727
|
+
- Ensure file path is correctly formatted
|
|
362
728
|
|
|
363
729
|
### Debug Mode
|
|
364
730
|
|
|
@@ -367,6 +733,49 @@ Enable debug logging to troubleshoot issues:
|
|
|
367
733
|
```typescript
|
|
368
734
|
// Set debug mode in your app
|
|
369
735
|
localStorage.setItem('pace-core-debug', 'true');
|
|
736
|
+
|
|
737
|
+
// Or enable in specific components
|
|
738
|
+
<FileUpload
|
|
739
|
+
// ... other props
|
|
740
|
+
onUploadError={(error, file) => {
|
|
741
|
+
console.error('Upload error:', error, file);
|
|
742
|
+
// Additional error handling
|
|
743
|
+
}}
|
|
744
|
+
/>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### Checking File Reference Status
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
// Check file reference in database
|
|
751
|
+
const { data, error } = await supabase
|
|
752
|
+
.from('file_references')
|
|
753
|
+
.select('*')
|
|
754
|
+
.eq('table_name', 'pace_person')
|
|
755
|
+
.eq('record_id', personId)
|
|
756
|
+
.eq('organisation_id', orgId);
|
|
757
|
+
|
|
758
|
+
console.log('File references:', data);
|
|
759
|
+
console.log('Is public:', data[0]?.is_public);
|
|
760
|
+
console.log('Bucket:', data[0]?.is_public ? 'public-files' : 'files');
|
|
761
|
+
```
|
|
762
|
+
|
|
763
|
+
### Verifying Storage Bucket
|
|
764
|
+
|
|
765
|
+
```typescript
|
|
766
|
+
import { getBucketName } from '@jmruthers/pace-core';
|
|
767
|
+
|
|
768
|
+
// Check which bucket should be used
|
|
769
|
+
const isPublic = true; // or false
|
|
770
|
+
const bucket = getBucketName(isPublic);
|
|
771
|
+
console.log('Using bucket:', bucket); // 'public-files' or 'files'
|
|
772
|
+
|
|
773
|
+
// List files in bucket
|
|
774
|
+
const { data, error } = await supabase.storage
|
|
775
|
+
.from(bucket)
|
|
776
|
+
.list('org-123/profile_photos');
|
|
777
|
+
|
|
778
|
+
console.log('Files in bucket:', data);
|
|
370
779
|
```
|
|
371
780
|
|
|
372
781
|
## API Reference
|
|
@@ -410,32 +819,157 @@ interface FileMetadata {
|
|
|
410
819
|
|
|
411
820
|
### Step 1: Database Migration
|
|
412
821
|
Run the migration scripts in order:
|
|
822
|
+
|
|
823
|
+
**Latest Migration (Required for bucket support):**
|
|
824
|
+
1. `20251030124830_refactor_file_reference_rpc_functions.sql`
|
|
825
|
+
- Renames RPCs to follow `<family>_<domain>_<verb>` convention
|
|
826
|
+
- Adds bucket-aware functions
|
|
827
|
+
- Updates security checks
|
|
828
|
+
|
|
829
|
+
**Previous Migrations (if not already applied):**
|
|
413
830
|
1. `20250202030001_add_file_references_indexes_and_functions.sql`
|
|
414
831
|
2. `20250202030002_migrate_file_columns_to_file_references.sql`
|
|
415
832
|
3. `20250202030003_drop_old_file_columns.sql`
|
|
416
833
|
4. `20250202030004_drop_old_file_columns_final.sql`
|
|
417
834
|
|
|
418
835
|
### Step 2: Update Application Code
|
|
419
|
-
Replace direct file URL usage with file reference system:
|
|
420
836
|
|
|
837
|
+
**Replace Direct File URL Usage:**
|
|
421
838
|
```typescript
|
|
422
839
|
// Old
|
|
423
840
|
const profilePhoto = person.profile_photo_url;
|
|
841
|
+
<img src={profilePhoto} />
|
|
424
842
|
|
|
425
843
|
// New
|
|
426
|
-
const { fileUrl } = useFileReferenceForRecord(
|
|
844
|
+
const { fileUrl } = useFileReferenceForRecord(
|
|
845
|
+
supabase,
|
|
846
|
+
'pace_person',
|
|
847
|
+
personId,
|
|
848
|
+
orgId
|
|
849
|
+
);
|
|
850
|
+
<img src={fileUrl || fallback} />
|
|
851
|
+
```
|
|
852
|
+
|
|
853
|
+
**Update File Upload Code:**
|
|
854
|
+
```typescript
|
|
855
|
+
// Old - Manual bucket selection
|
|
856
|
+
const { data } = await supabase.storage
|
|
857
|
+
.from('files')
|
|
858
|
+
.upload(path, file);
|
|
859
|
+
|
|
860
|
+
// New - Automatic bucket selection
|
|
861
|
+
const { uploadFile } = useFileReference(supabase);
|
|
862
|
+
await uploadFile({
|
|
863
|
+
table_name: 'pace_person',
|
|
864
|
+
record_id: personId,
|
|
865
|
+
organisation_id: orgId,
|
|
866
|
+
app_id: appId,
|
|
867
|
+
category: FileCategory.PROFILE_PHOTOS,
|
|
868
|
+
is_public: false // Automatically uses correct bucket
|
|
869
|
+
}, file);
|
|
427
870
|
```
|
|
428
871
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
872
|
+
**Migrate Public Files:**
|
|
873
|
+
```typescript
|
|
874
|
+
// If migrating event logos or other public files
|
|
875
|
+
await uploadFile({
|
|
876
|
+
// ... other options
|
|
877
|
+
category: FileCategory.EVENT_LOGOS,
|
|
878
|
+
is_public: true // Uses 'public-files' bucket
|
|
879
|
+
}, logoFile);
|
|
880
|
+
```
|
|
434
881
|
|
|
435
|
-
### Step
|
|
436
|
-
|
|
882
|
+
### Step 3: Update RPC Function Calls
|
|
883
|
+
|
|
884
|
+
**Old RPC Names (deprecated):**
|
|
885
|
+
- `insert_file_reference` → `data_file_reference_create`
|
|
886
|
+
- `get_file_url` → `data_file_reference_url_get`
|
|
887
|
+
- `get_file_signed_url` → `data_file_reference_signed_url_get`
|
|
888
|
+
- `delete_file_reference` → `data_file_reference_delete`
|
|
889
|
+
- `get_record_files` → `data_file_reference_list`
|
|
890
|
+
- `get_record_file_count` → `data_file_reference_count_get`
|
|
891
|
+
|
|
892
|
+
**New RPC Names:**
|
|
893
|
+
```typescript
|
|
894
|
+
// Use the service layer instead of direct RPC calls
|
|
895
|
+
const service = createFileReferenceService(supabase);
|
|
896
|
+
|
|
897
|
+
// Old direct RPC call
|
|
898
|
+
await supabase.rpc('get_file_url', { /* ... */ });
|
|
899
|
+
|
|
900
|
+
// New service method (recommended)
|
|
901
|
+
const url = await service.getFileUrl(table_name, record_id, org_id);
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
### Step 4: Verify Bucket Configuration
|
|
905
|
+
|
|
906
|
+
1. **Check existing file references:**
|
|
907
|
+
```sql
|
|
908
|
+
SELECT
|
|
909
|
+
id,
|
|
910
|
+
file_path,
|
|
911
|
+
is_public,
|
|
912
|
+
CASE
|
|
913
|
+
WHEN is_public THEN 'public-files'
|
|
914
|
+
ELSE 'files'
|
|
915
|
+
END as bucket
|
|
916
|
+
FROM file_references
|
|
917
|
+
WHERE organisation_id = 'your-org-id';
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
2. **Ensure files are in correct buckets:**
|
|
921
|
+
- Files with `is_public = true` should be in `public-files` bucket
|
|
922
|
+
- Files with `is_public = false` should be in `files` bucket
|
|
923
|
+
|
|
924
|
+
3. **Migrate files if needed:**
|
|
925
|
+
```typescript
|
|
926
|
+
// If you find files in wrong bucket, use storage helpers to move them
|
|
927
|
+
import { uploadFile, deleteFile } from '@jmruthers/pace-core';
|
|
928
|
+
|
|
929
|
+
// Read from old bucket and upload to correct bucket
|
|
930
|
+
const { data: fileBlob } = await supabase.storage
|
|
931
|
+
.from('wrong-bucket')
|
|
932
|
+
.download(oldPath);
|
|
933
|
+
|
|
934
|
+
await uploadFile(supabase, fileBlob, {
|
|
935
|
+
orgId,
|
|
936
|
+
isPublic: true, // or false
|
|
937
|
+
customPath: category
|
|
938
|
+
});
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
### Step 5: Test and Verify
|
|
942
|
+
|
|
943
|
+
1. **Test file uploads:**
|
|
944
|
+
- Upload private file (should use `files` bucket)
|
|
945
|
+
- Upload public file (should use `public-files` bucket)
|
|
946
|
+
- Verify file reference is created correctly
|
|
947
|
+
|
|
948
|
+
2. **Test file access:**
|
|
949
|
+
- Access private file (should generate signed URL)
|
|
950
|
+
- Access public file (should use public URL)
|
|
951
|
+
- Verify URLs are correct format
|
|
952
|
+
|
|
953
|
+
3. **Verify RLS policies:**
|
|
954
|
+
- Test access from different organizations
|
|
955
|
+
- Verify access is denied for unauthorized users
|
|
956
|
+
- Check public files are accessible without auth
|
|
957
|
+
|
|
958
|
+
4. **Run audit script:**
|
|
959
|
+
```bash
|
|
960
|
+
node scripts/audit-storage-files.js
|
|
961
|
+
```
|
|
962
|
+
|
|
963
|
+
### Step 6: Cleanup
|
|
964
|
+
|
|
965
|
+
Run the cleanup migration (if applicable):
|
|
437
966
|
- `20250202040001_cleanup_migration_tables.sql`
|
|
438
967
|
|
|
968
|
+
**Remove old code:**
|
|
969
|
+
- Remove direct storage bucket references
|
|
970
|
+
- Remove manual URL generation code
|
|
971
|
+
- Remove deprecated RPC function calls
|
|
972
|
+
|
|
439
973
|
## Support
|
|
440
974
|
|
|
441
975
|
For issues or questions about the File Reference System:
|