UNPKG

alepha

Version:

Easy-to-use modern TypeScript framework for building many kind of applications.

140 lines (122 loc) 4.09 kB
import { $inject, Alepha, AlephaError } from "alepha"; import { EmailProvider } from "alepha/email"; import { $logger } from "alepha/logger"; import { SmsProvider } from "alepha/sms"; import { $notification } from "../primitives/$notification.ts"; import type { NotificationPayload } from "../schemas/notificationPayloadSchema.ts"; type NotificationMessageEmail = { subject: string; body: string | ((variables: any) => string); }; type NotificationMessageSms = { message: string | ((variables: any) => string); }; export class NotificationSenderService { protected readonly alepha = $inject(Alepha); protected readonly log = $logger(); protected readonly emailProvider = $inject(EmailProvider); protected readonly smsProvider = $inject(SmsProvider); public async send(payload: NotificationPayload) { this.log.debug("Processing notification", { type: payload.type, template: payload.template, contact: payload.contact, }); if (payload.type === "email") { const rendered = this.renderEmail(payload); await this.emailProvider.send(rendered); this.log.info("Email notification sent", { template: payload.template, contact: payload.contact, }); return { type: "email" as const, to: rendered.to, subject: rendered.subject, body: rendered.body, }; } if (payload.type === "sms") { const rendered = this.renderSms(payload); await this.smsProvider.send(rendered); this.log.info("SMS notification sent", { template: payload.template, contact: payload.contact, }); return { type: "sms" as const, to: rendered.to, message: rendered.message, }; } } public renderSms(payload: NotificationPayload) { const { variables, contact, template } = this.load(payload); const sms = this.translation(template.options, payload.lang)?.sms ?? template.options.sms; if (!sms) { throw new AlephaError( `Notification template ${payload.template} has no sms defined`, ); } const message = typeof sms.message === "function" ? sms.message(variables as any) : sms.message; return { to: contact, message }; } public renderEmail(payload: NotificationPayload) { const { variables, contact, template } = this.load(payload); const email = this.translation(template.options, payload.lang)?.email ?? template.options.email; if (!email) { throw new AlephaError( `Notification template ${payload.template} has no email defined`, ); } const subject = email.subject; const body = typeof email.body === "function" ? email.body(variables as any) : email.body; return { to: contact, subject, body }; } /** * Pick the template's `translations` entry for the payload language: * exact match ("fr-FR") first, then the base language ("fr"). Returns * undefined when there is no match — callers fall back to the default * (template-level) message. */ protected translation( options: { translations?: Record< string, { email?: unknown; sms?: unknown } | undefined >; }, lang?: string, ): | { email?: NotificationMessageEmail; sms?: NotificationMessageSms } | undefined { if (!lang || !options.translations) return undefined; const exact = options.translations[lang]; if (exact) return exact as any; const base = lang.split("-")[0]?.toLowerCase(); return base ? (options.translations[base] as any) : undefined; } protected load(payload: NotificationPayload) { const variables = payload.variables || {}; const contact = payload.contact; const template = this.alepha .primitives($notification) .find((it) => it.name === payload.template); if (!template) { throw new AlephaError( `No notification template found for ${payload.template}`, ); } return { template, variables, contact }; } }