feat: complete notdienst planning, quick wins and notifications

This commit is contained in:
t.indorf
2026-10-12 15:34:09 +02:00
parent b915857951
commit 00f602a7aa
50 changed files with 2443 additions and 513 deletions
+7
View File
@@ -59,4 +59,11 @@ UNION ALL
SELECT 'notdienst_alerts.educatorId' AS relation, count(*) AS invalid_rows FROM "notdienst_alerts" c JOIN "educators" p ON c."educatorId" = p.id WHERE c."kitaId" IS DISTINCT FROM p."kitaId"
UNION ALL
SELECT 'announcement_reads.userId' AS relation, count(*) AS invalid_rows FROM announcement_reads r JOIN announcements a ON r."announcementId" = a.id JOIN users u ON r."userId" = u.id WHERE u."kitaId" IS DISTINCT FROM a."kitaId"
UNION ALL
SELECT 'notdienst_emails.familyId' AS relation, count(*) AS invalid_rows FROM "notdienst_emails" c JOIN "families" p ON c."familyId" = p.id WHERE c."kitaId" IS DISTINCT FROM p."kitaId"
UNION ALL
SELECT 'notdienst_emails.userId' AS relation, count(*) AS invalid_rows FROM "notdienst_emails" c JOIN "users" p ON c."userId" = p.id WHERE c."kitaId" IS DISTINCT FROM p."kitaId"
UNION ALL
SELECT 'notdienst_emails.planId' AS relation, count(*) AS invalid_rows FROM "notdienst_emails" c JOIN "notdienst_plans" p ON c."planId" = p.id WHERE c."kitaId" IS DISTINCT FROM p."kitaId"
) violations WHERE invalid_rows > 0;
+3 -3
View File
@@ -2,7 +2,7 @@
const secret = process.env.NOTDIENST_JOB_SECRET;
if (!secret || secret.length < 32) {
console.error(
"Notdienst-Fristprüfung deaktiviert: NOTDIENST_JOB_SECRET muss mindestens 32 Zeichen haben.",
"Notdienst-Hintergrundjob deaktiviert: NOTDIENST_JOB_SECRET muss mindestens 32 Zeichen haben.",
);
process.exit(1);
}
@@ -23,11 +23,11 @@ async function tick() {
);
if (!response.ok)
console.error(
`Notdienst-Fristprüfung: HTTP ${response.status}; erneuter Versuch in einer Minute.`,
`Notdienst-Hintergrundjob: HTTP ${response.status}; erneuter Versuch in einer Minute.`,
);
} catch {
console.error(
"Notdienst-Fristprüfung momentan nicht erreichbar; erneuter Versuch in einer Minute.",
"Notdienst-Hintergrundjob momentan nicht erreichbar; erneuter Versuch in einer Minute.",
);
}
if (!stopped)
+2
View File
@@ -6,3 +6,5 @@ cpSync('scripts/start-production.sh', '.next/standalone/scripts/start-production
cpSync('scripts/notdienst-alarm-worker.mjs', '.next/standalone/scripts/notdienst-alarm-worker.mjs');
cpSync('scripts/runtime-database-url.mjs', '.next/standalone/scripts/runtime-database-url.mjs');
cpSync('scripts/verify-runtime-isolation.mjs', '.next/standalone/scripts/verify-runtime-isolation.mjs');
cpSync('scripts/start-runtime.mjs', '.next/standalone/scripts/start-runtime.mjs');
+1 -4
View File
@@ -7,7 +7,4 @@ if [ -n "${APP_DATABASE_PASSWORD_FILE:-}" ]; then
fi
unset POSTGRES_PASSWORD PGADMIN_DEFAULT_PASSWORD APP_DATABASE_PASSWORD MIGRATION_DATABASE_URL
# Schema changes are applied by the maintenance image before runtime startup.
if [ -n "${NOTDIENST_JOB_SECRET:-}" ]; then
node scripts/notdienst-alarm-worker.mjs &
fi
exec node server.js
exec node scripts/start-runtime.mjs
+58
View File
@@ -0,0 +1,58 @@
import { spawn } from "node:child_process";
let stopping = false,
worker,
restartTimer,
stopTimer;
const server = spawn(process.execPath, ["server.js"], { stdio: "inherit" });
function shutdown(code = 0) {
if (stopping) return;
stopping = true;
process.exitCode = code;
clearTimeout(restartTimer);
server.kill("SIGTERM");
worker?.kill("SIGTERM");
stopTimer = setTimeout(() => {
server.kill("SIGKILL");
worker?.kill("SIGKILL");
process.exit(code);
}, 4000);
stopTimer.unref();
Promise.all(
[server, worker]
.filter(Boolean)
.map((child) =>
child.exitCode !== null || child.signalCode !== null
? Promise.resolve()
: new Promise((resolve) => child.once("close", resolve)),
),
).then(() => {
clearTimeout(stopTimer);
process.exit(code);
});
}
function startWorker() {
if (stopping) return;
worker = spawn(process.execPath, ["scripts/notdienst-alarm-worker.mjs"], {
stdio: "inherit",
});
worker.on("close", () => {
if (stopping) return;
console.error(
"Notdienst-Hintergrundjob beendet; Neustart in fünf Sekunden.",
);
restartTimer = setTimeout(startWorker, 5000);
});
worker.on("error", () => {
if (!stopping)
console.error("Notdienst-Hintergrundjob konnte nicht starten.");
});
}
server.on("exit", (code) => shutdown(code ?? 1));
server.on("error", () => shutdown(1));
for (const signal of ["SIGTERM", "SIGINT"])
process.on(signal, () => shutdown(0));
if ((process.env.NOTDIENST_JOB_SECRET?.length ?? 0) >= 32) startWorker();
else
console.warn(
"Automatische Notdienst-Jobs deaktiviert: gültiges NOTDIENST_JOB_SECRET fehlt.",
);