import { prisma } from "../db"; import { sendTo } from "../mail"; /* Notices to the owner, by email, over the SMTP the product already has. * * Who gets them: OPS_ALERT_TO when set, otherwise every active OWNER operator. What they carry: * the name of a facility and what happened to it — never a coordinator's name or email, never a * wearer, never a record. A notice must be safe to sit in a mailbox. Each caller names its own * condition; nothing here fires on a timer, so there is nothing to dedupe — one event, one mail. * * Never throws and never awaited for its own sake: a notice that could fail the action it * describes would be a reason to skip sending it. */ export async function ownerAddresses(): Promise { const fixed = (process.env.OPS_ALERT_TO || "").split(",").map((s) => s.trim()).filter(Boolean); if (fixed.length) return fixed; try { const owners = await prisma.operator.findMany({ where: { role: "OWNER", inactive: false }, select: { email: true } }); return owners.map((o) => o.email); } catch { return []; } } export function notifyOwners(subject: string, text: string): void { ownerAddresses() .then((to) => Promise.all(to.map((a) => sendTo(a, subject, text)))) .catch(() => { /* a notice must not fail the action */ }); } const when = () => new Date().toLocaleString("en-AU", { timeZone: "Australia/Brisbane", dateStyle: "medium", timeStyle: "short" }); /** A facility signed up. Name only — the person who did it is a contact, and contacts are revealed, not mailed. */ export function alertNewSignup(facility: { id: string; name: string }): void { notifyOwners(`[ops] New facility — ${facility.name}`, [ `A facility signed up for ThreadCount.`, "", `Facility: ${facility.name}`, `When: ${when()} (Brisbane)`, `Console: https://ops.threadcount.tech/ops/facilities/${facility.id}`, "", "It appears on the console as \"never set up\" until its staff groups are named.", ].join("\n")); } /** A facility was deleted from the console. Sent to every owner so a second operator's deletion reaches the first. */ export function alertFacilityDeleted(args: { name: string; by: string; ip: string; counts: string }): void { notifyOwners(`[ops] Facility deleted — ${args.name}`, [ `${args.by} deleted a facility from the ThreadCount operations console.`, "", `Facility: ${args.name}`, `Had: ${args.counts}`, `When: ${when()} (Brisbane)`, `From: ${args.ip || "unknown address"}`, "", "Every record, image and account belonging to it is gone. The pre-deploy database dumps on the box are the only copies left.", ].join("\n")); }