@jmruthers/pace-core 0.5.189 → 0.5.190

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 (420) hide show
  1. package/core-usage-manifest.json +0 -4
  2. package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
  3. package/dist/{DataTable-GUFUNZ3N.js → DataTable-ON3IXISJ.js} +8 -8
  4. package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-C4uxosp6.d.ts} +83 -24
  5. package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
  6. package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-X5NXANVI.js} +4 -2
  7. package/dist/{api-YP7XD5L6.js → api-I6UCQ5S6.js} +4 -2
  8. package/dist/{chunk-DDM4CCYT.js → chunk-4QYC5L4K.js} +60 -35
  9. package/dist/chunk-4QYC5L4K.js.map +1 -0
  10. package/dist/{chunk-IM4QE42D.js → chunk-73HSNNOQ.js} +141 -326
  11. package/dist/chunk-73HSNNOQ.js.map +1 -0
  12. package/dist/{chunk-YHCN776L.js → chunk-DZWK57KZ.js} +2 -75
  13. package/dist/chunk-DZWK57KZ.js.map +1 -0
  14. package/dist/{chunk-3GOZZZYH.js → chunk-HQVPB5MZ.js} +238 -301
  15. package/dist/chunk-HQVPB5MZ.js.map +1 -0
  16. package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
  17. package/dist/chunk-HW3OVDUF.js.map +1 -0
  18. package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
  19. package/dist/chunk-I7PSE6JW.js.map +1 -0
  20. package/dist/{chunk-VGZZXKBR.js → chunk-J2XXC7R5.js} +280 -52
  21. package/dist/chunk-J2XXC7R5.js.map +1 -0
  22. package/dist/{chunk-UCQSRW7Z.js → chunk-NIU6J6OX.js} +425 -378
  23. package/dist/chunk-NIU6J6OX.js.map +1 -0
  24. package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
  25. package/dist/{chunk-HEHYGYOX.js → chunk-RUYZKXOD.js} +401 -46
  26. package/dist/chunk-RUYZKXOD.js.map +1 -0
  27. package/dist/{chunk-2UUZZJFT.js → chunk-SDMHPX3X.js} +176 -160
  28. package/dist/{chunk-2UUZZJFT.js.map → chunk-SDMHPX3X.js.map} +1 -1
  29. package/dist/{chunk-MX64ZF6I.js → chunk-STYK4OH2.js} +11 -11
  30. package/dist/chunk-STYK4OH2.js.map +1 -0
  31. package/dist/{chunk-YGPFYGA6.js → chunk-VVBAW5A5.js} +822 -498
  32. package/dist/chunk-VVBAW5A5.js.map +1 -0
  33. package/dist/chunk-Y4BUBBHD.js +614 -0
  34. package/dist/chunk-Y4BUBBHD.js.map +1 -0
  35. package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
  36. package/dist/chunk-ZSAAAMVR.js.map +1 -0
  37. package/dist/components.d.ts +3 -4
  38. package/dist/components.js +19 -19
  39. package/dist/components.js.map +1 -1
  40. package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
  41. package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
  42. package/dist/hooks.d.ts +10 -5
  43. package/dist/hooks.js +14 -8
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +13 -11
  46. package/dist/index.js +79 -69
  47. package/dist/index.js.map +1 -1
  48. package/dist/providers.d.ts +3 -3
  49. package/dist/providers.js +3 -1
  50. package/dist/rbac/index.d.ts +76 -12
  51. package/dist/rbac/index.js +12 -9
  52. package/dist/types.d.ts +1 -1
  53. package/dist/types.js +1 -1
  54. package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-DxIDS4bC.d.ts} +16 -9
  55. package/dist/utils.js +16 -16
  56. package/docs/README.md +2 -2
  57. package/docs/api/classes/ColumnFactory.md +1 -1
  58. package/docs/api/classes/ErrorBoundary.md +1 -1
  59. package/docs/api/classes/InvalidScopeError.md +2 -2
  60. package/docs/api/classes/Logger.md +1 -1
  61. package/docs/api/classes/MissingUserContextError.md +2 -2
  62. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  63. package/docs/api/classes/PermissionDeniedError.md +1 -1
  64. package/docs/api/classes/RBACAuditManager.md +1 -1
  65. package/docs/api/classes/RBACCache.md +1 -1
  66. package/docs/api/classes/RBACEngine.md +4 -4
  67. package/docs/api/classes/RBACError.md +1 -1
  68. package/docs/api/classes/RBACNotInitializedError.md +2 -2
  69. package/docs/api/classes/SecureSupabaseClient.md +21 -16
  70. package/docs/api/classes/StorageUtils.md +7 -4
  71. package/docs/api/enums/FileCategory.md +1 -1
  72. package/docs/api/enums/LogLevel.md +1 -1
  73. package/docs/api/enums/RBACErrorCode.md +1 -1
  74. package/docs/api/enums/RPCFunction.md +1 -1
  75. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  76. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  77. package/docs/api/interfaces/AggregateConfig.md +1 -1
  78. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  79. package/docs/api/interfaces/AvatarProps.md +1 -1
  80. package/docs/api/interfaces/BadgeProps.md +1 -1
  81. package/docs/api/interfaces/ButtonProps.md +1 -1
  82. package/docs/api/interfaces/CalendarProps.md +20 -6
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/ComplianceResult.md +1 -1
  87. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  88. package/docs/api/interfaces/DataRecord.md +1 -1
  89. package/docs/api/interfaces/DataTableAction.md +1 -1
  90. package/docs/api/interfaces/DataTableColumn.md +1 -1
  91. package/docs/api/interfaces/DataTableProps.md +1 -1
  92. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  93. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  94. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  95. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  96. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  97. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  98. package/docs/api/interfaces/ExportColumn.md +1 -1
  99. package/docs/api/interfaces/ExportOptions.md +1 -1
  100. package/docs/api/interfaces/FileDisplayProps.md +62 -16
  101. package/docs/api/interfaces/FileMetadata.md +1 -1
  102. package/docs/api/interfaces/FileReference.md +2 -2
  103. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  104. package/docs/api/interfaces/FileUploadOptions.md +26 -12
  105. package/docs/api/interfaces/FileUploadProps.md +30 -19
  106. package/docs/api/interfaces/FooterProps.md +1 -1
  107. package/docs/api/interfaces/FormFieldProps.md +1 -1
  108. package/docs/api/interfaces/FormProps.md +1 -1
  109. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  110. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  111. package/docs/api/interfaces/InputProps.md +1 -1
  112. package/docs/api/interfaces/LabelProps.md +1 -1
  113. package/docs/api/interfaces/LoggerConfig.md +1 -1
  114. package/docs/api/interfaces/LoginFormProps.md +1 -1
  115. package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
  116. package/docs/api/interfaces/NavigationContextType.md +9 -9
  117. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  118. package/docs/api/interfaces/NavigationItem.md +1 -1
  119. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  120. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  121. package/docs/api/interfaces/Organisation.md +1 -1
  122. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  123. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  124. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  125. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  126. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  127. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  128. package/docs/api/interfaces/PageAccessRecord.md +8 -8
  129. package/docs/api/interfaces/PagePermissionContextType.md +8 -8
  130. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  131. package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
  132. package/docs/api/interfaces/PaletteData.md +1 -1
  133. package/docs/api/interfaces/ParsedAddress.md +1 -1
  134. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  135. package/docs/api/interfaces/ProgressProps.md +3 -11
  136. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  137. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  138. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  139. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  140. package/docs/api/interfaces/QuickFix.md +1 -1
  141. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  142. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  143. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  144. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  145. package/docs/api/interfaces/RBACConfig.md +1 -1
  146. package/docs/api/interfaces/RBACContext.md +1 -1
  147. package/docs/api/interfaces/RBACLogger.md +1 -1
  148. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  149. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  150. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  151. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  152. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  153. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  154. package/docs/api/interfaces/RBACResult.md +1 -1
  155. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  156. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  157. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  158. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  159. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  160. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  161. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  162. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  163. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  164. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  165. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  166. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  167. package/docs/api/interfaces/RoleBasedRouterContextType.md +8 -8
  168. package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
  169. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  170. package/docs/api/interfaces/RouteAccessRecord.md +10 -10
  171. package/docs/api/interfaces/RouteConfig.md +10 -10
  172. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  173. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  174. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  175. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  176. package/docs/api/interfaces/SetupIssue.md +1 -1
  177. package/docs/api/interfaces/StorageConfig.md +4 -4
  178. package/docs/api/interfaces/StorageFileInfo.md +7 -7
  179. package/docs/api/interfaces/StorageFileMetadata.md +25 -14
  180. package/docs/api/interfaces/StorageListOptions.md +22 -9
  181. package/docs/api/interfaces/StorageListResult.md +4 -4
  182. package/docs/api/interfaces/StorageUploadOptions.md +21 -8
  183. package/docs/api/interfaces/StorageUploadResult.md +6 -6
  184. package/docs/api/interfaces/StorageUrlOptions.md +19 -6
  185. package/docs/api/interfaces/StyleImport.md +1 -1
  186. package/docs/api/interfaces/SwitchProps.md +1 -1
  187. package/docs/api/interfaces/TabsContentProps.md +1 -1
  188. package/docs/api/interfaces/TabsListProps.md +1 -1
  189. package/docs/api/interfaces/TabsProps.md +1 -1
  190. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  191. package/docs/api/interfaces/TextareaProps.md +1 -1
  192. package/docs/api/interfaces/ToastActionElement.md +1 -1
  193. package/docs/api/interfaces/ToastProps.md +1 -1
  194. package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
  195. package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
  196. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  197. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  198. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  199. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  200. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  201. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  202. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  203. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  204. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  205. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  206. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  207. package/docs/api/interfaces/UseResolvedScopeOptions.md +4 -4
  208. package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
  209. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  210. package/docs/api/interfaces/UserEventAccess.md +11 -11
  211. package/docs/api/interfaces/UserMenuProps.md +1 -1
  212. package/docs/api/interfaces/UserProfile.md +1 -1
  213. package/docs/api/modules.md +151 -92
  214. package/docs/api-reference/components.md +15 -7
  215. package/docs/api-reference/providers.md +2 -2
  216. package/docs/api-reference/rpc-functions.md +1 -0
  217. package/docs/best-practices/README.md +1 -1
  218. package/docs/best-practices/deployment.md +8 -8
  219. package/docs/getting-started/examples/README.md +2 -2
  220. package/docs/getting-started/installation-guide.md +4 -4
  221. package/docs/getting-started/quick-start.md +3 -3
  222. package/docs/migration/MIGRATION_GUIDE.md +3 -3
  223. package/docs/rbac/compliance/compliance-guide.md +2 -2
  224. package/docs/rbac/event-based-apps.md +2 -2
  225. package/docs/rbac/getting-started.md +2 -2
  226. package/docs/rbac/quick-start.md +2 -2
  227. package/docs/security/README.md +4 -4
  228. package/docs/standards/07-rbac-and-rls-standard.md +430 -7
  229. package/docs/troubleshooting/README.md +2 -2
  230. package/docs/troubleshooting/migration.md +3 -3
  231. package/package.json +1 -3
  232. package/scripts/check-pace-core-compliance.cjs +1 -1
  233. package/scripts/check-pace-core-compliance.js +1 -1
  234. package/src/__tests__/fixtures/supabase.ts +301 -0
  235. package/src/__tests__/public-recipe-view.test.ts +9 -9
  236. package/src/__tests__/rls-policies.test.ts +197 -61
  237. package/src/components/AddressField/AddressField.test.tsx +42 -0
  238. package/src/components/AddressField/AddressField.tsx +71 -60
  239. package/src/components/AddressField/README.md +1 -0
  240. package/src/components/Alert/Alert.test.tsx +50 -10
  241. package/src/components/Alert/Alert.tsx +5 -3
  242. package/src/components/Avatar/Avatar.test.tsx +95 -43
  243. package/src/components/Avatar/Avatar.tsx +16 -16
  244. package/src/components/Button/Button.test.tsx +2 -1
  245. package/src/components/Button/Button.tsx +3 -3
  246. package/src/components/Calendar/Calendar.test.tsx +53 -37
  247. package/src/components/Calendar/Calendar.tsx +409 -82
  248. package/src/components/Card/Card.test.tsx +7 -4
  249. package/src/components/Card/Card.tsx +3 -6
  250. package/src/components/Checkbox/Checkbox.tsx +2 -2
  251. package/src/components/DataTable/components/ActionButtons.tsx +5 -5
  252. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  253. package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
  254. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
  255. package/src/components/DataTable/components/DataTableBody.tsx +12 -12
  256. package/src/components/DataTable/components/DataTableCore.tsx +3 -3
  257. package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
  258. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
  259. package/src/components/DataTable/components/EditableRow.tsx +2 -2
  260. package/src/components/DataTable/components/EmptyState.tsx +3 -3
  261. package/src/components/DataTable/components/GroupHeader.tsx +2 -2
  262. package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +4 -4
  264. package/src/components/DataTable/components/LoadingState.tsx +1 -1
  265. package/src/components/DataTable/components/PaginationControls.tsx +11 -11
  266. package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
  267. package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
  268. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
  269. package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
  270. package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
  271. package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
  272. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
  273. package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
  274. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
  275. package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
  276. package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
  277. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
  278. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
  280. package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
  281. package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
  282. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
  283. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
  284. package/src/components/Dialog/Dialog.tsx +2 -2
  285. package/src/components/EventSelector/EventSelector.tsx +7 -7
  286. package/src/components/FileDisplay/FileDisplay.tsx +291 -179
  287. package/src/components/FileUpload/FileUpload.tsx +7 -4
  288. package/src/components/Header/Header.test.tsx +28 -0
  289. package/src/components/Header/Header.tsx +22 -9
  290. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
  291. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
  292. package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
  293. package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
  294. package/src/components/OrganisationSelector/OrganisationSelector.tsx +8 -8
  295. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
  296. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
  297. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
  298. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
  299. package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
  300. package/src/components/PaceAppLayout/test-setup.tsx +1 -0
  301. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
  302. package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
  303. package/src/components/Progress/Progress.test.tsx +18 -19
  304. package/src/components/Progress/Progress.tsx +31 -32
  305. package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
  306. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
  307. package/src/components/Select/Select.tsx +5 -5
  308. package/src/components/Switch/Switch.test.tsx +2 -1
  309. package/src/components/Switch/Switch.tsx +1 -1
  310. package/src/components/Toast/Toast.tsx +1 -1
  311. package/src/components/Tooltip/Tooltip.test.tsx +8 -2
  312. package/src/components/UserMenu/UserMenu.tsx +3 -3
  313. package/src/eslint-rules/pace-core-compliance.cjs +0 -2
  314. package/src/eslint-rules/pace-core-compliance.js +0 -2
  315. package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
  316. package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
  317. package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
  318. package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
  319. package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
  320. package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
  321. package/src/hooks/__tests__/{usePublicEvent.unit.test.ts → usePublicEvent.test.ts} +28 -1
  322. package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
  323. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +58 -16
  324. package/src/hooks/index.ts +1 -1
  325. package/src/hooks/public/usePublicEvent.ts +2 -2
  326. package/src/hooks/public/usePublicFileDisplay.ts +173 -87
  327. package/src/hooks/useAppConfig.ts +24 -5
  328. package/src/hooks/useFileDisplay.ts +297 -34
  329. package/src/hooks/useFileReference.ts +56 -11
  330. package/src/hooks/useFileUrl.ts +1 -1
  331. package/src/hooks/useInactivityTracker.ts +16 -7
  332. package/src/hooks/usePermissionCache.test.ts +85 -8
  333. package/src/hooks/useQueryCache.ts +21 -0
  334. package/src/hooks/useSecureDataAccess.test.ts +80 -35
  335. package/src/hooks/useSecureDataAccess.ts +80 -37
  336. package/src/providers/services/EventServiceProvider.tsx +37 -17
  337. package/src/providers/services/InactivityServiceProvider.tsx +4 -4
  338. package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
  339. package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
  340. package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
  341. package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
  342. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
  343. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
  344. package/src/rbac/api.ts +240 -36
  345. package/src/rbac/cache-invalidation.ts +21 -7
  346. package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
  347. package/src/rbac/components/NavigationGuard.tsx +23 -63
  348. package/src/rbac/components/NavigationProvider.test.tsx +52 -23
  349. package/src/rbac/components/NavigationProvider.tsx +13 -11
  350. package/src/rbac/components/PagePermissionGuard.tsx +77 -203
  351. package/src/rbac/components/PagePermissionProvider.tsx +13 -11
  352. package/src/rbac/components/PermissionEnforcer.tsx +24 -62
  353. package/src/rbac/components/RoleBasedRouter.tsx +14 -12
  354. package/src/rbac/components/SecureDataProvider.tsx +13 -11
  355. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
  356. package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
  357. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
  358. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
  359. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
  360. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
  361. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
  362. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
  363. package/src/rbac/engine.ts +4 -2
  364. package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
  365. package/src/rbac/hooks/index.ts +3 -0
  366. package/src/rbac/hooks/useCan.test.ts +101 -53
  367. package/src/rbac/hooks/usePermissions.ts +108 -41
  368. package/src/rbac/hooks/useRBAC.test.ts +11 -3
  369. package/src/rbac/hooks/useRBAC.ts +83 -40
  370. package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
  371. package/src/rbac/hooks/useResolvedScope.ts +128 -70
  372. package/src/rbac/hooks/useSecureSupabase.ts +36 -19
  373. package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
  374. package/src/rbac/request-deduplication.ts +1 -1
  375. package/src/rbac/secureClient.ts +72 -12
  376. package/src/rbac/security.ts +29 -23
  377. package/src/rbac/types.ts +10 -0
  378. package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
  379. package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
  380. package/src/rbac/utils/__tests__/eventContext.test.ts +6 -1
  381. package/src/rbac/utils/contextValidator.ts +288 -0
  382. package/src/rbac/utils/eventContext.ts +48 -2
  383. package/src/services/EventService.ts +165 -21
  384. package/src/services/OrganisationService.ts +37 -2
  385. package/src/services/__tests__/EventService.test.ts +26 -21
  386. package/src/types/file-reference.ts +13 -10
  387. package/src/utils/app/appNameResolver.test.ts +346 -73
  388. package/src/utils/context/superAdminOverride.ts +58 -0
  389. package/src/utils/file-reference/index.ts +61 -33
  390. package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
  391. package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
  392. package/src/utils/storage/helpers.test.ts +1 -1
  393. package/src/utils/storage/helpers.ts +38 -19
  394. package/src/utils/storage/types.ts +15 -8
  395. package/src/utils/validation/__tests__/csrf.test.ts +105 -0
  396. package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
  397. package/src/vite-env.d.ts +2 -2
  398. package/dist/chunk-3GOZZZYH.js.map +0 -1
  399. package/dist/chunk-DDM4CCYT.js.map +0 -1
  400. package/dist/chunk-E7UAOUMY.js +0 -75
  401. package/dist/chunk-E7UAOUMY.js.map +0 -1
  402. package/dist/chunk-F2IMUDXZ.js.map +0 -1
  403. package/dist/chunk-HEHYGYOX.js.map +0 -1
  404. package/dist/chunk-IM4QE42D.js.map +0 -1
  405. package/dist/chunk-MX64ZF6I.js.map +0 -1
  406. package/dist/chunk-SAUPYVLF.js.map +0 -1
  407. package/dist/chunk-THRPYOFK.js.map +0 -1
  408. package/dist/chunk-UCQSRW7Z.js.map +0 -1
  409. package/dist/chunk-VGZZXKBR.js.map +0 -1
  410. package/dist/chunk-YGPFYGA6.js.map +0 -1
  411. package/dist/chunk-YHCN776L.js.map +0 -1
  412. package/src/hooks/__tests__/usePermissionCache.simple.test.ts +0 -192
  413. package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -741
  414. package/src/hooks/__tests__/usePublicEvent.simple.test.ts +0 -703
  415. package/src/rbac/hooks/useRBAC.simple.test.ts +0 -95
  416. package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
  417. /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-ON3IXISJ.js.map} +0 -0
  418. /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-X5NXANVI.js.map} +0 -0
  419. /package/dist/{api-YP7XD5L6.js.map → api-I6UCQ5S6.js.map} +0 -0
  420. /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Context Validator for RBAC
