feat: Wabenhain Honig-Shop Anwendung

Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei:
Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich
(Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare
Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung,
PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma-
Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
This commit is contained in:
Nick Szaunig committed 2026-08-17 14:30:28 +02:00
1 parent 5c76fcc266
commit c7927f2883
240 files changed
+26096 -137

No files matched your search

+74
View File
@@ -0,0 +1,74 @@
import nodemailer from "nodemailer";
interface SendMailInput {
to: string;
subject: string;
html: string;
text: string;
}
/** Maskiert eine E-Mail-Adresse fuer Log-Ausgaben, z.B. "ma***@example.com". */
function maskEmail(email: string): string {
const [local, domain] = email.split("@");
if (!domain) return "***";
return `${local.slice(0, 2)}***@${domain}`;
}
let cachedTransport: ReturnType<typeof nodemailer.createTransport> | null = null;
// Nur einmal geprueft (nicht bei jedem sendMail-Aufruf) — macht ein in
// Produktion fehlendes SMTP-Setup im Server-Log sichtbar, statt dass
// Bestellbestaetigungen/Passwort-Resets fortlaufend lautlos verschwinden.
let warnedAboutMissingSmtpInProduction = false;
function getTransport() {
if (cachedTransport) return cachedTransport;
const host = process.env.SMTP_HOST;
const port = Number(process.env.SMTP_PORT ?? 587);
const user = process.env.SMTP_USER;
const pass = process.env.SMTP_PASSWORD;
if (!host || !user || !pass) {
if (process.env.NODE_ENV === "production" && !warnedAboutMissingSmtpInProduction) {
warnedAboutMissingSmtpInProduction = true;
console.warn(
"SMTP_HOST/SMTP_USER/SMTP_PASSWORD sind in Produktion nicht gesetzt — Bestellbestaetigungen, Passwort-Resets und weitere Transaktions-E-Mails werden nur geloggt, nicht versendet."
);
}
return null;
}
cachedTransport = nodemailer.createTransport({
host,
port,
secure: port === 465,
auth: { user, pass },
});
return cachedTransport;
}
/**
* Versendet eine E-Mail ueber den in SMTP_HOST/SMTP_USER/SMTP_PASSWORD
* konfigurierten Mailserver. Ohne Konfiguration wird die Mail NICHT
* gesendet, sondern nur geloggt — analog zum Stripe-Fallback ohne Keys,
* damit lokale Entwicklung und Deployments ohne Mail-Setup nicht abstuerzen.
* Wirft absichtlich keinen Fehler: ein Mail-Ausfall darf eine Bestellung
* oder Registrierung nicht verhindern.
*/
export async function sendMail(input: SendMailInput): Promise<void> {
const transport = getTransport();
const from = process.env.SMTP_FROM || "Wabenhain <[email protected]>";
if (!transport) {
console.warn(
`[mail] SMTP nicht konfiguriert — E-Mail an ${maskEmail(input.to)} ("${input.subject}") wurde nicht gesendet.`
);
return;
}
try {
await transport.sendMail({ from, to: input.to, subject: input.subject, html: input.html, text: input.text });
} catch (error: unknown) {
const message = error instanceof Error ? error.message : "Unbekannter Fehler";
console.error(`[mail] Versand an ${maskEmail(input.to)} fehlgeschlagen: ${message}`);
}
}