@jmruthers/pace-core 0.5.191 → 0.5.193

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) hide show
  1. package/dist/{AuthService-CbP_utw2.d.ts → AuthService-DjnJHDtC.d.ts} +1 -0
  2. package/dist/{DataTable-WKRZD47S.js → DataTable-5FU7IESH.js} +7 -6
  3. package/dist/{PublicPageProvider-ULXC_u6U.d.ts → PublicPageProvider-C0Sm_e5k.d.ts} +3 -1
  4. package/dist/{UnifiedAuthProvider-BYA9qB-o.d.ts → UnifiedAuthProvider-185Ih4dj.d.ts} +2 -0
  5. package/dist/{UnifiedAuthProvider-FTSG5XH7.js → UnifiedAuthProvider-RGJTDE2C.js} +3 -3
  6. package/dist/{api-IHKALJZD.js → api-N774RPUA.js} +2 -2
  7. package/dist/chunk-6C4YBBJM 5.js +628 -0
  8. package/dist/chunk-7D4SUZUM.js 2.map +1 -0
  9. package/dist/{chunk-LOMZXPSN.js → chunk-7EQTDTTJ.js} +47 -74
  10. package/dist/chunk-7EQTDTTJ.js 2.map +1 -0
  11. package/dist/chunk-7EQTDTTJ.js.map +1 -0
  12. package/dist/{chunk-6LTQQAT6.js → chunk-7FLMSG37.js} +336 -137
  13. package/dist/chunk-7FLMSG37.js 2.map +1 -0
  14. package/dist/chunk-7FLMSG37.js.map +1 -0
  15. package/dist/{chunk-XNYQOL3Z.js → chunk-BC4IJKSL.js} +9 -18
  16. package/dist/chunk-BC4IJKSL.js.map +1 -0
  17. package/dist/{chunk-ULHIJK66.js → chunk-E3SPN4VZ 5.js } +146 -36
  18. package/dist/chunk-E3SPN4VZ.js +12917 -0
  19. package/dist/{chunk-ULHIJK66.js.map → chunk-E3SPN4VZ.js.map} +1 -1
  20. package/dist/chunk-E66EQZE6 5.js +37 -0
  21. package/dist/chunk-E66EQZE6.js 2.map +1 -0
  22. package/dist/{chunk-6TQDD426.js → chunk-HWIIPPNI.js} +40 -221
  23. package/dist/chunk-HWIIPPNI.js.map +1 -0
  24. package/dist/chunk-I7PSE6JW 5.js +191 -0
  25. package/dist/chunk-I7PSE6JW.js 2.map +1 -0
  26. package/dist/{chunk-OETXORNB.js → chunk-IIELH4DL.js} +211 -136
  27. package/dist/chunk-IIELH4DL.js.map +1 -0
  28. package/dist/{chunk-ROXMHMY2.js → chunk-KNC55RTG.js} +13 -3
  29. package/dist/{chunk-ROXMHMY2.js.map → chunk-KNC55RTG.js 5.map } +1 -1
  30. package/dist/chunk-KNC55RTG.js.map +1 -0
  31. package/dist/chunk-KQCRWDSA.js 5.map +1 -0
  32. package/dist/{chunk-XYXSXPUK.js → chunk-LFNCN2SP.js} +7 -6
  33. package/dist/chunk-LFNCN2SP.js 2.map +1 -0
  34. package/dist/chunk-LFNCN2SP.js.map +1 -0
  35. package/dist/chunk-LMC26NLJ 2.js +84 -0
  36. package/dist/{chunk-VKB2CO4Z.js → chunk-NOAYCWCX 5.js } +84 -87
  37. package/dist/chunk-NOAYCWCX.js +4993 -0
  38. package/dist/chunk-NOAYCWCX.js.map +1 -0
  39. package/dist/chunk-QWWZ5CAQ.js 3.map +1 -0
  40. package/dist/chunk-QXHPKYJV 3.js +113 -0
  41. package/dist/chunk-R77UEZ4E 3.js +68 -0
  42. package/dist/chunk-VBXEHIUJ.js 6.map +1 -0
  43. package/dist/{chunk-VRGWKHDB.js → chunk-XNXXZ43G.js} +77 -33
  44. package/dist/chunk-XNXXZ43G.js.map +1 -0
  45. package/dist/chunk-ZSAAAMVR 6.js +25 -0
  46. package/dist/components.d.ts +2 -2
  47. package/dist/components.js +7 -7
  48. package/dist/components.js 5.map +1 -0
  49. package/dist/hooks.js +8 -8
  50. package/dist/index.d.ts +5 -5
  51. package/dist/index.js +12 -14
  52. package/dist/index.js.map +1 -1
  53. package/dist/providers.d.ts +3 -3
  54. package/dist/providers.js +2 -2
  55. package/dist/rbac/index.d.ts +1 -19
  56. package/dist/rbac/index.js +7 -9
  57. package/dist/styles/index 2.js +12 -0
  58. package/dist/styles/index.js 5.map +1 -0
  59. package/dist/theming/runtime 5.js +19 -0
  60. package/dist/theming/runtime.js 5.map +1 -0
  61. package/dist/utils.js +1 -1
  62. package/docs/api/classes/ColumnFactory.md +1 -1
  63. package/docs/api/classes/ErrorBoundary.md +1 -1
  64. package/docs/api/classes/InvalidScopeError.md +1 -1
  65. package/docs/api/classes/Logger.md +1 -1
  66. package/docs/api/classes/MissingUserContextError.md +1 -1
  67. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  68. package/docs/api/classes/PermissionDeniedError.md +2 -2
  69. package/docs/api/classes/RBACAuditManager.md +2 -2
  70. package/docs/api/classes/RBACCache.md +1 -1
  71. package/docs/api/classes/RBACEngine.md +2 -2
  72. package/docs/api/classes/RBACError.md +1 -1
  73. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  74. package/docs/api/classes/SecureSupabaseClient.md +10 -10
  75. package/docs/api/classes/StorageUtils.md +1 -1
  76. package/docs/api/enums/FileCategory.md +1 -1
  77. package/docs/api/enums/LogLevel.md +1 -1
  78. package/docs/api/enums/RBACErrorCode.md +1 -1
  79. package/docs/api/enums/RPCFunction.md +1 -1
  80. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  81. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  82. package/docs/api/interfaces/AggregateConfig.md +1 -1
  83. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  84. package/docs/api/interfaces/AvatarProps.md +1 -1
  85. package/docs/api/interfaces/BadgeProps.md +1 -1
  86. package/docs/api/interfaces/ButtonProps.md +1 -1
  87. package/docs/api/interfaces/CalendarProps.md +1 -1
  88. package/docs/api/interfaces/CardProps.md +1 -1
  89. package/docs/api/interfaces/ColorPalette.md +1 -1
  90. package/docs/api/interfaces/ColorShade.md +1 -1
  91. package/docs/api/interfaces/ComplianceResult.md +1 -1
  92. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  93. package/docs/api/interfaces/DataRecord.md +1 -1
  94. package/docs/api/interfaces/DataTableAction.md +1 -1
  95. package/docs/api/interfaces/DataTableColumn.md +1 -1
  96. package/docs/api/interfaces/DataTableProps.md +1 -1
  97. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  98. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  99. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  100. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  101. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  102. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  103. package/docs/api/interfaces/ExportColumn.md +1 -1
  104. package/docs/api/interfaces/ExportOptions.md +1 -1
  105. package/docs/api/interfaces/FileDisplayProps.md +24 -11
  106. package/docs/api/interfaces/FileMetadata.md +1 -1
  107. package/docs/api/interfaces/FileReference.md +1 -1
  108. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  109. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  110. package/docs/api/interfaces/FileUploadProps.md +1 -1
  111. package/docs/api/interfaces/FooterProps.md +1 -1
  112. package/docs/api/interfaces/FormFieldProps.md +1 -1
  113. package/docs/api/interfaces/FormProps.md +1 -1
  114. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  115. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  116. package/docs/api/interfaces/InputProps.md +1 -1
  117. package/docs/api/interfaces/LabelProps.md +1 -1
  118. package/docs/api/interfaces/LoggerConfig.md +1 -1
  119. package/docs/api/interfaces/LoginFormProps.md +1 -1
  120. package/docs/api/interfaces/NavigationAccessRecord.md +2 -2
  121. package/docs/api/interfaces/NavigationContextType.md +1 -1
  122. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  123. package/docs/api/interfaces/NavigationItem.md +1 -1
  124. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  125. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  126. package/docs/api/interfaces/Organisation.md +1 -1
  127. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  128. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  129. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  130. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  131. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  132. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  133. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  134. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  135. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  136. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  137. package/docs/api/interfaces/PaletteData.md +1 -1
  138. package/docs/api/interfaces/ParsedAddress.md +1 -1
  139. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  140. package/docs/api/interfaces/ProgressProps.md +1 -1
  141. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  142. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  143. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  144. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  145. package/docs/api/interfaces/QuickFix.md +1 -1
  146. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  147. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  148. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  149. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  150. package/docs/api/interfaces/RBACConfig.md +2 -2
  151. package/docs/api/interfaces/RBACContext.md +1 -1
  152. package/docs/api/interfaces/RBACLogger.md +1 -1
  153. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  154. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  155. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  156. package/docs/api/interfaces/RBACPermissionCheckResult.md +2 -2
  157. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  158. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  159. package/docs/api/interfaces/RBACResult.md +1 -1
  160. package/docs/api/interfaces/RBACRoleGrantParams.md +2 -2
  161. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  162. package/docs/api/interfaces/RBACRoleRevokeParams.md +2 -2
  163. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  164. package/docs/api/interfaces/RBACRoleValidateParams.md +2 -2
  165. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  166. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  167. package/docs/api/interfaces/RBACRolesListResult.md +2 -2
  168. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  169. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  170. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  171. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  172. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  173. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  174. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  175. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  176. package/docs/api/interfaces/RouteConfig.md +2 -2
  177. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  178. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  179. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  180. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  181. package/docs/api/interfaces/SetupIssue.md +1 -1
  182. package/docs/api/interfaces/StorageConfig.md +1 -1
  183. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  184. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  185. package/docs/api/interfaces/StorageListOptions.md +1 -1
  186. package/docs/api/interfaces/StorageListResult.md +1 -1
  187. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  188. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  189. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  190. package/docs/api/interfaces/StyleImport.md +1 -1
  191. package/docs/api/interfaces/SwitchProps.md +1 -1
  192. package/docs/api/interfaces/TabsContentProps.md +1 -1
  193. package/docs/api/interfaces/TabsListProps.md +1 -1
  194. package/docs/api/interfaces/TabsProps.md +1 -1
  195. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  196. package/docs/api/interfaces/TextareaProps.md +1 -1
  197. package/docs/api/interfaces/ToastActionElement.md +1 -1
  198. package/docs/api/interfaces/ToastProps.md +1 -1
  199. package/docs/api/interfaces/UnifiedAuthContextType.md +60 -38
  200. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  201. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  202. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  203. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  204. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  205. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  206. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  207. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  208. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  209. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  210. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  211. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  212. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  213. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  214. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  215. package/docs/api/interfaces/UserEventAccess.md +1 -1
  216. package/docs/api/interfaces/UserMenuProps.md +1 -1
  217. package/docs/api/interfaces/UserProfile.md +1 -1
  218. package/docs/api/modules.md +194 -209
  219. package/docs/migration/database-changes-december-2025.md +2 -1
  220. package/docs/rbac/event-based-apps.md +124 -6
  221. package/package.json +1 -1
  222. package/scripts/check-pace-core-compliance.cjs +292 -57
  223. package/src/__tests__/rls-policies.test.ts +3 -1
  224. package/src/components/DataTable/__tests__/DataTable.default-state.test.tsx +172 -45
  225. package/src/components/DataTable/__tests__/DataTable.grouping-aggregation.test.tsx +121 -28
  226. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +9 -8
  227. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +20 -52
  228. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +170 -34
  229. package/src/components/DataTable/__tests__/keyboard.test.tsx +75 -12
  230. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +75 -11
  231. package/src/components/DataTable/components/UnifiedTableBody.tsx +85 -14
  232. package/src/components/DataTable/hooks/useDataTablePermissions.ts +75 -10
  233. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -1
  234. package/src/components/FileDisplay/FileDisplay.tsx +16 -4
  235. package/src/components/NavigationMenu/NavigationMenu.test.tsx +6 -4
  236. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -10
  237. package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -1
  238. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +25 -2
  239. package/src/components/PaceAppLayout/PaceAppLayout.tsx +97 -68
  240. package/src/components/PaceLoginPage/PaceLoginPage.tsx +0 -7
  241. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +5 -9
  242. package/src/components/ProtectedRoute/ProtectedRoute.tsx +0 -1
  243. package/src/components/PublicLayout/PublicPageProvider.tsx +0 -1
  244. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +14 -7
  245. package/src/hooks/services/useAuthService.ts +21 -3
  246. package/src/hooks/services/useEventService.ts +21 -3
  247. package/src/hooks/services/useInactivityService.ts +21 -3
  248. package/src/hooks/services/useOrganisationService.ts +21 -3
  249. package/src/hooks/useFileDisplay.ts +10 -17
  250. package/src/hooks/useSecureDataAccess.test.ts +16 -9
  251. package/src/hooks/useSecureDataAccess.ts +3 -2
  252. package/src/providers/services/EventServiceProvider.tsx +0 -8
  253. package/src/providers/services/UnifiedAuthProvider.tsx +174 -24
  254. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +10 -16
  255. package/src/rbac/__tests__/isSuperAdmin.real.test.ts +82 -0
  256. package/src/rbac/adapters.tsx +3 -22
  257. package/src/rbac/api.test.ts +2 -2
  258. package/src/rbac/api.ts +7 -1
  259. package/src/rbac/components/EnhancedNavigationMenu.tsx +2 -15
  260. package/src/rbac/components/NavigationGuard.tsx +1 -10
  261. package/src/rbac/components/NavigationProvider.tsx +0 -1
  262. package/src/rbac/components/PermissionEnforcer.tsx +45 -12
  263. package/src/rbac/components/SecureDataProvider.tsx +0 -1
  264. package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +7 -43
  265. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +4 -11
  266. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +3 -3
  267. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +1 -1
  268. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +1 -1
  269. package/src/rbac/engine.ts +14 -2
  270. package/src/rbac/hooks/index.ts +0 -3
  271. package/src/rbac/hooks/usePermissions.ts +51 -11
  272. package/src/rbac/hooks/useRBAC.ts +3 -13
  273. package/src/rbac/hooks/useResolvedScope.test.ts +75 -54
  274. package/src/rbac/hooks/useResolvedScope.ts +58 -33
  275. package/src/rbac/hooks/useSecureSupabase.ts +4 -9
  276. package/src/rbac/secureClient.ts +31 -0
  277. package/src/services/EventService.ts +4 -57
  278. package/src/services/InactivityService.ts +127 -34
  279. package/src/services/OrganisationService.ts +68 -10
  280. package/dist/chunk-6LTQQAT6.js.map +0 -1
  281. package/dist/chunk-6TQDD426.js.map +0 -1
  282. package/dist/chunk-LOMZXPSN.js.map +0 -1
  283. package/dist/chunk-OETXORNB.js.map +0 -1
  284. package/dist/chunk-VKB2CO4Z.js.map +0 -1
  285. package/dist/chunk-VRGWKHDB.js.map +0 -1
  286. package/dist/chunk-XNYQOL3Z.js.map +0 -1
  287. package/dist/chunk-XYXSXPUK.js.map +0 -1
  288. package/scripts/check-pace-core-compliance.js +0 -512
  289. package/src/rbac/hooks/useSuperAdminBypass.ts +0 -126
  290. package/src/utils/context/superAdminOverride.ts +0 -58
  291. /package/dist/{DataTable-WKRZD47S.js.map → DataTable-5FU7IESH.js.map} +0 -0
  292. /package/dist/{UnifiedAuthProvider-FTSG5XH7.js.map → UnifiedAuthProvider-RGJTDE2C.js.map} +0 -0
  293. /package/dist/{api-IHKALJZD.js.map → api-N774RPUA.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/useDebounce.ts","../src/hooks/useQueryCache.ts","../src/hooks/useAddressAutocomplete.ts","../src/utils/storage/config.ts","../src/utils/storage/helpers.ts","../src/hooks/public/usePublicFileDisplay.ts","../src/hooks/useFileDisplay.ts","../src/utils/file-reference/index.ts","../src/hooks/useEventTheme.ts","../src/hooks/usePreventTabReload.ts"],"sourcesContent":["\nimport { useState, useEffect } from 'react';\n\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","/**\n * Query Result Caching Hook\n * @package @jmruthers/pace-core\n * @module Hooks/QueryCache\n * @since 2.0.0\n * \n * Provides in-memory caching for frequently accessed data to eliminate duplicate queries.\n * Useful for caching user profiles, app pages, and other relatively static data.\n */\n\nimport { useCallback, useRef, useEffect } from 'react';\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useQueryCache');\n\ninterface CachedQueryEntry<T> {\n data: T;\n expiresAt: number; // Unix timestamp\n promise?: Promise<T>; // For in-flight requests\n}\n\n/**\n * In-memory cache for query results\n * Key format: `table:filterKey:filterValue`\n */\nconst queryCache = new Map<string, CachedQueryEntry<any>>();\n\n// Cleanup interval (every 5 minutes)\nconst CLEANUP_INTERVAL_MS = 5 * 60 * 1000;\nlet cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\nfunction runCacheCleanup() {\n const now = Date.now();\n const expiredKeys: string[] = [];\n \n queryCache.forEach((entry, key) => {\n if (entry.expiresAt <= now) {\n expiredKeys.push(key);\n }\n });\n \n expiredKeys.forEach(key => {\n queryCache.delete(key);\n log.debug(`Removed expired query from cache: ${key}`);\n });\n}\n\n// Initialize cleanup timer once\nif (typeof window !== 'undefined' && !cleanupTimer) {\n cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);\n log.debug('Query cache cleanup initialized.');\n \n // Cleanup on page unload to prevent memory leaks\n window.addEventListener('beforeunload', () => {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared on page unload.');\n }\n });\n}\n\n/**\n * Manually cleanup the query cache cleanup timer\n * Useful for testing or when the module is reloaded\n */\nexport function cleanupQueryCache(): void {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared.');\n }\n}\n\nexport interface UseQueryCacheOptions {\n /** Time to live in seconds (default: 300 = 5 minutes) */\n ttl?: number;\n /** Whether to enable caching (default: true) */\n enabled?: boolean;\n}\n\nexport interface UseQueryCacheReturn {\n /** Get cached query result or fetch if not cached */\n getCachedQuery: <T>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options?: UseQueryCacheOptions\n ) => Promise<T>;\n /** Invalidate a specific cached query */\n invalidateQuery: (table: string, filterKey: string, filterValue: string) => void;\n /** Clear all cached queries */\n clearCache: () => void;\n /** Get cache statistics */\n getCacheStats: () => { size: number; keys: string[] };\n}\n\n/**\n * Hook for query result caching\n * \n * Provides caching for frequently accessed data to eliminate duplicate queries.\n * Automatically handles cache expiration and cleanup.\n * \n * @param supabase - Supabase client (optional, can be passed in fetchFn)\n * @returns Query cache utilities\n * \n * @example\n * ```tsx\n * const { getCachedQuery } = useQueryCache(supabase);\n * \n * const person = await getCachedQuery(\n * 'core_person',\n * 'user_id',\n * userId,\n * async () => {\n * const { data } = await supabase\n * .from('core_person')\n * .select('id, first_name, last_name, email')\n * .eq('user_id', userId)\n * .single();\n * return data;\n * },\n * { ttl: 300 } // 5 minutes\n * );\n * ```\n */\nexport function useQueryCache(supabase?: SupabaseClient<Database>): UseQueryCacheReturn {\n const getCachedQuery = useCallback(async <T,>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options: UseQueryCacheOptions = {}\n ): Promise<T> => {\n const { ttl = 300, enabled = true } = options; // Default 5 minutes\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n const now = Date.now();\n \n if (!enabled) {\n return fetchFn();\n }\n \n // Check cache\n const cached = queryCache.get(cacheKey);\n if (cached) {\n // If data is still valid, return it\n if (cached.expiresAt > now && cached.data !== undefined) {\n log.debug(`Cache hit for query: ${cacheKey}`);\n return cached.data as T;\n }\n \n // If there's an in-flight request, wait for it\n if (cached.promise) {\n log.debug(`Waiting for in-flight request: ${cacheKey}`);\n return cached.promise as Promise<T>;\n }\n }\n \n // Fetch data\n log.debug(`Cache miss for query: ${cacheKey}, fetching...`);\n const fetchPromise = fetchFn();\n \n // Store promise for in-flight request deduplication\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + (ttl * 1000),\n promise: fetchPromise,\n });\n \n try {\n const data = await fetchPromise;\n \n // Update cache with actual data\n queryCache.set(cacheKey, {\n data,\n expiresAt: now + (ttl * 1000),\n });\n \n log.debug(`Cached query result: ${cacheKey}, expires in ${ttl}s`);\n return data;\n } catch (error) {\n // Remove failed request from cache\n queryCache.delete(cacheKey);\n log.error(`Query failed for ${cacheKey}:`, error);\n throw error;\n }\n }, []);\n \n const invalidateQuery = useCallback((table: string, filterKey: string, filterValue: string) => {\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n queryCache.delete(cacheKey);\n log.debug(`Invalidated query cache: ${cacheKey}`);\n }, []);\n \n const clearCache = useCallback(() => {\n queryCache.clear();\n log.debug('Cleared all query cache entries.');\n }, []);\n \n const getCacheStats = useCallback(() => {\n return {\n size: queryCache.size,\n keys: Array.from(queryCache.keys()),\n };\n }, []);\n \n return {\n getCachedQuery,\n invalidateQuery,\n clearCache,\n getCacheStats,\n };\n}\n\n/**\n * Pre-configured cache helpers for common queries\n */\nexport const queryCacheHelpers = {\n /**\n * Cache core_person queries by user_id\n * TTL: 5 minutes\n */\n pacePersonByUserId: <T>(\n supabase: SupabaseClient<Database>,\n userId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_person:user_id:${userId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache core_member queries by person_id\n * TTL: 5 minutes\n */\n paceMemberByPersonId: <T>(\n supabase: SupabaseClient<Database>,\n personId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_member:person_id:${personId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache rbac_app_pages queries by app_id\n * TTL: 15 minutes (app pages are relatively static)\n */\n rbacAppPagesByAppId: <T>(\n supabase: SupabaseClient<Database>,\n appId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `rbac_app_pages:app_id:${appId}`;\n const now = Date.now();\n const ttl = 15 * 60 * 1000; // 15 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n};\n\n","/**\n * @file Address Autocomplete Hook\n * @package @jmruthers/pace-core\n * @module Hooks/AddressAutocomplete\n * @since 0.1.0\n *\n * Hook for managing Google Places API autocomplete functionality.\n * Provides debounced input, caching, and address selection.\n */\n\nimport { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useDebounce } from './useDebounce';\nimport { useQueryCache } from './useQueryCache';\nimport {\n fetchPlaceAutocomplete,\n fetchPlaceDetails,\n createAddressFromPlaceResult,\n getAddressByPlaceId,\n} from '../utils/google-places';\nimport type {\n GooglePlaceAutocompletePrediction,\n ParsedAddress,\n AutocompleteOptions,\n} from '../utils/google-places';\n\nexport interface UseAddressAutocompleteOptions {\n /** Google Places API options */\n autocompleteOptions?: AutocompleteOptions;\n /** Debounce delay in milliseconds (default: 300) */\n debounceDelay?: number;\n /** Enable caching (default: true) */\n cacheEnabled?: boolean;\n /** Cache TTL configuration */\n cacheTTL?: {\n /** Autocomplete cache TTL in seconds (default: 3600 = 1 hour) */\n autocomplete?: number;\n /** Place details cache TTL in seconds (default: 86400 = 24 hours) */\n placeDetails?: number;\n };\n}\n\nexport interface UseAddressAutocompleteReturn {\n /** Array of autocomplete suggestions */\n suggestions: GooglePlaceAutocompletePrediction[];\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: Error | null;\n /** Select an address by place_id */\n selectAddress: (placeId: string) => Promise<ParsedAddress | null>;\n /** Get address by place_id (uses cache) */\n getAddressByPlaceId: (placeId: string) => Promise<ParsedAddress | null>;\n /** Clear suggestions */\n clearSuggestions: () => void;\n}\n\n/**\n * Hook for Google Places API autocomplete with caching\n *\n * @param apiKey - Google Places API key\n * @param inputValue - Current input value\n * @param options - Hook configuration options\n * @returns Autocomplete state and functions\n *\n * @example\n * ```tsx\n * const { suggestions, isLoading, selectAddress } = useAddressAutocomplete(\n * apiKey,\n * inputValue,\n * { debounceDelay: 300, cacheEnabled: true }\n * );\n * ```\n */\nexport function useAddressAutocomplete(\n apiKey: string,\n inputValue: string,\n options: UseAddressAutocompleteOptions = {}\n): UseAddressAutocompleteReturn {\n const {\n debounceDelay = 300,\n cacheEnabled = true,\n cacheTTL = {\n autocomplete: 3600, // 1 hour\n placeDetails: 86400, // 24 hours\n },\n autocompleteOptions,\n } = options;\n\n const [suggestions, setSuggestions] = useState<GooglePlaceAutocompletePrediction[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const debouncedInput = useDebounce(inputValue, debounceDelay);\n const { getCachedQuery } = useQueryCache();\n const abortControllerRef = useRef<AbortController | null>(null);\n\n // Memoize autocompleteOptions to prevent unnecessary re-renders\n const memoizedAutocompleteOptions = useMemo(\n () => autocompleteOptions,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(autocompleteOptions)]\n );\n\n // Fetch autocomplete suggestions\n useEffect(() => {\n // Cancel previous request if still in flight\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Reset state if input is empty\n if (!debouncedInput.trim()) {\n setSuggestions([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n // Don't fetch if no API key\n if (!apiKey) {\n setError(new Error('Google Places API key is required'));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n const fetchSuggestions = async () => {\n try {\n let predictions: GooglePlaceAutocompletePrediction[];\n\n if (cacheEnabled) {\n // Use cached query with TTL\n predictions = await getCachedQuery(\n 'google-places-autocomplete',\n 'query',\n debouncedInput,\n async () => {\n return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n },\n { ttl: cacheTTL.autocomplete, enabled: true }\n );\n } else {\n // Direct fetch without cache\n predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n }\n\n setSuggestions(predictions);\n setIsLoading(false);\n } catch (err) {\n // Ignore aborted requests\n if (err instanceof Error && err.name === 'AbortError') {\n return;\n }\n\n const error = err instanceof Error ? err : new Error('Failed to fetch autocomplete suggestions');\n setError(error);\n setSuggestions([]);\n setIsLoading(false);\n }\n };\n\n fetchSuggestions();\n\n // Cleanup function to cancel in-flight requests\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete]);\n\n // Select address by place_id\n const selectAddress = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n let placeDetails;\n\n if (cacheEnabled) {\n // Use cached query with TTL\n placeDetails = await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n return fetchPlaceDetails(placeId, apiKey);\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n // Direct fetch without cache\n placeDetails = await fetchPlaceDetails(placeId, apiKey);\n }\n\n const parsedAddress = createAddressFromPlaceResult(placeDetails);\n setIsLoading(false);\n return parsedAddress;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch place details');\n setError(error);\n setIsLoading(false);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Get address by place_id (utility function)\n const getAddressByPlaceIdFn = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n try {\n if (cacheEnabled) {\n // Use cached query with TTL\n return await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n const result = await getAddressByPlaceId(placeId, apiKey);\n if (!result) {\n throw new Error('Failed to fetch address');\n }\n return result;\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n return await getAddressByPlaceId(placeId, apiKey);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to get address by place_id');\n setError(error);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Clear suggestions\n const clearSuggestions = useCallback(() => {\n setSuggestions([]);\n setError(null);\n }, []);\n\n return {\n suggestions,\n isLoading,\n error,\n selectAddress,\n getAddressByPlaceId: getAddressByPlaceIdFn,\n clearSuggestions,\n };\n}\n\n","/**\n * Storage configuration for pace-core\n */\n\nimport { FileSizeLimits, StorageConfig } from './types';\n\n/**\n * File size limits by MIME type (in bytes)\n */\nexport const FILE_SIZE_LIMITS: FileSizeLimits = {\n // Images\n 'image/jpeg': 5 * 1024 * 1024, // 5MB\n 'image/png': 5 * 1024 * 1024, // 5MB\n 'image/gif': 10 * 1024 * 1024, // 10MB (for animations)\n 'image/webp': 5 * 1024 * 1024, // 5MB\n 'image/svg+xml': 1 * 1024 * 1024, // 1MB (vector graphics)\n \n // Documents\n 'application/pdf': 50 * 1024 * 1024, // 50MB\n 'application/msword': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 25 * 1024 * 1024, // 25MB\n 'application/vnd.ms-excel': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 25 * 1024 * 1024, // 25MB\n \n // Archives\n 'application/zip': 100 * 1024 * 1024, // 100MB\n 'application/x-rar-compressed': 100 * 1024 * 1024, // 100MB\n \n // Text files\n 'text/plain': 1 * 1024 * 1024, // 1MB\n 'text/csv': 10 * 1024 * 1024, // 10MB\n 'application/json': 10 * 1024 * 1024, // 10MB\n};\n\n/**\n * Default file size limit for unknown MIME types\n */\nexport const DEFAULT_FILE_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB\n\n/**\n * App-specific path mappings for file organization\n * Maps app names to their primary category in the organization-first structure\n */\nexport const APP_PATH_MAPPING: Record<string, string> = {\n 'PACE': 'event_logos',\n 'TRAC': 'trac_accommodation', // Default category for TRAC files\n 'MEDI': 'documents',\n 'CAKE': 'documents'\n};\n\n/**\n * Storage configuration\n */\nexport const STORAGE_CONFIG: StorageConfig = {\n bucketName: 'files',\n fileSizeLimits: FILE_SIZE_LIMITS,\n defaultFileSizeLimit: DEFAULT_FILE_SIZE_LIMIT,\n};\n\n/**\n * Get the file size limit for a given MIME type\n */\nexport function getFileSizeLimit(mimeType: string): number {\n return STORAGE_CONFIG.fileSizeLimits[mimeType] || STORAGE_CONFIG.defaultFileSizeLimit;\n}\n\n/**\n * Get the bucket name based on whether the file is public or private\n * @param isPublic - Whether the file should be publicly accessible\n * @returns The bucket name: 'public-files' for public files, 'files' for private files\n */\nexport function getBucketName(isPublic: boolean): 'files' | 'public-files' {\n return isPublic ? 'public-files' : 'files';\n}\n\n/**\n * Validate file size against limits\n */\nexport function validateFileSize(file: File): { isValid: boolean; error?: string } {\n const limit = getFileSizeLimit(file.type);\n \n if (file.size > limit) {\n const limitMB = Math.round(limit / (1024 * 1024));\n const fileMB = Math.round(file.size / (1024 * 1024));\n return {\n isValid: false,\n error: `File size (${fileMB}MB) exceeds limit (${limitMB}MB) for ${file.type}`\n };\n }\n \n return { isValid: true };\n}\n\n/**\n * Get human-readable file size\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n if (bytes < 0) return `${bytes} Bytes`; // Handle negative numbers\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n // Ensure we don't exceed the available size units\n const sizeIndex = Math.min(Math.max(i, 0), sizes.length - 1);\n \n return parseFloat((bytes / Math.pow(k, sizeIndex)).toFixed(2)) + ' ' + sizes[sizeIndex];\n}\n","/**\n * Storage helper functions for pace-core\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n StorageUploadOptions, \n StorageUploadResult, \n StorageFileMetadata,\n StorageUrlOptions,\n StorageListOptions,\n StorageListResult,\n StorageFileInfo\n} from './types';\nimport { validateFileSize, STORAGE_CONFIG, getBucketName } from './config';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('StorageHelpers');\n\n/**\n * Generate a file path based on organization-first or user-first structure\n * - If orgId is provided: {orgId}/{folder}/filename\n * - If userId is provided (and orgId is not): users/{userId}/{folder}/filename\n */\nexport function generateFilePath(options: StorageUploadOptions, fileName: string): string {\n const { orgId, userId, isPublic = false, customPath } = options;\n \n // Validate that either orgId or userId is provided\n if (!orgId && !userId) {\n throw new Error('Either orgId or userId is required for file path generation');\n }\n \n // Determine base path: organisation-based or user-based\n const basePath = orgId ? orgId : `users/${userId}`;\n \n if (isPublic) {\n // Public files go to {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n return `${basePath}/public/${fileName}`;\n }\n \n // Organization-first or user-first structure: {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n \n // Use customPath if available, otherwise default to files\n const pathFolder = customPath || 'files';\n return `${basePath}/${pathFolder}/${fileName}`;\n}\n\n/**\n * Generate a unique filename with timestamp and UUID\n * Sanitizes the original filename to remove spaces and invalid characters for storage compatibility\n */\nexport function generateUniqueFileName(originalName: string): string {\n const timestamp = Date.now();\n const uuid = crypto.randomUUID();\n const extension = originalName.split('.').pop() || '';\n let baseName = originalName.replace(/\\.[^/.]+$/, '');\n \n // Sanitize the base name for storage compatibility\n // Replace spaces with underscores and remove invalid characters\n baseName = baseName\n .trim()\n .replace(/\\s+/g, '_') // Replace spaces with underscores\n .replace(/[<>:\"/\\\\|?*]/g, '') // Remove invalid file name characters\n .replace(/\\.\\./g, '') // Remove directory traversal attempts\n .replace(/^\\.+|\\.+$/g, '') // Remove leading/trailing dots\n .substring(0, 200); // Limit length to leave room for timestamp and UUID\n \n // If there's no extension, don't add one\n if (!extension || extension === originalName) {\n return `${timestamp}-${uuid}-${baseName}`;\n }\n \n return `${timestamp}-${uuid}-${baseName}.${extension}`;\n}\n\n/**\n * Extract file metadata from a File object\n */\nexport async function extractFileMetadata(\n file: File, \n options: StorageUploadOptions,\n uploadedBy: string\n): Promise<StorageFileMetadata> {\n const metadata: StorageFileMetadata = {\n mimeType: file.type,\n size: file.size,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName || 'pace-core',\n uploadedBy,\n uploadedAt: new Date().toISOString(),\n tags: options.tags || [],\n isPublic: options.isPublic || false,\n customMetadata: options.metadata || {}\n };\n\n // Extract image dimensions if it's an image\n if (file.type.startsWith('image/')) {\n try {\n const dimensions = await getImageDimensions(file);\n metadata.width = dimensions.width;\n metadata.height = dimensions.height;\n } catch (error) {\n // Non-critical error - image dimensions are optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - dimensions are optional\n }\n }\n\n // Generate file hash if possible\n try {\n metadata.hash = await generateFileHash(file);\n } catch (error) {\n // Non-critical error - file hash is optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - hash is optional\n }\n\n return metadata;\n}\n\n/**\n * Get image dimensions from a File object\n */\nasync function getImageDimensions(file: File): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(file);\n \n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve({ width: img.width, height: img.height });\n };\n \n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('Could not load image'));\n };\n \n img.src = url;\n });\n}\n\n/**\n * Generate a hash for a file\n */\nasync function generateFileHash(file: File): Promise<string> {\n const buffer = await file.arrayBuffer();\n const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n return `sha256:${hashHex}`;\n}\n\n/**\n * Ensure a folder exists in the storage bucket\n * In Supabase storage, folders are created automatically when files are uploaded,\n * but this function explicitly creates the folder structure by uploading a placeholder\n * file if the folder doesn't exist\n * @param supabase - Supabase client instance\n * @param folderPath - Folder path to ensure exists (e.g., 'orgId/folder')\n * @param bucketName - Bucket name\n * @returns True if folder exists or was created, false on error\n */\nasync function ensureFolderExists(\n supabase: SupabaseClient,\n folderPath: string,\n bucketName: string\n): Promise<boolean> {\n try {\n // Check if folder exists by trying to list it\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(folderPath, {\n limit: 1\n });\n\n // If listing succeeds (even with empty data), the folder exists\n if (!error) {\n return true;\n }\n\n // If we get a \"not found\" error, the folder doesn't exist yet\n // Create it by uploading a placeholder file\n // Supabase storage doesn't support empty folders, so we create a .keep file\n const placeholderPath = `${folderPath}/.keep`;\n const placeholderBlob = new Blob([''], { type: 'text/plain' });\n const placeholderFile = new File([placeholderBlob], '.keep', { type: 'text/plain' });\n\n const { error: uploadError } = await supabase.storage\n .from(bucketName)\n .upload(placeholderPath, placeholderFile, {\n cacheControl: '3600',\n upsert: true, // Use upsert to avoid errors if file already exists\n contentType: 'text/plain'\n });\n\n if (uploadError) {\n // If we can't create the placeholder, log it but don't fail\n // The folder will be created automatically when we upload the actual file\n log.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);\n return true; // Still return true - folder will be created on actual file upload\n }\n\n // Folder structure now exists\n return true;\n } catch (error) {\n // If there's an exception, log it but proceed anyway\n // The folder structure will be created when we upload the actual file\n log.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : 'Unknown error'}`);\n return true; // Return true to proceed - folder will be created on actual file upload\n }\n}\n\n/**\n * Upload a file to Supabase storage with app segregation\n */\nexport async function uploadFile(\n supabase: SupabaseClient,\n file: File,\n options: StorageUploadOptions\n): Promise<StorageUploadResult> {\n try {\n // Validate file size\n const sizeValidation = validateFileSize(file);\n if (!sizeValidation.isValid) {\n return {\n success: false,\n error: sizeValidation.error\n };\n }\n\n // Generate unique filename and path\n const uniqueFileName = generateUniqueFileName(file.name);\n const filePath = generateFilePath(options, uniqueFileName);\n\n // Extract folder path from file path (everything except the filename)\n const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));\n\n // Extract metadata\n const metadata = await extractFileMetadata(file, options, 'current-user'); // TODO: Get actual user ID\n\n // Select bucket based on isPublic flag\n const bucketName = getBucketName(options.isPublic || false);\n\n // Ensure folder exists (Supabase creates folders automatically on upload,\n // but we verify the path is accessible)\n await ensureFolderExists(supabase, folderPath, bucketName);\n\n // Upload file to Supabase\n // Note: Supabase will automatically create the folder structure if it doesn't exist\n const { data, error } = await supabase.storage\n .from(bucketName)\n .upload(filePath, file, {\n cacheControl: '3600',\n upsert: false,\n contentType: file.type\n });\n\n if (error) {\n return {\n success: false,\n error: `Upload failed: ${error.message}`\n };\n }\n\n // Generate public URL if file is public\n let publicUrl: string | undefined;\n if (options.isPublic) {\n const { data: urlData } = supabase.storage\n .from(bucketName)\n .getPublicUrl(filePath);\n publicUrl = urlData.publicUrl;\n }\n\n return {\n success: true,\n path: filePath,\n publicUrl,\n metadata\n };\n\n } catch (error) {\n return {\n success: false,\n error: `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * Get a public URL for a file\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: true)\n * @returns Public URL for the file\n */\nexport function getPublicUrl(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = true\n): string {\n if (!supabase) {\n throw new Error('Supabase client is required to generate a public URL');\n }\n\n if (!path || typeof path !== 'string') {\n throw new Error('A valid storage path is required to generate a public URL');\n }\n\n // If the path is already an absolute URL, return it directly\n if (/^https?:\\/\\//i.test(path)) {\n return path;\n }\n\n // Normalise path by trimming whitespace and leading slashes\n let normalisedPath = path.trim().replace(/^\\/+/, '');\n\n if (!normalisedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n const { bucketNameFromPath, storagePath } = resolveBucketHint(normalisedPath);\n\n const bucketName = bucketNameFromPath || getBucketName(isPublic);\n\n const { data } = supabase.storage\n .from(bucketName)\n .getPublicUrl(storagePath);\n\n return data.publicUrl;\n}\n\nfunction resolveBucketHint(pathWithPotentialBucket: string): { bucketNameFromPath: string | null; storagePath: string } {\n const BUCKET_NAME_PATTERN = /^[a-z0-9][a-z0-9-_\\.]{1,62}$/i;\n const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n const KNOWN_BUCKET_NAMES = new Set(['files', 'public-files']);\n\n const trimmedPath = pathWithPotentialBucket.trim();\n\n if (!trimmedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n // Support \"bucket::path\" notation to remove ambiguity with colon usage in file names.\n const doubleColonIndex = trimmedPath.indexOf('::');\n if (doubleColonIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, doubleColonIndex).trim();\n const remainingPath = trimmedPath.slice(doubleColonIndex + 2).replace(/^\\/+/, '');\n\n if (potentialBucket && remainingPath && BUCKET_NAME_PATTERN.test(potentialBucket)) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n // Support \"bucket/path\" hints, provided the first segment looks like a bucket name and not a directory prefix.\n const firstSlashIndex = trimmedPath.indexOf('/');\n if (firstSlashIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, firstSlashIndex).trim();\n const remainingPath = trimmedPath.slice(firstSlashIndex + 1).replace(/^\\/+/, '');\n\n if (\n potentialBucket &&\n remainingPath &&\n BUCKET_NAME_PATTERN.test(potentialBucket) &&\n !UUID_PATTERN.test(potentialBucket) &&\n (KNOWN_BUCKET_NAMES.has(potentialBucket) || !potentialBucket.includes('-')) &&\n !/^\\d+$/.test(potentialBucket)\n ) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n return {\n bucketNameFromPath: null,\n storagePath: trimmedPath\n };\n}\n\n/**\n * Get a signed URL for a protected file\n * Private files are always in the 'files' bucket, so this always uses 'files' bucket\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - URL options including expiry time\n * @returns Signed URL with expiration timestamp, or null if failed\n */\nexport async function getSignedUrl(\n supabase: SupabaseClient,\n path: string,\n options: StorageUrlOptions\n): Promise<{ url: string; expiresAt: string } | null> {\n try {\n // Signed URLs are only for private files, which are always in the 'files' bucket\n const bucketName = getBucketName(false);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .createSignedUrl(path, options.expiresIn || 3600);\n\n if (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n\n return {\n url: data.signedUrl,\n expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1000).toISOString()\n };\n } catch (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n}\n\n// Global URL cache for batch operations (shared with useFileUrlCache)\nconst globalUrlCache = new Map<string, { url: string; expiresAt: number }>();\nconst MAX_CACHE_SIZE = 500;\nconst DEFAULT_TTL_MS = 3600 * 1000;\n\nfunction getCacheKey(fileId: string, filePath: string, isPublic: boolean): string {\n return `file-url:${fileId}:${isPublic ? 'public' : 'private'}`;\n}\n\nfunction cleanupUrlCache(): void {\n const now = Date.now();\n \n // Remove expired entries\n for (const [key, value] of globalUrlCache.entries()) {\n if (value.expiresAt < now) {\n globalUrlCache.delete(key);\n }\n }\n \n // Enforce size limit by removing oldest entries\n if (globalUrlCache.size > MAX_CACHE_SIZE) {\n const entries = Array.from(globalUrlCache.entries());\n entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);\n \n const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);\n for (let i = 0; i < toRemove && i < entries.length; i++) {\n globalUrlCache.delete(entries[i][0]);\n }\n }\n}\n\n/**\n * Generate URLs for multiple file references in parallel\n * This batches URL generation to reduce sequential requests and uses caching\n * @param supabase - Supabase client instance\n * @param fileReferences - Array of file references to generate URLs for\n * @param options - URL options including expiry time and organisation ID\n * @returns Map of file ID to URL string (only includes successful URL generations)\n */\nexport async function generateFileUrlsBatch(\n supabase: SupabaseClient,\n fileReferences: Array<{ id: string; file_path: string; is_public: boolean }>,\n options: StorageUrlOptions & { orgId?: string }\n): Promise<Map<string, string>> {\n const urlMap = new Map<string, string>();\n \n if (fileReferences.length === 0) {\n return urlMap;\n }\n\n const now = Date.now();\n const ttl = (options.expiresIn || 3600) * 1000; // Convert seconds to milliseconds\n \n // Separate files into cached, public, and private\n const publicFiles: Array<{ id: string; file_path: string }> = [];\n const privateFiles: Array<{ id: string; file_path: string }> = [];\n const uncachedFiles: Array<{ id: string; file_path: string; is_public: boolean }> = [];\n\n for (const fileRef of fileReferences) {\n const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);\n const cached = globalUrlCache.get(cacheKey);\n \n // Use cached URL if still valid\n if (cached && cached.expiresAt > now) {\n urlMap.set(fileRef.id, cached.url);\n continue;\n }\n \n // Add to processing queue\n if (fileRef.is_public) {\n publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n } else {\n privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n }\n uncachedFiles.push(fileRef);\n }\n\n // Generate public URLs synchronously (they're just string concatenation)\n for (const file of publicFiles) {\n try {\n const url = getPublicUrl(supabase, file.file_path, true);\n if (url) {\n urlMap.set(file.id, url);\n // Cache the URL\n const cacheKey = getCacheKey(file.id, file.file_path, true);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n } catch (err) {\n log.error(`Failed to generate public URL for file ${file.id}:`, err);\n }\n }\n\n // Generate signed URLs in parallel using Promise.all\n if (privateFiles.length > 0) {\n const signedUrlPromises = privateFiles.map(async (file) => {\n try {\n const signedUrlResult = await getSignedUrl(supabase, file.file_path, {\n appName: options.appName || 'pace-core',\n orgId: options.orgId,\n expiresIn: options.expiresIn || 3600\n });\n const url = signedUrlResult?.url || null;\n \n // Cache the URL if generated successfully\n if (url) {\n const cacheKey = getCacheKey(file.id, file.file_path, false);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n \n return { id: file.id, url };\n } catch (err) {\n log.error(`Failed to generate signed URL for file ${file.id}:`, err);\n return { id: file.id, url: null };\n }\n });\n\n const signedUrlResults = await Promise.all(signedUrlPromises);\n \n for (const result of signedUrlResults) {\n if (result.url) {\n urlMap.set(result.id, result.url);\n }\n }\n }\n\n // Clean up cache after adding new entries\n if (uncachedFiles.length > 0) {\n cleanupUrlCache();\n }\n\n return urlMap;\n}\n\n/**\n * Delete a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns Success status and optional error message\n */\nexport async function deleteFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { error } = await supabase.storage\n .from(bucketName)\n .remove([path]);\n\n if (error) {\n return {\n success: false,\n error: `Delete failed: ${error.message}`\n };\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Delete failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * List files in an organization scope\n * @param supabase - Supabase client instance\n * @param options - List options including bucket selection via isPublic\n * @returns List of files with metadata\n */\nexport async function listFiles(\n supabase: SupabaseClient,\n options: StorageListOptions & { isPublic?: boolean }\n): Promise<StorageListResult> {\n try {\n // Select bucket based on isPublic flag (default to private files bucket)\n const bucketName = getBucketName(options.isPublic || false);\n \n // Validate that either orgId or userId is provided\n if (!options.orgId && !options.userId) {\n throw new Error('Either orgId or userId is required for listing files');\n }\n \n // Organization-first or user-first structure\n const basePath = options.orgId ? options.orgId : `users/${options.userId}`;\n const pathPrefix = `${basePath}/`;\n const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;\n\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(searchPath, {\n limit: options.limit || 100,\n offset: options.offset || 0,\n sortBy: { column: 'created_at', order: 'desc' }\n });\n\n if (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n\n const files: StorageFileInfo[] = (data || []).map(item => ({\n name: item.name,\n path: `${searchPath}${item.name}`,\n size: item.metadata?.size || 0,\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n lastModified: item.updated_at || item.created_at || new Date().toISOString(),\n metadata: {\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n size: item.metadata?.size || 0,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName,\n uploadedBy: 'unknown',\n uploadedAt: item.created_at || new Date().toISOString(),\n isPublic: options.isPublic || false\n }\n }));\n\n return {\n files,\n totalCount: files.length,\n hasMore: files.length >= (options.limit || 100)\n };\n } catch (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n}\n\n/**\n * Download a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns File blob with metadata, or null if failed\n */\nexport async function downloadFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ blob: Blob; metadata: { name: string; size: number; type: string } } | null> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .download(path);\n\n if (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n\n if (!data) {\n return null;\n }\n\n // Extract file name from path\n const fileName = path.split('/').pop() || 'download';\n \n // Get file metadata\n const { data: fileInfo } = await supabase.storage\n .from(bucketName)\n .list(path.split('/').slice(0, -1).join('/'), {\n search: fileName\n });\n\n const metadata = fileInfo?.[0]?.metadata || {};\n \n return {\n blob: data,\n metadata: {\n name: fileName,\n size: metadata.size || data.size,\n type: metadata.mimetype || 'application/octet-stream'\n }\n };\n } catch (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n}\n\n/**\n * Move a file to archived location (soft delete)\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - Archive options including bucket selection via isPublic\n */\nexport async function archiveFile(\n supabase: SupabaseClient,\n path: string,\n options: { appName: string; orgId?: string; userId?: string; isPublic?: boolean }\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(options.isPublic || false);\n \n // Generate archived path for organization-first or user-first structure\n let archivedPath: string;\n if (options.orgId) {\n archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);\n } else if (options.userId) {\n archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);\n } else {\n throw new Error('Either orgId or userId is required for archiving files');\n }\n \n // Copy file to archived location\n const { error: copyError } = await supabase.storage\n .from(bucketName)\n .copy(path, archivedPath);\n\n if (copyError) {\n return {\n success: false,\n error: `Archive failed: ${copyError.message}`\n };\n }\n\n // Delete original file\n const deleteResult = await deleteFile(supabase, path, options.isPublic || false);\n if (!deleteResult.success) {\n return deleteResult;\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Archive failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n","/**\n * @file Public File Display Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for accessing file references in public contexts without authentication.\n * Provides file URLs and metadata for public pages.\n *\n * Features:\n * - No authentication required\n * - Only returns public files (is_public = true)\n * - Caching for performance\n * - Error handling and loading states\n * - TypeScript support\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { usePublicFileDisplay } from '@jmruthers/pace-core';\n *\n * function PublicFileView() {\n * const { fileUrl, fileReference, isLoading, error } = usePublicFileDisplay(\n * 'event',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS,\n * { supabase }\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { FileReference, FileCategory } from '../../types/file-reference';\nimport { getPublicUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';\nimport { logger } from '../../utils/core/logger';\n\n// Simple in-memory cache for public file data\nconst publicFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\nexport interface UsePublicFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UsePublicFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient<Database>;\n}\n\n/**\n * Hook for accessing public file references\n * \n * This hook provides access to file references without requiring\n * authentication. It only returns public files and generates public URLs.\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function usePublicFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UsePublicFileDisplayOptions\n): UsePublicFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('usePublicFileDisplay', 'Invalid organisationId format (not a valid UUID)', { organisation_id });\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = publicFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n setFileUrl(cachedData.fileUrl || null);\n setFileReference(cachedData.fileReference || null);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n let files: any[] = [];\n\n // When organisation_id is undefined, search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n if (organisation_id === undefined) {\n logger.debug('usePublicFileDisplay', 'organisation_id is undefined, searching both user-scoped and organisation-scoped files:', {\n table_name,\n record_id,\n category\n });\n\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: any[] = [];\n let orgScopedFiles: any[] = [];\n\n // Query user-scoped files\n if (category) {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n userScopedFiles = userData\n .filter((item: any) => item.is_public === true && item.id && item.file_path && item.file_metadata)\n .map((item: any) => ({\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n }));\n }\n } else {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n const ids = userData.map((item: any) => item.id);\n if (ids.length > 0) {\n const { data: fullData } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true);\n userScopedFiles = fullData || [];\n }\n }\n }\n\n // For public pages, we can't query user's organisations (user is anonymous)\n // But we can try to find organisation-scoped files by querying the record's context\n // For now, we'll just use user-scoped files. If needed, the page context can provide organisation_id\n // Note: This is a limitation of public pages - they should provide organisation_id if needed\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n files = allFiles;\n\n logger.debug('usePublicFileDisplay', 'Found files with undefined organisation_id:', {\n userScopedCount: userScopedFiles.length,\n orgScopedCount: orgScopedFiles.length,\n totalCount: files.length\n });\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - use RPC to get files by category\n const rpcParams = {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n };\n \n const { data, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', rpcParams);\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_by_category_list',\n table_name,\n record_id,\n category,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file reference');\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n files = [];\n } else {\n // Construct file reference objects from RPC response\n // This avoids RLS issues - the RPC already validated permissions and filtered public files\n files = data\n .filter((item: any) => {\n // RPC should only return public files for public context, but verify\n return item.is_public === true && item.id && item.file_path && item.file_metadata;\n })\n .map((item: any) => {\n // Construct complete file reference from RPC response + function parameters\n return {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true, // RPC already filtered for public files\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n };\n });\n }\n } else {\n // Multiple files mode - use RPC to get all files\n const { data: fileIds, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_list',\n table_name,\n record_id,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file references');\n }\n\n if (!fileIds || fileIds.length === 0) {\n files = [];\n } else {\n // Fetch full file reference data for each ID, but only public files\n const ids = fileIds.map((item: any) => item.id);\n const { data: fullData, error: fetchError } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true); // Only public files in public context\n\n if (fetchError) {\n throw new Error(fetchError.message || 'Failed to fetch file references');\n }\n\n files = fullData || [];\n }\n }\n }\n\n // Ensure all files are public (category RPC might return both)\n const publicFiles = files.filter((f: any) => f.is_public === true);\n\n if (publicFiles.length === 0) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n \n // Cache empty result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n return;\n }\n\n // Convert to FileReference format\n const fileRefs: FileReference[] = publicFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? true,\n created_at: f.created_at,\n updated_at: f.updated_at\n }));\n\n setFileReferences(fileRefs);\n setFileCount(fileRefs.length);\n\n if (category && fileRefs.length > 0) {\n // Single file mode - get first file\n const firstFile = fileRefs[0];\n setFileReference(firstFile);\n \n // Generate public URL\n const url = getPublicUrl(supabase, firstFile.file_path, true);\n setFileUrl(url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {\n appName: 'pace-core',\n orgId: organisation_id,\n expiresIn: 3600\n });\n setFileUrls(urlMap);\n setFileReference(null);\n setFileUrl(null);\n }\n\n // Cache the result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: {\n fileUrl: category ? (fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null) : null,\n fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,\n fileReferences: fileRefs,\n fileUrls: category ? new Map() : (() => {\n const urlMap = new Map<string, string>();\n for (const fileRef of fileRefs) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n return urlMap;\n })(),\n fileCount: fileRefs.length\n },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n\n } catch (err) {\n logger.error('usePublicFileDisplay', 'Error fetching files', {\n table_name,\n record_id,\n organisation_id,\n category,\n error: err instanceof Error ? err.message : 'Unknown error',\n errorDetails: err instanceof Error ? err.stack : String(err)\n });\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n } finally {\n setIsLoading(false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n if (table_name && record_id) {\n fetchFiles();\n } else {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n setError(null);\n }\n }, [fetchFiles, table_name, record_id, organisation_id]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n publicFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached public file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearPublicFileDisplayCache(): void {\n for (const [key] of publicFileCache) {\n if (key.startsWith('public_file_')) {\n publicFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getPublicFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(publicFileCache.keys()).filter(key => key.startsWith('public_file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n","/**\n * @file File Display Hook (Authenticated)\n * @package @jmruthers/pace-core\n * @module Hooks\n *\n * A React hook for accessing file references in authenticated contexts.\n * Can handle both public and private files using the core_file_references system.\n *\n * Features:\n * - Works in authenticated contexts\n * - Supports both public and private files\n * - Automatic signed URL generation for private files\n * - Caching for performance\n * - Error handling and loading states\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { useFileDisplay } from '@jmruthers/pace-core';\n *\n * function FileView() {\n * const { fileUrl, fileReference, isLoading, error } = useFileDisplay(\n * 'core_events',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport { FileReference, FileCategory } from '../types/file-reference';\nimport { getPublicUrl, getSignedUrl, generateFileUrlsBatch } from '../utils/storage/helpers';\nimport { createFileReferenceService } from '../utils/file-reference';\nimport { logger } from '../utils/core/logger';\n\n// Simple in-memory cache for authenticated file data\nconst authenticatedFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n// Cache size limit to prevent memory leaks\nconst MAX_CACHE_SIZE = 100;\n\n// Helper function to clean up expired entries and enforce size limit\nfunction cleanupCache() {\n const now = Date.now();\n const entries = Array.from(authenticatedFileCache.entries());\n \n // Remove expired entries\n const expiredKeys: string[] = [];\n entries.forEach(([key, value]) => {\n if (now - value.timestamp >= value.ttl) {\n expiredKeys.push(key);\n }\n });\n expiredKeys.forEach(key => authenticatedFileCache.delete(key));\n \n // If still over limit, remove oldest entries\n if (authenticatedFileCache.size > MAX_CACHE_SIZE) {\n const sorted = entries\n .filter(([key]) => !expiredKeys.includes(key))\n .sort((a, b) => a[1].timestamp - b[1].timestamp);\n const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE);\n toRemove.forEach(([key]) => authenticatedFileCache.delete(key));\n }\n}\n\nexport interface UseFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UseFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient | null;\n}\n\n/**\n * Hook for accessing file references in authenticated contexts\n * \n * This hook provides access to file references for authenticated users.\n * It supports both public and private files, generating appropriate URLs\n * (public URLs for public files, signed URLs for private files).\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function useFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UseFileDisplayOptions\n): UseFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('useFileDisplay', 'Invalid organisationId format (not a valid UUID):', organisation_id);\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = authenticatedFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n \n // FIX: Regenerate signed URL for private files if missing\n if (cachedData.fileReference && !cachedData.fileUrl && \n cachedData.fileReference.is_public === false && supabase) {\n // Regenerate signed URL without fetching from database\n try {\n const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n const regeneratedUrl = signedUrlResult?.url || null;\n setFileUrl(regeneratedUrl);\n setFileReference(cachedData.fileReference);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n } catch (err) {\n // If signed URL regeneration fails, fall through to normal fetch\n logger.warn('useFileDisplay', 'Failed to regenerate signed URL from cache, falling back to fetch:', err);\n }\n }\n \n // Normal cache hit for public files or files with URLs\n setFileUrl(cachedData.fileUrl || null);\n setFileReference(cachedData.fileReference || null);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n const service = createFileReferenceService(supabase);\n let files: FileReference[] = [];\n\n // When organisation_id is undefined (not provided), search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n // Note: Explicitly passing null or empty string should only search user-scoped files\n const shouldSearchBothScopes = organisation_id === undefined;\n \n if (shouldSearchBothScopes) {\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying user-scoped files:', err);\n userScopedFiles = [];\n }\n\n // For organisation-scoped files, we need to query the user's organisations\n // Get user's organisations from the authenticated session\n try {\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError) {\n logger.warn('useFileDisplay', 'Error getting user:', userError);\n }\n \n if (user) {\n // Query user's active organisation memberships\n const { data: memberships, error: membershipError } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id);\n\n if (membershipError) {\n logger.warn('useFileDisplay', 'Error querying organisation memberships:', membershipError);\n }\n\n if (memberships && memberships.length > 0) {\n // Try each organisation the user belongs to\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n \n // Query each organisation in parallel\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n orgId\n );\n } else {\n return await service.listFileReferences(\n table_name,\n record_id,\n orgId\n );\n }\n } catch (err) {\n // Silently fail for individual org queries - user might not have access\n return [];\n }\n });\n\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n } else {\n // When user has no organisation memberships, try querying files with any organisation_id\n // as a fallback - the file might be organisation-scoped but accessible via RLS\n // This handles edge cases where RLS allows access but we can't enumerate organisations\n try {\n // Try querying without organisation_id filter - let RLS handle security\n let fallbackQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n if (category) {\n fallbackQuery = fallbackQuery.eq('file_metadata->>category', category);\n }\n\n const { data: fallbackFiles } = await fallbackQuery;\n \n if (fallbackFiles && fallbackFiles.length > 0) {\n // Convert to FileReference format\n orgScopedFiles = fallbackFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying organisation-scoped files:', err);\n orgScopedFiles = [];\n }\n\n // Merge results: prefer organisation-scoped files if both exist, otherwise use user-scoped\n // Sort by created_at DESC to get most recent first\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n // If we have both types, prefer organisation-scoped (non-null organisation_id)\n // Otherwise, use whatever we found\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n // Prefer organisation-scoped files - filter to only those with organisation_id\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n // Use all files found (either user-scoped or org-scoped, but not both)\n files = allFiles;\n }\n\n // If no files found through RPC, try a direct query as fallback\n // This handles cases where RLS policy allows access but RPC security check is too strict\n // (e.g., core_person files where user owns the person record but record_id != user_id)\n if (files.length === 0) {\n try {\n let directQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n // If category is provided, filter by category in metadata\n if (category) {\n directQuery = directQuery.eq('file_metadata->>category', category);\n }\n\n const { data: directFiles } = await directQuery;\n\n if (directFiles && directFiles.length > 0) {\n // Convert to FileReference format\n files = directFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - get files by category using RPC\n logger.debug('useFileDisplay', 'Using RPC function for category filtering:', {\n table_name,\n record_id,\n category,\n organisation_id\n });\n files = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n organisation_id\n );\n } else {\n // Multiple files mode - get all files using RPC\n files = await service.listFileReferences(\n table_name,\n record_id,\n organisation_id\n );\n }\n\n // Fallback: If no files found and organisation_id is falsy (undefined, null, empty string),\n // try searching both scopes as a fallback\n // This handles cases where the prop might be passed as empty string or the check above didn't catch it\n if (files.length === 0 && (!organisation_id || organisation_id === '')) {\n // Try the dual-scope search logic\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined\n );\n }\n } catch (err) {\n // Silently fail\n }\n\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (user) {\n const { data: memberships } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id)\n .or('status.is.null,status.eq.active');\n\n if (memberships && memberships.length > 0) {\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(table_name, record_id, category, orgId);\n } else {\n return await service.listFileReferences(table_name, record_id, orgId);\n }\n } catch (err) {\n return [];\n }\n });\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n }\n }\n } catch (err) {\n // Silently fail\n }\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n files = allFiles;\n }\n }\n }\n\n if (files.length === 0) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n \n // Cache empty result\n if (enableCache) {\n authenticatedFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n return;\n }\n\n setFileReferences(files);\n setFileCount(files.length);\n\n if (category && files.length > 0) {\n // Single file mode - get first file\n const firstFile = files[0];\n logger.debug('useFileDisplay', 'Processing category files, first file:', {\n id: firstFile.id,\n file_path: firstFile.file_path,\n is_public: firstFile.is_public,\n has_file_metadata: !!firstFile.file_metadata,\n category_in_metadata: firstFile.file_metadata?.category\n });\n \n setFileReference(firstFile);\n \n // Generate URL based on file visibility\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n logger.debug('useFileDisplay', 'Generated public URL:', url);\n } else {\n const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n url = signedUrlResult?.url || null;\n logger.debug('useFileDisplay', 'Generated signed URL:', url ? 'URL generated' : 'URL generation failed');\n }\n logger.debug('useFileDisplay', 'Setting file URL:', url ? 'URL set' : 'URL is null');\n setFileUrl(url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, files, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n setFileUrls(urlMap);\n setFileReference(null);\n setFileUrl(null);\n }\n\n // Cache the result\n if (enableCache) {\n // Prepare cache data\n let cacheData: any = {\n fileReference: category && files.length > 0 ? files[0] : null,\n fileReferences: files,\n fileUrls: new Map(),\n fileCount: files.length\n };\n\n if (category && files.length > 0) {\n const firstFile = files[0];\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n }\n cacheData.fileUrl = url;\n } else {\n const urlMap = new Map<string, string>();\n for (const fileRef of files) {\n if (fileRef.is_public) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n }\n cacheData.fileUrls = urlMap;\n }\n\n authenticatedFileCache.set(cacheKey, {\n data: cacheData,\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n\n } catch (err) {\n logger.error('useFileDisplay', 'Error fetching files:', err);\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n } finally {\n setIsLoading(false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n if (table_name && record_id && supabase) {\n fetchFiles();\n } else {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n setError(null);\n }\n // fetchFiles is memoized; we only need to re-run when parameters change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [table_name, record_id, organisation_id, supabase]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached authenticated file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearFileDisplayCache(): void {\n for (const [key] of authenticatedFileCache) {\n if (key.startsWith('file_')) {\n authenticatedFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(authenticatedFileCache.keys()).filter(key => key.startsWith('file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n/**\n * Invalidate cache for a specific file display entry\n * Useful for clearing cache after file uploads or updates\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to invalidate (if provided, also invalidates 'all' category)\n */\nexport function invalidateFileDisplayCache(\n table_name: string,\n record_id: string,\n organisation_id: string | null | undefined,\n category?: FileCategory\n): void {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n // Also invalidate 'all' category if specific category invalidated\n if (category) {\n const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_all`;\n authenticatedFileCache.delete(allCategoryKey);\n }\n}\n\n","// File Reference Service\n// Provides CRUD operations for the centralized file reference system\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n FileReference, \n FileUploadOptions, \n FileReferenceService, \n FileUploadResult,\n FileCategory,\n FileMetadata\n} from '../../types/file-reference';\nimport { uploadFile, getPublicUrl, getSignedUrl, deleteFile, extractFileMetadata } from '../storage/helpers';\nimport { setOrganisationContext } from '../context/organisationContext';\nimport { invalidateFileDisplayCache } from '../../hooks/useFileDisplay';\nimport { createLogger } from '../core/logger';\nimport { assertAppId } from '../../types/core';\n\nconst log = createLogger('FileReferenceService');\n\nexport class FileReferenceServiceImpl implements FileReferenceService {\n constructor(private supabase: SupabaseClient) {}\n\n /**\n * Creates a file reference by uploading a file to storage and linking it in the database.\n * \n * Storage Flow:\n * 1. Upload file to storage bucket first (files or public-files based on is_public flag)\n * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}\n * - Bucket selection: 'files' (private) or 'public-files' (public)\n * 2. Extract file metadata (dimensions, hash, etc.)\n * 3. Set organisation context for RLS policies\n * 4. Create database reference via RPC function\n * 5. If DB insert fails, rollback by deleting uploaded file\n * \n * This ensures atomicity: either both storage and DB succeed, or both are cleaned up.\n */\n async createFileReference(options: FileUploadOptions, file: File): Promise<FileReference> {\n try {\n\n // organisation_id is optional for user-scoped files (e.g., profile photos)\n const isUserScoped = !options.organisation_id && options.userId;\n if (!isUserScoped && !options.organisation_id) {\n throw new Error('organisation_id is required for file upload, or userId must be provided for user-scoped files');\n }\n if (!options.table_name) {\n throw new Error('table_name is required for file upload');\n }\n if (!options.record_id) {\n throw new Error('record_id is required for file upload');\n }\n if (!options.folder) {\n throw new Error('folder is required for file upload. The folder prop determines the storage path.');\n }\n\n // For user-scoped files, we MUST use auth.uid() for the path to match RLS policies\n // Get the authenticated user ID from the Supabase session\n let authenticatedUserId: string | undefined = undefined;\n if (isUserScoped) {\n const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();\n if (authError || !authUser) {\n throw new Error('User must be authenticated to upload user-scoped files');\n }\n authenticatedUserId = authUser.id;\n log.debug('Using authenticated user ID for user-scoped file upload', { userId: authenticatedUserId });\n }\n\n // Step 1: Upload file to storage bucket first\n // This generates a unique path: {orgId}/{folder}/{timestamp-uuid-filename} or users/{auth.uid()}/{folder}/{timestamp-uuid-filename}\n // Bucket is automatically selected based on is_public flag\n const uploadResult = await uploadFile(this.supabase, file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false,\n customPath: options.folder // Use folder prop as the custom path segment\n });\n if (!uploadResult.success) {\n throw new Error(`Failed to upload file: ${uploadResult.error}`);\n }\n\n if (!uploadResult.path) {\n throw new Error('File upload did not return a path');\n }\n\n const filePath = uploadResult.path;\n\n // Step 2: Extract file metadata (dimensions, hash, etc.)\n const metadata = await extractFileMetadata(file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false\n }, 'system');\n\n // Step 3: Set organisation context in database session before creating file reference\n // Skip for user-scoped files (no org context needed)\n if (!isUserScoped && options.organisation_id) {\n await setOrganisationContext(this.supabase, options.organisation_id);\n }\n\n // Step 4: Create file reference in database using RPC function\n // This links the storage path to the record in core_file_references table\n const { data, error } = await this.supabase\n .rpc('data_file_reference_create', {\n p_table_name: options.table_name,\n p_record_id: options.record_id,\n p_file_path: filePath, // Storage path from step 1\n p_organisation_id: options.organisation_id ?? null,\n p_app_id: options.app_id,\n p_page_context: options.pageContext,\n p_event_id: options.event_id || null, // Pass event_id for event-based apps\n p_file_metadata: {\n fileName: file.name,\n fileType: file.type,\n fileSize: file.size,\n category: options.category,\n ...metadata,\n ...options.custom_metadata\n },\n p_is_public: options.is_public || false,\n p_user_id: authenticatedUserId || options.userId || null // Pass authenticated user ID for user-scoped files\n });\n\n // Step 5: Rollback - if database insert fails, clean up uploaded file\n if (error) {\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to create file reference: ${error.message}`);\n }\n\n // Check if RPC returned null (permission denied or other failure)\n if (!data || data === null) {\n // Clean up the uploaded file since DB insert failed\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`File upload denied: insufficient permissions. You need 'create:page.${options.pageContext}' or 'update:page.${options.pageContext}' permission for the '${options.pageContext}' page. Make sure the page exists in rbac_app_pages table.`);\n }\n\n // Get the created file reference\n const { data: fileRef, error: fetchError } = await this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('id', data)\n .single();\n\n if (fetchError || !fileRef) {\n // Clean up uploaded file if we can't fetch the reference\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);\n }\n\n // Invalidate cache for this file display entry so newly uploaded files appear immediately\n // For user-scoped files, pass null for organisation_id\n invalidateFileDisplayCache(\n options.table_name,\n options.record_id,\n options.organisation_id || null,\n options.category\n );\n\n return fileRef as FileReference;\n } catch (error) {\n log.error('Error creating file reference:', error);\n throw error;\n }\n }\n\n async getFileReference(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n let query = this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id);\n \n // Handle NULL organisation_id for user-owned files\n if (organisation_id === null || organisation_id === undefined) {\n query = query.is('organisation_id', null);\n } else {\n query = query.eq('organisation_id', organisation_id);\n }\n \n const { data, error } = await query.single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // No rows found\n }\n throw new Error(`Failed to get file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error getting file reference:', error);\n throw error;\n }\n }\n\n async getFileUrl(table_name: string, record_id: string, organisation_id?: string): Promise<string | null> {\n try {\n // Get file reference to check if it's public\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n if (!fileRef) {\n return null;\n }\n\n // For public files, RPC returns file path - generate public URL client-side\n if (fileRef.is_public) {\n const { data: pathData } = await this.supabase\n .rpc('data_file_reference_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id\n });\n\n if (!pathData) {\n return null;\n }\n\n // Generate public URL using bucket-aware helper\n return getPublicUrl(this.supabase, pathData, true);\n } else {\n // For private files, use signed URL\n return await this.getSignedUrl(table_name, record_id, organisation_id);\n }\n } catch (error) {\n log.error('Error getting file URL:', error);\n throw error;\n }\n }\n\n async getSignedUrl(table_name: string, record_id: string, organisation_id?: string, expires_in: number = 3600): Promise<string | null> {\n try {\n // Get file path from RPC function\n const { data: filePath, error } = await this.supabase\n .rpc('data_file_reference_signed_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_expires_in: expires_in\n });\n\n if (error) {\n throw new Error(`Failed to get signed URL: ${error.message}`);\n }\n\n if (!filePath) {\n return null;\n }\n\n // Generate signed URL client-side using bucket-aware helper (files bucket for private files)\n const signedUrlResult = await getSignedUrl(this.supabase, filePath, {\n appName: 'file-reference',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: expires_in\n });\n\n return signedUrlResult?.url || null;\n } catch (error) {\n log.error('Error getting signed URL:', error);\n throw error;\n }\n }\n\n async updateFileReference(id: string, updates: Partial<FileReference>): Promise<FileReference> {\n try {\n const { data, error } = await this.supabase\n .from('core_file_references')\n .update(updates)\n .eq('id', id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error updating file reference:', error);\n throw error;\n }\n }\n\n async deleteFileReference(table_name: string, record_id: string, organisation_id?: string, delete_file: boolean = false): Promise<boolean> {\n try {\n // Get file reference first to determine bucket\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n \n const { error } = await this.supabase\n .rpc('data_file_reference_delete', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_delete_file: delete_file\n });\n\n if (error) {\n throw new Error(`Failed to delete file reference: ${error.message}`);\n }\n\n // If delete_file is true and we have the file reference, delete from storage\n if (delete_file && fileRef) {\n await deleteFile(this.supabase, fileRef.file_path, fileRef.is_public || false);\n }\n\n return true;\n } catch (error) {\n log.error('Error deleting file reference:', error);\n throw error;\n }\n }\n\n async listFileReferences(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference[]> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to list file references: ${error.message}`);\n }\n\n // RPC returns: id, file_path, file_metadata, is_public, created_at\n // We can construct FileReference objects directly from RPC response + function parameters\n // This avoids a second query and reduces network requests\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => item.id && item.file_path && item.file_metadata)\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error listing file references:', error);\n throw error;\n }\n }\n\n async getFileCount(table_name: string, record_id: string, organisation_id?: string): Promise<number> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_count_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file count: ${error.message}`);\n }\n\n return data || 0;\n } catch (error) {\n log.error('Error getting file count:', error);\n throw error;\n }\n }\n\n async getFileReferenceById(id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_get', {\n p_file_reference_id: id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file reference by ID: ${error.message}`);\n }\n\n if (!data || data.length === 0) {\n return null;\n }\n\n return data[0] as FileReference;\n } catch (error) {\n log.error('Error getting file reference by ID:', error);\n throw error;\n }\n }\n\n async getFilesByCategory(\n table_name: string, \n record_id: string, \n category: FileCategory, \n organisation_id?: string\n ): Promise<FileReference[]> {\n try {\n // CRITICAL: Use RPC function to get files by category - this correctly filters on file_metadata->>'category'\n // NOTE: We MUST use RPC function. Direct queries with .eq('category', ...) will FAIL with HTTP 406\n // because there is NO 'category' column. The category is stored in the JSONB file_metadata field.\n const { data, error } = await this.supabase\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n // Provide clear error message about category filtering\n log.error('RPC ERROR getting files by category:', {\n error,\n errorCode: error.code,\n errorMessage: error.message,\n errorDetails: error.details,\n table_name,\n record_id,\n category,\n organisation_id,\n message: 'CRITICAL: Category filtering MUST use RPC function data_file_reference_by_category_list. Direct queries with .eq(\\'category\\', ...) will FAIL with HTTP 406 because category is stored in file_metadata JSONB field, not a direct column.'\n });\n throw new Error(`Failed to get files by category: ${error.message}. Category filtering uses file_metadata JSONB field, not a direct column. RPC function required.`);\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { category?: string; app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => {\n // Verify category matches (defensive check)\n const fileCategory = item.file_metadata?.category;\n const matches = fileCategory === category;\n if (!matches) {\n log.warn('File category mismatch in RPC response:', {\n fileId: item.id,\n expectedCategory: category,\n actualCategory: fileCategory\n });\n }\n return matches && item.id && item.file_path && item.file_metadata;\n })\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n category: (item.file_metadata?.category as FileCategory) || FileCategory.GENERAL_DOCUMENTS,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error getting files by category:', error);\n throw error;\n }\n }\n\n async uploadMultipleFiles(\n options: FileUploadOptions,\n files: File[]\n ): Promise<import('../../types/file-reference').BulkUploadResult> {\n const success: FileReference[] = [];\n const failed: { file: File; error: string }[] = [];\n const results: Array<{ file: File; result: FileUploadResult | null; error?: string }> = [];\n\n // Upload files sequentially to avoid overwhelming the server\n for (const file of files) {\n try {\n const fileReference = await this.createFileReference(options, file);\n\n success.push(fileReference);\n results.push({\n file,\n result: {\n file_reference: fileReference,\n file_url: fileReference.is_public\n ? getPublicUrl(this.supabase, fileReference.file_path, true)\n : '',\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n failed.push({ file, error: message });\n results.push({ file, result: null, error: message });\n }\n }\n\n return {\n success,\n failed,\n total: results.length,\n successful: success.length,\n results,\n };\n }\n}\n\n// Factory function to create file reference service\nexport function createFileReferenceService(supabase: SupabaseClient): FileReferenceService {\n return new FileReferenceServiceImpl(supabase);\n}\n\n// Helper function to upload file and create reference in one operation\nexport async function uploadFileWithReference(\n supabase: SupabaseClient,\n options: FileUploadOptions,\n file: File\n): Promise<FileUploadResult> {\n \n const service = createFileReferenceService(supabase);\n const fileReference = await service.createFileReference(options, file);\n \n const fileUrl = options.is_public \n ? getPublicUrl(supabase, fileReference.file_path, true)\n : await getSignedUrl(supabase, fileReference.file_path, { \n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: options.userId || undefined,\n expiresIn: 3600 \n });\n\n const urlString = typeof fileUrl === 'string' ? fileUrl : fileUrl?.url || '';\n\n return {\n file_reference: fileReference,\n file_url: urlString,\n signed_url: options.is_public ? undefined : urlString || undefined\n };\n}\n","/**\n * @file Event Theme Hook\n * @package @jmruthers/pace-core\n * @module Hooks/EventTheme\n * @since 2.0.0\n * \n * Hook that automatically applies event-specific theming when an event is selected.\n * This ensures consistent UX across all apps in the pace suite.\n * \n * Works in two modes:\n * 1. Authenticated mode: Uses EventProvider context via useEvents() hook\n * 2. Public page mode: Accepts event prop directly (no EventProvider required)\n * \n * @example\n * ```tsx\n * // Authenticated pages (with EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function MyApp() {\n * // Automatically applies event colors when event is selected via EventProvider\n * useEventTheme();\n * \n * return <div>Your app content</div>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public pages (without EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function PublicPageLayout({ event }) {\n * // Applies event colors directly from event prop\n * useEventTheme(event);\n * \n * return <div>Public page content</div>;\n * }\n * ```\n */\n\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useEvents } from './useEvents';\nimport { applyPalette, clearPalette } from '../theming/runtime';\nimport { parseAndNormalizeEventColours } from '../theming/parseEventColours';\nimport type { Event } from '../types/event';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useEventTheme');\n\n/**\n * Hook that automatically applies event-specific theming\n * \n * This hook applies event colors using the theming system. It works in two modes:\n * \n * **Authenticated Mode (default):**\n * - Uses EventProvider context via useEvents() hook\n * - Automatically watches selectedEvent changes\n * - Used in authenticated pages with EventProvider\n * \n * **Public Page Mode:**\n * - Accepts event prop directly\n * - No EventProvider required\n * - Used in public pages without authentication context\n * \n * Behavior:\n * - Applies event colors when an event with `event_colours` is provided\n * - Clears theming when no event is provided\n * - Skips theme application when on the login route to prevent login screen styling\n * - Handles cleanup and error cases gracefully\n * \n * @param event - Optional event object. If provided, uses this event directly (public page mode).\n * If not provided, uses EventProvider context via useEvents() (authenticated mode).\n * \n * @returns void - This is an effect hook with no return value\n * \n * @example\n * ```tsx\n * // Authenticated mode - uses EventProvider\n * function MyApp() {\n * useEventTheme(); // Watches selectedEvent from EventProvider\n * return <div>App content</div>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public page mode - uses event prop\n * function PublicPageLayout({ event }) {\n * useEventTheme(event); // Uses event prop directly\n * return <div>Public content</div>;\n * }\n * ```\n */\nexport function useEventTheme(event?: Event | null): void {\n const location = useLocation();\n \n // Try to get event from EventProvider context (authenticated mode)\n // Only use useEvents() if event prop is not provided\n let selectedEvent: Event | null | undefined;\n try {\n if (event === undefined) {\n // No event prop provided, try to use EventProvider context\n const eventsContext = useEvents();\n selectedEvent = eventsContext.selectedEvent;\n } else {\n // Event prop provided, use it directly (public page mode)\n selectedEvent = event;\n }\n } catch (error) {\n // useEvents() throws if EventProvider is not available\n // This is expected for public pages - use the event prop if provided\n if (event !== undefined) {\n selectedEvent = event;\n } else {\n // No event prop and no EventProvider - can't apply theme\n selectedEvent = null;\n }\n }\n\n useEffect(() => {\n // Skip theme application when on login route\n // This prevents event colors from styling the login screen\n const isOnLoginRoute = location.pathname === '/login' || location.pathname.startsWith('/login');\n \n if (isOnLoginRoute) {\n // Clear any active theme when on login route\n clearPalette();\n return;\n }\n\n // If there's no selected event, clear any dynamic theming\n if (!selectedEvent) {\n clearPalette();\n return;\n }\n\n // Check if the event has theme colors\n const eventColours = selectedEvent.event_colours;\n\n // Parse and normalize event_colours using shared utility\n const normalized = parseAndNormalizeEventColours(eventColours);\n \n if (!normalized) {\n clearPalette();\n return;\n }\n\n // Apply the normalized palette\n try {\n applyPalette(normalized);\n } catch (error) {\n log.error('Failed to apply event palette:', error);\n }\n\n // Cleanup function to clear palette when component unmounts or event changes\n return () => {\n // Don't clear on unmount since we want the theme to persist\n // The next event selection will update it\n };\n }, [selectedEvent, location.pathname]);\n}\n","/**\n * @file usePreventTabReload Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.6.0\n *\n * Prevents full page reloads when switching browser tabs.\n * Handles browser back-forward cache (bfcache) restoration and tab visibility changes.\n */\n\nimport { useEffect, useRef } from 'react';\n\nexport interface UsePreventTabReloadOptions {\n /**\n * Whether to enable the prevention logic\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Grace period in milliseconds to wait after tab becomes visible\n * before allowing any potential reloads\n * @default 2000\n */\n gracePeriodMs?: number;\n}\n\n/**\n * Hook to prevent full page reloads when switching browser tabs.\n * \n * This hook handles:\n * - Browser back-forward cache (bfcache) restoration\n * - Tab visibility changes\n * - Prevents unwanted page reloads when returning to a tab\n * \n * @param options - Configuration options\n * \n * @example\n * ```tsx\n * import { usePreventTabReload } from '@jmruthers/pace-core';\n * \n * function MyComponent() {\n * usePreventTabReload();\n * // Component code...\n * }\n * ```\n */\nexport function usePreventTabReload(options: UsePreventTabReloadOptions = {}): void {\n const { enabled = true, gracePeriodMs = 2000 } = options;\n const isRestoringFromCacheRef = useRef(false);\n const gracePeriodTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (!enabled || typeof window === 'undefined') return;\n\n // Handle pageshow event - detects when page is restored from bfcache\n const handlePageShow = (event: PageTransitionEvent) => {\n // If page was restored from bfcache, prevent any reload behavior\n if (event.persisted) {\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period to prevent reloads\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Handle visibility changes - when tab becomes visible\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n // Tab just became visible - set flag to prevent reloads\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Add event listeners\n window.addEventListener('pageshow', handlePageShow);\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n return () => {\n window.removeEventListener('pageshow', handlePageShow);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n \n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n };\n }, [enabled, gracePeriodMs]);\n}\n\n"],"mappings":"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","names":["useState","useEffect","useCallback","useRef","useState","useRef","useEffect","error","useCallback","log","useState","useEffect","useCallback","useState","useCallback","error","useEffect","useState","useEffect","useCallback","log","MAX_CACHE_SIZE","useState","useCallback","error","useEffect","useEffect","log","useEffect","useEffect","useRef"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/hooks/useSecureDataAccess.ts"],"sourcesContent":["/**\n * @file useSecureDataAccess Hook\n * @package @jmruthers/pace-core\n * @module Hooks/useSecureDataAccess\n * @since 0.4.0\n *\n * Hook for secure database operations with mandatory organisation context.\n * Ensures all data access is properly scoped to the user's current organisation.\n *\n * @example\n * ```tsx\n * function DataComponent() {\n * const { secureQuery, secureInsert, secureUpdate, secureDelete } = useSecureDataAccess();\n * \n * const loadData = async () => {\n * try {\n * // Automatically includes organisation_id filter\n * const events = await secureQuery('core_events', '*', { is_visible: true });\n * console.log('Organisation events:', events);\n * } catch (error) {\n * console.error('Failed to load data:', error);\n * }\n * };\n * \n * const createEvent = async (eventData) => {\n * try {\n * // Automatically sets organisation_id\n * const newEvent = await secureInsert('core_events', eventData);\n * console.log('Created event:', newEvent);\n * } catch (error) {\n * console.error('Failed to create event:', error);\n * }\n * };\n * \n * return (\n * <div>\n * <button onClick={loadData}>Load Data</button>\n * <button onClick={() => createEvent({ event_name: 'New Event' })}>\n * Create Event\n * </button>\n * </div>\n * );\n * }\n * ```\n *\n * @security\n * - All queries automatically include organisation_id filter\n * - Validates organisation context before any operation\n * - Prevents data leaks between organisations\n * - Error handling for security violations\n * - Type-safe database operations\n */\n\nimport { useCallback, useState, useContext } from 'react';\nimport { useUnifiedAuth } from '../providers';\nimport { useOrganisations } from './useOrganisations';\nimport { EventServiceContext } from '../providers/services/EventServiceProvider';\nimport { setOrganisationContext } from '../utils/context/organisationContext';\nimport { logger } from '../utils/core/logger';\nimport type { Permission } from '../rbac/types';\nimport type { OrganisationSecurityError } from '../types/organisation';\nimport { useSuperAdminBypass } from '../rbac/hooks/useSuperAdminBypass';\nimport { useResolvedScope } from '../rbac/hooks/useResolvedScope';\n\nexport interface SecureDataAccessReturn {\n /** Execute a secure query with organisation filtering */\n secureQuery: <T = any>(\n table: string,\n columns: string,\n filters?: Record<string, any>,\n options?: {\n orderBy?: string;\n ascending?: boolean;\n limit?: number;\n offset?: number;\n }\n ) => Promise<T[]>;\n \n /** Execute a secure insert with organisation context */\n secureInsert: <T = any>(\n table: string,\n data: Record<string, any>\n ) => Promise<T>;\n \n /** Execute a secure update with organisation filtering */\n secureUpdate: <T = any>(\n table: string,\n data: Record<string, any>,\n filters: Record<string, any>\n ) => Promise<T[]>;\n \n /** Execute a secure delete with organisation filtering */\n secureDelete: (\n table: string,\n filters: Record<string, any>\n ) => Promise<void>;\n \n /** Execute a secure RPC call with organisation context */\n secureRpc: <T = any>(\n functionName: string,\n params?: Record<string, any>\n ) => Promise<T>;\n \n /** Get current organisation ID */\n getCurrentOrganisationId: () => string;\n \n /** Validate organisation context */\n validateContext: () => void;\n \n // NEW: Phase 1 - Enhanced Security Features\n /** Check if data access is allowed for a table and operation */\n isDataAccessAllowed: (table: string, operation: string) => boolean;\n \n /** Get all data access permissions for current user */\n getDataAccessPermissions: () => Record<string, string[]>;\n \n /** Check if strict mode is enabled */\n isStrictMode: boolean;\n \n /** Check if audit logging is enabled */\n isAuditLogEnabled: boolean;\n \n /** Get data access history */\n getDataAccessHistory: () => DataAccessRecord[];\n \n /** Clear data access history */\n clearDataAccessHistory: () => void;\n \n /** Validate data access attempt */\n validateDataAccess: (table: string, operation: string) => boolean;\n}\n\nexport interface DataAccessRecord {\n table: string;\n operation: string;\n userId: string;\n organisationId: string;\n allowed: boolean;\n timestamp: string;\n query?: string;\n filters?: Record<string, any>;\n}\n\n/**\n * Hook for secure data access with automatic organisation filtering\n * \n * All database operations automatically include organisation context:\n * - Queries filter by organisation_id\n * - Inserts include organisation_id\n * - Updates/deletes are scoped to organisation\n * - RPC calls include organisation_id parameter\n */\nexport function useSecureDataAccess(): SecureDataAccessReturn {\n const { supabase, user, session, selectedOrganisation, selectedEvent } = useUnifiedAuth();\n \n // Get selected event for event-scoped RPC calls\n // Use useContext directly to safely check if EventServiceProvider is available\n const eventServiceContext = useContext(EventServiceContext);\n const eventFromContext = eventServiceContext?.eventService?.getSelectedEvent() || null;\n const effectiveSelectedEvent = selectedEvent || eventFromContext;\n const { isSuperAdmin } = useSuperAdminBypass();\n\n // Use resolved scope to get organisationId (derived from event if needed)\n const { resolvedScope } = useResolvedScope({\n supabase,\n selectedOrganisationId: selectedOrganisation?.id || null,\n selectedEventId: effectiveSelectedEvent?.event_id || null\n });\n\n const validateContext = useCallback((): void => {\n if (!supabase) {\n throw new Error('No Supabase client available') as OrganisationSecurityError;\n }\n if (!user || !session) {\n throw new Error('User must be authenticated with valid session') as OrganisationSecurityError;\n }\n \n if (isSuperAdmin) {\n return;\n }\n \n if (!resolvedScope?.organisationId) {\n throw new Error('Organisation context is required for data access') as OrganisationSecurityError;\n }\n }, [supabase, user, session, resolvedScope, isSuperAdmin]);\n\n const getCurrentOrganisationId = useCallback((): string => {\n if (isSuperAdmin) {\n // For super admins, try to get org from resolved scope or selectedOrganisation\n return resolvedScope?.organisationId || selectedOrganisation?.id || '';\n }\n\n validateContext();\n return resolvedScope?.organisationId || '';\n }, [validateContext, resolvedScope, selectedOrganisation, isSuperAdmin]);\n\n // Set organisation context in database session\n const setOrganisationContextInSession = useCallback(async (organisationId?: string): Promise<void> => {\n if (!supabase || !organisationId) {\n return;\n }\n\n await setOrganisationContext(supabase, organisationId);\n }, [supabase]);\n\n const secureQuery = useCallback(async <T = any>(\n table: string,\n columns: string,\n filters: Record<string, any> = {},\n options: {\n orderBy?: string;\n ascending?: boolean;\n limit?: number;\n offset?: number;\n } = {}\n ): Promise<T[]> => {\n validateContext();\n const bypassOrganisationFilter = isSuperAdmin;\n const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();\n\n // Set organisation context in database session\n await setOrganisationContextInSession(organisationId);\n\n // Build query with organisation filter\n let query = supabase!\n .from(table)\n .select(columns);\n\n // Add organisation filter only if table has organisation_id column\n // Defense in depth strategy:\n // - RLS policies are the primary security layer (cannot be bypassed)\n // - Application-level filtering adds an additional layer of protection\n const tablesWithOrganisation = [\n 'core_events', 'organisation_settings',\n 'rbac_event_app_roles', 'rbac_organisation_roles',\n // SECURITY: Phase 2 additions - complete organisation table mapping\n 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',\n // SECURITY: Emergency additions for Phase 1 fixes\n 'cake_meal', 'cake_mealtype', 'core_person',\n // NOTE: core_member, medi_profile, core_contact, core_consent, core_identification, core_qualification\n // are now person-scoped (not organisation-scoped) - removed from this list\n // SECURITY: Phase 3A additions - medical and personal data\n // NOTE: medi_condition, medi_diet, medi_action_plan, medi_profile_versions are now person-scoped\n // (via medi_profile) - removed from this list\n // core_identification_type remains organisation-scoped (lookup table)\n 'core_identification_type',\n 'form_responses', 'form_response_values', 'forms',\n // SECURITY: Phase 3B additions - remaining critical tables\n 'invoice', 'line_item', 'credit_balance', 'payment_method',\n 'form_contexts', 'form_field_config', 'form_fields',\n 'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item', \n 'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier', \n 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions',\n // rbac_user_profiles has organisation_id but uses conditional filtering\n 'rbac_user_profiles'\n ];\n \n // Apply organisation filtering based on table and super admin status\n if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {\n // For rbac_user_profiles: Super admins see all (no filter), non-super-admins get filtered (defense in depth)\n // For other tables: Always apply filter\n if (table === 'rbac_user_profiles' && isSuperAdmin) {\n // Super admin: No org filter - RLS handles access control\n // This allows super admins to see all users across all organisations\n } else {\n // Non-super-admin OR other tables: Apply org filter as defense in depth\n query = query.eq('organisation_id', organisationId);\n }\n }\n\n // Apply additional filters\n Object.entries(filters).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n // Handle qualified column names (e.g., 'users.role')\n const columnName = key.includes('.') ? key.split('.').pop()! : key;\n query = query.eq(columnName, value);\n }\n });\n\n // Apply options\n if (options.orderBy) {\n // Only use the column name, not a qualified name\n const orderByColumn = options.orderBy.split('.').pop();\n if (orderByColumn) {\n query = query.order(orderByColumn, { ascending: options.ascending ?? true });\n }\n }\n \n if (options.limit) {\n query = query.limit(options.limit);\n }\n \n if (options.offset) {\n query = query.range(options.offset, options.offset + (options.limit || 100) - 1);\n }\n\n const { data, error } = await query;\n \n if (error) {\n logger.error('useSecureDataAccess', 'Query failed', { table, columns, filters, error });\n // NEW: Phase 1 - Record failed data access attempt\n recordDataAccess(table, 'read', false, `SELECT ${columns} FROM ${table}`, filters);\n throw error;\n }\n\n // NEW: Phase 1 - Record successful data access attempt\n recordDataAccess(table, 'read', true, `SELECT ${columns} FROM ${table}`, filters);\n\n return (data as T[]) || [];\n }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);\n\n const secureInsert = useCallback(async <T = any>(\n table: string,\n data: Record<string, any>\n ): Promise<T> => {\n validateContext();\n const bypassOrganisationFilter = isSuperAdmin;\n const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();\n\n // Set organisation context in database session\n await setOrganisationContextInSession(organisationId);\n\n // Ensure organisation_id is set\n const secureData = bypassOrganisationFilter\n ? { ...data }\n : {\n ...data,\n organisation_id: organisationId\n };\n\n const { data: insertData, error } = await supabase!\n .from(table)\n .insert(secureData)\n .select()\n .single();\n\n if (error) {\n logger.error('useSecureDataAccess', 'Insert failed', { table, data: secureData, error });\n throw error;\n }\n\n return insertData as T;\n }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);\n\n const secureUpdate = useCallback(async <T = any>(\n table: string,\n data: Record<string, any>,\n filters: Record<string, any>\n ): Promise<T[]> => {\n validateContext();\n const bypassOrganisationFilter = isSuperAdmin;\n const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();\n\n // Set organisation context in database session\n await setOrganisationContextInSession(organisationId);\n\n // Filter out organisation_id from data to prevent manipulation\n const { organisation_id, ...secureData } = data;\n \n // Build update query with organisation filter\n let query = supabase!\n .from(table)\n .update(secureData);\n\n // Add organisation filter only if table has organisation_id column\n const tablesWithOrganisation = [\n 'core_events', 'organisation_settings',\n 'rbac_event_app_roles', 'rbac_organisation_roles',\n // SECURITY: Phase 2 additions - complete organisation table mapping\n 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',\n // SECURITY: Emergency additions for Phase 1 fixes\n 'cake_meal', 'cake_mealtype', 'core_person', 'core_member'\n ];\n \n if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {\n query = query.eq('organisation_id', organisationId);\n }\n\n // Apply filters\n Object.entries(filters).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n query = query.eq(key, value);\n }\n });\n\n const { data: updateData, error } = await query.select();\n\n if (error) {\n logger.error('useSecureDataAccess', 'Update failed', { table, data: secureData, filters, error });\n throw error;\n }\n\n return (updateData as T[]) || [];\n }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);\n\n const secureDelete = useCallback(async (\n table: string,\n filters: Record<string, any>\n ): Promise<void> => {\n validateContext();\n const bypassOrganisationFilter = isSuperAdmin;\n const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();\n\n // Set organisation context in database session\n await setOrganisationContextInSession(organisationId);\n\n // Build delete query with organisation filter\n let query = supabase!\n .from(table)\n .delete();\n\n // Add organisation filter only if table has organisation_id column\n const tablesWithOrganisation = [\n 'core_events', 'organisation_settings',\n 'rbac_event_app_roles', 'rbac_organisation_roles',\n // SECURITY: Phase 2 additions - complete organisation table mapping\n 'organisation_audit_log', 'organisation_invitations', 'organisation_app_access',\n // SECURITY: Emergency additions for Phase 1 fixes\n 'cake_meal', 'cake_mealtype', 'core_person', 'core_member',\n // SECURITY: Phase 3A additions - medical and personal data\n 'medi_profile', 'medi_condition', 'medi_diet', 'medi_action_plan', 'medi_profile_versions',\n 'core_consent', 'core_contact', 'core_identification', 'core_identification_type', 'core_qualification',\n 'form_responses', 'form_response_values', 'forms',\n // SECURITY: Phase 3B additions - remaining critical tables\n 'invoice', 'line_item', 'credit_balance', 'payment_method',\n 'form_contexts', 'form_field_config', 'form_fields',\n 'cake_delivery', 'cake_diettype', 'cake_diner', 'cake_dish', 'cake_item', \n 'cake_logistics', 'cake_mealplan', 'cake_package', 'cake_recipe', 'cake_supplier', \n 'cake_supply', 'cake_unit', 'event_app_access', 'base_application', 'base_questions'\n ];\n \n if (!bypassOrganisationFilter && organisationId && tablesWithOrganisation.includes(table)) {\n query = query.eq('organisation_id', organisationId);\n }\n\n // Apply filters\n Object.entries(filters).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n query = query.eq(key, value);\n }\n });\n\n const { error } = await query;\n\n if (error) {\n logger.error('useSecureDataAccess', 'Delete failed', { table, filters, error });\n throw error;\n }\n }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, isSuperAdmin]);\n\n const secureRpc = useCallback(async <T = any>(\n functionName: string,\n params: Record<string, any> = {}\n ): Promise<T> => {\n validateContext();\n const bypassOrganisationFilter = isSuperAdmin;\n const organisationId = bypassOrganisationFilter ? undefined : getCurrentOrganisationId();\n\n // Set organisation context in database session\n await setOrganisationContextInSession(organisationId);\n\n // Include organisation_id in RPC parameters\n // Some functions use p_organisation_id instead of organisation_id (to avoid conflicts with RETURNS TABLE columns)\n const functionsWithPOrganisationId = [\n 'data_cake_diners_list',\n 'data_cake_mealplans_list'\n ];\n \n const paramName = functionsWithPOrganisationId.includes(functionName) \n ? 'p_organisation_id' \n : 'organisation_id';\n \n // Functions that need p_event_id for event-app role permission checks\n // Note: Even org-scoped functions (like items, packages, suppliers) need event_id\n // for permission checks when users have event-app roles\n const functionsNeedingEventId = [\n 'data_cake_items_list',\n 'data_cake_packages_list',\n 'data_cake_suppliers_list',\n 'data_cake_diettypes_list',\n 'data_cake_mealtypes_list',\n 'data_cake_diners_list',\n 'data_cake_mealplans_list',\n 'data_cake_dishes_list',\n 'data_cake_recipes_list',\n 'data_cake_meals_list',\n 'data_cake_units_list',\n 'data_cake_orders_list',\n 'app_cake_item_create',\n 'app_cake_item_update',\n 'app_cake_package_create',\n 'app_cake_package_update',\n 'app_cake_supplier_create',\n 'app_cake_supplier_update',\n 'app_cake_supplier_delete',\n 'app_cake_meal_create',\n 'app_cake_meal_update',\n 'app_cake_meal_delete',\n 'app_cake_unit_create',\n 'app_cake_unit_update',\n 'app_cake_unit_delete',\n 'app_cake_delivery_upsert'\n ];\n \n // Build secureParams with correct parameter order\n // For functions that require p_event_id as first parameter, ensure it's first\n const secureParams: Record<string, any> = {};\n \n // Functions where p_event_id is the FIRST required parameter (no default)\n const functionsWithEventIdFirst = [\n 'data_cake_meals_list',\n 'data_cake_units_list'\n ];\n \n // Add p_user_id explicitly for functions that need it (even though it has a default)\n // This ensures parameter matching works correctly\n if (user?.id) {\n secureParams.p_user_id = user.id;\n }\n \n // Add organisation_id parameter when needed\n if (!bypassOrganisationFilter && organisationId) {\n secureParams[paramName] = organisationId;\n } else if (organisationId && !(paramName in params)) {\n // Default to the current organisation if caller didn't specify one\n secureParams[paramName] = organisationId;\n }\n \n // Add p_event_id if function needs it and event is selected\n // CRITICAL: This must be added AFTER organisation_id but BEFORE caller params\n // to ensure it's not overwritten. For data_cake_items_list, p_event_id is the 3rd param.\n if (functionsNeedingEventId.includes(functionName) && selectedEvent?.event_id) {\n secureParams.p_event_id = selectedEvent.event_id;\n }\n \n // Add any other params passed by caller (limit, offset, etc.)\n // NOTE: This will NOT overwrite p_event_id if caller passes it, but we want to ensure\n // our value takes precedence if event is selected\n Object.assign(secureParams, params);\n \n // Ensure p_event_id is set if needed (after Object.assign, so it overrides caller params)\n if (functionsNeedingEventId.includes(functionName) && selectedEvent?.event_id) {\n secureParams.p_event_id = selectedEvent.event_id;\n }\n\n const { data, error } = await supabase!.rpc(functionName, secureParams);\n\n if (error) {\n logger.error('useSecureDataAccess', 'RPC failed', { functionName, params: secureParams, error });\n throw error;\n }\n\n return data as T;\n }, [validateContext, getCurrentOrganisationId, setOrganisationContextInSession, supabase, selectedEvent?.event_id, user?.id, isSuperAdmin]);\n\n // NEW: Phase 1 - Enhanced Security Features\n const [dataAccessHistory, setDataAccessHistory] = useState<DataAccessRecord[]>([]);\n const [isStrictMode] = useState(true); // Always enabled in Phase 1\n const [isAuditLogEnabled] = useState(true); // Always enabled in Phase 1\n\n // Check if data access is allowed for a table and operation\n const isDataAccessAllowed = useCallback((table: string, operation: string): boolean => {\n if (!user?.id) return false;\n \n // Use the existing RBAC system to check data access permissions\n // This is a synchronous check for the context - actual permission checking\n // happens in the secure data operations using the RBAC engine\n const permission = `${operation}:data.${table}` as Permission;\n \n // For now, we'll return true and let the secure data operations\n // handle the actual permission checking asynchronously\n // This context is mainly for tracking and audit purposes\n return true;\n }, [user?.id]);\n\n // Get all data access permissions for current user\n const getDataAccessPermissions = useCallback((): Record<string, string[]> => {\n if (!user?.id) return {};\n \n // For now, return empty object - this will be enhanced with actual permission checking\n // when we integrate with the existing RBAC system\n return {};\n }, [user?.id]);\n\n // Get data access history\n const getDataAccessHistory = useCallback((): DataAccessRecord[] => {\n return [...dataAccessHistory];\n }, [dataAccessHistory]);\n\n // Clear data access history\n const clearDataAccessHistory = useCallback(() => {\n setDataAccessHistory([]);\n }, []);\n\n // Validate data access attempt\n const validateDataAccess = useCallback((table: string, operation: string): boolean => {\n if (!user?.id) return false;\n \n // Validate organisation context\n try {\n validateContext();\n } catch (error) {\n logger.error('useSecureDataAccess', 'Organisation context validation failed', { table, operation, error });\n return false;\n }\n \n return isDataAccessAllowed(table, operation);\n }, [user?.id, validateContext, isDataAccessAllowed]);\n\n // Record data access attempt\n const recordDataAccess = useCallback((\n table: string,\n operation: string,\n allowed: boolean,\n query?: string,\n filters?: Record<string, any>\n ) => {\n if (!isAuditLogEnabled || !user?.id) return;\n const auditOrganisationId = getCurrentOrganisationId() || 'super-admin-bypass';\n\n const record: DataAccessRecord = {\n table,\n operation,\n userId: user.id,\n organisationId: auditOrganisationId,\n allowed,\n timestamp: new Date().toISOString(),\n query,\n filters\n };\n \n setDataAccessHistory(prev => {\n const newHistory = [record, ...prev];\n return newHistory.slice(0, 1000); // Keep last 1000 records\n });\n \n if (isStrictMode && !allowed) {\n logger.error('useSecureDataAccess', 'STRICT MODE VIOLATION: User attempted data access without permission', {\n table,\n operation,\n userId: user.id,\n organisationId: auditOrganisationId,\n timestamp: new Date().toISOString()\n });\n }\n }, [isAuditLogEnabled, isStrictMode, user?.id, getCurrentOrganisationId]);\n\n return {\n secureQuery,\n secureInsert,\n secureUpdate,\n secureDelete,\n secureRpc,\n getCurrentOrganisationId,\n validateContext,\n // NEW: Phase 1 - Enhanced Security Features\n isDataAccessAllowed,\n getDataAccessPermissions,\n isStrictMode,\n isAuditLogEnabled,\n getDataAccessHistory,\n clearDataAccessHistory,\n validateDataAccess\n };\n} "],"mappings":"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","names":[]}