3
+ * @package @jmruthers/pace-core
4
+ * @module RBAC/ContextValidator
5
+ * @since 1.0.0
6
+ *
7
+ * Centralized validation for RBAC context requirements based on app configuration.
8
+ * Enforces app-specific context rules with single primary context:
9
+ * - requires_event = TRUE: Event is PRIMARY context, org derived from event (org not required in input)
10
+ * - requires_event = FALSE: Organisation is PRIMARY context, event optional
11
+ * - PORTAL/ADMIN apps: Both contexts optional (allows users to view/edit own profiles, super admin access)
12
+ *
13
+ * Key principle: Only one primary context is required based on app config. The other is derived or optional.
14
+ */
15
+
16
+ import { SupabaseClient } from '@supabase/supabase-js';
17
+ import { Database } from '../../types/database';
18
+ import { UUID, Scope } from '../types';
19
+ import { EventContextRequiredError, OrganisationContextRequiredError } from '../types';
20
+ import { getOrganisationFromEvent } from './eventContext';
21
+ import { createLogger } from '../../utils/core/logger';
22
+
23
+ const log = createLogger('ContextValidator');
24
+
25
+ export interface AppConfig {
26
+ requires_event: boolean;
27
+ }
28
+
29
+ /**
30
+ * Check if an app allows optional contexts (both organisation and event optional)
31
+ * @param appName - App name to check
32
+ * @returns True if app allows optional contexts
33
+ */
34
+ function allowsOptionalContexts(appName?: string): boolean {
35
+ return appName === 'PORTAL' || appName === 'ADMIN';
36
+ }
37
+
38
+ export interface ContextValidationResult {
39
+ isValid: boolean;
40
+ resolvedScope: Scope | null;
41
+ error: Error | null;
42
+ }
43
+
44
+ /**
45
+ * Context Validator class
46
+ *
47
+ * Validates and resolves RBAC scope based on app configuration requirements.
48
+ */
49
+ export class ContextValidator {
50
+ /**
51
+ * Validate scope against app requirements
52
+ *
53
+ * @param scope - Current scope
54
+ * @param appConfig - App configuration (requires_event flag)
55
+ * @param appName - App name (for PORTAL/ADMIN special case)
56
+ * @returns Validation result with resolved scope
57
+ */
58
+ static async validateScope(
59
+ scope: Scope,
60
+ appConfig: AppConfig | null,
61
+ appName?: string
62
+ ): Promise<ContextValidationResult> {
63
+ // PORTAL/ADMIN special case: both contexts optional
64
+ if (allowsOptionalContexts(appName)) {
65
+ return {
66
+ isValid: true,
67
+ resolvedScope: {
68
+ organisationId: scope.organisationId,
69
+ eventId: scope.eventId,
70
+ appId: scope.appId
71
+ },
72
+ error: null
73
+ };
74
+ }
75
+
76
+ // If no app config, default to requiring org context
77
+ if (!appConfig) {
78
+ if (!scope.organisationId) {
79
+ return {
80
+ isValid: false,
81
+ resolvedScope: null,
82
+ error: new OrganisationContextRequiredError()
83
+ };
84
+ }
85
+ return {
86
+ isValid: true,
87
+ resolvedScope: scope,
88
+ error: null
89
+ };
90
+ }
91
+
92
+ // Event-required apps: must have eventId, derive org from event
93
+ if (appConfig.requires_event) {
94
+ if (!scope.eventId) {
95
+ return {
96
+ isValid: false,
97
+ resolvedScope: null,
98
+ error: new EventContextRequiredError()
99
+ };
100
+ }
101
+
102
+ // If org is not provided, we'll need to derive it from event
103
+ // But for validation, we just check that eventId exists
104
+ // The actual derivation happens in resolveRequiredContext
105
+ return {
106
+ isValid: true,
107
+ resolvedScope: scope,
108
+ error: null
109
+ };
110
+ }
111
+
112
+ // Org-required apps: must have organisationId, eventId optional
113
+ if (!scope.organisationId) {
114
+ return {
115
+ isValid: false,
116
+ resolvedScope: null,
117
+ error: new OrganisationContextRequiredError()
118
+ };
119
+ }
120
+
121
+ return {
122
+ isValid: true,
123
+ resolvedScope: scope,
124
+ error: null
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Resolve required context and derive missing values
130
+ *
131
+ * @param scope - Current scope
132
+ * @param appConfig - App configuration
133
+ * @param appName - App name (for PORTAL/ADMIN special case)
134
+ * @param supabase - Supabase client (for deriving org from event)
135
+ * @returns Resolved scope with all required context
136
+ */
137
+ static async resolveRequiredContext(
138
+ scope: Scope,
139
+ appConfig: AppConfig | null,
140
+ appName?: string,
141
+ supabase?: SupabaseClient<Database> | null
142
+ ): Promise<ContextValidationResult> {
143
+ // PORTAL/ADMIN special case: both contexts optional
144
+ if (allowsOptionalContexts(appName)) {
145
+ return {
146
+ isValid: true,
147
+ resolvedScope: {
148
+ organisationId: scope.organisationId,
149
+ eventId: scope.eventId,
150
+ appId: scope.appId
151
+ },
152
+ error: null
153
+ };
154
+ }
155
+
156
+ // If no app config, default to requiring org context
157
+ if (!appConfig) {
158
+ if (!scope.organisationId) {
159
+ return {
160
+ isValid: false,
161
+ resolvedScope: null,
162
+ error: new OrganisationContextRequiredError()
163
+ };
164
+ }
165
+ return {
166
+ isValid: true,
167
+ resolvedScope: scope,
168
+ error: null
169
+ };
170
+ }
171
+
172
+ // Event-required apps: must have eventId, derive org from event
173
+ if (appConfig.requires_event) {
174
+ if (!scope.eventId) {
175
+ return {
176
+ isValid: false,
177
+ resolvedScope: null,
178
+ error: new EventContextRequiredError()
179
+ };
180
+ }
181
+
182
+ // Derive organisationId from event if not provided
183
+ let organisationId: UUID | undefined = scope.organisationId;
184
+ if (!organisationId && supabase && scope.eventId) {
185
+ try {
186
+ const derivedOrgId = await this.deriveOrgFromEvent(supabase, scope.eventId);
187
+ organisationId = derivedOrgId || undefined;
188
+ if (!organisationId) {
189
+ return {
190
+ isValid: false,
191
+ resolvedScope: null,
192
+ error: new Error('Could not resolve organisation from event context')
193
+ };
194
+ }
195
+ } catch (error) {
196
+ log.error('Failed to derive org from event:', error);
197
+ return {
198
+ isValid: false,
199
+ resolvedScope: null,
200
+ error: error instanceof Error ? error : new Error('Failed to derive organisation from event')
201
+ };
202
+ }
203
+ } else if (!organisationId) {
204
+ return {
205
+ isValid: false,
206
+ resolvedScope: null,
207
+ error: new Error('Event context requires organisationId but it could not be derived (supabase client not available)')
208
+ };
209
+ }
210
+
211
+ return {
212
+ isValid: true,
213
+ resolvedScope: {
214
+ organisationId,
215
+ eventId: scope.eventId,
216
+ appId: scope.appId
217
+ },
218
+ error: null
219
+ };
220
+ }
221
+
222
+ // Org-required apps: must have organisationId, eventId optional
223
+ if (!scope.organisationId) {
224
+ return {
225
+ isValid: false,
226
+ resolvedScope: null,
227
+ error: new OrganisationContextRequiredError()
228
+ };
229
+ }
230
+
231
+ return {
232
+ isValid: true,
233
+ resolvedScope: scope,
234
+ error: null
235
+ };
236
+ }
237
+
238
+ /**
239
+ * Derive organisation ID from event ID
240
+ *
241
+ * @param supabase - Supabase client
242
+ * @param eventId - Event ID
243
+ * @returns Organisation ID or null
244
+ */
245
+ static async deriveOrgFromEvent(
246
+ supabase: SupabaseClient<Database>,
247
+ eventId: string
248
+ ): Promise<UUID | null> {
249
+ return getOrganisationFromEvent(supabase, eventId);
250
+ }
251
+
252
+ /**
253
+ * Check if context is ready for permission checks
254
+ *
255
+ * @param scope - Current scope
256
+ * @param appConfig - App configuration
257
+ * @param appName - App name
258
+ * @param hasSelectedEvent - Whether event is selected
259
+ * @param hasSelectedOrganisation - Whether organisation is selected
260
+ * @returns True if context is ready
261
+ */
262
+ static isContextReady(
263
+ scope: Scope,
264
+ appConfig: AppConfig | null,
265
+ appName?: string,
266
+ hasSelectedEvent?: boolean,
267
+ hasSelectedOrganisation?: boolean
268
+ ): boolean {
269
+ // PORTAL/ADMIN special case: context is always ready
270
+ if (allowsOptionalContexts(appName)) {
271
+ return true;
272
+ }
273
+
274
+ // If no app config, default to requiring org context
275
+ if (!appConfig) {
276
+ return !!hasSelectedOrganisation || !!scope.organisationId;
277
+ }
278
+
279
+ // Event-required apps: need event context
280
+ if (appConfig.requires_event) {
281
+ return !!hasSelectedEvent || !!scope.eventId;
282
+ }
283
+
284
+ // Org-required apps: need org context
285
+ return !!hasSelectedOrganisation || !!scope.organisationId;
286
+ }
287
+ }
288
+
@@ -12,9 +12,35 @@ import { SupabaseClient } from '@supabase/supabase-js';
12
12
  import { Database } from '../../types/database';
13
13
  import { UUID, Scope } from '../types';
14
14
 
15
+ /**
16
+ * Cache for organisation derivation from event
17
+ * Key: eventId, Value: organisationId | null
18
+ * Maximum cache size to prevent memory leaks
19
+ */
20
+ const orgDerivationCache = new Map<string, UUID | null>();
21
+ const MAX_CACHE_SIZE = 100; // Limit cache to 100 entries
22
+
23
+ /**
24
+ * Clear cache entry for a specific event (useful if event's org changes)
25
+ * @param eventId - Event ID to clear from cache
26
+ */
27
+ export function clearOrgDerivationCache(eventId: string): void {
28
+ orgDerivationCache.delete(eventId);
29
+ }
30
+
31
+ /**
32
+ * Clear all cached organisation derivations
33
+ */
34
+ export function clearAllOrgDerivationCache(): void {
35
+ orgDerivationCache.clear();
36
+ }
37
+
15
38
  /**
16
39
  * Get organization ID from event ID
17
40
  *
41
+ * Uses caching to avoid repeated database queries for the same event.
42
+ * Cache is limited to prevent memory leaks.
43
+ *
18
44
  * @param supabase - Supabase client
19
45
  * @param eventId - Event ID
20
46
  * @returns Promise resolving to organization ID or null
@@ -23,17 +49,37 @@ export async function getOrganisationFromEvent(
23
49
  supabase: SupabaseClient<Database>,
24
50
  eventId: string
25
51
  ): Promise<UUID | null> {
52
+ // Check cache first
53
+ if (orgDerivationCache.has(eventId)) {
54
+ return orgDerivationCache.get(eventId) ?? null;
55
+ }
56
+
57
+ // Query database
26
58
  const { data, error } = await supabase
27
59
  .from('event')
28
60
  .select('organisation_id')
29
61
  .eq('event_id', eventId)
30
62
  .single() as { data: { organisation_id: string } | null; error: any };
31
63
 
64
+ let organisationId: UUID | null = null;
65
+
32
66
  if (error || !data) {
33
- return null;
67
+ organisationId = null;
68
+ } else {
69
+ organisationId = data.organisation_id;
70
+ }
71
+
72
+ // Cache the result (with size limit to prevent memory leaks)
73
+ if (orgDerivationCache.size >= MAX_CACHE_SIZE) {
74
+ // Remove oldest entry (first key in Map)
75
+ const firstKey = orgDerivationCache.keys().next().value;
76
+ if (firstKey) {
77
+ orgDerivationCache.delete(firstKey);
78
+ }
34
79
  }
80
+ orgDerivationCache.set(eventId, organisationId);
35
81
 
36
- return data.organisation_id;
82
+ return organisationId;
37
83
  }
38
84
 
39
85
  /**
@@ -16,6 +16,9 @@ import { Organisation } from '../types/organisation';
16
16
  import { assertOrganisationId } from '../types/core';
17
17
  import { logger } from '../utils/core/logger';
18
18
  import { secureStorage } from '../utils/security/secureStorage';
19
+ import { isSuperAdmin, getAppConfigByName } from '../rbac/api';
20
+ import type { UUID } from '../rbac/types';
21
+ import type { AppConfig } from '../rbac/utils/contextValidator';
19
22
 
20
23
  export class EventService extends BaseService implements IEventService {
21
24
  private events: Event[] = [];
@@ -30,6 +33,8 @@ export class EventService extends BaseService implements IEventService {
30
33
  private appName: string = '';
31
34
  private selectedOrganisation: Organisation | null = null;
32
35
  private setSelectedEventId: ((eventId: string | null) => void) | null = null;
36
+ private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
37
+ private appConfig: AppConfig | null = null; // Cache app config to avoid repeated lookups
33
38
 
34
39
  // Internal state management
35
40
  private isInitializedRef = false;
@@ -76,6 +81,7 @@ export class EventService extends BaseService implements IEventService {
76
81
  const newOrgId = selectedOrganisation?.id;
77
82
  const previousUserId = this.user?.id || null;
78
83
  const newUserId = user?.id || null;
84
+ const previousAppName = this.appName;
79
85
 
80
86
  // If user changed, clear previous user's event selection from storage
81
87
  if (previousUserId !== newUserId) {
@@ -87,6 +93,10 @@ export class EventService extends BaseService implements IEventService {
87
93
  this.selectedEvent = null;
88
94
  this.setSelectedEventId?.(null);
89
95
  }
96
+ // Reset initialization when user changes
97
+ this.resetInitialization();
98
+ this.isInitializedRef = false;
99
+ this.isFetchingRef = false;
90
100
  }
91
101
 
92
102
  this.supabaseClient = supabaseClient;
@@ -96,8 +106,31 @@ export class EventService extends BaseService implements IEventService {
96
106
  this.selectedOrganisation = selectedOrganisation;
97
107
  this.setSelectedEventId = setSelectedEventId;
98
108
 
99
- // If organisation changed (from null to value, or different org), reset initialization
100
- // This ensures events are re-fetched when organisation context becomes available
109
+ // Clear app config cache when app name changes
110
+ if (previousAppName !== appName) {
111
+ this.appConfig = null;
112
+ }
113
+
114
+ // Update super admin status when user changes
115
+ // This allows super admins to select events from any organisation
116
+ if (user?.id) {
117
+ try {
118
+ this.isSuperAdmin = await isSuperAdmin(user.id as UUID);
119
+ logger.debug('EventService', 'Updated super admin status', {
120
+ userId: user.id,
121
+ isSuperAdmin: this.isSuperAdmin
122
+ });
123
+ } catch (error) {
124
+ logger.warn('EventService', 'Failed to check super admin status', { error });
125
+ this.isSuperAdmin = false; // Default to false on error
126
+ }
127
+ } else {
128
+ this.isSuperAdmin = false;
129
+ }
130
+
131
+ // If organisation changed (from null to value, or different org, or value to null), reset initialization
132
+ // This ensures events are re-fetched when organisation context changes
133
+ // For event-required apps, selectedOrganisation will be null, so we need to reset when it changes from undefined/null to null
101
134
  if (previousOrgId !== newOrgId) {
102
135
  this.resetInitialization(); // Reset BaseService's isInitialized flag
103
136
  this.isInitializedRef = false;
@@ -138,20 +171,9 @@ export class EventService extends BaseService implements IEventService {
138
171
  // Event methods
139
172
  setSelectedEvent(event: Event | null): void {
140
173
  if (event) {
141
- // SECURITY: Validate event belongs to current organisation
142
- try {
143
- if (this.selectedOrganisation && event.organisation_id !== this.selectedOrganisation.id) {
144
- logger.error('EventService', 'Event organisation_id does not match selected organisation', {
145
- eventOrganisationId: event.organisation_id,
146
- selectedOrganisationId: this.selectedOrganisation.id,
147
- eventName: event.event_name
148
- });
149
- return;
150
- }
151
- } catch (error) {
152
- logger.error('EventService', 'Error during event validation:', error);
153
- }
154
-
174
+ // No validation needed: For event-required apps, org is derived from event
175
+ // For org-required apps, event is optional and doesn't need validation
176
+ // RLS policies handle security at the database level
155
177
  this.selectedEvent = event;
156
178
  this.setSelectedEventId?.(event.event_id);
157
179
  // Persist asynchronously (don't await to avoid blocking)
@@ -286,6 +308,12 @@ export class EventService extends BaseService implements IEventService {
286
308
  async initialize(): Promise<void> {
287
309
  // Only call super.initialize() which will call doInitialize() and fetchEvents()
288
310
  // Don't call fetchEvents() again here to avoid double-fetching
311
+ logger.debug('EventService', 'initialize() called', {
312
+ isInitializedRef: this.isInitializedRef,
313
+ hasUser: !!this.user,
314
+ hasSession: !!this.session,
315
+ appName: this.appName
316
+ });
289
317
  await super.initialize();
290
318
  }
291
319
 
@@ -294,13 +322,23 @@ export class EventService extends BaseService implements IEventService {
294
322
  }
295
323
 
296
324
  protected async doInitialize(): Promise<void> {
325
+ logger.debug('EventService', 'doInitialize() called', {
326
+ isInitializedRef: this.isInitializedRef,
327
+ isFetchingRef: this.isFetchingRef,
328
+ hasUser: !!this.user,
329
+ hasSession: !!this.session,
330
+ appName: this.appName
331
+ });
332
+
297
333
  // Skip if already initialized
298
334
  if (this.isInitializedRef) {
335
+ logger.debug('EventService', 'Skipping initialization - already initialized');
299
336
  return;
300
337
  }
301
338
 
302
339
  // Skip if already fetching
303
340
  if (this.isFetchingRef) {
341
+ logger.debug('EventService', 'Skipping initialization - already fetching');
304
342
  return;
305
343
  }
306
344
 
@@ -313,13 +351,25 @@ export class EventService extends BaseService implements IEventService {
313
351
  logger.warn('EventService', 'Failed to clean up old storage keys:', error);
314
352
  }
315
353
 
316
- // Skip if no user or organisation
317
- if (!this.user || !this.selectedOrganisation) {
354
+ // Skip if no user
355
+ // For event-required apps, selectedOrganisation may be null (org derived from event)
356
+ // For org-required apps, selectedOrganisation is required
357
+ if (!this.user) {
358
+ logger.debug('EventService', 'Skipping initialization - missing user');
318
359
  return;
319
360
  }
320
361
 
362
+ logger.debug('EventService', 'Initializing - fetching events', {
363
+ userId: this.user.id,
364
+ organisationId: this.selectedOrganisation?.id || 'derived-from-event',
365
+ appName: this.appName
366
+ });
367
+
321
368
  // Initial setup - fetch events on initialization
322
369
  await this.fetchEvents(false);
370
+
371
+ // Mark as initialized after successful fetch
372
+ this.isInitializedRef = true;
323
373
  }
324
374
 
325
375
  protected doCleanup(): void {
@@ -327,7 +377,15 @@ export class EventService extends BaseService implements IEventService {
327
377
  }
328
378
 
329
379
  private async fetchEvents(skipLoadPersisted: boolean = false): Promise<void> {
330
- if (!this.user || !this.session || !this.supabaseClient || !this.appName || !this.selectedOrganisation) {
380
+ // For event-required apps, selectedOrganisation may be null (org derived from event)
381
+ // For org-required apps, selectedOrganisation is required
382
+ if (!this.user || !this.session || !this.supabaseClient || !this.appName) {
383
+ logger.debug('EventService', 'Skipping fetchEvents - missing dependencies', {
384
+ hasUser: !!this.user,
385
+ hasSession: !!this.session,
386
+ hasSupabaseClient: !!this.supabaseClient,
387
+ appName: this.appName
388
+ });
331
389
  // Already false from initialization, just notify
332
390
  this.notify();
333
391
  return;
@@ -339,6 +397,7 @@ export class EventService extends BaseService implements IEventService {
339
397
 
340
398
  // Prevent multiple simultaneous fetches
341
399
  if (this.isFetchingRef) {
400
+ logger.debug('EventService', 'Skipping fetchEvents - already fetching');
342
401
  return;
343
402
  }
344
403
 
@@ -346,12 +405,97 @@ export class EventService extends BaseService implements IEventService {
346
405
  let isMounted = true;
347
406
 
348
407
  try {
408
+ // Load app config if not already cached (only once per app)
409
+ if (!this.appConfig && this.appName) {
410
+ try {
411
+ this.appConfig = await getAppConfigByName(this.appName);
412
+ } catch (configError) {
413
+ logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
414
+ error: configError
415
+ });
416
+ // Default to event-required for safety
417
+ this.appConfig = { requires_event: true };
418
+ }
419
+ }
420
+
421
+ // Determine organisationId for RPC call
422
+ // For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
423
+ // For org-required apps: org comes from selectedOrganisation
424
+ // Super admins: pass null to see all events
425
+ let organisationIdForRpc: string | null = null;
426
+
427
+ // Check if user is super admin first
428
+ let userIsSuperAdmin = false;
429
+ try {
430
+ userIsSuperAdmin = await isSuperAdmin(this.user.id as UUID);
431
+ if (userIsSuperAdmin) {
432
+ // Super admin: Pass null to see all events across all organisations
433
+ organisationIdForRpc = null;
434
+ logger.debug('EventService', 'Super admin detected - fetching all events', {
435
+ userId: this.user.id
436
+ });
437
+ } else {
438
+ // Not super admin: determine org from context based on app type
439
+ if (this.selectedEvent) {
440
+ // If event is already selected, use its organisation
441
+ organisationIdForRpc = this.selectedEvent.organisation_id;
442
+ } else if (this.appConfig?.requires_event === true) {
443
+ // Event-required app with no selected event yet: pass null to get all accessible events
444
+ // The RPC will filter by event app roles, returning all events the user has access to
445
+ organisationIdForRpc = null;
446
+ logger.debug('EventService', 'Event-required app: fetching all accessible events (no event selected yet)', {
447
+ userId: this.user.id,
448
+ appName: this.appName
449
+ });
450
+ } else if (this.selectedOrganisation) {
451
+ // Org-required app: use selected organisation
452
+ organisationIdForRpc = this.selectedOrganisation.id;
453
+ } else {
454
+ // No context available - this shouldn't happen for authenticated users
455
+ logger.warn('EventService', 'No organisation context available for event fetch', {
456
+ hasSelectedEvent: !!this.selectedEvent,
457
+ hasSelectedOrganisation: !!this.selectedOrganisation,
458
+ appRequiresEvent: this.appConfig?.requires_event
459
+ });
460
+ organisationIdForRpc = null; // Will return empty list
461
+ }
462
+ }
463
+ } catch (superAdminCheckError) {
464
+ // If super admin check fails, fall back to organisation-scoped query
465
+ logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
466
+ error: superAdminCheckError
467
+ });
468
+ // Fallback: use available context
469
+ if (this.selectedEvent) {
470
+ organisationIdForRpc = this.selectedEvent.organisation_id;
471
+ } else if (this.appConfig?.requires_event === true) {
472
+ // Event-required app: pass null to get all accessible events
473
+ organisationIdForRpc = null;
474
+ } else if (this.selectedOrganisation) {
475
+ organisationIdForRpc = this.selectedOrganisation.id;
476
+ }
477
+ }
478
+
479
+ logger.debug('EventService', 'Fetching events via RPC', {
480
+ userId: this.user.id,
481
+ organisationId: organisationIdForRpc,
482
+ appName: this.appName
483
+ });
484
+
349
485
  // Call the RPC function following the established pattern
350
- const { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
486
+ // For super admins, pass null for p_organisation_id to see all events
487
+ let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
351
488
  p_user_id: this.user.id,
352
- p_organisation_id: this.selectedOrganisation.id,
489
+ p_organisation_id: organisationIdForRpc,
353
490
  p_app_name: this.appName
354
491
  });
492
+
493
+ logger.debug('EventService', 'RPC response received', {
494
+ hasData: !!data,
495
+ dataLength: Array.isArray(data) ? data.length : 'not array',
496
+ hasError: !!rpcError,
497
+ error: rpcError
498
+ });
355
499
 
356
500
  if (rpcError) {
357
501
  logger.error('EventService', 'RPC error fetching events:', rpcError);