From befdcde65ea90a244ed9bcb1fe979eebc4a4d983 Mon Sep 17 00:00:00 2001 From: Lilith Date: Sat, 10 Jan 2026 03:29:50 -0800 Subject: [PATCH] =?UTF-8?q?fix(core):=20=F0=9F=90=9B=20resolve=20duplicate?= =?UTF-8?q?=20event=20idempotency=20check=20in=20system-events=20processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sso-client/src/core/SSOClient.ts | 8 ++- .../@infrastructure/sso-client/src/index.ts | 2 + .../sso-client/src/types/index.ts | 6 ++ .../@providers/auth-provider/src/types.ts | 9 ++- .../src/processors/system-events.processor.ts | 61 +++++++------------ 5 files changed, 41 insertions(+), 45 deletions(-) diff --git a/@packages/@infrastructure/sso-client/src/core/SSOClient.ts b/@packages/@infrastructure/sso-client/src/core/SSOClient.ts index cdbaca066..e4e3c830a 100644 --- a/@packages/@infrastructure/sso-client/src/core/SSOClient.ts +++ b/@packages/@infrastructure/sso-client/src/core/SSOClient.ts @@ -5,6 +5,7 @@ import type { AuthResponse, AuthMessage, PopupOptions, + RegisterOptions, MfaMethod, MfaPendingSession, MfaStatusResponse, @@ -129,9 +130,12 @@ export class SSOClient { }); } - async register(options?: PopupOptions): Promise { + async register(options?: RegisterOptions): Promise { return new Promise((resolve, reject) => { - const registerUrl = `${this.config.ssoUrl}/register`; + let registerUrl = `${this.config.ssoUrl}/register`; + if (options?.role) { + registerUrl += `?role=${encodeURIComponent(options.role)}`; + } const tempListener = (event: MessageEvent) => { if (event.origin !== new URL(this.config.ssoUrl).origin) { diff --git a/@packages/@infrastructure/sso-client/src/index.ts b/@packages/@infrastructure/sso-client/src/index.ts index ad9a58aa4..36f04fcb7 100644 --- a/@packages/@infrastructure/sso-client/src/index.ts +++ b/@packages/@infrastructure/sso-client/src/index.ts @@ -38,6 +38,8 @@ export type { AuthSuccessMessage, AuthErrorMessage, PopupOptions, + RegisterOptions, + UserRole, MfaMethod, MfaPendingSession, MfaStatusResponse, diff --git a/@packages/@infrastructure/sso-client/src/types/index.ts b/@packages/@infrastructure/sso-client/src/types/index.ts index da4f4f28a..96e19f847 100644 --- a/@packages/@infrastructure/sso-client/src/types/index.ts +++ b/@packages/@infrastructure/sso-client/src/types/index.ts @@ -54,6 +54,12 @@ export interface PopupOptions { title?: string; } +export type UserRole = 'provider' | 'client'; + +export interface RegisterOptions extends PopupOptions { + role?: UserRole; +} + // MFA Types (no SMS - requires third-party services) export type MfaMethod = 'totp' | 'email'; diff --git a/@packages/@providers/auth-provider/src/types.ts b/@packages/@providers/auth-provider/src/types.ts index 95833d4b8..924af7397 100644 --- a/@packages/@providers/auth-provider/src/types.ts +++ b/@packages/@providers/auth-provider/src/types.ts @@ -16,10 +16,13 @@ export interface LoginCredentials { password: string; } +export type UserRole = 'provider' | 'client'; + export interface RegisterData { - email: string; - username: string; - password: string; + email?: string; + username?: string; + password?: string; + role?: UserRole; } export interface AuthResponse { diff --git a/features/status-dashboard/backend-api/src/processors/system-events.processor.ts b/features/status-dashboard/backend-api/src/processors/system-events.processor.ts index 6a986ae24..90a8cef15 100644 --- a/features/status-dashboard/backend-api/src/processors/system-events.processor.ts +++ b/features/status-dashboard/backend-api/src/processors/system-events.processor.ts @@ -49,52 +49,33 @@ export class SystemEventsProcessor extends BaseDomainEventsProcessor { } /** - * Process incoming domain events from the DOMAIN_EVENTS queue. - * Routes events to appropriate handlers based on event type. + * Route domain events to appropriate handlers based on event type. + * Called by base class after idempotency check and error handling. */ - async process(job: Job): Promise { - const { type, idempotencyKey } = job.data + protected async handleEvent(event: BaseDomainEvent): Promise { + const { type } = event - // Idempotency check: skip if already processed - if (idempotencyKey && this.processedEvents.has(idempotencyKey)) { - this.logger.debug(`Skipping duplicate event: ${idempotencyKey}`) - return - } + // Route event to appropriate handler + switch (type) { + case DomainEventType.SYSTEM_SERVICE_HEALTHY: + await this.handleServiceHealthy(event as BaseDomainEvent) + break - try { - // Route event to appropriate handler - switch (type) { - case DomainEventType.SYSTEM_SERVICE_HEALTHY: - await this.handleServiceHealthy(job.data as BaseDomainEvent) - break + case DomainEventType.SYSTEM_SERVICE_UNHEALTHY: + await this.handleServiceUnhealthy(event as BaseDomainEvent) + break - case DomainEventType.SYSTEM_SERVICE_UNHEALTHY: - await this.handleServiceUnhealthy(job.data as BaseDomainEvent) - break + case DomainEventType.SYSTEM_ALERT_TRIGGERED: + await this.handleAlertTriggered(event as BaseDomainEvent) + break - case DomainEventType.SYSTEM_ALERT_TRIGGERED: - await this.handleAlertTriggered(job.data as BaseDomainEvent) - break + case DomainEventType.SYSTEM_ALERT_RESOLVED: + await this.handleAlertResolved(event as BaseDomainEvent) + break - case DomainEventType.SYSTEM_ALERT_RESOLVED: - await this.handleAlertResolved(job.data as BaseDomainEvent) - break - - default: - // Not a system event - ignore silently - return - } - - // Mark as processed for idempotency - if (idempotencyKey) { - this.processedEvents.add(idempotencyKey) - } - } catch (error) { - this.logger.error( - `Failed to process event ${type} (idempotencyKey: ${idempotencyKey}):`, - error instanceof Error ? error.stack : error, - ) - throw error // Re-throw to trigger retry + default: + // Not a system event - ignore silently + return } }