This commit is contained in:
2025-12-16 13:13:03 +01:00
parent 7d3393e1b9
commit 84f05598a6
78 changed files with 161 additions and 157 deletions

View File

@@ -5,7 +5,7 @@
*/
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '../../../shared/src/logging/Logger';
import type { Logger } from '@core/shared/application';
import type { Notification } from '../../domain/entities/Notification';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
@@ -35,7 +35,7 @@ export class GetUnreadNotificationsUseCase implements AsyncUseCase<string, Unrea
totalCount: notifications.length,
};
} catch (error) {
this.logger.error(`Failed to retrieve unread notifications for recipient ID: ${recipientId}`, error);
this.logger.error(`Failed to retrieve unread notifications for recipient ID: ${recipientId}`, error instanceof Error ? error : new Error(String(error)));
throw error;
}
}

View File

@@ -7,7 +7,7 @@
import type { AsyncUseCase } from '@core/shared/application';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
import { NotificationDomainError } from '../../domain/errors/NotificationDomainError';
import type { Logger } from '../../../shared/src/logging/Logger';
import type { Logger } from '@core/shared/application';
export interface MarkNotificationReadCommand {
notificationId: string;
@@ -44,7 +44,7 @@ export class MarkNotificationReadUseCase implements AsyncUseCase<MarkNotificatio
await this.notificationRepository.update(updatedNotification);
this.logger.info(`Notification ${command.notificationId} successfully marked as read.`);
} catch (error) {
this.logger.error(`Failed to mark notification ${command.notificationId} as read: ${error.message}`);
this.logger.error(`Failed to mark notification ${command.notificationId} as read: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
}

View File

@@ -5,7 +5,7 @@
*/
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '@core/shared/logging/Logger';
import type { Logger } from '@core/shared/application';
import { NotificationPreference } from '../../domain/entities/NotificationPreference';
import type { ChannelPreference, TypePreference } from '../../domain/entities/NotificationPreference';
import type { INotificationPreferenceRepository } from '../../domain/repositories/INotificationPreferenceRepository';

View File

@@ -7,7 +7,7 @@
import { v4 as uuid } from 'uuid';
import type { AsyncUseCase } from '@core/shared/application';
import type { Logger } from '../../../shared/src/logging/Logger';
import type { Logger } from '@core/shared/application';
import { Notification } from '../../domain/entities/Notification';
import type { NotificationData } from '../../domain/entities/Notification';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
@@ -60,83 +60,87 @@ export class SendNotificationUseCase implements AsyncUseCase<SendNotificationCom
// Get recipient's preferences
this.logger.debug('Checking notification preferences.', { type: command.type, recipientId: command.recipientId });
const preferences = await this.preferenceRepository.getOrCreateDefault(command.recipientId);
// Check if this notification type is enabled
if (!preferences.isTypeEnabled(command.type)) {
// User has disabled this type - create but don't deliver
const notification = Notification.create({
id: uuid(),
recipientId: command.recipientId,
type: command.type,
title: command.title,
body: command.body,
channel: 'in_app',
status: 'dismissed', // Auto-dismiss since user doesn't want these
...(command.data ? { data: command.data } : {}),
...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
});
await this.notificationRepository.create(notification);
return {
notification,
deliveryResults: [],
};
}
// Determine which channels to use
const channels = command.forceChannels ?? preferences.getEnabledChannelsForType(command.type);
// Check quiet hours (skip external channels during quiet hours)
const effectiveChannels = preferences.isInQuietHours()
? channels.filter(ch => ch === 'in_app')
: channels;
// Ensure at least in_app is used
if (!effectiveChannels.includes('in_app')) {
effectiveChannels.unshift('in_app');
}
const deliveryResults: NotificationDeliveryResult[] = [];
let primaryNotification: Notification | null = null;
// Send through each channel
for (const channel of effectiveChannels) {
const notification = Notification.create({
id: uuid(),
recipientId: command.recipientId,
type: command.type,
title: command.title,
body: command.body,
channel,
...(command.urgency ? { urgency: command.urgency } : {}),
...(command.data ? { data: command.data } : {}),
...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
...(command.actions ? { actions: command.actions } : {}),
...(command.requiresResponse !== undefined
? { requiresResponse: command.requiresResponse }
: {}),
});
// Save to repository (in_app channel) or attempt delivery (external channels)
if (channel === 'in_app') {
await this.notificationRepository.create(notification);
primaryNotification = notification;
deliveryResults.push({
success: true,
channel,
attemptedAt: new Date(),
// Check if this notification type is enabled
if (!preferences.isTypeEnabled(command.type)) {
// User has disabled this type - create but don't deliver
const notification = Notification.create({
id: uuid(),
recipientId: command.recipientId,
type: command.type,
title: command.title,
body: command.body,
channel: 'in_app',
status: 'dismissed', // Auto-dismiss since user doesn't want these
...(command.data ? { data: command.data } : {}),
...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
});
} else {
// Attempt external delivery
const result = await this.gatewayRegistry.send(notification);
deliveryResults.push(result);
await this.notificationRepository.create(notification);
return {
notification,
deliveryResults: [],
};
}
// Determine which channels to use
const channels = command.forceChannels ?? preferences.getEnabledChannelsForType(command.type);
// Check quiet hours (skip external channels during quiet hours)
const effectiveChannels = preferences.isInQuietHours()
? channels.filter(ch => ch === 'in_app')
: channels;
// Ensure at least in_app is used
if (!effectiveChannels.includes('in_app')) {
effectiveChannels.unshift('in_app');
}
const deliveryResults: NotificationDeliveryResult[] = [];
let primaryNotification: Notification | null = null;
// Send through each channel
for (const channel of effectiveChannels) {
const notification = Notification.create({
id: uuid(),
recipientId: command.recipientId,
type: command.type,
title: command.title,
body: command.body,
channel,
...(command.urgency ? { urgency: command.urgency } : {}),
...(command.data ? { data: command.data } : {}),
...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
...(command.actions ? { actions: command.actions } : {}),
...(command.requiresResponse !== undefined
? { requiresResponse: command.requiresResponse }
: {}),
});
// Save to repository (in_app channel) or attempt delivery (external channels)
if (channel === 'in_app') {
await this.notificationRepository.create(notification);
primaryNotification = notification;
deliveryResults.push({
success: true,
channel,
attemptedAt: new Date(),
});
} else {
// Attempt external delivery
const result = await this.gatewayRegistry.send(notification);
deliveryResults.push(result);
}
}
return {
notification: primaryNotification!,
deliveryResults,
};
} catch (error) {
this.logger.error('Error sending notification', error as Error);
throw error;
}
return {
notification: primaryNotification!,
deliveryResults,
};
}
}