/** * Domain Entity: Notification * * Represents a notification sent to a user. * Immutable entity with factory methods and domain validation. */ import type { NotificationType } from '../value-objects/NotificationType'; import type { NotificationChannel } from '../value-objects/NotificationChannel'; export type NotificationStatus = 'unread' | 'read' | 'dismissed'; export interface NotificationData { /** Reference to related protest */ protestId?: string; /** Reference to related race */ raceId?: string; /** Reference to related league */ leagueId?: string; /** Reference to related driver (e.g., who filed protest) */ actorDriverId?: string; /** Reference to related penalty */ penaltyId?: string; /** Reference to related team */ teamId?: string; /** Deadline for action (e.g., defense deadline) */ deadline?: Date; /** Any additional context data */ [key: string]: unknown; } export interface NotificationProps { id: string; /** Driver who receives this notification */ recipientId: string; /** Type of notification */ type: NotificationType; /** Human-readable title */ title: string; /** Notification body/message */ body: string; /** Channel this notification was/will be sent through */ channel: NotificationChannel; /** Current status */ status: NotificationStatus; /** Structured data for linking/context */ data?: NotificationData; /** Optional action URL */ actionUrl?: string; /** When the notification was created */ createdAt: Date; /** When the notification was read (if applicable) */ readAt?: Date; } export class Notification { private constructor(private readonly props: NotificationProps) {} static create(props: Omit & { status?: NotificationStatus; createdAt?: Date; }): Notification { if (!props.id) throw new Error('Notification ID is required'); if (!props.recipientId) throw new Error('Recipient ID is required'); if (!props.type) throw new Error('Notification type is required'); if (!props.title?.trim()) throw new Error('Notification title is required'); if (!props.body?.trim()) throw new Error('Notification body is required'); if (!props.channel) throw new Error('Notification channel is required'); return new Notification({ ...props, status: props.status ?? 'unread', createdAt: props.createdAt ?? new Date(), }); } get id(): string { return this.props.id; } get recipientId(): string { return this.props.recipientId; } get type(): NotificationType { return this.props.type; } get title(): string { return this.props.title; } get body(): string { return this.props.body; } get channel(): NotificationChannel { return this.props.channel; } get status(): NotificationStatus { return this.props.status; } get data(): NotificationData | undefined { return this.props.data ? { ...this.props.data } : undefined; } get actionUrl(): string | undefined { return this.props.actionUrl; } get createdAt(): Date { return this.props.createdAt; } get readAt(): Date | undefined { return this.props.readAt; } isUnread(): boolean { return this.props.status === 'unread'; } isRead(): boolean { return this.props.status === 'read'; } isDismissed(): boolean { return this.props.status === 'dismissed'; } /** * Mark the notification as read */ markAsRead(): Notification { if (this.props.status !== 'unread') { return this; // Already read or dismissed, no change } return new Notification({ ...this.props, status: 'read', readAt: new Date(), }); } /** * Dismiss the notification */ dismiss(): Notification { if (this.props.status === 'dismissed') { return this; // Already dismissed } return new Notification({ ...this.props, status: 'dismissed', readAt: this.props.readAt ?? new Date(), }); } /** * Convert to plain object for serialization */ toJSON(): NotificationProps { return { ...this.props }; } }