Files
order_site/apps/backend/src/repositories/admin-notification-repo.ts
T

158 lines
4.0 KiB
TypeScript

import { query } from '../db/client.js'
export type AdminNotificationRow = {
id: number
notification_type: string
priority: string
entity_type: string
entity_id: number
dedupe_key: string
title: string
body: string
action_path: string
payload_json: string | Record<string, unknown>
visible: boolean
sound_enabled: boolean
reminder_count: number
last_reminded_at: string | null
status: string
resolved_at: string | null
resolution_reason: string
created_at: string
updated_at: string
}
export async function createAdminNotification(input: {
notificationType: string
priority: string
entityType: string
entityId: number
dedupeKey: string
title: string
body: string
actionPath: string
payloadJson?: string
visible?: boolean
soundEnabled?: boolean
now: string
}): Promise<AdminNotificationRow | null> {
const result = await query<AdminNotificationRow>(
`
INSERT INTO admin_notifications (
notification_type, priority, entity_type, entity_id, dedupe_key,
title, body, action_path, payload_json, visible, sound_enabled, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10, $11, $12, $12)
ON CONFLICT (dedupe_key) DO NOTHING
RETURNING *
`,
[
input.notificationType,
input.priority,
input.entityType,
input.entityId,
input.dedupeKey,
input.title,
input.body,
input.actionPath,
input.payloadJson || '{}',
input.visible !== false,
input.soundEnabled !== false,
input.now,
],
)
return result.rows[0] || null
}
export async function getPendingAdminNotificationByDedupeKey(
dedupeKey: string,
): Promise<AdminNotificationRow | null> {
const result = await query<AdminNotificationRow>(
`SELECT * FROM admin_notifications WHERE dedupe_key = $1 AND status = 'pending' LIMIT 1`,
[dedupeKey],
)
return result.rows[0] || null
}
export async function bumpAdminNotificationReminder(input: {
dedupeKey: string
notificationType: string
title: string
visible: boolean
soundEnabled: boolean
now: string
}): Promise<AdminNotificationRow | null> {
const result = await query<AdminNotificationRow>(
`
UPDATE admin_notifications
SET
notification_type = $1,
title = $2,
priority = 'high',
visible = visible OR $3,
sound_enabled = $4,
reminder_count = reminder_count + 1,
last_reminded_at = $5,
updated_at = $5
WHERE dedupe_key = $6
AND status = 'pending'
AND reminder_count < 3
AND (
last_reminded_at IS NULL
OR last_reminded_at <= $5::timestamptz - INTERVAL '30 minutes'
)
RETURNING *
`,
[
input.notificationType,
input.title,
input.visible,
input.soundEnabled,
input.now,
input.dedupeKey,
],
)
return result.rows[0] || null
}
export async function resolveAdminNotificationsByEntity(input: {
entityType: string
entityId: number
reason: string
now: string
}): Promise<void> {
await query(
`
UPDATE admin_notifications
SET status = 'resolved', resolved_at = $1, resolution_reason = $2, updated_at = $1
WHERE entity_type = $3 AND entity_id = $4 AND status = 'pending'
`,
[input.now, input.reason, input.entityType, input.entityId],
)
}
export async function listPendingAdminNotifications(
limit: number,
): Promise<AdminNotificationRow[]> {
const result = await query<AdminNotificationRow>(
`
SELECT *
FROM admin_notifications
WHERE status = 'pending' AND visible = TRUE
ORDER BY
CASE priority WHEN 'urgent' THEN 3 WHEN 'high' THEN 2 ELSE 1 END DESC,
updated_at DESC,
id DESC
LIMIT $1
`,
[limit],
)
return result.rows
}
export async function countPendingAdminNotifications(): Promise<number> {
const result = await query<{ total: number }>(
`SELECT COUNT(*)::int AS total FROM admin_notifications WHERE status = 'pending' AND visible = TRUE`,
)
return Number(result.rows[0]?.total || 0)
}