Initial commit — Grow baby tracker
Next.js 16 App Router, Prisma + PostgreSQL, NextAuth v5 JWT. Features: dashboard quick-log, timeline, growth charts (WHO percentiles), stats, journal/notes, doctor notes, milestones, vaccinations, settings, superadmin panel. Mobile-first with sidebar nav + bottom nav + quick-add FAB. Dark mode, PWA push notifications, multi-family invite system. Docker: multi-stage Dockerfile + docker-compose with postgres service.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import { prisma } from "./prisma";
|
||||
|
||||
interface EmailPayload {
|
||||
to: string;
|
||||
subject: string;
|
||||
html: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
async function getConfig(key: string): Promise<string> {
|
||||
const row = await prisma.systemConfig.findUnique({ where: { key } });
|
||||
return row?.value ?? process.env[key.toUpperCase()] ?? "";
|
||||
}
|
||||
|
||||
async function sendMailgun(payload: EmailPayload): Promise<void> {
|
||||
const apiKey = await getConfig("mailgun_api_key");
|
||||
const domain = await getConfig("mailgun_domain");
|
||||
const from = await getConfig("mailgun_from");
|
||||
if (!apiKey || !domain) throw new Error("Mailgun non configuré");
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("from", from || `Grow <noreply@${domain}>`);
|
||||
formData.append("to", payload.to);
|
||||
formData.append("subject", payload.subject);
|
||||
formData.append("html", payload.html);
|
||||
if (payload.text) formData.append("text", payload.text);
|
||||
|
||||
const res = await fetch(`https://api.mailgun.net/v3/${domain}/messages`, {
|
||||
method: "POST",
|
||||
headers: { Authorization: `Basic ${Buffer.from(`api:${apiKey}`).toString("base64")}` },
|
||||
body: formData,
|
||||
});
|
||||
if (!res.ok) {
|
||||
const err = await res.text();
|
||||
throw new Error(`Mailgun error: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function sendSmtp(payload: EmailPayload): Promise<void> {
|
||||
const host = await getConfig("smtp_host");
|
||||
const port = parseInt(await getConfig("smtp_port") || "587");
|
||||
const user = await getConfig("smtp_user");
|
||||
const pass = await getConfig("smtp_pass");
|
||||
const from = await getConfig("smtp_from");
|
||||
if (!host) throw new Error("SMTP non configuré");
|
||||
|
||||
const nodemailer = await import("nodemailer");
|
||||
const transport = nodemailer.default.createTransport({ host, port, auth: { user, pass } });
|
||||
await transport.sendMail({ from, to: payload.to, subject: payload.subject, html: payload.html, text: payload.text });
|
||||
}
|
||||
|
||||
export async function sendEmail(payload: EmailPayload): Promise<void> {
|
||||
const provider = await getConfig("email_provider");
|
||||
if (provider === "smtp") return sendSmtp(payload);
|
||||
if (provider === "mailgun") return sendMailgun(payload);
|
||||
// Fallback: check env vars directly
|
||||
if (process.env.MAILGUN_API_KEY) return sendMailgun(payload);
|
||||
if (process.env.SMTP_HOST) return sendSmtp(payload);
|
||||
throw new Error("Aucun service email configuré");
|
||||
}
|
||||
|
||||
export function resetPasswordEmail(name: string, resetUrl: string): { html: string; text: string } {
|
||||
return {
|
||||
text: `Bonjour ${name},\n\nCliquez sur ce lien pour réinitialiser votre mot de passe :\n${resetUrl}\n\nLien valide 1 heure.\n\nGrow`,
|
||||
html: `
|
||||
<div style="font-family:sans-serif;max-width:480px;margin:0 auto">
|
||||
<h2 style="color:#4f46e5">Grow — Réinitialisation du mot de passe</h2>
|
||||
<p>Bonjour ${name},</p>
|
||||
<p>Cliquez sur le bouton ci-dessous pour réinitialiser votre mot de passe. Le lien est valide pendant <strong>1 heure</strong>.</p>
|
||||
<a href="${resetUrl}" style="display:inline-block;margin:16px 0;padding:12px 24px;background:#4f46e5;color:#fff;border-radius:8px;text-decoration:none;font-weight:600">
|
||||
Réinitialiser mon mot de passe
|
||||
</a>
|
||||
<p style="color:#64748b;font-size:13px">Si vous n'avez pas demandé cette réinitialisation, ignorez cet email.</p>
|
||||
<p style="color:#64748b;font-size:13px">Ou copiez ce lien : ${resetUrl}</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user