All files / services/notification preferences.service.ts

100% Statements 18/18
100% Branches 8/8
100% Functions 4/4
100% Lines 18/18

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80                                14x           7x           7x 7x 126x 378x 267x   378x               7x                 6x 6x 59x     2x   57x     1x   56x             3x           3x      
import type { Dal } from "../../dal";
import {
	NOTIFICATION_EVENT_TYPES,
	NOTIFICATION_CHANNELS,
} from "../../db/schema";
 
type EventType = (typeof NOTIFICATION_EVENT_TYPES)[number];
type Channel = (typeof NOTIFICATION_CHANNELS)[number];
 
export type EffectivePreference = {
	eventType: string;
	channel: string;
	enabled: boolean;
};
 
export class NotificationPreferencesService {
	constructor(private dal: Dal) {}
 
	async getEffectivePreferences(
		userId: string,
		proId: string,
	): Promise<EffectivePreference[]> {
		const stored = await this.dal.notificationPreferences.findByUserPro(
			userId,
			proId,
		);
 
		// Build full matrix with defaults (all ON), overlay stored preferences
		const matrix: EffectivePreference[] = [];
		for (const eventType of NOTIFICATION_EVENT_TYPES) {
			for (const channel of NOTIFICATION_CHANNELS) {
				const pref = stored.find(
					(p) => p.eventType === eventType && p.channel === channel,
				);
				matrix.push({
					eventType,
					channel,
					enabled: pref ? pref.enabled : true,
				});
			}
		}
 
		return matrix;
	}
 
	async updatePreferences(
		userId: string,
		proId: string,
		prefs: { eventType: string; channel: string; enabled: boolean }[],
	): Promise<EffectivePreference[]> {
		// Validate and cast all entries
		const validated: { eventType: EventType; channel: Channel; enabled: boolean }[] = [];
		for (const pref of prefs) {
			if (
				!NOTIFICATION_EVENT_TYPES.includes(pref.eventType as EventType)
			) {
				throw new Error(`Invalid event type: ${pref.eventType}`);
			}
			if (
				!NOTIFICATION_CHANNELS.includes(pref.channel as Channel)
			) {
				throw new Error(`Invalid channel: ${pref.channel}`);
			}
			validated.push({
				eventType: pref.eventType as EventType,
				channel: pref.channel as Channel,
				enabled: pref.enabled,
			});
		}
 
		await this.dal.notificationPreferences.bulkUpsert(
			userId,
			proId,
			validated,
		);
 
		return this.getEffectivePreferences(userId, proId);
	}
}