import { fail, redirect } from '@sveltejs/kit'; import { getSessionUser } from '$lib/server/auth/session'; import { getDatabase } from '$lib/server/database'; import { getPagination } from '$lib/server/pagination'; import { NOTIFICATION_CATEGORIES } from '$lib/server/notifications'; import type { Actions, PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ cookies, url }) => { const user = await getSessionUser(cookies); if (!user) redirect(303, '/sign-in'); const database = await getDatabase(); const filtering = url.searchParams.get('filtering') === '1'; const categories = filtering ? url.searchParams.getAll('channel').filter((value) => NOTIFICATION_CATEGORIES.includes(value as any)) : [...NOTIFICATION_CATEGORIES]; const states = filtering ? url.searchParams.getAll('state').filter((value) => value === 'unread' || value === 'read') : ['unread', 'read']; const conditions = ['n.user_id=?', '(n.mandatory=1 OR COALESCE(p.in_app_enabled,1)=1)']; const args: unknown[] = [user.id]; if (categories.length === 0 || states.length === 0) { conditions.push('1=0'); } else { if (categories.length < NOTIFICATION_CATEGORIES.length) { conditions.push(`n.category IN (${categories.map(() => '?').join(',')})`); args.push(...categories); } if (states.length === 1) conditions.push(states[0] === 'unread' ? 'n.read_at IS NULL' : 'n.read_at IS NOT NULL'); } const where = `WHERE ${conditions.join(' AND ')}`; const total = Number((await (await database.prepare(`SELECT COUNT(*) AS total FROM notifications n LEFT JOIN notification_preferences p ON p.user_id=n.user_id AND p.category=n.category ${where}`)).get(...args))?.total ?? 0); const pagination = getPagination(url, total); const notifications = await (await database.prepare(`SELECT n.* FROM notifications n LEFT JOIN notification_preferences p ON p.user_id=n.user_id AND p.category=n.category ${where} ORDER BY n.created_at DESC LIMIT ? OFFSET ?`)).all(...args, pagination.perPage, pagination.offset); const preferenceRows = await (await database.prepare('SELECT * FROM notification_preferences WHERE user_id=?')).all(user.id); const preferences = NOTIFICATION_CATEGORIES.map((name) => { const stored = preferenceRows.find((row) => row.category === name); return { category: name, inApp: name === 'isac' ? true : stored?.in_app_enabled === undefined ? true : Boolean(stored.in_app_enabled), browser: Boolean(stored?.browser_enabled), emailFrequency: String(stored?.email_frequency ?? 'none') }; }); const unread = Number((await (await database.prepare(`SELECT COUNT(*) AS count FROM notifications n LEFT JOIN notification_preferences p ON p.user_id=n.user_id AND p.category=n.category WHERE n.user_id=? AND n.read_at IS NULL AND (n.mandatory=1 OR COALESCE(p.in_app_enabled,1)=1)`)).get(user.id))?.count ?? 0); return { notifications, preferences, pagination, filters: { categories, states }, unread, hasVerifiedEmail: Boolean(await (await database.prepare('SELECT 1 FROM user_emails WHERE user_id=?')).get(user.id)) }; }; export const actions: Actions = { markRead: async ({ cookies, request }) => { const user = await getSessionUser(cookies); if (!user) redirect(303, '/sign-in'); const id = String((await request.formData()).get('id') ?? ''); await (await (await getDatabase()).prepare('UPDATE notifications SET read_at=COALESCE(read_at,?) WHERE id=? AND user_id=?')).run(new Date().toISOString(), id, user.id); return { notificationUpdated: true }; }, markAllRead: async ({ cookies }) => { const user = await getSessionUser(cookies); if (!user) redirect(303, '/sign-in'); await (await (await getDatabase()).prepare('UPDATE notifications SET read_at=? WHERE user_id=? AND read_at IS NULL')).run(new Date().toISOString(), user.id); return { notificationsCleared: true }; }, savePreferences: async ({ cookies, request }) => { const user = await getSessionUser(cookies); if (!user) redirect(303, '/sign-in'); const form = await request.formData(); const database = await getDatabase(); const now = new Date().toISOString(); const hasEmail = Boolean(await (await database.prepare('SELECT 1 FROM user_emails WHERE user_id=?')).get(user.id)); for (const category of NOTIFICATION_CATEGORIES) { const inApp = category === 'isac' || form.get(`inApp:${category}`) === 'on'; const browser = form.get(`browser:${category}`) === 'on'; const emailFrequency = String(form.get(`email:${category}`) ?? 'none'); if (!['none', 'immediate', 'daily'].includes(emailFrequency)) return fail(400, { notificationPreferenceError: 'Choose a valid email frequency.' }); if (emailFrequency !== 'none' && !hasEmail) return fail(400, { notificationPreferenceError: 'Add and verify a magic-link email before enabling email notifications.' }); await (await database.prepare(`INSERT INTO notification_preferences(user_id,category,in_app_enabled,browser_enabled,email_frequency,updated_at) VALUES(?,?,?,?,?,?) ON CONFLICT(user_id,category) DO UPDATE SET in_app_enabled=excluded.in_app_enabled,browser_enabled=excluded.browser_enabled,email_frequency=excluded.email_frequency,updated_at=excluded.updated_at`)) .run(user.id, category, inApp ? 1 : 0, browser ? 1 : 0, emailFrequency, now); } return { notificationPreferencesSaved: true, notificationPreferenceSaveToken: crypto.randomUUID() }; } };