feat(notifications): alert oversight review queues
This commit is contained in:
@@ -3,6 +3,8 @@ 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 { getPermissions } from '$lib/server/auth/authorization';
|
||||
import { OVERSIGHT_NOTIFICATION_CATEGORIES, OVERSIGHT_NOTIFICATION_FREQUENCIES } from '$lib/server/oversight-notifications';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
@@ -34,6 +36,17 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
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 permissions = await getPermissions(user);
|
||||
const oversightCategories = OVERSIGHT_NOTIFICATION_CATEGORIES.filter(
|
||||
(category) => permissions.includes('*') || permissions.includes(category.permission)
|
||||
);
|
||||
const oversightRows = oversightCategories.length
|
||||
? await (await database.prepare('SELECT * FROM oversight_notification_preferences WHERE user_id=?')).all(user.id)
|
||||
: [];
|
||||
const oversightPreferences = oversightCategories.map((category) => ({
|
||||
...category,
|
||||
frequency: String(oversightRows.find((row) => row.oversight_category === category.id)?.frequency ?? 'immediate')
|
||||
}));
|
||||
const preferences = NOTIFICATION_CATEGORIES.map((name) => {
|
||||
const stored = preferenceRows.find((row) => row.category === name);
|
||||
return {
|
||||
@@ -46,7 +59,7 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
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)) };
|
||||
return { notifications, preferences, oversightPreferences, 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 = {
|
||||
@@ -85,5 +98,30 @@ export const actions: Actions = {
|
||||
notificationPreferencesSaved: true,
|
||||
notificationPreferenceSaveToken: crypto.randomUUID()
|
||||
};
|
||||
},
|
||||
saveOversightPreferences: async ({ cookies, request }) => {
|
||||
const user = await getSessionUser(cookies);
|
||||
if (!user) redirect(303, '/sign-in');
|
||||
const permissions = await getPermissions(user);
|
||||
const available = OVERSIGHT_NOTIFICATION_CATEGORIES.filter(
|
||||
(category) => permissions.includes('*') || permissions.includes(category.permission)
|
||||
);
|
||||
if (!available.length) return fail(403, { oversightPreferenceError: 'Oversight permission is required.' });
|
||||
const form = await request.formData();
|
||||
const database = await getDatabase();
|
||||
const now = new Date().toISOString();
|
||||
for (const category of available) {
|
||||
const frequency = String(form.get(`oversight:${category.id}`) ?? 'immediate');
|
||||
if (!OVERSIGHT_NOTIFICATION_FREQUENCIES.includes(frequency as any)) {
|
||||
return fail(400, { oversightPreferenceError: 'Choose a valid Oversight notification frequency.' });
|
||||
}
|
||||
await (await database.prepare(`INSERT INTO oversight_notification_preferences(user_id,oversight_category,frequency,updated_at)
|
||||
VALUES(?,?,?,?) ON CONFLICT(user_id,oversight_category) DO UPDATE SET
|
||||
frequency=excluded.frequency,updated_at=excluded.updated_at`)).run(user.id, category.id, frequency, now);
|
||||
}
|
||||
return {
|
||||
oversightPreferencesSaved: true,
|
||||
notificationPreferenceSaveToken: crypto.randomUUID()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
if (notification.category === 'guides') return { label: 'OPEN GUIDE', href: notification.link };
|
||||
if (notification.category === 'builds') return { label: 'OPEN BUILD', href: notification.link };
|
||||
if (notification.source === 'SHD CONTACT RELAY') return { label: notification.title.startsWith('DMCA NOTICE') ? 'REVIEW COPYRIGHT REQUEST' : 'OPEN CONTACT MESSAGE', href: notification.link };
|
||||
if (notification.source === 'ISAC OVERSIGHT NETWORK') return { label: 'OPEN OVERSIGHT QUEUE', href: notification.link };
|
||||
if (notification.link === '/account') return { label: 'OPEN AGENT PROFILE', href: notification.link };
|
||||
return { label: 'OPEN RELATED PAGE', href: notification.link };
|
||||
};
|
||||
@@ -83,6 +84,34 @@
|
||||
{#if form?.notificationPreferencesSaved}<p class="success">Notification preferences synchronized.</p>{/if}
|
||||
<div class="form-actions"><button class="primary">SAVE PREFERENCES →</button></div>
|
||||
</PendingForm>
|
||||
{#if data.oversightPreferences.length}
|
||||
<div class="oversight-preference-heading">
|
||||
<div><p class="eyebrow">REVIEW QUEUE ALERTS</p><h3>OVERSIGHT DELIVERY CADENCE</h3></div>
|
||||
<span>10:00 AM EASTERN DAILY DISPATCH</span>
|
||||
</div>
|
||||
<p class="notification-rule"><b>PERMISSION-AWARE DELIVERY.</b> Only queues you are authorized to review appear below. Consolidated alerts combine new review items by queue.</p>
|
||||
<PendingForm action="?/saveOversightPreferences" class="oversight-preference-form">
|
||||
{#each data.oversightPreferences as preference}
|
||||
<div class="oversight-preference-row">
|
||||
<div><b>{preference.label}</b><small>{preference.id === 'data_changes' ? 'CONTRIBUTED DATA AWAITING APPROVAL' : 'CUSTOM AGENT IMAGES AWAITING REVIEW'}</small></div>
|
||||
<SegmentedChoice
|
||||
legend={`${preference.label} DELIVERY`}
|
||||
name={`oversight:${preference.id}`}
|
||||
options={[
|
||||
{ value: 'immediate', label: 'IMMEDIATE' },
|
||||
{ value: '15_minutes', label: '15 MIN' },
|
||||
{ value: '1_hour', label: '1 HOUR' },
|
||||
{ value: 'daily', label: 'DAILY · 10 AM ET' }
|
||||
]}
|
||||
value={preference.frequency}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
{#if form?.oversightPreferenceError}<p class="error">{form.oversightPreferenceError}</p>{/if}
|
||||
{#if form?.oversightPreferencesSaved}<p class="success">Oversight delivery cadence synchronized.</p>{/if}
|
||||
<div class="form-actions"><button class="primary">SAVE OVERSIGHT CADENCE →</button></div>
|
||||
</PendingForm>
|
||||
{/if}
|
||||
</section>
|
||||
{/if}
|
||||
<div class="notification-toolbar">
|
||||
|
||||
Reference in New Issue
Block a user