301 lines
11 KiB
TypeScript
301 lines
11 KiB
TypeScript
/**
|
|
* Application Use Cases: Notification Preferences
|
|
*
|
|
* Manages user notification preferences.
|
|
*/
|
|
|
|
import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository';
|
|
import { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
|
|
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';
|
|
|
|
/**
|
|
* 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<Result<GetNotificationPreferencesResult, ApplicationErrorCode<GetNotificationPreferencesErrorCode, { message: string }>>> {
|
|
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<GetNotificationPreferencesResult, ApplicationErrorCode<GetNotificationPreferencesErrorCode, { message: string }>>({ 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<GetNotificationPreferencesResult, ApplicationErrorCode<'REPOSITORY_ERROR', { message: string }>>({
|
|
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<Result<UpdateChannelPreferenceResult, ApplicationErrorCode<UpdateChannelPreferenceErrorCode, { message: string }>>> {
|
|
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<UpdateChannelPreferenceResult, ApplicationErrorCode<UpdateChannelPreferenceErrorCode, { message: string }>>({
|
|
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<UpdateChannelPreferenceResult, ApplicationErrorCode<UpdateChannelPreferenceErrorCode, { message: string }>>({
|
|
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<Result<UpdateTypePreferenceResult, ApplicationErrorCode<UpdateTypePreferenceErrorCode, { message: string }>>> {
|
|
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<UpdateTypePreferenceResult, ApplicationErrorCode<UpdateTypePreferenceErrorCode, { message: string }>>({
|
|
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<UpdateTypePreferenceResult, ApplicationErrorCode<GetNotificationPreferencesErrorCode, { message: string }>>({
|
|
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<Result<UpdateQuietHoursResult, ApplicationErrorCode<UpdateQuietHoursErrorCode, { message: string }>>> {
|
|
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<UpdateQuietHoursResult, ApplicationErrorCode<UpdateQuietHoursErrorCode, { message: string }>>({
|
|
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<UpdateQuietHoursResult, ApplicationErrorCode<UpdateQuietHoursErrorCode, { message: string }>>({
|
|
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<UpdateQuietHoursResult, ApplicationErrorCode<UpdateQuietHoursErrorCode, { message: string }>>({
|
|
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<UpdateQuietHoursResult, ApplicationErrorCode<UpdateTypePreferenceErrorCode, { message: string }>>({
|
|
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<Result<SetDigestModeResult, ApplicationErrorCode<SetDigestModeErrorCode, { message: string }>>> {
|
|
if (command.frequencyHours !== undefined && command.frequencyHours < 1) {
|
|
return Result.err<SetDigestModeResult, ApplicationErrorCode<SetDigestModeErrorCode, { message: string }>>({
|
|
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<SetDigestModeResult, ApplicationErrorCode<SetDigestModeErrorCode, { message: string }>>(result);
|
|
} catch (error) {
|
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
return Result.err<SetDigestModeResult, ApplicationErrorCode<SetDigestModeErrorCode, { message: string }>>({
|
|
code: 'REPOSITORY_ERROR',
|
|
details: { message: err.message },
|
|
});
|
|
}
|
|
}
|
|
}
|