Format admin notifications with unified subject prefix

This commit is contained in:
Remi
2025-09-17 17:45:05 +02:00
committed by itsrubberduck
parent 8366845f15
commit 09481b6bfa
4 changed files with 179 additions and 33 deletions

View File

@@ -44,14 +44,18 @@ export default defineEventHandler(async (event) => {
consentPrivacy: true,
})
const lines = [
`Titel: ${title}`,
`Beschreibung: ${details}`,
`Kontakt: ${email || '—'}`,
allowContact ? 'Kontaktaufnahme erwünscht' : 'Keine Kontaktaufnahme erwünscht',
const dataEntries = [
['Titel', title],
['Beschreibung', details],
['E-Mail', email || null],
['Kontaktaufnahme erlaubt', allowContact],
]
await sendAdminNotification('[OpenSquawk] Neuer Roadmap-Vorschlag', lines.join('\n'))
await sendAdminNotification({
event: 'Neuer Roadmap-Vorschlag',
summary: `Neuer Roadmap-Vorschlag: ${title}`,
data: dataEntries,
})
return {
success: true,

View File

@@ -37,12 +37,16 @@ export default defineEventHandler(async (event) => {
})
if (result.created) {
const lines = [
`E-Mail: ${email}`,
name ? `Name: ${name}` : null,
`Quelle: ${source}`,
].filter(Boolean)
await sendAdminNotification('[OpenSquawk] Neue Updates-Liste', lines.join('\n'))
const dataEntries = [
['E-Mail', email],
['Name', name || null],
['Quelle', source],
]
await sendAdminNotification({
event: 'Neue Updates-Liste',
summary: `Neue Updates-Anmeldung: ${email}`,
data: dataEntries,
})
}
return {

View File

@@ -56,16 +56,23 @@ export default defineEventHandler(async (event) => {
})
if (!previouslyWantedUpdates && updateResult.created) {
const lines = [
`E-Mail: ${email}`,
name ? `Name: ${name}` : null,
notes ? `Notizen: ${notes}` : null,
`Quelle: ${source}`,
].filter(Boolean)
await sendAdminNotification(
'[OpenSquawk] Neue Updates-Liste (via Warteliste)',
`${lines.join('\n')}\nOpt-In: Produkt-Updates`,
)
const dataEntries = [
['E-Mail', email],
]
if (name) {
dataEntries.push(['Name', name])
}
if (notes) {
dataEntries.push(['Notizen', notes])
}
dataEntries.push(['Quelle', source])
dataEntries.push(['Opt-In', 'Produkt-Updates'])
await sendAdminNotification({
event: 'Neue Updates-Liste (via Warteliste)',
summary: `Produkt-Updates Opt-in (Warteliste): ${email}`,
data: dataEntries,
})
}
}
@@ -98,14 +105,23 @@ export default defineEventHandler(async (event) => {
})
}
const lines = [
`E-Mail: ${email}`,
name ? `Name: ${name}` : null,
notes ? `Notizen: ${notes}` : null,
`Quelle: ${source}`,
wantsProductUpdates ? 'Opt-In: Produkt-Updates' : 'Opt-In: nur Warteliste',
].filter(Boolean)
await sendAdminNotification('[OpenSquawk] Neue Wartelisten-Anmeldung', lines.join('\n'))
const dataEntries = [
['E-Mail', email],
]
if (name) {
dataEntries.push(['Name', name])
}
if (notes) {
dataEntries.push(['Notizen', notes])
}
dataEntries.push(['Quelle', source])
dataEntries.push(['Opt-In', wantsProductUpdates ? 'Produkt-Updates' : 'Nur Warteliste'])
await sendAdminNotification({
event: 'Neue Wartelisten-Anmeldung',
summary: `Neue Wartelisten-Anmeldung: ${email}`,
data: dataEntries,
})
return {
success: true,

View File

@@ -1,6 +1,23 @@
const ADMIN_EMAIL_FALLBACK = 'info@opensquawk.de'
const RESEND_ENDPOINT = 'https://api.resend.com/emails'
const ADMIN_NOTIFICATION_SUBJECT_PREFIX = process.env.NOTIFY_SUBJECT_PREFIX || '[OpenSquawk Web]'
type NotificationPrimitive = string | number | boolean | Date | null | undefined
type NotificationValue =
| NotificationPrimitive
| NotificationPrimitive[]
| Record<string, NotificationPrimitive>
type NotificationData = Record<string, NotificationValue> | Array<[string, NotificationValue]>
export interface AdminNotificationOptions {
event: string
summary?: string
data?: NotificationData
to?: string
from?: string
}
interface MailOptions {
to: string
@@ -122,11 +139,116 @@ export async function sendMail(options: MailOptions) {
return success
}
export async function sendAdminNotification(subject: string, text: string) {
const to = process.env.NOTIFY_EMAIL_TO || ADMIN_EMAIL_FALLBACK
const success = await sendMailInternal({ to, subject, text })
function resolveNotificationSubject(event: string) {
const trimmedEvent = event.trim()
if (!trimmedEvent) {
return ADMIN_NOTIFICATION_SUBJECT_PREFIX
}
if (trimmedEvent.toLowerCase().startsWith(ADMIN_NOTIFICATION_SUBJECT_PREFIX.toLowerCase())) {
return trimmedEvent
}
return `${ADMIN_NOTIFICATION_SUBJECT_PREFIX} ${trimmedEvent}`
}
function toNotificationEntries(data?: NotificationData): Array<[string, NotificationValue]> {
if (!data) {
return []
}
if (Array.isArray(data)) {
return data
}
return Object.entries(data)
}
function formatNotificationValue(value: NotificationValue): string {
if (value === null || value === undefined) {
return '—'
}
if (Array.isArray(value)) {
const formatted = value
.map((entry) => formatNotificationValue(entry))
.filter((entry) => entry && entry !== '—')
return formatted.length ? formatted.join(', ') : '—'
}
if (value instanceof Date) {
return value.toISOString()
}
if (typeof value === 'object') {
return JSON.stringify(value, null, 2)
}
if (typeof value === 'boolean') {
return value ? 'Ja' : 'Nein'
}
if (typeof value === 'string') {
const trimmed = value.trim()
return trimmed ? trimmed : '—'
}
return String(value)
}
function formatDataEntries(entries: Array<[string, NotificationValue]>) {
return entries.map(([label, rawValue]) => {
const formattedValue = formatNotificationValue(rawValue)
if (formattedValue.includes('\n')) {
const indented = formattedValue
.split('\n')
.map((line) => ` ${line}`)
.join('\n')
return `${label}:\n${indented}`
}
return `${label}: ${formattedValue}`
})
}
function buildAdminNotificationText(options: AdminNotificationOptions) {
const lines: string[] = []
const summary = options.summary?.trim()
if (summary) {
lines.push(summary)
} else {
lines.push(`Neue Aktivität auf der Website: ${options.event}`)
}
const dataEntries = formatDataEntries([
['Ereignis', options.event],
...toNotificationEntries(options.data),
])
if (dataEntries.length) {
lines.push('')
lines.push('Details:')
lines.push(...dataEntries)
}
return lines.join('\n')
}
export async function sendAdminNotification(options: AdminNotificationOptions) {
const to = options.to || process.env.NOTIFY_EMAIL_TO || ADMIN_EMAIL_FALLBACK
const subject = resolveNotificationSubject(options.event)
const text = buildAdminNotificationText(options)
const success = await sendMailInternal({
to,
subject,
text,
from: options.from,
})
if (!success) {
console.info(`[notify:fallback] ${subject}\nEmpfänger: ${to}\n${text}`)
}
return success
}