Merged changes from o10aupva/main

Replit-Task-Id: 96838fc6-bf00-4a8d-ae18-84ba08feec56
This commit is contained in:
joachimhummel
2026-05-15 16:11:01 +00:00
parent 83a3bf9c62
commit e9f0d1ed98
18 changed files with 612 additions and 140 deletions

View File

@@ -10,6 +10,7 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@getbrevo/brevo": "^5.0.4",
"@workspace/api-zod": "workspace:*",
"@workspace/db": "workspace:*",
"cookie-parser": "^1.4.7",

View File

@@ -0,0 +1,73 @@
import { Router, type IRouter } from "express";
import { BrevoClient } from "@getbrevo/brevo";
import { SendContactMessageBody } from "@workspace/api-zod";
const router: IRouter = Router();
function getBrevoClient() {
const apiKey = process.env.BREVO_API_KEY;
if (!apiKey) throw new Error("BREVO_API_KEY is not set");
return new BrevoClient({ apiKey });
}
router.post("/contact", async (req, res) => {
const parsed = SendContactMessageBody.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({
success: false,
message: "Ungültige Eingabe. Bitte überprüfen Sie Ihre Angaben.",
});
return;
}
const { name, email, subject, message } = parsed.data;
try {
const brevo = getBrevoClient();
await brevo.transactionalEmails.sendTransacEmail({
sender: { name: "Kontaktformular Portfolio", email: "jh@unixweb.de" },
to: [{ email: "jh@unixweb.de", name: "Joachim Hummel" }],
replyTo: { email, name },
subject: subject ? `[Portfolio] ${subject}` : `[Portfolio] Neue Anfrage von ${name}`,
textContent: [
`Name: ${name}`,
`E-Mail: ${email}`,
subject ? `Betreff: ${subject}` : "",
"",
message,
]
.filter((l) => l !== undefined)
.join("\n"),
htmlContent: `
<p><strong>Name:</strong> ${escapeHtml(name)}</p>
<p><strong>E-Mail:</strong> ${escapeHtml(email)}</p>
${subject ? `<p><strong>Betreff:</strong> ${escapeHtml(subject)}</p>` : ""}
<hr />
<p style="white-space:pre-wrap">${escapeHtml(message)}</p>
`,
});
req.log.info({ to: "jh@unixweb.de", from: email }, "Contact message sent via Brevo");
res.json({ success: true, message: "Ihre Nachricht wurde erfolgreich gesendet." });
} catch (err) {
req.log.error({ err }, "Failed to send contact email via Brevo");
res.status(500).json({
success: false,
message:
"Die Nachricht konnte nicht gesendet werden. Bitte versuchen Sie es später erneut.",
});
}
});
function escapeHtml(text: string): string {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
export default router;

View File

@@ -1,8 +1,10 @@
import { Router, type IRouter } from "express";
import healthRouter from "./health";
import contactRouter from "./contact";
const router: IRouter = Router();
router.use(healthRouter);
router.use(contactRouter);
export default router;