@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.
Files changed (243) hide show
  1. package/dist/{AuthService-Df3IozMG.d.ts → AuthService-DcTI5Ov4.d.ts} +9 -0
  2. package/dist/{DataTable-FA6EUX5M.js → DataTable-PWBMKMOG.js} +7 -7
  3. package/dist/{PublicLoadingSpinner-DecuJBX0.d.ts → PublicLoadingSpinner-BQXD1fbO.d.ts} +160 -130
  4. package/dist/{UnifiedAuthProvider-K2IZAY5F.js → UnifiedAuthProvider-5D3HEQND.js} +4 -4
  5. package/dist/{UnifiedAuthProvider-B391Aqum.d.ts → UnifiedAuthProvider-BVKmQd9u.d.ts} +4 -0
  6. package/dist/auth-DReDSLq9.d.ts +16 -0
  7. package/dist/{chunk-CBSD3BZ3.js → chunk-3RZBKQ5Y.js} +2 -6
  8. package/dist/{chunk-CBSD3BZ3.js.map → chunk-3RZBKQ5Y.js.map} +1 -1
  9. package/dist/{chunk-NTW3KGS4.js → chunk-6UHXQH7P.js} +5 -5
  10. package/dist/{chunk-ZFLOV3OM.js → chunk-7VJDS5QD.js} +401 -16
  11. package/dist/chunk-7VJDS5QD.js.map +1 -0
  12. package/dist/{chunk-YVUZWLQG.js → chunk-AQGF5OG7.js} +3 -3
  13. package/dist/{chunk-CVMVPYAL.js → chunk-BDZUMRBD.js} +3 -5
  14. package/dist/chunk-BDZUMRBD.js.map +1 -0
  15. package/dist/{chunk-KAY3K5TP.js → chunk-BNXBJOGL.js} +4 -4
  16. package/dist/{chunk-S3JKDMD5.js → chunk-CXKMRKRF.js} +4 -4
  17. package/dist/{chunk-5BN3YGNK.js → chunk-DP5X5ORK.js} +217 -27
  18. package/dist/chunk-DP5X5ORK.js.map +1 -0
  19. package/dist/{chunk-RIXPZJUB.js → chunk-KTPG5VCH.js} +2 -2
  20. package/dist/{chunk-2FQEQUJT.js → chunk-KWICIQVK.js} +4 -4
  21. package/dist/{chunk-WUXCWRL6.js → chunk-XJ2HZOBU.js} +6 -1
  22. package/dist/chunk-XJ2HZOBU.js.map +1 -0
  23. package/dist/{chunk-I7O3RSMN.js → chunk-YWAFPVJA.js} +1298 -769
  24. package/dist/chunk-YWAFPVJA.js.map +1 -0
  25. package/dist/{chunk-I2VVV5PQ.js → chunk-YY4YYM3E.js} +2 -2
  26. package/dist/components.d.ts +6 -55
  27. package/dist/components.js +24 -205
  28. package/dist/components.js.map +1 -1
  29. package/dist/{file-reference-9xUOnwyt.d.ts → file-reference-C9isKNPn.d.ts} +67 -2
  30. package/dist/hooks.js +9 -8
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/index.d.ts +152 -26
  33. package/dist/index.js +64 -194
  34. package/dist/index.js.map +1 -1
  35. package/dist/providers.d.ts +5 -3
  36. package/dist/providers.js +3 -3
  37. package/dist/rbac/index.js +8 -8
  38. package/dist/types.d.ts +2 -1
  39. package/dist/types.js +3 -3
  40. package/dist/utils.js +2 -2
  41. package/docs/DOCUMENTATION_AUDIT.md +6 -6
  42. package/docs/DOCUMENTATION_STANDARD.md +137 -0
  43. package/docs/README.md +1 -1
  44. package/docs/api/classes/ColumnFactory.md +1 -1
  45. package/docs/api/classes/ErrorBoundary.md +1 -1
  46. package/docs/api/classes/InvalidScopeError.md +1 -1
  47. package/docs/api/classes/MissingUserContextError.md +1 -1
  48. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  49. package/docs/api/classes/PermissionDeniedError.md +1 -1
  50. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  51. package/docs/api/classes/RBACAuditManager.md +1 -1
  52. package/docs/api/classes/RBACCache.md +1 -1
  53. package/docs/api/classes/RBACEngine.md +1 -1
  54. package/docs/api/classes/RBACError.md +1 -1
  55. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  56. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  57. package/docs/api/classes/StorageUtils.md +83 -40
  58. package/docs/api/enums/FileCategory.md +56 -1
  59. package/docs/api/interfaces/AggregateConfig.md +1 -1
  60. package/docs/api/interfaces/ButtonProps.md +1 -1
  61. package/docs/api/interfaces/CardProps.md +1 -1
  62. package/docs/api/interfaces/ColorPalette.md +1 -1
  63. package/docs/api/interfaces/ColorShade.md +1 -1
  64. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  65. package/docs/api/interfaces/DataRecord.md +1 -1
  66. package/docs/api/interfaces/DataTableAction.md +1 -1
  67. package/docs/api/interfaces/DataTableColumn.md +1 -1
  68. package/docs/api/interfaces/DataTableProps.md +1 -1
  69. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  70. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  71. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  72. package/docs/api/interfaces/EventLogoProps.md +11 -11
  73. package/docs/api/interfaces/FileDisplayProps.md +10 -10
  74. package/docs/api/interfaces/FileMetadata.md +1 -1
  75. package/docs/api/interfaces/FileReference.md +1 -1
  76. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  77. package/docs/api/interfaces/FileUploadOptions.md +8 -8
  78. package/docs/api/interfaces/FileUploadProps.md +137 -42
  79. package/docs/api/interfaces/FooterProps.md +1 -1
  80. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  81. package/docs/api/interfaces/InputProps.md +1 -1
  82. package/docs/api/interfaces/LabelProps.md +1 -1
  83. package/docs/api/interfaces/LoginFormProps.md +1 -1
  84. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  85. package/docs/api/interfaces/NavigationContextType.md +1 -1
  86. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  87. package/docs/api/interfaces/NavigationItem.md +1 -1
  88. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  89. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  90. package/docs/api/interfaces/Organisation.md +1 -1
  91. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  92. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  93. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  94. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  95. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  96. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  97. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  98. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  99. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  100. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  101. package/docs/api/interfaces/PaletteData.md +1 -1
  102. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  103. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  104. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  105. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  106. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  107. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  108. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  109. package/docs/api/interfaces/RBACConfig.md +1 -1
  110. package/docs/api/interfaces/RBACLogger.md +1 -1
  111. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  112. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  113. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  114. package/docs/api/interfaces/RouteConfig.md +1 -1
  115. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  116. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  117. package/docs/api/interfaces/StorageConfig.md +1 -1
  118. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  119. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  120. package/docs/api/interfaces/StorageListOptions.md +1 -1
  121. package/docs/api/interfaces/StorageListResult.md +1 -1
  122. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  123. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  124. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  125. package/docs/api/interfaces/StyleImport.md +1 -1
  126. package/docs/api/interfaces/SwitchProps.md +1 -1
  127. package/docs/api/interfaces/ToastActionElement.md +1 -1
  128. package/docs/api/interfaces/ToastProps.md +1 -1
  129. package/docs/api/interfaces/UnifiedAuthContextType.md +83 -50
  130. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  131. package/docs/api/interfaces/UseEventLogoOptions.md +74 -0
  132. package/docs/api/interfaces/UseEventLogoReturn.md +81 -0
  133. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  134. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  135. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  136. package/docs/api/interfaces/UsePublicEventLogoReturn.md +6 -6
  137. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  138. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  139. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  140. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  141. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  142. package/docs/api/interfaces/UserEventAccess.md +11 -11
  143. package/docs/api/interfaces/UserMenuProps.md +1 -1
  144. package/docs/api/interfaces/UserProfile.md +1 -1
  145. package/docs/api/modules.md +290 -95
  146. package/docs/api-reference/components.md +1 -18
  147. package/docs/api-reference/hooks.md +1 -4
  148. package/docs/best-practices/testing.md +2 -0
  149. package/docs/documentation-index.md +1 -1
  150. package/docs/getting-started/faq.md +1 -1
  151. package/docs/implementation-guides/file-reference-system.md +592 -58
  152. package/docs/implementation-guides/file-upload-storage.md +137 -73
  153. package/docs/implementation-guides/public-pages-advanced.md +10 -0
  154. package/docs/rbac/super-admin-guide.md +18 -70
  155. package/docs/testing/README.md +2 -0
  156. package/package.json +1 -1
  157. package/src/__tests__/TEST_STANDARD.md +674 -0
  158. package/src/__tests__/helpers/test-utils.tsx +3 -2
  159. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx.skip → DataTable.comprehensive.test.tsx} +17 -18
  160. package/src/components/DataTable/__tests__/{DataTable.test.tsx.skip → DataTable.test.tsx} +14 -22
  161. package/src/components/DataTable/__tests__/{ssr.strict-mode.test.tsx.skip → ssr.strict-mode.test.tsx} +42 -47
  162. package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +1 -1
  163. package/src/components/DataTable/examples/__tests__/PerformanceExample.test.tsx +13 -4
  164. package/src/components/DataTable/utils/__tests__/COVERAGE_NOTE.md +1 -1
  165. package/src/components/DataTable/utils/__tests__/performanceUtils.test.ts +10 -6
  166. package/src/components/FileDisplay/FileDisplay.test.tsx +257 -0
  167. package/src/components/{FileDisplay.tsx → FileDisplay/FileDisplay.tsx} +111 -10
  168. package/src/components/FileDisplay/index.tsx +4 -0
  169. package/src/components/FileUpload/FileUpload.test.tsx +171 -621
  170. package/src/components/FileUpload/FileUpload.tsx +512 -168
  171. package/src/components/FileUpload/index.tsx +4 -0
  172. package/src/components/Progress/Progress.test.tsx +38 -0
  173. package/src/components/PublicLayout/EventLogo.tsx +6 -4
  174. package/src/components/Select/Select.test.tsx +1 -1
  175. package/src/components/SessionRestorationLoader.tsx +48 -0
  176. package/src/components/Toast/Toast.tsx +13 -8
  177. package/src/components/index.ts +16 -16
  178. package/src/hooks/__tests__/ServiceHooks.test.tsx +615 -0
  179. package/src/hooks/public/usePublicEventLogo.ts +16 -20
  180. package/src/hooks/useEventLogo.ts +316 -0
  181. package/src/hooks/useEvents.ts +0 -5
  182. package/src/hooks/useFileReference.test.ts +659 -0
  183. package/src/hooks/useFileReference.ts +207 -3
  184. package/src/hooks/useSessionRestoration.ts +64 -0
  185. package/src/index.ts +17 -5
  186. package/src/providers/{UnifiedAuthProvider.test.simple.tsx → UnifiedAuthProvider.smoke.test.tsx} +81 -60
  187. package/src/providers/services/AuthServiceProvider.tsx +27 -3
  188. package/src/providers/services/UnifiedAuthProvider.tsx +34 -5
  189. package/src/rbac/{engine.test.simple.ts → RBACEngine.smoke.test.ts} +17 -12
  190. package/src/services/AuthService.ts +142 -20
  191. package/src/services/EventService.ts +0 -4
  192. package/src/types/auth.ts +15 -0
  193. package/src/types/file-reference.ts +73 -1
  194. package/src/types/index.ts +1 -0
  195. package/src/utils/__tests__/organisationContext.unit.test.ts +2 -4
  196. package/src/utils/appNameResolver.simple.test.ts +99 -29
  197. package/src/utils/file-reference.test.ts +535 -0
  198. package/src/utils/file-reference.ts +200 -30
  199. package/src/utils/organisationContext.test.ts +5 -19
  200. package/src/utils/organisationContext.ts +3 -5
  201. package/src/utils/storage/README.md +269 -262
  202. package/src/utils/storage/config.ts +9 -0
  203. package/src/utils/storage/helpers.test.ts +735 -0
  204. package/src/utils/storage/helpers.ts +189 -16
  205. package/src/utils/storage/index.ts +3 -0
  206. package/src/validation/__tests__/sanitization.unit.test.ts +1 -1
  207. package/src/validation/__tests__/schemaUtils.unit.test.ts +1 -1
  208. package/src/validation/__tests__/user.unit.test.ts +1 -1
  209. package/dist/chunk-5BN3YGNK.js.map +0 -1
  210. package/dist/chunk-CVMVPYAL.js.map +0 -1
  211. package/dist/chunk-I7O3RSMN.js.map +0 -1
  212. package/dist/chunk-WUXCWRL6.js.map +0 -1
  213. package/dist/chunk-ZFLOV3OM.js.map +0 -1
  214. package/docs/CONTENT_AUDIT_REPORT.md +0 -253
  215. package/docs/STYLE_GUIDE.md +0 -37
  216. package/examples/RBAC/__tests__/PermissionExample.test.tsx +0 -150
  217. package/examples/public-pages/__tests__/PublicPageUsageExample.test.tsx +0 -159
  218. package/src/__tests__/TEST_GUIDE_CURSOR.md +0 -1605
  219. package/src/__tests__/TEST_GUIDE_HUMAN.md +0 -103
  220. package/src/components/FileUpload/FileUpload.example.tsx +0 -218
  221. package/src/components/FileUpload/index.ts +0 -6
  222. package/src/components/FileUpload.tsx +0 -176
  223. package/src/components/Progress/index.ts +0 -3
  224. package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -666
  225. package/src/components/SuperAdminGuard.tsx +0 -116
  226. package/src/components/__tests__/FileDisplay.test.tsx +0 -575
  227. package/src/components/__tests__/FileUpload.test.tsx +0 -446
  228. package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -627
  229. package/src/components/examples/PermissionExample.tsx +0 -173
  230. package/src/hooks/__tests__/usePublicEvent.unit.test.ts +0 -583
  231. package/src/hooks/__tests__/usePublicEventLogo.unit.test.ts +0 -640
  232. package/src/types/__tests__/file-reference.test.ts +0 -447
  233. package/src/utils/__tests__/file-reference.test.ts +0 -383
  234. /package/dist/{DataTable-FA6EUX5M.js.map → DataTable-PWBMKMOG.js.map} +0 -0
  235. /package/dist/{UnifiedAuthProvider-K2IZAY5F.js.map → UnifiedAuthProvider-5D3HEQND.js.map} +0 -0
  236. /package/dist/{chunk-NTW3KGS4.js.map → chunk-6UHXQH7P.js.map} +0 -0
  237. /package/dist/{chunk-YVUZWLQG.js.map → chunk-AQGF5OG7.js.map} +0 -0
  238. /package/dist/{chunk-KAY3K5TP.js.map → chunk-BNXBJOGL.js.map} +0 -0
  239. /package/dist/{chunk-S3JKDMD5.js.map → chunk-CXKMRKRF.js.map} +0 -0
  240. /package/dist/{chunk-RIXPZJUB.js.map → chunk-KTPG5VCH.js.map} +0 -0
  241. /package/dist/{chunk-2FQEQUJT.js.map → chunk-KWICIQVK.js.map} +0 -0
  242. /package/dist/{chunk-I2VVV5PQ.js.map → chunk-YY4YYM3E.js.map} +0 -0
  243. /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) => console.log('Uploaded:', result.file_path)}
