@jmruthers/pace-core 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/cursor-rules/00-pace-core-compliance.mdc +34 -2
- package/dist/{AuthService-BPvc3Ka0.d.ts → AuthService-Cb34EQs3.d.ts} +9 -1
- package/dist/{DataTable-TPTKCX4D.js → DataTable-E7YQZD7D.js} +9 -8
- package/dist/{PublicPageProvider-DC6kCaqf.d.ts → PublicPageProvider-DEMpysFR.d.ts} +45 -67
- package/dist/{UnifiedAuthProvider-CVcTjx-d.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +1 -8
- package/dist/{UnifiedAuthProvider-CH6Z342H.js → UnifiedAuthProvider-QPXO24B4.js} +5 -4
- package/dist/{api-MVVQZLJI.js → api-6LVZTHDS.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/chunk-36LVWXB2.js +227 -0
- package/dist/chunk-36LVWXB2.js.map +1 -0
- package/dist/{chunk-24UVZUZG.js → chunk-3LPHPB62.js} +129 -387
- package/dist/chunk-3LPHPB62.js.map +1 -0
- package/dist/{chunk-2UOI2FG5.js → chunk-5EC5MEWX.js} +4 -4
- package/dist/{chunk-3XC4CPTD.js → chunk-7JPAB3T5.js} +244 -5727
- package/dist/chunk-7JPAB3T5.js.map +1 -0
- package/dist/{chunk-6J4GEEJR.js → chunk-ATKZM7RX.js} +53 -27
- package/dist/chunk-ATKZM7RX.js.map +1 -0
- package/dist/{chunk-EHMR7VYL.js → chunk-AVMLPIM7.js} +443 -189
- package/dist/chunk-AVMLPIM7.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-NECFR5MM.js → chunk-I6DAQMWX.js} +575 -647
- package/dist/chunk-I6DAQMWX.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-M7MPQISP.js} +2 -2
- package/dist/{chunk-XWQCNGTQ.js → chunk-NN6WWZ5U.js} +173 -79
- package/dist/chunk-NN6WWZ5U.js.map +1 -0
- package/dist/{chunk-MMZ7JXPU.js → chunk-OEWDTMG7.js} +13 -21
- package/dist/{chunk-MMZ7JXPU.js.map → chunk-OEWDTMG7.js.map} +1 -1
- package/dist/{chunk-SFZUDBL5.js → chunk-YKRAFF5K.js} +70 -56
- package/dist/chunk-YKRAFF5K.js.map +1 -0
- package/dist/components.d.ts +2 -2
- package/dist/components.js +12 -13
- package/dist/contextValidator-OOPCLPZW.js +9 -0
- package/dist/contextValidator-OOPCLPZW.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/hooks.d.ts +2 -2
- package/dist/hooks.js +7 -6
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +21 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +67 -27
- package/dist/rbac/index.js +15 -8
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-1oMokgLF.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +7 -16
- package/dist/utils.js +5 -7
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +14 -16
- package/docs/api/modules.md +3796 -2513
- package/docs/components/context-selector.md +126 -0
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/secure-client-protection.md +330 -0
- package/package.json +10 -5
- package/scripts/audit/core/checks/compliance.cjs +72 -0
- package/scripts/audit/core/checks/dependencies.cjs +568 -28
- package/scripts/audit/core/checks/documentation.cjs +68 -3
- package/scripts/audit/core/checks/environment.cjs +2 -14
- package/scripts/audit/core/checks/error-handling.cjs +47 -6
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/components/RowComponent.tsx +19 -19
- package/src/components/DataTable/components/UnifiedTableBody.tsx +2 -2
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +8 -6
- package/src/components/Dialog/Dialog.tsx +29 -1
- package/src/components/FileDisplay/FileDisplay.tsx +42 -10
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +44 -45
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +157 -36
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +2 -2
- package/src/components/index.ts +5 -5
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useFileDisplay.ts +77 -50
- package/src/index.ts +4 -5
- package/src/providers/services/AuthServiceProvider.tsx +17 -7
- package/src/providers/services/EventServiceProvider.tsx +33 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +90 -134
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/adapters.tsx +2 -2
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +178 -132
- package/src/rbac/components/PagePermissionGuard.tsx +38 -10
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/useAccessLevel.ts +1 -1
- package/src/rbac/hooks/permissions/useCan.ts +41 -11
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +1 -1
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +1 -1
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +0 -9
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useSecureSupabase.ts +7 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +103 -16
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +1 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +285 -56
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/dist/chunk-24UVZUZG.js.map +0 -1
- package/dist/chunk-3XC4CPTD.js.map +0 -1
- package/dist/chunk-6J4GEEJR.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-EHMR7VYL.js.map +0 -1
- package/dist/chunk-NECFR5MM.js.map +0 -1
- package/dist/chunk-SFZUDBL5.js.map +0 -1
- package/dist/chunk-XWQCNGTQ.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -163
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -34
- package/docs/api/interfaces/ButtonProps.md +0 -56
- package/docs/api/interfaces/CalendarProps.md +0 -73
- package/docs/api/interfaces/CardProps.md +0 -69
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -252
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/ErrorBoundaryProps.md +0 -147
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +0 -36
- package/docs/api/interfaces/ErrorBoundaryState.md +0 -75
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -296
- package/docs/api/interfaces/FooterProps.md +0 -107
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -56
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -187
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -409
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -49
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -78
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -185
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -12
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -823
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -173
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -138
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -84
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -71
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -123
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -97
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -121
- package/docs/api/interfaces/UserMenuProps.md +0 -88
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -423
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -327
- package/src/components/OrganisationSelector/index.ts +0 -9
- /package/dist/{DataTable-TPTKCX4D.js.map → DataTable-E7YQZD7D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-CH6Z342H.js.map → UnifiedAuthProvider-QPXO24B4.js.map} +0 -0
- /package/dist/{api-MVVQZLJI.js.map → api-6LVZTHDS.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-2UOI2FG5.js.map → chunk-5EC5MEWX.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/dist/{chunk-F2IMUDXZ.js.map → chunk-M7MPQISP.js.map} +0 -0
|
@@ -23,6 +23,8 @@ type AuthStateSubscription = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export class AuthService extends BaseService implements IAuthService {
|
|
26
|
+
private static instanceCount = 0;
|
|
27
|
+
private instanceId: number;
|
|
26
28
|
private user: User | null = null;
|
|
27
29
|
private session: Session | null = null;
|
|
28
30
|
private authLoading = false;
|
|
@@ -43,12 +45,23 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
43
45
|
|
|
44
46
|
constructor(supabaseClient: SupabaseClient, appName?: string) {
|
|
45
47
|
super();
|
|
48
|
+
this.instanceId = ++AuthService.instanceCount;
|
|
46
49
|
this.supabaseClient = supabaseClient;
|
|
47
50
|
this.appName = appName;
|
|
51
|
+
logger.debug('AuthService', `Instance created [ID:${this.instanceId}]`, { appName });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getInstanceId(): number {
|
|
55
|
+
return this.instanceId;
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
// Auth state getters
|
|
51
59
|
getUser(): User | null {
|
|
60
|
+
if (this.user) {
|
|
61
|
+
logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning user: ${this.user.id}`);
|
|
62
|
+
} else {
|
|
63
|
+
logger.debug('AuthService', `getUser() [ID:${this.instanceId}] returning null`);
|
|
64
|
+
}
|
|
52
65
|
return this.user;
|
|
53
66
|
}
|
|
54
67
|
|
|
@@ -398,6 +411,11 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
398
411
|
const subscription = this.supabaseClient.auth.onAuthStateChange(
|
|
399
412
|
(event: AuthChangeEvent, session: SupabaseSession | null) => {
|
|
400
413
|
try {
|
|
414
|
+
logger.debug('AuthService', `Auth state change [ID:${this.instanceId}]`, {
|
|
415
|
+
event,
|
|
416
|
+
hasSession: !!session,
|
|
417
|
+
userId: session?.user?.id
|
|
418
|
+
});
|
|
401
419
|
// Handle different auth events
|
|
402
420
|
if (event === 'SIGNED_OUT') {
|
|
403
421
|
this.session = null;
|
|
@@ -407,15 +425,15 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
407
425
|
// Automatic session tracking (non-blocking)
|
|
408
426
|
if (session?.user) {
|
|
409
427
|
this.trackSession('logout', session).catch(err => {
|
|
410
|
-
logger.warn('AuthService',
|
|
428
|
+
logger.warn('AuthService', `Failed to track logout session [ID:${this.instanceId}]:`, err);
|
|
411
429
|
});
|
|
412
430
|
}
|
|
413
431
|
} else if (event === 'SIGNED_IN' || event === 'TOKEN_REFRESHED') {
|
|
414
432
|
this.session = session;
|
|
415
433
|
this.user = session?.user ?? null;
|
|
416
434
|
|
|
417
|
-
//
|
|
418
|
-
if (session) {
|
|
435
|
+
// Ensure state is set before non-blocking tracking calls
|
|
436
|
+
if (session?.user) {
|
|
419
437
|
this.authError = null;
|
|
420
438
|
}
|
|
421
439
|
|
|
@@ -423,7 +441,7 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
423
441
|
// Only track on SIGNED_IN, not TOKEN_REFRESHED (to avoid duplicate login records)
|
|
424
442
|
if (event === 'SIGNED_IN' && session?.user) {
|
|
425
443
|
this.trackSession('login', session).catch(err => {
|
|
426
|
-
logger.warn('AuthService',
|
|
444
|
+
logger.warn('AuthService', `Failed to track login session [ID:${this.instanceId}]:`, err);
|
|
427
445
|
});
|
|
428
446
|
}
|
|
429
447
|
} else if (event === 'INITIAL_SESSION') {
|
|
@@ -452,16 +470,30 @@ export class AuthService extends BaseService implements IAuthService {
|
|
|
452
470
|
// This ensures ProtectedRoute waits for session restoration to complete
|
|
453
471
|
// before checking authentication state
|
|
454
472
|
this.authLoading = false;
|
|
473
|
+
|
|
474
|
+
// Final check: Ensure state matches what we just logged
|
|
475
|
+
logger.debug('AuthService', `State synchronized after INITIAL_SESSION [ID:${this.instanceId}]`, {
|
|
476
|
+
hasUser: !!this.user,
|
|
477
|
+
userId: this.user?.id
|
|
478
|
+
});
|
|
479
|
+
|
|
455
480
|
this.notify();
|
|
456
481
|
return; // Return early to avoid setting loading to false again below
|
|
457
482
|
}
|
|
458
483
|
|
|
459
|
-
// For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false
|
|
460
|
-
// INITIAL_SESSION is handled above and returns early
|
|
484
|
+
// For other events (SIGNED_IN, SIGNED_OUT, TOKEN_REFRESHED), set loading to false and notify
|
|
461
485
|
this.authLoading = false;
|
|
486
|
+
|
|
487
|
+
// Final check: Ensure state matches what we just logged
|
|
488
|
+
logger.debug('AuthService', `State synchronized after event [ID:${this.instanceId}]`, {
|
|
489
|
+
event,
|
|
490
|
+
hasUser: !!this.user,
|
|
491
|
+
userId: this.user?.id
|
|
492
|
+
});
|
|
493
|
+
|
|
462
494
|
this.notify();
|
|
463
495
|
} catch (error) {
|
|
464
|
-
logger.warn('AuthService',
|
|
496
|
+
logger.warn('AuthService', `Error in auth state change handler [ID:${this.instanceId}]:`, error);
|
|
465
497
|
this.authLoading = false;
|
|
466
498
|
this.notify();
|
|
467
499
|
}
|
|
@@ -16,11 +16,12 @@ 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
|
|
19
|
+
import { isSuperAdmin } from '../rbac/api';
|
|
20
20
|
import type { UUID } from '../rbac/types';
|
|
21
|
-
import type { AppConfig } from '../rbac/utils/contextValidator';
|
|
22
21
|
|
|
23
22
|
export class EventService extends BaseService implements IEventService {
|
|
23
|
+
private static instanceCount = 0;
|
|
24
|
+
private instanceId: number;
|
|
24
25
|
private events: Event[] = [];
|
|
25
26
|
private selectedEvent: Event | null = null;
|
|
26
27
|
private _isLoading = false; // Start as false to avoid blocking UI
|
|
@@ -34,7 +35,7 @@ export class EventService extends BaseService implements IEventService {
|
|
|
34
35
|
private selectedOrganisation: Organisation | null = null;
|
|
35
36
|
private setSelectedEventId: ((eventId: string | null) => void) | null = null;
|
|
36
37
|
private isSuperAdmin: boolean = false; // Track super admin status for conditional validation
|
|
37
|
-
|
|
38
|
+
// App config removed - scope is now page-level only (rbac_app_pages.scope_type)
|
|
38
39
|
|
|
39
40
|
// Internal state management
|
|
40
41
|
private isInitializedRef = false;
|
|
@@ -51,12 +52,22 @@ export class EventService extends BaseService implements IEventService {
|
|
|
51
52
|
setSelectedEventId: (eventId: string | null) => void
|
|
52
53
|
) {
|
|
53
54
|
super();
|
|
55
|
+
this.instanceId = ++EventService.instanceCount;
|
|
54
56
|
this.supabaseClient = supabaseClient;
|
|
55
57
|
this.user = user;
|
|
56
58
|
this.session = session;
|
|
57
59
|
this.appName = appName;
|
|
58
60
|
this.selectedOrganisation = selectedOrganisation;
|
|
59
61
|
this.setSelectedEventId = setSelectedEventId;
|
|
62
|
+
logger.debug('EventService', `Instance created [ID:${this.instanceId}]`, {
|
|
63
|
+
appName,
|
|
64
|
+
hasUser: !!user,
|
|
65
|
+
userId: user?.id
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getInstanceId(): number {
|
|
70
|
+
return this.instanceId;
|
|
60
71
|
}
|
|
61
72
|
|
|
62
73
|
// Helper method to get user-scoped storage key
|
|
@@ -97,6 +108,15 @@ export class EventService extends BaseService implements IEventService {
|
|
|
97
108
|
this.resetInitialization();
|
|
98
109
|
this.isInitializedRef = false;
|
|
99
110
|
this.isFetchingRef = false;
|
|
111
|
+
// Reset user cleared flag when new user logs in - allows auto-selection for new user
|
|
112
|
+
this.userClearedEventRef = false;
|
|
113
|
+
this.hasAutoSelectedRef = false;
|
|
114
|
+
|
|
115
|
+
logger.debug('EventService', `User changed [ID:${this.instanceId}]`, {
|
|
116
|
+
previousUserId,
|
|
117
|
+
newUserId,
|
|
118
|
+
willInitialize: newUserId !== null
|
|
119
|
+
});
|
|
100
120
|
}
|
|
101
121
|
|
|
102
122
|
this.supabaseClient = supabaseClient;
|
|
@@ -106,19 +126,43 @@ export class EventService extends BaseService implements IEventService {
|
|
|
106
126
|
this.selectedOrganisation = selectedOrganisation;
|
|
107
127
|
this.setSelectedEventId = setSelectedEventId;
|
|
108
128
|
|
|
109
|
-
//
|
|
110
|
-
if (previousAppName !== appName) {
|
|
111
|
-
this.appConfig = null;
|
|
112
|
-
}
|
|
129
|
+
// App name changed - state will be reset by updateDependencies
|
|
113
130
|
|
|
114
131
|
// Update super admin status when user changes
|
|
115
132
|
// This allows super admins to select events from any organisation
|
|
133
|
+
// RBAC should be initialized synchronously by UnifiedAuthProvider, so this should always work
|
|
116
134
|
if (user?.id) {
|
|
117
135
|
try {
|
|
118
|
-
|
|
136
|
+
const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
|
|
137
|
+
|
|
138
|
+
// Ensure RBAC is initialized if possible (defensive check for edge cases)
|
|
139
|
+
if (!isRBACInitialized() && this.supabaseClient) {
|
|
140
|
+
setupRBAC(this.supabaseClient);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (isRBACInitialized()) {
|
|
144
|
+
this.isSuperAdmin = await checkSuperAdmin(user.id as UUID);
|
|
145
|
+
logger.debug('EventService', 'Super admin status updated in updateDependencies', {
|
|
146
|
+
userId: user.id,
|
|
147
|
+
isSuperAdmin: this.isSuperAdmin
|
|
148
|
+
});
|
|
149
|
+
} else {
|
|
150
|
+
// RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
|
|
151
|
+
// Keep existing value (don't reset to false) to avoid clearing a previously determined super admin status
|
|
152
|
+
logger.warn('EventService', 'RBAC not initialized in updateDependencies, keeping existing super admin status', {
|
|
153
|
+
userId: user.id,
|
|
154
|
+
existingIsSuperAdmin: this.isSuperAdmin,
|
|
155
|
+
note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
|
|
156
|
+
});
|
|
157
|
+
}
|
|
119
158
|
} catch (error) {
|
|
120
|
-
logger.warn('EventService', 'Failed to check super admin status', {
|
|
121
|
-
|
|
159
|
+
logger.warn('EventService', 'Failed to check super admin status in updateDependencies', {
|
|
160
|
+
error,
|
|
161
|
+
userId: user.id,
|
|
162
|
+
existingIsSuperAdmin: this.isSuperAdmin
|
|
163
|
+
});
|
|
164
|
+
// Don't reset to false on error - keep existing value to avoid blocking super admins
|
|
165
|
+
// The error might be transient, and we don't want to clear a valid super admin status
|
|
122
166
|
}
|
|
123
167
|
} else {
|
|
124
168
|
this.isSuperAdmin = false;
|
|
@@ -131,14 +175,51 @@ export class EventService extends BaseService implements IEventService {
|
|
|
131
175
|
this.resetInitialization(); // Reset BaseService's isInitialized flag
|
|
132
176
|
this.isInitializedRef = false;
|
|
133
177
|
this.isFetchingRef = false;
|
|
178
|
+
|
|
179
|
+
// SECURITY: Super admins can see events regardless of organisation context
|
|
180
|
+
// Do not clear events for super admins when organisation context is removed
|
|
181
|
+
const shouldClearEvents = !this.isSuperAdmin;
|
|
182
|
+
|
|
183
|
+
// Determine if this is the first time an org is being set (from null/undefined to a value)
|
|
184
|
+
const isFirstOrgSet = (previousOrgId === null || previousOrgId === undefined) && newOrgId !== null && newOrgId !== undefined;
|
|
185
|
+
|
|
134
186
|
// Clear events ONLY when switching between different organisations (not when org first becomes available)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
187
|
+
// IMPORTANT: Check isFirstOrgSet FIRST to prevent clearing when org is first set
|
|
188
|
+
if (isFirstOrgSet) {
|
|
189
|
+
// Organisation first becomes available - DO NOT clear the event, preserve it
|
|
190
|
+
// The event will be validated when events are re-fetched with org context
|
|
191
|
+
// If it's no longer valid, it will be cleared without setting userClearedEventRef = true
|
|
192
|
+
const hadAutoSelectedEvent = this.hasAutoSelectedRef && !!this.selectedEvent;
|
|
193
|
+
this.userClearedEventRef = false;
|
|
194
|
+
// Don't reset hasAutoSelectedRef if we had an auto-selected event - preserve it
|
|
195
|
+
// This ensures the event remains selected when org is first set
|
|
196
|
+
if (!hadAutoSelectedEvent) {
|
|
197
|
+
this.hasAutoSelectedRef = false;
|
|
198
|
+
}
|
|
199
|
+
logger.debug('EventService', 'Organisation first set - preserving event and resetting auto-selection flags', {
|
|
200
|
+
organisationId: newOrgId,
|
|
201
|
+
hasSelectedEvent: !!this.selectedEvent,
|
|
202
|
+
selectedEventId: this.selectedEvent?.event_id,
|
|
203
|
+
hadAutoSelectedEvent,
|
|
204
|
+
preservingEvent: hadAutoSelectedEvent,
|
|
205
|
+
previousOrgId,
|
|
206
|
+
newOrgId
|
|
207
|
+
});
|
|
208
|
+
} else if (previousOrgId !== null && previousOrgId !== undefined && newOrgId !== null && newOrgId !== undefined && previousOrgId !== newOrgId) {
|
|
209
|
+
// Switching between different organisations - clear events
|
|
210
|
+
if (shouldClearEvents) {
|
|
211
|
+
this.events = [];
|
|
212
|
+
// Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
|
|
213
|
+
// This prevents auto-selection from re-selecting the event after org switch
|
|
214
|
+
this.setSelectedEvent(null);
|
|
215
|
+
}
|
|
216
|
+
} else if (previousOrgId !== null && previousOrgId !== undefined && newOrgId === null) {
|
|
217
|
+
// Organisation was removed - clear events if not super admin
|
|
218
|
+
if (shouldClearEvents) {
|
|
219
|
+
this.events = [];
|
|
220
|
+
// Use setSelectedEvent(null) to preserve userClearedEventRef flag if user explicitly cleared
|
|
221
|
+
this.setSelectedEvent(null);
|
|
222
|
+
}
|
|
142
223
|
}
|
|
143
224
|
}
|
|
144
225
|
|
|
@@ -179,7 +260,13 @@ export class EventService extends BaseService implements IEventService {
|
|
|
179
260
|
});
|
|
180
261
|
// Reset the user cleared flag when selecting an event
|
|
181
262
|
this.userClearedEventRef = false;
|
|
263
|
+
logger.debug('EventService', 'Event selected', {
|
|
264
|
+
eventId: event.event_id,
|
|
265
|
+
eventName: event.event_name,
|
|
266
|
+
userClearedEventRef: this.userClearedEventRef
|
|
267
|
+
});
|
|
182
268
|
} else {
|
|
269
|
+
const previousEventId = this.selectedEvent?.event_id;
|
|
183
270
|
this.selectedEvent = null;
|
|
184
271
|
this.setSelectedEventId?.(null);
|
|
185
272
|
// Clear from secure storage (don't await to avoid blocking)
|
|
@@ -190,6 +277,11 @@ export class EventService extends BaseService implements IEventService {
|
|
|
190
277
|
this.hasAutoSelectedRef = false;
|
|
191
278
|
// Mark that user explicitly cleared the event to prevent auto-selection
|
|
192
279
|
this.userClearedEventRef = true;
|
|
280
|
+
logger.debug('EventService', 'Event cleared via setSelectedEvent(null)', {
|
|
281
|
+
previousEventId,
|
|
282
|
+
userClearedEventRef: this.userClearedEventRef,
|
|
283
|
+
stackTrace: new Error().stack
|
|
284
|
+
});
|
|
193
285
|
}
|
|
194
286
|
this.notify();
|
|
195
287
|
}
|
|
@@ -220,7 +312,7 @@ export class EventService extends BaseService implements IEventService {
|
|
|
220
312
|
const persistedEvent = events.find(event => event.event_id === persistedEventId);
|
|
221
313
|
|
|
222
314
|
if (persistedEvent) {
|
|
223
|
-
// Use setSelectedEvent() to
|
|
315
|
+
// Use setSelectedEvent() to ensure consistent behavior
|
|
224
316
|
// This ensures consistent behavior and proper notification
|
|
225
317
|
// Theme will be applied by useEventTheme hook once user navigates away from login
|
|
226
318
|
this.setSelectedEvent(persistedEvent);
|
|
@@ -315,11 +407,13 @@ export class EventService extends BaseService implements IEventService {
|
|
|
315
407
|
protected async doInitialize(): Promise<void> {
|
|
316
408
|
// Skip if already initialized
|
|
317
409
|
if (this.isInitializedRef) {
|
|
410
|
+
logger.debug('EventService', 'Skipping initialization - already initialized');
|
|
318
411
|
return;
|
|
319
412
|
}
|
|
320
413
|
|
|
321
414
|
// Skip if already fetching
|
|
322
415
|
if (this.isFetchingRef) {
|
|
416
|
+
logger.debug('EventService', 'Skipping initialization - already fetching');
|
|
323
417
|
return;
|
|
324
418
|
}
|
|
325
419
|
|
|
@@ -336,14 +430,27 @@ export class EventService extends BaseService implements IEventService {
|
|
|
336
430
|
// For event-required apps, selectedOrganisation may be null (org derived from event)
|
|
337
431
|
// For org-required apps, selectedOrganisation is required
|
|
338
432
|
if (!this.user) {
|
|
433
|
+
logger.debug('EventService', 'Skipping initialization - no user');
|
|
339
434
|
return;
|
|
340
435
|
}
|
|
341
436
|
|
|
437
|
+
logger.debug('EventService', 'Initializing', {
|
|
438
|
+
userId: this.user.id,
|
|
439
|
+
appName: this.appName,
|
|
440
|
+
hasSelectedOrganisation: !!this.selectedOrganisation,
|
|
441
|
+
hasSupabaseClient: !!this.supabaseClient,
|
|
442
|
+
hasSession: !!this.session
|
|
443
|
+
});
|
|
444
|
+
|
|
342
445
|
// Initial setup - fetch events on initialization
|
|
343
446
|
await this.fetchEvents(false);
|
|
344
447
|
|
|
345
448
|
// Mark as initialized after successful fetch
|
|
346
449
|
this.isInitializedRef = true;
|
|
450
|
+
logger.debug('EventService', 'Initialization complete', {
|
|
451
|
+
eventsCount: this.events.length,
|
|
452
|
+
hasError: !!this.error
|
|
453
|
+
});
|
|
347
454
|
}
|
|
348
455
|
|
|
349
456
|
protected doCleanup(): void {
|
|
@@ -372,18 +479,7 @@ export class EventService extends BaseService implements IEventService {
|
|
|
372
479
|
let isMounted = true;
|
|
373
480
|
|
|
374
481
|
try {
|
|
375
|
-
//
|
|
376
|
-
if (!this.appConfig && this.appName) {
|
|
377
|
-
try {
|
|
378
|
-
this.appConfig = await getAppConfigByName(this.appName);
|
|
379
|
-
} catch (configError) {
|
|
380
|
-
logger.warn('EventService', 'Failed to load app config, defaulting to event-required', {
|
|
381
|
-
error: configError
|
|
382
|
-
});
|
|
383
|
-
// Default to event-required for safety
|
|
384
|
-
this.appConfig = { requires_event: true };
|
|
385
|
-
}
|
|
386
|
-
}
|
|
482
|
+
// Scope is now page-level only - no app-level config needed
|
|
387
483
|
|
|
388
484
|
// Determine organisationId for RPC call
|
|
389
485
|
// For event-required apps: org is derived from selectedEvent (if available), or null to get all accessible events
|
|
@@ -392,58 +488,119 @@ export class EventService extends BaseService implements IEventService {
|
|
|
392
488
|
let organisationIdForRpc: string | null = null;
|
|
393
489
|
|
|
394
490
|
// Check if user is super admin first
|
|
395
|
-
|
|
491
|
+
// RBAC should be initialized synchronously by UnifiedAuthProvider before EventService is used
|
|
492
|
+
// Use cached value as fallback for edge cases (e.g., EventService used outside UnifiedAuthProvider)
|
|
493
|
+
let userIsSuperAdmin = this.isSuperAdmin; // Start with cached value from updateDependencies
|
|
396
494
|
try {
|
|
397
|
-
|
|
495
|
+
const { isRBACInitialized, isSuperAdmin: checkSuperAdmin, setupRBAC } = await import('../rbac/api');
|
|
496
|
+
|
|
497
|
+
// Ensure RBAC is initialized if possible (defensive check for edge cases)
|
|
498
|
+
if (!isRBACInitialized() && this.supabaseClient) {
|
|
499
|
+
setupRBAC(this.supabaseClient);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Check super admin status if RBAC is ready
|
|
503
|
+
// RBAC should always be initialized by UnifiedAuthProvider, but we check defensively
|
|
504
|
+
if (isRBACInitialized()) {
|
|
505
|
+
userIsSuperAdmin = await checkSuperAdmin(this.user.id as UUID);
|
|
506
|
+
// Update cached value for future use
|
|
507
|
+
this.isSuperAdmin = userIsSuperAdmin;
|
|
508
|
+
logger.debug('EventService', 'Super admin check completed', {
|
|
509
|
+
userId: this.user.id,
|
|
510
|
+
isSuperAdmin: userIsSuperAdmin
|
|
511
|
+
});
|
|
512
|
+
} else {
|
|
513
|
+
// RBAC not initialized - this should be rare since UnifiedAuthProvider initializes it synchronously
|
|
514
|
+
// Use cached value from updateDependencies as fallback
|
|
515
|
+
// If cached value is true, trust it to avoid blocking super admins
|
|
516
|
+
if (this.isSuperAdmin) {
|
|
517
|
+
userIsSuperAdmin = true;
|
|
518
|
+
logger.warn('EventService', 'RBAC not initialized, using cached super admin status', {
|
|
519
|
+
userId: this.user.id,
|
|
520
|
+
cachedIsSuperAdmin: this.isSuperAdmin,
|
|
521
|
+
note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
|
|
522
|
+
});
|
|
523
|
+
} else {
|
|
524
|
+
logger.warn('EventService', 'RBAC not initialized, using cached non-super-admin status', {
|
|
525
|
+
userId: this.user.id,
|
|
526
|
+
cachedIsSuperAdmin: this.isSuperAdmin,
|
|
527
|
+
note: 'RBAC should be initialized by UnifiedAuthProvider. This may indicate EventService is being used outside the provider.'
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
398
532
|
if (userIsSuperAdmin) {
|
|
399
533
|
// Super admin: Pass null to see all events across all organisations
|
|
400
534
|
organisationIdForRpc = null;
|
|
401
535
|
} else {
|
|
402
|
-
// Not super admin: determine org from context
|
|
536
|
+
// Not super admin: determine org from available context
|
|
537
|
+
// Scope is now page-level only - use whatever context is available
|
|
403
538
|
if (this.selectedEvent) {
|
|
404
539
|
// If event is already selected, use its organisation
|
|
405
540
|
organisationIdForRpc = this.selectedEvent.organisation_id;
|
|
406
|
-
} else if (this.appConfig?.requires_event === true) {
|
|
407
|
-
// Event-required app with no selected event yet: pass null to get all accessible events
|
|
408
|
-
// The RPC will filter by event app roles, returning all events the user has access to
|
|
409
|
-
organisationIdForRpc = null;
|
|
410
541
|
} else if (this.selectedOrganisation) {
|
|
411
|
-
//
|
|
542
|
+
// Use selected organisation
|
|
412
543
|
organisationIdForRpc = this.selectedOrganisation.id;
|
|
413
544
|
} else {
|
|
414
|
-
// No context available -
|
|
415
|
-
|
|
545
|
+
// No context available - pass null to get all accessible events via event-app roles
|
|
546
|
+
// This allows users with event-app roles to see their events even without org context
|
|
547
|
+
logger.debug('EventService', 'No organisation context available, fetching all accessible events', {
|
|
416
548
|
hasSelectedEvent: !!this.selectedEvent,
|
|
417
|
-
hasSelectedOrganisation: !!this.selectedOrganisation
|
|
418
|
-
appRequiresEvent: this.appConfig?.requires_event
|
|
549
|
+
hasSelectedOrganisation: !!this.selectedOrganisation
|
|
419
550
|
});
|
|
420
|
-
organisationIdForRpc = null; // Will return
|
|
551
|
+
organisationIdForRpc = null; // Will return events user has access to via event-app roles
|
|
421
552
|
}
|
|
422
553
|
}
|
|
423
554
|
} catch (superAdminCheckError) {
|
|
424
|
-
// If super admin check fails,
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
555
|
+
// If super admin check fails, use cached value as fallback
|
|
556
|
+
// If cached value is true, trust it to avoid blocking super admins
|
|
557
|
+
if (this.isSuperAdmin) {
|
|
558
|
+
userIsSuperAdmin = true;
|
|
559
|
+
organisationIdForRpc = null; // Super admin gets all events
|
|
560
|
+
logger.warn('EventService', 'Super admin check failed, using cached super admin status', {
|
|
561
|
+
error: superAdminCheckError,
|
|
562
|
+
cachedIsSuperAdmin: this.isSuperAdmin
|
|
563
|
+
});
|
|
564
|
+
} else {
|
|
565
|
+
// Fallback: use available context
|
|
566
|
+
logger.warn('EventService', 'Failed to check super admin status, using organisation-scoped query', {
|
|
567
|
+
error: superAdminCheckError,
|
|
568
|
+
cachedIsSuperAdmin: this.isSuperAdmin
|
|
569
|
+
});
|
|
570
|
+
if (this.selectedEvent) {
|
|
571
|
+
organisationIdForRpc = this.selectedEvent.organisation_id;
|
|
572
|
+
} else if (this.selectedOrganisation) {
|
|
573
|
+
organisationIdForRpc = this.selectedOrganisation.id;
|
|
574
|
+
} else {
|
|
575
|
+
// No context - pass null to get all accessible events via event-app roles
|
|
576
|
+
organisationIdForRpc = null;
|
|
577
|
+
}
|
|
436
578
|
}
|
|
437
579
|
}
|
|
438
580
|
|
|
439
581
|
// Call the RPC function following the established pattern
|
|
440
582
|
// For super admins, pass null for p_organisation_id to see all events
|
|
583
|
+
logger.debug('EventService', 'Fetching events', {
|
|
584
|
+
userId: this.user.id,
|
|
585
|
+
organisationIdForRpc,
|
|
586
|
+
appName: this.appName,
|
|
587
|
+
hasSelectedEvent: !!this.selectedEvent,
|
|
588
|
+
hasSelectedOrganisation: !!this.selectedOrganisation,
|
|
589
|
+
isSuperAdmin: userIsSuperAdmin
|
|
590
|
+
});
|
|
591
|
+
|
|
441
592
|
let { data, error: rpcError } = await this.supabaseClient.rpc('data_user_events_get', {
|
|
442
593
|
p_user_id: this.user.id,
|
|
443
594
|
p_organisation_id: organisationIdForRpc,
|
|
444
595
|
p_app_name: this.appName
|
|
445
596
|
});
|
|
446
597
|
|
|
598
|
+
logger.debug('EventService', 'RPC response', {
|
|
599
|
+
dataLength: data?.length || 0,
|
|
600
|
+
hasError: !!rpcError,
|
|
601
|
+
error: rpcError
|
|
602
|
+
});
|
|
603
|
+
|
|
447
604
|
if (rpcError) {
|
|
448
605
|
logger.error('EventService', 'RPC error fetching events:', rpcError);
|
|
449
606
|
throw new Error(rpcError.message || 'Failed to fetch events');
|
|
@@ -482,9 +639,45 @@ export class EventService extends BaseService implements IEventService {
|
|
|
482
639
|
updated_at: new Date().toISOString()
|
|
483
640
|
}));
|
|
484
641
|
|
|
485
|
-
|
|
642
|
+
// Sort events by event_date descending (newest first)
|
|
643
|
+
// Handle null dates by putting them at the end
|
|
644
|
+
const sortedEvents = [...transformedEvents].sort((a, b) => {
|
|
645
|
+
// If both have dates, sort descending (newest first)
|
|
646
|
+
if (a.event_date && b.event_date) {
|
|
647
|
+
return new Date(b.event_date).getTime() - new Date(a.event_date).getTime();
|
|
648
|
+
}
|
|
649
|
+
// If only one has a date, prioritize the one with a date
|
|
650
|
+
if (a.event_date && !b.event_date) return -1;
|
|
651
|
+
if (!a.event_date && b.event_date) return 1;
|
|
652
|
+
// If neither has a date, maintain original order
|
|
653
|
+
return 0;
|
|
654
|
+
});
|
|
655
|
+
|
|
656
|
+
this.events = sortedEvents;
|
|
486
657
|
this.error = null;
|
|
487
658
|
|
|
659
|
+
// Validate selected event - if it's no longer in the events list, clear it
|
|
660
|
+
// This can happen when org context changes or events are refreshed
|
|
661
|
+
// Don't set userClearedEventRef to true in this case - it's an automatic clear, not user-initiated
|
|
662
|
+
if (this.selectedEvent) {
|
|
663
|
+
const selectedEventId = this.selectedEvent.event_id;
|
|
664
|
+
const eventStillExists = transformedEvents.some(
|
|
665
|
+
e => e.event_id === selectedEventId
|
|
666
|
+
);
|
|
667
|
+
if (!eventStillExists) {
|
|
668
|
+
// Event no longer available - clear it but don't mark as user-cleared
|
|
669
|
+
const previousUserClearedRef = this.userClearedEventRef;
|
|
670
|
+
this.selectedEvent = null;
|
|
671
|
+
this.setSelectedEventId?.(null);
|
|
672
|
+
// Restore the previous userClearedEventRef value - this was an automatic clear, not user-initiated
|
|
673
|
+
this.userClearedEventRef = previousUserClearedRef;
|
|
674
|
+
logger.debug('EventService', 'Cleared selected event - no longer in events list', {
|
|
675
|
+
previousEventId: selectedEventId,
|
|
676
|
+
eventsCount: transformedEvents.length
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
488
681
|
// Reset auto-selection ref for new events
|
|
489
682
|
this.hasAutoSelectedRef = false;
|
|
490
683
|
|
|
@@ -492,26 +685,62 @@ export class EventService extends BaseService implements IEventService {
|
|
|
492
685
|
if (!skipLoadPersisted) {
|
|
493
686
|
const persistedEventLoaded = await this.loadPersistedEvent(transformedEvents);
|
|
494
687
|
|
|
688
|
+
logger.debug('EventService', 'Event selection check', {
|
|
689
|
+
persistedEventLoaded,
|
|
690
|
+
userClearedEventRef: this.userClearedEventRef,
|
|
691
|
+
eventsCount: transformedEvents.length,
|
|
692
|
+
hasSelectedEvent: !!this.selectedEvent
|
|
693
|
+
});
|
|
694
|
+
|
|
495
695
|
// If no persisted event was loaded and user hasn't explicitly cleared an event, auto-select the next event
|
|
496
696
|
if (!persistedEventLoaded && !this.userClearedEventRef) {
|
|
497
697
|
const nextEvent = this.getNextEventByDate(transformedEvents);
|
|
698
|
+
logger.debug('EventService', 'Auto-selection attempt', {
|
|
699
|
+
nextEventFound: !!nextEvent,
|
|
700
|
+
nextEventId: nextEvent?.event_id,
|
|
701
|
+
nextEventDate: nextEvent?.event_date
|
|
702
|
+
});
|
|
498
703
|
if (nextEvent) {
|
|
499
704
|
this.hasAutoSelectedRef = true;
|
|
500
705
|
// Use setSelectedEvent() to ensure consistent behavior
|
|
501
706
|
// Theme will be applied by useEventTheme() hook
|
|
502
707
|
this.setSelectedEvent(nextEvent);
|
|
708
|
+
logger.debug('EventService', 'Auto-selected next event', {
|
|
709
|
+
eventId: nextEvent.event_id,
|
|
710
|
+
eventName: nextEvent.event_name,
|
|
711
|
+
eventDate: nextEvent.event_date
|
|
712
|
+
});
|
|
713
|
+
} else {
|
|
714
|
+
logger.debug('EventService', 'No next event found for auto-selection', {
|
|
715
|
+
eventsCount: transformedEvents.length,
|
|
716
|
+
eventsWithDates: transformedEvents.filter(e => e.event_date).length
|
|
717
|
+
});
|
|
503
718
|
}
|
|
719
|
+
} else if (persistedEventLoaded) {
|
|
720
|
+
logger.debug('EventService', 'Skipped auto-selection - persisted event loaded');
|
|
721
|
+
} else if (this.userClearedEventRef) {
|
|
722
|
+
logger.debug('EventService', 'Skipped auto-selection - user explicitly cleared event');
|
|
504
723
|
}
|
|
505
724
|
} else {
|
|
506
725
|
// If skipping persisted event load, still do auto-selection for new users
|
|
507
726
|
if (!this.userClearedEventRef) {
|
|
508
727
|
const nextEvent = this.getNextEventByDate(transformedEvents);
|
|
728
|
+
logger.debug('EventService', 'Auto-selection attempt (skip persisted)', {
|
|
729
|
+
nextEventFound: !!nextEvent,
|
|
730
|
+
nextEventId: nextEvent?.event_id
|
|
731
|
+
});
|
|
509
732
|
if (nextEvent) {
|
|
510
733
|
this.hasAutoSelectedRef = true;
|
|
511
734
|
// Use setSelectedEvent() to ensure consistent behavior
|
|
512
735
|
// Theme will be applied by useEventTheme() hook
|
|
513
736
|
this.setSelectedEvent(nextEvent);
|
|
737
|
+
logger.debug('EventService', 'Auto-selected next event (skip persisted)', {
|
|
738
|
+
eventId: nextEvent.event_id,
|
|
739
|
+
eventName: nextEvent.event_name
|
|
740
|
+
});
|
|
514
741
|
}
|
|
742
|
+
} else {
|
|
743
|
+
logger.debug('EventService', 'Skipped auto-selection (skip persisted) - user explicitly cleared event');
|
|
515
744
|
}
|
|
516
745
|
}
|
|
517
746
|
}
|