wip
This commit is contained in:
@@ -5,10 +5,11 @@
|
||||
* Immutable entity with factory methods and domain validation.
|
||||
*/
|
||||
|
||||
import type { IEntity } from '@gridpilot/shared/domain';
|
||||
import { NotificationDomainError } from '../errors/NotificationDomainError';
|
||||
import { NotificationId } from '../value-objects/NotificationId';
|
||||
|
||||
import type { NotificationType } from '../value-objects/NotificationType';
|
||||
import type { NotificationChannel } from '../value-objects/NotificationChannel';
|
||||
import type { NotificationType, NotificationChannel } from '../types/NotificationTypes';
|
||||
|
||||
export type NotificationStatus = 'unread' | 'read' | 'dismissed' | 'action_required';
|
||||
|
||||
@@ -54,7 +55,7 @@ export interface NotificationAction {
|
||||
}
|
||||
|
||||
export interface NotificationProps {
|
||||
id: string;
|
||||
id: NotificationId;
|
||||
/** Driver who receives this notification */
|
||||
recipientId: string;
|
||||
/** Type of notification */
|
||||
@@ -85,15 +86,17 @@ export interface NotificationProps {
|
||||
respondedAt?: Date;
|
||||
}
|
||||
|
||||
export class Notification {
|
||||
export class Notification implements IEntity<string> {
|
||||
private constructor(private readonly props: NotificationProps) {}
|
||||
|
||||
static create(props: Omit<NotificationProps, 'status' | 'createdAt' | 'urgency'> & {
|
||||
static create(props: Omit<NotificationProps, 'id' | 'status' | 'createdAt' | 'urgency'> & {
|
||||
id: string;
|
||||
status?: NotificationStatus;
|
||||
createdAt?: Date;
|
||||
urgency?: NotificationUrgency;
|
||||
}): Notification {
|
||||
if (!props.id) throw new NotificationDomainError('Notification ID is required');
|
||||
const id = NotificationId.create(props.id);
|
||||
|
||||
if (!props.recipientId) throw new NotificationDomainError('Recipient ID is required');
|
||||
if (!props.type) throw new NotificationDomainError('Notification type is required');
|
||||
if (!props.title?.trim()) throw new NotificationDomainError('Notification title is required');
|
||||
@@ -105,13 +108,14 @@ export class Notification {
|
||||
|
||||
return new Notification({
|
||||
...props,
|
||||
id,
|
||||
status: props.status ?? defaultStatus,
|
||||
urgency: props.urgency ?? 'silent',
|
||||
createdAt: props.createdAt ?? new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
get id(): string { return this.props.id; }
|
||||
get id(): string { return this.props.id.value; }
|
||||
get recipientId(): string { return this.props.recipientId; }
|
||||
get type(): NotificationType { return this.props.type; }
|
||||
get title(): string { return this.props.title; }
|
||||
@@ -210,7 +214,10 @@ export class Notification {
|
||||
/**
|
||||
* Convert to plain object for serialization
|
||||
*/
|
||||
toJSON(): NotificationProps {
|
||||
return { ...this.props };
|
||||
toJSON(): Omit<NotificationProps, 'id'> & { id: string } {
|
||||
return {
|
||||
...this.props,
|
||||
id: this.props.id.value,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,10 @@
|
||||
* Represents a user's notification preferences for different channels and types.
|
||||
*/
|
||||
|
||||
import type { NotificationType } from '../value-objects/NotificationType';
|
||||
import type { NotificationChannel } from '../value-objects/NotificationChannel';
|
||||
import type { IEntity } from '@gridpilot/shared/domain';
|
||||
import type { NotificationType, NotificationChannel } from '../types/NotificationTypes';
|
||||
import { NotificationDomainError } from '../errors/NotificationDomainError';
|
||||
import { DEFAULT_ENABLED_CHANNELS } from '../value-objects/NotificationChannel';
|
||||
import { QuietHours } from '../value-objects/QuietHours';
|
||||
|
||||
export interface ChannelPreference {
|
||||
/** Whether this channel is enabled */
|
||||
@@ -24,6 +24,8 @@ export interface TypePreference {
|
||||
}
|
||||
|
||||
export interface NotificationPreferenceProps {
|
||||
/** Aggregate ID for this preference (usually same as driverId) */
|
||||
id: string;
|
||||
/** Driver ID this preference belongs to */
|
||||
driverId: string;
|
||||
/** Global channel preferences */
|
||||
@@ -42,10 +44,13 @@ export interface NotificationPreferenceProps {
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export class NotificationPreference {
|
||||
export class NotificationPreference implements IEntity<string> {
|
||||
private constructor(private readonly props: NotificationPreferenceProps) {}
|
||||
|
||||
static create(props: Omit<NotificationPreferenceProps, 'updatedAt'> & { updatedAt?: Date }): NotificationPreference {
|
||||
static create(
|
||||
props: Omit<NotificationPreferenceProps, 'updatedAt'> & { updatedAt?: Date },
|
||||
): NotificationPreference {
|
||||
if (!props.id) throw new NotificationDomainError('Preference ID is required');
|
||||
if (!props.driverId) throw new NotificationDomainError('Driver ID is required');
|
||||
if (!props.channels) throw new NotificationDomainError('Channel preferences are required');
|
||||
|
||||
@@ -60,6 +65,7 @@ export class NotificationPreference {
|
||||
*/
|
||||
static createDefault(driverId: string): NotificationPreference {
|
||||
return new NotificationPreference({
|
||||
id: driverId,
|
||||
driverId,
|
||||
channels: {
|
||||
in_app: { enabled: true },
|
||||
@@ -72,6 +78,7 @@ export class NotificationPreference {
|
||||
});
|
||||
}
|
||||
|
||||
get id(): string { return this.props.id; }
|
||||
get driverId(): string { return this.props.driverId; }
|
||||
get channels(): Record<NotificationChannel, ChannelPreference> { return { ...this.props.channels }; }
|
||||
get typePreferences(): Partial<Record<NotificationType, TypePreference>> | undefined {
|
||||
@@ -83,6 +90,13 @@ export class NotificationPreference {
|
||||
get quietHoursEnd(): number | undefined { return this.props.quietHoursEnd; }
|
||||
get updatedAt(): Date { return this.props.updatedAt; }
|
||||
|
||||
get quietHours(): QuietHours | undefined {
|
||||
if (this.props.quietHoursStart === undefined || this.props.quietHoursEnd === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return QuietHours.create(this.props.quietHoursStart, this.props.quietHoursEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a specific channel is enabled
|
||||
*/
|
||||
@@ -117,20 +131,13 @@ export class NotificationPreference {
|
||||
* Check if current time is in quiet hours
|
||||
*/
|
||||
isInQuietHours(): boolean {
|
||||
if (this.props.quietHoursStart === undefined || this.props.quietHoursEnd === undefined) {
|
||||
const quietHours = this.quietHours;
|
||||
if (!quietHours) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const currentHour = now.getHours();
|
||||
|
||||
if (this.props.quietHoursStart < this.props.quietHoursEnd) {
|
||||
// Normal range (e.g., 22:00 to 07:00 next day is NOT this case)
|
||||
return currentHour >= this.props.quietHoursStart && currentHour < this.props.quietHoursEnd;
|
||||
} else {
|
||||
// Overnight range (e.g., 22:00 to 07:00)
|
||||
return currentHour >= this.props.quietHoursStart || currentHour < this.props.quietHoursEnd;
|
||||
}
|
||||
return quietHours.containsHour(now.getHours());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,10 +172,12 @@ export class NotificationPreference {
|
||||
* Update quiet hours
|
||||
*/
|
||||
updateQuietHours(start: number | undefined, end: number | undefined): NotificationPreference {
|
||||
const validated = start === undefined || end === undefined ? undefined : QuietHours.create(start, end);
|
||||
|
||||
return new NotificationPreference({
|
||||
...this.props,
|
||||
quietHoursStart: start,
|
||||
quietHoursEnd: end,
|
||||
quietHoursStart: validated?.props.startHour,
|
||||
quietHoursEnd: validated?.props.endHour,
|
||||
updatedAt: new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user