/** * Application Use Cases: Notification Preferences * * Manages user notification preferences. */ import type { Logger } from '@core/shared/domain/Logger'; import { Result } from '@core/shared/domain/Result'; import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode'; import type { ChannelPreference, TypePreference } from '../../domain/entities/NotificationPreference'; import { NotificationPreference } from '../../domain/entities/NotificationPreference'; import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository'; import { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes'; /** * Query: GetNotificationPreferencesQuery */ export interface GetNotificationPreferencesInput { driverId: string; } export interface GetNotificationPreferencesResult { preference: NotificationPreference; } export type GetNotificationPreferencesErrorCode = 'REPOSITORY_ERROR'; export class GetNotificationPreferencesQuery { constructor( private readonly preferenceRepository: NotificationPreferenceRepository, private readonly logger: Logger, ) {} async execute( input: GetNotificationPreferencesInput, ): Promise>> { const { driverId } = input; this.logger.debug(`Fetching notification preferences for driver: ${driverId}`); try { const preferences = await this.preferenceRepository.getOrCreateDefault(driverId); this.logger.info(`Successfully fetched preferences for driver: ${driverId}`); return Result.ok>({ preference: preferences }); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); this.logger.error(`Failed to fetch preferences for driver: ${driverId}`, err); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } } /** * Use Case: UpdateChannelPreferenceUseCase */ export interface UpdateChannelPreferenceCommand { driverId: string; channel: NotificationChannel; preference: ChannelPreference; } export interface UpdateChannelPreferenceResult { driverId: string; channel: NotificationChannel; } export type UpdateChannelPreferenceErrorCode = | 'REPOSITORY_ERROR'; export class UpdateChannelPreferenceUseCase { constructor( private readonly preferenceRepository: NotificationPreferenceRepository, private readonly logger: Logger, ) {} async execute( command: UpdateChannelPreferenceCommand, ): Promise>> { this.logger.debug( `Updating channel preference for driver: ${command.driverId}, channel: ${command.channel}, preference: ${JSON.stringify(command.preference)}`, ); try { const preferences = await this.preferenceRepository.getOrCreateDefault( command.driverId, ); const updated = preferences.updateChannel(command.channel, command.preference); await this.preferenceRepository.save(updated); this.logger.info( `Successfully updated channel preference for driver: ${command.driverId}`, ); return Result.ok>({ driverId: command.driverId, channel: command.channel, }); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); this.logger.error( `Failed to update channel preference for driver: ${command.driverId}, channel: ${command.channel}`, err, ); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } } /** * Use Case: UpdateTypePreferenceUseCase */ export interface UpdateTypePreferenceCommand { driverId: string; type: NotificationType; preference: TypePreference; } export interface UpdateTypePreferenceResult { driverId: string; type: NotificationType; } export type UpdateTypePreferenceErrorCode = 'REPOSITORY_ERROR'; export class UpdateTypePreferenceUseCase { constructor( private readonly preferenceRepository: NotificationPreferenceRepository, private readonly logger: Logger, ) {} async execute( command: UpdateTypePreferenceCommand, ): Promise>> { this.logger.debug( `Updating type preference for driver: ${command.driverId}, type: ${command.type}, preference: ${JSON.stringify(command.preference)}`, ); try { const preferences = await this.preferenceRepository.getOrCreateDefault( command.driverId, ); const updated = preferences.updateTypePreference(command.type, command.preference); await this.preferenceRepository.save(updated); this.logger.info( `Successfully updated type preference for driver: ${command.driverId}`, ); return Result.ok>({ driverId: command.driverId, type: command.type, }); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); this.logger.error( `Failed to update type preference for driver: ${command.driverId}, type: ${command.type}`, err, ); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } } /** * Use Case: UpdateQuietHoursUseCase */ export interface UpdateQuietHoursCommand { driverId: string; startHour: number | undefined; endHour: number | undefined; } export interface UpdateQuietHoursResult { driverId: string; startHour: number | undefined; endHour: number | undefined; } export type UpdateQuietHoursErrorCode = | 'INVALID_START_HOUR' | 'INVALID_END_HOUR' | 'REPOSITORY_ERROR'; export class UpdateQuietHoursUseCase { constructor( private readonly preferenceRepository: NotificationPreferenceRepository, private readonly logger: Logger, ) {} async execute( command: UpdateQuietHoursCommand, ): Promise>> { this.logger.debug( `Updating quiet hours for driver: ${command.driverId}, startHour: ${command.startHour}, endHour: ${command.endHour}`, ); try { // Validate hours if provided if (command.startHour !== undefined && (command.startHour < 0 || command.startHour > 23)) { this.logger.warn( `Invalid start hour provided for driver: ${command.driverId}. startHour: ${command.startHour}`, ); return Result.err>({ code: 'INVALID_START_HOUR', details: { message: 'Start hour must be between 0 and 23' }, }); } if (command.endHour !== undefined && (command.endHour < 0 || command.endHour > 23)) { this.logger.warn( `Invalid end hour provided for driver: ${command.driverId}. endHour: ${command.endHour}`, ); return Result.err>({ code: 'INVALID_END_HOUR', details: { message: 'End hour must be between 0 and 23' }, }); } const preferences = await this.preferenceRepository.getOrCreateDefault( command.driverId, ); const updated = preferences.updateQuietHours( command.startHour, command.endHour, ); await this.preferenceRepository.save(updated); this.logger.info(`Successfully updated quiet hours for driver: ${command.driverId}`); return Result.ok>({ driverId: command.driverId, startHour: command.startHour, endHour: command.endHour, }); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); this.logger.error(`Failed to update quiet hours for driver: ${command.driverId}`, err); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } } /** * Use Case: SetDigestModeUseCase */ export interface SetDigestModeCommand { driverId: string; enabled: boolean; frequencyHours?: number; } export interface SetDigestModeResult { driverId: string; enabled: boolean; frequencyHours?: number | undefined; } export type SetDigestModeErrorCode = | 'INVALID_FREQUENCY' | 'REPOSITORY_ERROR'; export class SetDigestModeUseCase { constructor( private readonly preferenceRepository: NotificationPreferenceRepository, ) {} async execute( command: SetDigestModeCommand, ): Promise>> { if (command.frequencyHours !== undefined && command.frequencyHours < 1) { return Result.err>({ code: 'INVALID_FREQUENCY', details: { message: 'Digest frequency must be at least 1 hour' }, }); } try { const preferences = await this.preferenceRepository.getOrCreateDefault( command.driverId, ); const updated = preferences.setDigestMode( command.enabled, command.frequencyHours, ); await this.preferenceRepository.save(updated); const result: SetDigestModeResult = { driverId: command.driverId, enabled: command.enabled, frequencyHours: command.frequencyHours, }; return Result.ok>(result); } catch (error) { const err = error instanceof Error ? error : new Error(String(error)); return Result.err>({ code: 'REPOSITORY_ERROR', details: { message: err.message }, }); } } }