feat(termine+matrix): Nutzer-Termin-Einreichung (Draft) + Matrix-Notifications
Deploy / verify (push) Successful in 1m32s
Deploy / deploy (push) Successful in 1m54s

- /termin-melden: öffentliches Formular → /api/termin legt calendar_item als
  Draft an (scoped Write-Key), Admin bestätigt per draft→published
- /api/cms-webhook: empfängt RustyCMS-Webhooks (X-Webhook-Secret) → Matrix
- src/lib/server/matrix.ts: notifyMatrix() Helper
- Form-Proxies (stellungnahme/contact/newsletter/mitmachen) pingen Matrix
- deploy.yml: Matrix/Webhook/Termin-Key Env-Vars

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-06-29 10:32:36 +02:00
parent 5b5fe8789e
commit ca4dfad8be
9 changed files with 390 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
import { env } from "$env/dynamic/private";
const MATRIX_URL = (env.MATRIX_NOTIFY_URL || "").replace(/\/$/, "");
const MATRIX_TOKEN = env.MATRIX_NOTIFY_TOKEN || "";
const MATRIX_ROOM = env.MATRIX_NOTIFY_ROOM || ""; // raw, e.g. !abc:matrix.pm86.de
/**
* Fire-and-forget Matrix notification into the configured room.
* No-op (silently) when not configured, so local dev / preview never break.
* Sends both plain `body` and an HTML `formatted_body` (Markdown-ish links).
*/
export async function notifyMatrix(text: string, html?: string): Promise<void> {
if (!MATRIX_URL || !MATRIX_TOKEN || !MATRIX_ROOM) return;
const room = encodeURIComponent(MATRIX_ROOM);
const txn = `ww-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
const body: Record<string, unknown> = { msgtype: "m.text", body: text };
if (html) {
body.format = "org.matrix.custom.html";
body.formatted_body = html;
}
try {
await fetch(`${MATRIX_URL}/_matrix/client/v3/rooms/${room}/send/m.room.message/${txn}`, {
method: "PUT",
headers: { Authorization: `Bearer ${MATRIX_TOKEN}`, "Content-Type": "application/json" },
body: JSON.stringify(body),
signal: AbortSignal.timeout(8_000),
});
} catch (e) {
console.error("[matrix] notify failed:", e);
}
}