89
- onUploadError={(error) => console.error('Upload failed:', error)}
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 { uploadFile, getFileUrl, deleteFile, isLoading, error } = useFileReference(supabase);
129
-
130
- // Upload a file
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
- // Get file URL
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 deleteFile('pace_person', personId, orgId, true);
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
- ### Helper Functions
396
+ ### RPC Functions
397
+
398
+ **All RPCs follow the `<family>_<domain>_<verb>` naming convention:**
189
399
 
190
400
  ```sql
191
- -- Insert file reference
192
- SELECT insert_file_reference(
193
- 'pace_person', -- table_name
194
- 'person-uuid', -- record_id
195
- 'org-123/profile_photos/file.jpg', -- file_path
196
- 'org-uuid', -- organisation_id
197
- 'app-uuid', -- app_id
198
- '{"fileName": "photo.jpg"}'::jsonb, -- file_metadata
199
- false -- is_public
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 URL
203
- SELECT get_file_url('pace_person', 'person-uuid', 'org-uuid');
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 signed URL for private files
206
- SELECT get_file_signed_url('pace_person', 'person-uuid', 'org-uuid', 3600);
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 delete_file_reference('pace_person', 'person-uuid', 'org-uuid', true);
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 get_record_files('pace_person', 'person-uuid', 'org-uuid');
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 get_record_file_count('pace_person', 'person-uuid', 'org-uuid');
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
- ```typescript
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
- // Private files - require signed URL
305
- const signedUrl = await getSignedUrl(supabase, filePath, { expiresIn: 3600 });
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**: Check if file reference exists in database
359
- 2. **Access denied**: Verify user has access to organization
360
- 3. **Upload fails**: Check file size limits and storage permissions
361
- 4. **Signed URL expired**: Refresh signed URL for private files
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(supabase, 'pace_person', personId, orgId);
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
- ### Step 3: Test and Verify
430
- 1. Test file uploads and downloads
431
- 2. Verify RLS policies work correctly
432
- 3. Run audit script to check for orphaned files
433
- 4. Clean up any issues found
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 4: Cleanup
436
- Run the cleanup migration:
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: