From bede91921d3835e87ed4368b2a27403d28e71b36 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sun, 6 Sep 2026 22:06:49 +0200 Subject: [PATCH] Der sechste Bereich, und zwei Pruefungen, die den Lauf lahmgelegt haben DER AGENTUR-BEREICH (Stufe 4 des Plans) Kernprinzip 04 des Konzepts lautet woertlich "Die Agentur bleibt angebunden" -- und dafuer gab es bis heute nichts. Fuenf Bereiche standen, der sechste fehlte vollstaendig. Damit stand nirgends, welche Kampagne laeuft, welche Schulung ansteht und wie ein Anliegen an die Agentur ausgegangen ist. Das lief ueber private Nachrichten: nicht auffindbar, nicht nachvollziehbar, beim naechsten Mal von vorn. Vier Arten, und die vierte ist der Punkt: kampagne, schulung, anliegen und ZUSTAENDIGKEIT. Die letzte ist keine Verlegenheit, sondern das, was das Konzept ausdruecklich fordert -- Betreuung und Umsetzung sind unsere Seite, offizielle Wege sind Agenturseite. Solange das nur im Kopf steht, wird es bei jedem Streitfall neu verhandelt. Der Umbau war das eigentliche Risiko, nicht der Bereich: Ein CHECK laesst sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden -- und dabei werden ALLE vorhandenen Eintraege umgeschrieben. Deshalb geht pruef-agentur.mjs (31 Pruefungen) den Weg wirklich: Sie baut eine Datenbank im ALTEN Stand nach, fuellt sie mit allen fuenf Bereichen, laesst die echte Anwendung darueberlaufen und zaehlt danach jede Zeile UND jedes Feld nach -- auch die seltenen Spalten (hook, creator_extern), die man beim Abschreiben des Bauplans vergisst. Wichtigste Gegenprobe: Der CHECK muss danach noch BEISSEN. Eine Umstellung, die nebenbei die Schranke entfernt, sieht aus wie ein Erfolg und ist der schlimmere Ausgang. Nebenbei drei abgeschriebene Bereichslisten beseitigt (Wochenbericht, Suche, Report-Ziele). Im Wochenbericht waere der neue Bereich sonst schlicht nicht vorgekommen -- ohne Fehler, ohne Luecke, einfach nicht da. ZWEI PRUEFUNGEN, DIE DEN GESAMTLAUF ZUM STILLSTAND BRACHTEN pruef-call-kategorien.mjs hing DREI STUNDEN. Ursache war ein Zeitzuender in ihr selbst: Sie legte Calls auf "heute 18:00" und erwartete zwei davon unter "Heute". Ab 18 Uhr sind die vorbei, der Server schiebt sie nach "Protokoll fehlt", und die Oberflaeche unterteilt erst ab FUENF anstehenden. Uebrig blieben vier, die Bloecke verschwanden, und die Pruefung wartete auf einen Klick auf einen Block, den es nicht gab. Genau die Sorte Fehler, vor der die Projektnotiz vom 06.09. warnt: Sie war um 17:59 gruen und um 18:01 rot, ohne dass sich am Code etwas geaendert hatte. Jetzt liegen alle Zeiten RELATIV zu jetzt, und die Erwartung wird mit derselben Vorschrift gerechnet, die calls.js benutzt -- statt Zahlen, die nur zu einer bestimmten Tageszeit stimmen. Und der Grund, warum daraus ein Stillstand statt eines Fehlers wurde: index.js setzt bewusst zwei Auffangnetze (uncaughtException, unhandledRejection). Fuer den Betrieb richtig -- ein Fehler darf die Website nicht offline nehmen. Fuer eine Pruefung fatal: Sie importiert index.js in denselben Prozess und erbt die Netze. Ihr eigener Absturz wird dann nur protokolliert, und der Express-Server haelt den Prozess danach ewig am Leben. Kein Fehler, kein Ergebnis, kein Ende. Deshalb zwei Abhilfen, die zusammengehoeren: * server/helfer-notbremse.mjs -- haengt eigene Zuhoerer DANEBEN (process.on ergaenzt, es ersetzt nicht) und beendet den Lauf mit Fehler; dazu ein Wecker. In alle 42 Browserpruefungen eingebaut. Der Betrieb bleibt unveraendert. * tools/alles-pruefen.mjs gibt jeder Datei eine Frist von 600 s. Wer sie reisst, wird abgebrochen und ZAEHLT ALS FEHLGESCHLAGEN, mit der letzten Ausgabe davor als Beleg. Einzelne Dateien nachzuruesten hilft nur bis zur naechsten ohne Notbremse -- deshalb sitzt sie dort, wo sie fuer alle gilt, auch fuer die, die es noch nicht gibt. AUSSERDEM: pruef-breiten.mjs pruefte 16 Seiten aus einer Liste von Hand, chat.html und leistung.html fehlten darin. Der Chat war bei keiner einzigen der neun Breiten je gemessen worden. Wie bei pruef-handy kommt die Liste jetzt aus dem Verzeichnis. Co-Authored-By: Claude Opus 5 --- server/helfer-notbremse.mjs | 75 ++++ server/pruef-abbrechen-optik.mjs | 7 + server/pruef-agentur.mjs | 474 ++++++++++++++++++++++++ server/pruef-ampel.mjs | 7 + server/pruef-aufgabenbrett.mjs | 7 + server/pruef-barrierefrei-workspace.mjs | 7 + server/pruef-breiten.mjs | 51 ++- server/pruef-browser.mjs | 7 + server/pruef-buehne.mjs | 7 + server/pruef-call-kategorien.mjs | 161 ++++++-- server/pruef-chat-optik.mjs | 7 + server/pruef-checkliste.mjs | 7 + server/pruef-code.mjs | 7 + server/pruef-content-ansicht.mjs | 7 + server/pruef-dabei-optik.mjs | 7 + server/pruef-formulare.mjs | 7 + server/pruef-freie-namen.mjs | 7 + server/pruef-glocke.mjs | 7 + server/pruef-grosscheck.mjs | 7 + server/pruef-handy.mjs | 7 + server/pruef-kalender.mjs | 7 + server/pruef-kopf-messen.mjs | 7 + server/pruef-leistung-optik.mjs | 7 + server/pruef-lesbarkeit.mjs | 7 + server/pruef-manager-sicht.mjs | 7 + server/pruef-personen-formular.mjs | 7 + server/pruef-personen-liste.mjs | 7 + server/pruef-protokoll-klappe.mjs | 7 + server/pruef-protokoll-loeschen.mjs | 7 + server/pruef-rollen.mjs | 7 + server/pruef-schulung.mjs | 7 + server/pruef-scout-zuteilung.mjs | 7 + server/pruef-serien.mjs | 7 + server/pruef-sicht.mjs | 7 + server/pruef-sprung.mjs | 7 + server/pruef-start-ansicht.mjs | 7 + server/pruef-steckbrief.mjs | 7 + server/pruef-tagesblick.mjs | 7 + server/pruef-team.mjs | 7 + server/pruef-tempo-workspace.mjs | 7 + server/pruef-uebersicht-browser.mjs | 16 +- server/pruef-wissen-neu.mjs | 7 + server/pruef-workspace-seiten.mjs | 7 + server/pruef-zustand-ansicht.mjs | 7 + server/workspace-bereiche.js | 37 ++ server/workspace-reports.js | 8 +- server/workspace-suche.js | 12 +- server/workspace.js | 96 ++++- tools/alles-pruefen.mjs | 56 ++- workspace/assets/js/bereich.js | 13 +- workspace/assets/js/bereiche.js | 26 ++ workspace/assets/js/report.js | 3 +- workspace/aufgaben.html | 16 +- workspace/automation.html | 18 +- workspace/bereich.html | 26 +- workspace/calls.html | 20 +- workspace/chat.html | 16 +- workspace/content.html | 22 +- workspace/dateien.html | 18 +- workspace/index.html | 4 +- workspace/kalender.html | 18 +- workspace/leistung.html | 18 +- workspace/personen.html | 18 +- workspace/profil.html | 20 +- workspace/report.html | 20 +- workspace/scouting.html | 18 +- workspace/start.html | 14 +- workspace/startcheck.html | 20 +- workspace/steckbrief.html | 18 +- workspace/uebersicht.html | 14 +- workspace/wissen.html | 18 +- 71 files changed, 1413 insertions(+), 224 deletions(-) create mode 100644 server/helfer-notbremse.mjs create mode 100644 server/pruef-agentur.mjs diff --git a/server/helfer-notbremse.mjs b/server/helfer-notbremse.mjs new file mode 100644 index 00000000..a35920ed --- /dev/null +++ b/server/helfer-notbremse.mjs @@ -0,0 +1,75 @@ +/* DIE NOTBREMSE FÜR PRÜFUNGEN + + --------------------------------------------------------------------- + DER FEHLER, DEN DAS VERHINDERT (06.09.2026, drei Stunden gekostet) + + index.js setzt am Ende bewusst zwei Auffangnetze: + + process.on("uncaughtException", (f) => console.error(…)); + process.on("unhandledRejection", (g) => console.error(…)); + + Für den echten Betrieb ist das genau richtig: Ein Fehler in einer + Nebensache darf die Website nicht offline nehmen. + + Für eine PRÜFUNG ist es das Gegenteil. Eine Prüfung importiert + index.js in denselben Prozess -- und erbt damit die Netze. Wenn dann + ihr eigener Ablauf abstürzt (ein Playwright-Klick auf ein Element, + das es nicht gibt), wird der Absturz nur PROTOKOLLIERT. Danach + passiert nichts mehr: Das Skript ist zu Ende, aber der Express-Server + hält den Prozess am Leben. Kein Fehler, kein Ergebnis, kein Ende. + + Genau so blieb pruef-call-kategorien.mjs drei Stunden stehen -- und + mit ihr der gesamte Regressionslauf, der auf sie wartete. Von aussen + sieht "läuft noch" genauso aus wie "hängt für immer". + + --------------------------------------------------------------------- + WAS DIESE DATEI TUT + + Sie hängt ZWEI eigene Zuhörer daneben (process.on ergänzt, es + ersetzt nicht -- die Meldung von index.js bleibt also erhalten) und + beendet den Lauf mit einem Fehler. Und sie stellt einen Wecker: Wer + nach der Frist noch läuft, wartet nicht mehr, er hängt. + + Beides greift NUR in Prüfungen. Der Betrieb importiert diese Datei + nicht und behält seine Netze unverändert. + + --------------------------------------------------------------------- + BENUTZUNG — nach dem Import von index.js, vor dem ersten Browser: + + import { notbremse } from "./helfer-notbremse.mjs"; + await import("./index.js"); + notbremse(300_000, "die Kategorienpruefung"); +*/ + +/** @param {number} fristMs Nach dieser Zeit gilt der Lauf als haengend. + * @param {string} wer Name für die Meldung. */ +export function notbremse(fristMs = 300_000, wer = "diese Pruefung") { + let schonAus = false; + const raus = (art, was) => { + if (schonAus) return; + schonAus = true; + console.error(`\n FEHL ${wer}: ${art}`); + console.error(` ${String(was?.stack || was).split("\n").slice(0, 6).join("\n ")}`); + console.error(`\n Abgebrochen. Ohne diese Notbremse wuerde der Prozess`); + console.error(` weiterlaufen, weil der Server ihn am Leben haelt --`); + console.error(` ohne Fehler, ohne Ergebnis, ohne Ende.\n`); + process.exit(1); + }; + + process.on("uncaughtException", (f) => raus("unbehandelter Fehler", f)); + process.on("unhandledRejection", (g) => raus("unbehandeltes Versprechen", g)); + + /* unref(): Der Wecker allein soll den Prozess NICHT am Leben halten. + Sonst haette ausgerechnet die Notbremse dafuer gesorgt, dass eine + fertige Pruefung noch fuenf Minuten herumsteht. */ + const wecker = setTimeout(() => { + console.error(`\n FEHL ${wer} HING: nach ${Math.round(fristMs / 1000)} s war sie ` + + `noch nicht fertig.`); + console.error(` Haeufigste Ursachen: ein Warten auf ein Element, das nie kommt,`); + console.error(` oder eine Verbindung, die nie endet (Ereignisstrom).\n`); + process.exit(1); + }, fristMs); + wecker.unref?.(); + + return () => clearTimeout(wecker); +} diff --git a/server/pruef-abbrechen-optik.mjs b/server/pruef-abbrechen-optik.mjs index 4e78ee77..3f5fa393 100644 --- a/server/pruef-abbrechen-optik.mjs +++ b/server/pruef-abbrechen-optik.mjs @@ -28,6 +28,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-abopt-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4297, "die Optikpruefung des Abbrechens"); @@ -39,6 +40,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-abbrechen-optik"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4297"; diff --git a/server/pruef-agentur.mjs b/server/pruef-agentur.mjs new file mode 100644 index 00000000..9a518ad1 --- /dev/null +++ b/server/pruef-agentur.mjs @@ -0,0 +1,474 @@ +/* DER SECHSTE BEREICH — "Die Agentur bleibt angebunden" + + Kernprinzip 04 des Konzepts, und bis zum 06.09.2026 gab es dafuer + nichts: Fuenf Bereiche standen (LIVE, Content, Technik, Community, + Schutz), der sechste fehlte vollstaendig. + + --------------------------------------------------------------------- + DAS EIGENTLICHE RISIKO IST NICHT DER BEREICH, SONDERN DER UMBAU + + Der erlaubte Wertebereich einer Spalte steckt in SQLite in einem + CHECK, und ein CHECK laesst sich nicht aendern -- die Tabelle muss + neu gebaut werden. Dabei werden ALLE vorhandenen Eintraege + umgeschrieben: LIVE-Auswertungen, Content-Ideen, Technikprobleme, + Community-Konflikte, Vorfaelle. Wer dort eine Spalte vergisst, + verliert ihren Inhalt still. + + Und das ist ein Weg, den man nicht zurueckdrehen kann. Deshalb wird + er hier nicht "im Prinzip" geprueft, sondern WIRKLICH GEGANGEN: Die + Pruefung baut eine Datenbank im ALTEN Stand nach, fuellt sie, laesst + die echte Anwendung darueberlaufen und zaehlt danach jede Zeile und + jedes Feld nach. + + GEGENPROBEN durchgehend -- vor allem die eine, auf die es ankommt: + Der CHECK muss danach immer noch BEISSEN. Eine Umstellung, die + nebenbei die Schranke entfernt, sieht aus wie ein Erfolg und ist der + schlimmere Ausgang. + ===================================================================== */ + +import { mkdtempSync, rmSync, appendFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; +import { spawn } from "node:child_process"; +import { DatabaseSync } from "node:sqlite"; +import { scryptSync, randomBytes } from "node:crypto"; +import { heuteLokal } from "./helfer-zeit.mjs"; + +const HIER = dirname(fileURLToPath(import.meta.url)); +const { portMussFreiSein } = await import("./helfer-port.mjs"); +const PORT = 4341; +await portMussFreiSein(PORT, "die Agentur-Pruefung"); +const BASIS = `http://127.0.0.1:${PORT}`; + +const ordner = mkdtempSync(join(tmpdir(), "ws-agentur-")); +const DB = join(ordner, "workspace.db"); + +const PROTOKOLL = join(process.cwd(), "pruef-agentur-lauf.txt"); +try { rmSync(PROTOKOLL, { force: true }); } catch { /* gab es noch nicht */ } +const melde = (z) => { + console.log(z); + try { appendFileSync(PROTOKOLL, z + "\n"); } catch { /* Zugabe */ } +}; +let fehler = 0; +let gemacht = 0; +const ok = (b, t) => { gemacht++; melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +/* --------------------------------------------------------------------- + Die Anwendung als eigener Prozess -- weil sie ZWEIMAL starten muss: + einmal, um die Datenbank anzulegen, und einmal nach dem Zurueckbau, + damit die Wanderung wirklich laeuft. In einem Prozess ginge das + nicht: Ein Modul wird nur einmal geladen. + --------------------------------------------------------------------- */ +let laufend = null; +async function serverStarten() { + const kind = spawn(process.execPath, [join(HIER, "index.js")], { + env: { + ...process.env, + WORKSPACE_DB: DB, + PORT: String(PORT), + SITE_ACCESS_SECRET: "lokaler-test", + SITE_PUBLIC_LAUNCH_AT: "2020-01-01T00:00:00+01:00", + }, + stdio: ["ignore", "pipe", "pipe"], + }); + let aus = ""; + kind.stdout.on("data", (d) => { aus += d; }); + kind.stderr.on("data", (d) => { aus += d; }); + for (let i = 0; i < 80; i++) { + await new Promise((r) => setTimeout(r, 250)); + if (kind.exitCode !== null) break; + try { + const a = await fetch(BASIS + "/workspace/", { redirect: "manual" }); + if (a.status < 500) { laufend = kind; return { kind, aus: () => aus }; } + } catch { /* noch nicht oben */ } + } + throw new Error("Server startete nicht:\n" + aus.slice(-1200)); +} +async function serverStoppen() { + if (!laufend) return; + laufend.kill(); + laufend = null; + /* Warten, bis der Port wirklich frei ist -- sonst startet der zweite + Lauf gegen einen halb geschlossenen Anschluss und misst Unsinn. */ + for (let i = 0; i < 40; i++) { + await new Promise((r) => setTimeout(r, 150)); + try { await fetch(BASIS + "/workspace/", { redirect: "manual" }); } catch { return; } + } +} + +/* ======================================================================= + 1. Erster Start: die Datenbank entsteht + ======================================================================= */ +melde("=== Die Datenbank anlegen ==="); +{ + const { aus } = await serverStarten(); + ok(true, "die Anwendung ist oben und hat das Schema angelegt"); + void aus; +} + +const jetzt = new Date().toISOString(); +/* ORTSZEIT fuer das Tagesdatum, nicht der Schnitt aus dem UTC-Stempel. + `jetzt.slice(0, 10)` liegt zwischen Mitternacht und zwei Uhr einen Tag + daneben -- die Pruefung waere dann nachts rot, ohne dass sich etwas + geaendert haette. Gefunden von pruef-struktur, das genau danach sucht. */ +const heute = heuteLokal(); + +function anlegen(d, name, rolle, code) { + const salz = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salz, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)") + .run(name, rolle, hash, salz, 32768, jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; +} + +let idDogi, idLuna; +{ + const d = new DatabaseSync(DB); + idDogi = anlegen(d, "Filipe", "admin", "CODE-DOGI-0001"); + idLuna = anlegen(d, "Luna", "creator", "CODE-CREA-0001"); + d.close(); +} + +/* ======================================================================= + 2. Die Datenbank auf den ALTEN Stand zurueckbauen + ======================================================================= + + Genau so sieht sie auf dem Server aus, bevor die Wanderung laeuft. + Ohne diesen Schritt pruefte die ganze Datei nur eine frisch angelegte + Tabelle -- und damit ausgerechnet den Fall, den es in der Wirklichkeit + nicht gibt. */ +melde("\n=== Zurueck auf den alten Stand (fuenf Bereiche) ==="); +await serverStoppen(); + +const ALTE_EINTRAEGE = [ + ["live", "auswertung", "Auswertung Montag", "Der Hook saß, die Mitte hing.", 4, "hoch"], + ["content", "idee", "Serie: Basteln mit Kindern", "Fünf Teile.", null, "mittel"], + ["technik", "problem", "Ton bricht ab", "Nur bei OBS 30.", null, "hoch"], + ["community", "konflikt", "Streit in den Kommentaren", "Zwei Zuschauer.", null, "niedrig"], + ["schutz", "vorfall", "Meldung wegen Sprache", "Geklärt.", null, "hoch"], +]; + +{ + const d = new DatabaseSync(DB); + d.exec("PRAGMA foreign_keys = OFF"); + d.exec("DROP TABLE IF EXISTS eintraege"); + /* WOERTLICH der alte Bauplan, mit allen Spalten, die zu diesem + Zeitpunkt schon da waren. */ + d.exec(` + CREATE TABLE eintraege ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bereich TEXT NOT NULL + CHECK (bereich IN ('live','content','technik','community','schutz')), + art TEXT NOT NULL, + titel TEXT NOT NULL, + text TEXT, + datum TEXT NOT NULL, + bewertung INTEGER CHECK (bewertung IS NULL OR (bewertung BETWEEN 1 AND 5)), + dringlichkeit TEXT NOT NULL DEFAULT 'mittel' + CHECK (dringlichkeit IN ('hoch','mittel','niedrig')), + status TEXT NOT NULL DEFAULT 'offen' + CHECK (status IN ('offen','erledigt')), + creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL, + erstellt TEXT NOT NULL, + erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL, + geaendert TEXT, + hook TEXT, + format TEXT, + saeule_id INTEGER, + geplant TEXT, + creator_extern TEXT + ); + CREATE INDEX IF NOT EXISTS idx_eintraege_bereich ON eintraege (bereich, creator_id);`); + + const setzen = d.prepare(`INSERT INTO eintraege + (bereich, art, titel, text, datum, bewertung, dringlichkeit, status, + creator_id, erstellt, erstellt_von, hook, creator_extern) + VALUES (?,?,?,?,?,?,?, 'offen', ?,?,?,?,?)`); + for (const [b, art, titel, text, bew, dring] of ALTE_EINTRAEGE) { + setzen.run(b, art, titel, text, heute, bew, dring, idLuna, jetzt, idDogi, + b === "content" ? "Der Hook: 'Das habt ihr noch nie gesehen'" : null, + b === "live" ? "Gastauftritt Mia" : null); + } + d.exec("PRAGMA foreign_keys = ON"); + + const n = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n; + ok(n === ALTE_EINTRAEGE.length, `${n} Eintraege im alten Stand angelegt`); + + /* GEGENPROBE: Der alte CHECK muss "agentur" WIRKLICH ablehnen -- + sonst gaebe es nichts umzustellen, und der Rest dieser Datei + bewiese nichts. */ + let abgelehnt = false; + try { + d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt) + VALUES ('agentur','kampagne','Darf noch nicht',?,?)`).run(heute, jetzt); + } catch { abgelehnt = true; } + ok(abgelehnt, "im alten Stand wird 'agentur' von der Datenbank abgelehnt"); + d.close(); +} + +/* ======================================================================= + 3. Die Wanderung -- der Weg, den man nicht zurueckdrehen kann + ======================================================================= */ +melde("\n=== Die Umstellung ==="); +let ausgabe = ""; +{ + const { aus } = await serverStarten(); + await new Promise((r) => setTimeout(r, 600)); + ausgabe = aus(); +} + +ok(/Bereich 'agentur' freigeschaltet/.test(ausgabe), + "die Anwendung meldet die Umstellung" + + (/agentur/.test(ausgabe) ? "" : ` — Ausgabe: ${ausgabe.slice(-300)}`)); + +{ + const d = new DatabaseSync(DB, { readOnly: true }); + const plan = d.prepare( + "SELECT sql FROM sqlite_master WHERE type='table' AND name='eintraege'").get()?.sql || ""; + ok(/'agentur'/.test(plan), "der neue Bauplan kennt 'agentur'"); + + /* KEINE ZEILE DARF FEHLEN -- und keine darf sich VERAENDERT haben. + Die Zeilenzahl allein reichte nicht: Wer beim Umschreiben eine + Spalte vergisst, behaelt alle Zeilen und verliert ihren Inhalt. + Genau das faellt sonst erst Wochen spaeter auf. */ + const alle = d.prepare("SELECT * FROM eintraege ORDER BY id").all(); + ok(alle.length === ALTE_EINTRAEGE.length, + `alle ${alle.length} Eintraege sind noch da (vorher ${ALTE_EINTRAEGE.length})`); + + let abweichungen = []; + ALTE_EINTRAEGE.forEach(([b, art, titel, text, bew, dring], i) => { + const z = alle[i]; + if (!z) { abweichungen.push(`${b}: Zeile fehlt`); return; } + if (z.bereich !== b) abweichungen.push(`${b}: bereich = ${z.bereich}`); + if (z.art !== art) abweichungen.push(`${b}: art = ${z.art}`); + if (z.titel !== titel) abweichungen.push(`${b}: titel = ${z.titel}`); + if (z.text !== text) abweichungen.push(`${b}: text = ${z.text}`); + if (z.bewertung !== bew) abweichungen.push(`${b}: bewertung = ${z.bewertung}`); + if (z.dringlichkeit !== dring) abweichungen.push(`${b}: dringlichkeit = ${z.dringlichkeit}`); + if (z.creator_id !== idLuna) abweichungen.push(`${b}: creator_id = ${z.creator_id}`); + if (z.erstellt_von !== idDogi) abweichungen.push(`${b}: erstellt_von = ${z.erstellt_von}`); + }); + ok(abweichungen.length === 0, + abweichungen.length ? `Inhalte haben sich veraendert: ${abweichungen.slice(0, 5).join(" | ")}` + : `jedes Feld jeder Zeile ist unveraendert (${alle.length} Zeilen x 8 Felder geprueft)`); + + /* Auch die SELTENEN Spalten -- gerade die vergisst man beim + Abschreiben des Bauplans. */ + const mitHook = alle.find((z) => z.bereich === "content"); + const mitExtern = alle.find((z) => z.bereich === "live"); + ok(/Das habt ihr noch nie gesehen/.test(mitHook?.hook || ""), + `die Spalte "hook" hat ihren Inhalt behalten ("${String(mitHook?.hook).slice(0, 30)}…")`); + ok(mitExtern?.creator_extern === "Gastauftritt Mia", + `die Spalte "creator_extern" ebenfalls ("${mitExtern?.creator_extern}")`); + + /* Der Index muss nach dem Tausch wieder da sein -- er wird beim DROP + mit entfernt. Ohne ihn laeuft alles weiter, nur langsam, und + niemand merkt es je. */ + const indizes = d.prepare( + "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='eintraege'").all() + .map((r) => r.name); + ok(indizes.includes("idx_eintraege_bereich"), + `der Index steht wieder (${indizes.join(", ")})`); + d.close(); +} + +/* GEGENPROBE, DIE WICHTIGSTE: Die Schranke muss noch BEISSEN. Eine + Umstellung, die nebenbei den CHECK entfernt, sieht aus wie ein Erfolg + und ist der schlimmere Ausgang -- ab da kann jeder Tippfehler einen + Bereich erfinden, den es nicht gibt. */ +{ + const d = new DatabaseSync(DB); + let abgelehnt = false; + try { + d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt) + VALUES ('erfundenerbereich','x','Darf nicht',?,?)`).run(heute, jetzt); + } catch { abgelehnt = true; } + ok(abgelehnt, "ein erfundener Bereich wird weiterhin abgelehnt — der CHECK beisst noch"); + + let angenommen = false; + try { + d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt) + VALUES ('agentur','kampagne','Direkt in die Datenbank',?,?)`).run(heute, jetzt); + angenommen = true; + d.exec("DELETE FROM eintraege WHERE titel = 'Direkt in die Datenbank'"); + } catch { /* bleibt false */ } + ok(angenommen, "'agentur' wird jetzt angenommen"); + d.close(); +} + +/* ======================================================================= + 4. Der Bereich im Betrieb + ======================================================================= */ +melde("\n=== Der Bereich im Betrieb ==="); + +async function anmelden(rolle, code) { + const a = await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle, code }) }); + const keks = (a.headers.getSetCookie?.() || []).map((z) => z.split(";")[0]).join("; "); + if (!keks) throw new Error(`Anmeldung ${rolle} fehlgeschlagen (${a.status})`); + return keks; +} +const keksDogi = await anmelden("admin", "CODE-DOGI-0001"); +const keksLuna = await anmelden("creator", "CODE-CREA-0001"); + +async function ruf(art, weg, keks, koerper) { + const a = await fetch(BASIS + weg, { + method: art, + headers: { "Content-Type": "application/json", cookie: keks, origin: BASIS }, + body: koerper ? JSON.stringify(koerper) : undefined, + }); + let daten = null; + try { daten = await a.json(); } catch { /* kein JSON */ } + return { status: a.status, daten }; +} + +/* Die vier Arten -- jede einzeln, denn eine, die der Server nicht kennt, + waere im Formular waehlbar und beim Speichern ein Fehler. */ +const ARTEN = ["kampagne", "schulung", "anliegen", "zustaendig"]; +{ + const { daten } = await ruf("GET", "/workspace/api/bereich/agentur", keksDogi); + ok(daten?.einstellung?.name === "Agentur", + `der Bereich meldet sich als "${daten?.einstellung?.name}"`); + const arten = Object.keys(daten?.einstellung?.arten || {}); + ok(arten.length === 4 && ARTEN.every((a) => arten.includes(a)), + `vier Arten: ${arten.join(", ")}`); + ok(daten?.einstellung?.dringlichkeit === true, + "Dringlichkeit ist an — ein Anliegen von gestern ist etwas anderes als eines von vor drei Wochen"); + ok(daten?.einstellung?.bewertung === false, + "Bewertung ist aus — hier wird zusammengearbeitet, nicht benotet"); +} + +let angelegt = 0; +for (const art of ARTEN) { + const { status } = await ruf("POST", "/workspace/api/bereich/agentur", keksDogi, { + art, titel: `Test ${art}`, text: "Inhalt", datum: heute, + dringlichkeit: "mittel", creator_id: idLuna, + }); + if (status === 200 || status === 201) angelegt++; + else ok(false, `Art "${art}" liess sich nicht anlegen (HTTP ${status})`); +} +ok(angelegt === ARTEN.length, `alle ${angelegt} Arten lassen sich anlegen`); + +/* GEGENPROBE: eine erfundene Art muss abgelehnt werden. Sonst pruefte + die Zeile darueber nur, dass der Server irgendetwas annimmt. */ +{ + const { status } = await ruf("POST", "/workspace/api/bereich/agentur", keksDogi, { + art: "erfundeneart", titel: "Darf nicht", datum: heute, + }); + ok(status >= 400, `eine erfundene Art wird abgelehnt (HTTP ${status})`); +} + +/* Der Creator SIEHT seinen Agentur-Bereich -- er ist der Grund, warum + es ihn gibt. */ +{ + const { status, daten } = await ruf("GET", "/workspace/api/bereich/agentur", keksLuna); + ok(status === 200, `Luna darf den Bereich oeffnen (HTTP ${status})`); + ok((daten?.eintraege || []).length === ARTEN.length, + `sie sieht ihre ${(daten?.eintraege || []).length} Eintraege`); +} + +/* ======================================================================= + 5. Kommt er ueberall an, wo Bereiche vorkommen? + ======================================================================= */ +melde("\n=== Ueberall, wo Bereiche vorkommen ==="); +{ + /* Der Wochenbericht. Hier stand die Liste der fuenf Bereiche noch + einmal von Hand abgeschrieben -- der neue waere schlicht nicht + vorgekommen, ohne Fehler und ohne Luecke. */ + const { daten } = await ruf("GET", "/workspace/api/report", keksDogi); + const b = daten?.bereiche || {}; + ok(Object.keys(b).length === 6, + `der Wochenbericht kennt ${Object.keys(b).length} Bereiche: ${Object.keys(b).join(", ")}`); + ok(b.agentur && b.agentur.neu >= ARTEN.length, + `und zaehlt ${b.agentur?.neu} neue Agentur-Eintraege`); +} +{ + /* Die Suche. Ein Treffer ohne Ueberschrift sieht aus wie ein Fehler. */ + const { daten } = await ruf("GET", "/workspace/api/suche?q=" + encodeURIComponent("Test kampagne"), keksDogi); + const text = JSON.stringify(daten); + ok(/Agentur/.test(text), + "die Suche beschriftet Agentur-Treffer mit dem richtigen Namen"); +} + +/* ======================================================================= + 6. Die Kachel — mit einem echten Browser + ======================================================================= */ +melde("\n=== Die Kachel ==="); +{ + const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); + const browser = await pw.chromium.launch(); + const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } }); + const seite = await ctx.newPage(); + await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); + await seite.click('.rolle[data-rolle="admin"]'); + await seite.fill("#code", "CODE-DOGI-0001"); + await seite.click("button[type=submit]"); + await seite.waitForURL("**/start.html", { timeout: 25000 }); + await seite.waitForTimeout(1100); + + const kacheln = await seite.$$eval(".kachel", (k) => k.map((x) => ({ + name: x.querySelector(".kachel__name")?.textContent.trim(), + ziel: x.querySelector(".kachel__link")?.getAttribute("href"), + ton: x.dataset.ton, + pfade: x.querySelectorAll(".kachel__zeichen path").length, + }))); + const a = kacheln.find((x) => x.name === "Agentur"); + ok(!!a, `die Kachel "Agentur" steht da (${kacheln.length} Kacheln insgesamt)`); + ok(a?.ziel === "bereich.html?b=agentur", `sie fuehrt nach "${a?.ziel}"`); + ok(Number(a?.pfade) >= 4, `ihr Zeichen hat ${a?.pfade} Pfade — es ist wirklich gezeichnet`); + + /* KEINE ZWEI KACHELN MIT DEMSELBEN FARBTON NEBENEINANDER. Das Auge + vergleicht nur, was benachbart ist -- zwei gleiche Toene direkt + nebeneinander lesen sich als Fehler. */ + const toene = kacheln.map((x) => x.ton); + const nachbarnGleich = toene.filter((t, i) => i > 0 && t === toene[i - 1]); + ok(nachbarnGleich.length === 0, + nachbarnGleich.length ? `zwei Nachbarkacheln haben denselben Ton (${nachbarnGleich.join(", ")})` + : `keine zwei benachbarten Kacheln teilen einen Farbton (${toene.length} geprueft)`); + + /* Die Seite selbst muss aufgehen und ihren Namen tragen. */ + await seite.goto(BASIS + "/workspace/bereich.html?b=agentur", { waitUntil: "networkidle" }); + await seite.waitForTimeout(900); + const titel = await seite.$eval(".titel", (e) => e.textContent.trim()).catch(() => ""); + ok(/Agentur/i.test(titel), `die Bereichsseite heisst "${titel}"`); + + /* .eintrag-karte -- der Name aus bereich.js. Beim ersten Anlauf stand + hier geraten ".eintrag, .karte"; das traf nichts und meldete "0 + Eintraege", obwohl vier dastanden. Ein falscher Selektor meldet + einen Fehler, den es nicht gibt -- genauso schaedlich wie einer, + der etwas uebersieht. */ + const eintraege = await seite.$$eval(".eintrag-karte", (e) => e.length).catch(() => 0); + ok(eintraege >= ARTEN.length, + `sie zeigt ${eintraege} Eintraege (mindestens die ${ARTEN.length} angelegten)`); + + /* Und die Titel muessen die sein, die oben angelegt wurden -- sonst + bewiese die Zahl nur, dass IRGENDWAS gezeichnet wurde. */ + const kartenTitel = await seite.$$eval(".eintrag-titel", (e) => e.map((x) => x.textContent.trim())); + const fehlend = ARTEN.map((a) => `Test ${a}`).filter((t) => !kartenTitel.includes(t)); + ok(fehlend.length === 0, + fehlend.length ? `diese Eintraege fehlen auf der Seite: ${fehlend.join(", ")}` + : `alle vier Titel stehen da: ${kartenTitel.join(" · ")}`); + + /* GEGENPROBE zum Selektor: Ein erfundener Bereich darf NICHT wie ein + echter aussehen -- weder in der Ueberschrift noch mit Eintraegen. + Die Anwendung schickt einen dorthin zurueck, wo er hingehoert. */ + await seite.goto(BASIS + "/workspace/bereich.html?b=gibtesnicht", { waitUntil: "networkidle" }); + await seite.waitForTimeout(900); + const falsch = await seite.$eval(".titel", (e) => e.textContent.trim()).catch(() => ""); + const falschKarten = await seite.$$eval(".eintrag-karte", (e) => e.length).catch(() => 0); + ok(!/Agentur/i.test(falsch) && falschKarten === 0, + `ein erfundener Bereich zeigt weder die Agentur noch Eintraege ` + + `("${falsch}", ${falschKarten} Karten)`); + + await ctx.close(); + await browser.close(); +} + +await serverStoppen(); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } + +melde(`\n${gemacht} Pruefungen`); +melde(fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`); +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-ampel.mjs b/server/pruef-ampel.mjs index b6efcf34..8b062b84 100644 --- a/server/pruef-ampel.mjs +++ b/server/pruef-ampel.mjs @@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-ampel"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4206"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.(); diff --git a/server/pruef-aufgabenbrett.mjs b/server/pruef-aufgabenbrett.mjs index 3765e497..8077a4da 100644 --- a/server/pruef-aufgabenbrett.mjs +++ b/server/pruef-aufgabenbrett.mjs @@ -43,7 +43,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-aufgabenbrett"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4216"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-barrierefrei-workspace.mjs b/server/pruef-barrierefrei-workspace.mjs index 6d0ea39b..76a25d0d 100644 --- a/server/pruef-barrierefrei-workspace.mjs +++ b/server/pruef-barrierefrei-workspace.mjs @@ -34,6 +34,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-a11y-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); /* Der Port muss frei sein -- sonst wird gegen einen fremden Server gemessen. Begruendung in helfer-port.mjs. */ +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4253, "die Barrierefreiheitspruefung"); @@ -45,6 +46,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-barrierefrei-workspace"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4253"; diff --git a/server/pruef-breiten.mjs b/server/pruef-breiten.mjs index 6e9c6fa8..66426ce7 100644 --- a/server/pruef-breiten.mjs +++ b/server/pruef-breiten.mjs @@ -37,6 +37,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-breit-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); /* Der Port muss frei sein -- sonst wird gegen einen fremden Server gemessen. Begruendung in helfer-port.mjs. */ +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4277, "die Breitenpruefung"); @@ -48,6 +49,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-breiten"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4277"; @@ -130,12 +137,44 @@ const BREITEN = [ [2560, 1440, "sehr breit"], ]; -const SEITEN = [ - "start.html", "uebersicht.html", "aufgaben.html", "kalender.html", "calls.html", - "dateien.html", "content.html", "bereich.html?b=live", "profil.html", - "startcheck.html", "report.html", "scouting.html", "personen.html", - "wissen.html", "automation.html", "steckbrief.html", -]; +/* ---------- WELCHE SEITEN? ALLE. ----------------------------------------- + + Hier stand eine Liste von Hand -- und wie ihre Schwester in + pruef-handy.mjs war sie veraltet: chat.html und leistung.html fehlten. + Ausgerechnet der Chat, den man auf dem Handy am ehesten benutzt, war + bei keiner einzigen der neun Breiten je gemessen worden. + + Eine Liste von Hand altert in dem Moment, in dem jemand weiterarbeitet, + und sie sagt nichts dazu. Der Lauf blieb gruen, die Zahl der + Pruefungen stimmte -- gemessen wurde trotzdem zu wenig. Deshalb kommt + sie jetzt aus dem Verzeichnis: Die naechste neue Seite ist von selbst + dabei, so wie sie durch die Schranke in workspace.js von selbst + geschuetzt ist. + + index.html bleibt draussen -- die Anmeldeseite leitet nach der + Anmeldung weiter und wuerde hier nur start.html ein zweites Mal + messen. */ +const { readdirSync } = await import("node:fs"); +const { dirname } = await import("node:path"); +const { fileURLToPath } = await import("node:url"); +/* Der Ort DIESER Datei, nicht process.cwd(): Der Gesamtlauf startet vom + Wurzelverzeichnis, von Hand startet man aus server/. */ +const SEITEN_ORDNER = join(dirname(fileURLToPath(import.meta.url)), "..", "workspace"); +const MIT_ABFRAGE = { "bereich.html": "?b=live" }; +const SEITEN = readdirSync(SEITEN_ORDNER) + .filter((n) => n.endsWith(".html") && n !== "index.html") + .sort() + .map((n) => n + (MIT_ABFRAGE[n] || "")); + +/* Die Zahl gehoert in die BEDINGUNG, nicht nur in den Meldetext: Faende + readdirSync nichts, liefe die Schleife unten null Mal -- und der Lauf + waere gruen, ohne eine einzige Seite angesehen zu haben. */ +if (SEITEN.length < 15) { + console.log(` FEHL nur ${SEITEN.length} Seiten in ${SEITEN_ORDNER} gefunden ` + + "-- die Breitenpruefung kann so nichts messen"); + process.exit(1); +} +console.log(`${SEITEN.length} Seiten x ${BREITEN.length} Breiten: ${SEITEN.join(", ")}`); const messen = () => { const sichtbar = (e) => { diff --git a/server/pruef-browser.mjs b/server/pruef-browser.mjs index 7b338958..2deb0338 100644 --- a/server/pruef-browser.mjs +++ b/server/pruef-browser.mjs @@ -39,6 +39,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-browser-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); /* Der Port muss frei sein -- sonst wird gegen einen fremden Server gemessen. Begruendung in helfer-port.mjs. */ +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4289, "die Browserpruefung"); @@ -50,6 +51,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-browser"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4289"; diff --git a/server/pruef-buehne.mjs b/server/pruef-buehne.mjs index 4b52958f..e89e02d8 100644 --- a/server/pruef-buehne.mjs +++ b/server/pruef-buehne.mjs @@ -25,7 +25,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-buehne"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4193"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 220_000).unref?.(); diff --git a/server/pruef-call-kategorien.mjs b/server/pruef-call-kategorien.mjs index cfb14295..15b77fee 100644 --- a/server/pruef-call-kategorien.mjs +++ b/server/pruef-call-kategorien.mjs @@ -26,6 +26,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-callkat-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4321, "die Kategorienpruefung"); @@ -37,6 +38,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-call-kategorien"); await new Promise((r) => setTimeout(r, 1000)); const BASIS = "http://127.0.0.1:4321"; @@ -81,25 +88,89 @@ const tag = (versatz) => { const p = (n) => String(n).padStart(2, "0"); return `${t.getFullYear()}-${p(t.getMonth() + 1)}-${p(t.getDate())}`; }; -function call(titel, versatz) { +/* --------------------------------------------------------------------- + DIE WANDUHR DARF KEINE ANNAHME SEIN (korrigiert 06.09.2026) + + Hier stand vorher `${tag(versatz)}T18:00` und die feste Erwartung + "zwei Termine unter Heute". Das ging gut, solange jemand vormittags + prueft. Ab 18 Uhr sind die beiden heutigen Calls VORBEI -- der Server + schiebt sie dann von "anstehend" nach "Protokoll fehlt", und die + Oberflaeche unterteilt erst ab FUENF anstehenden ueberhaupt in + Zeitraeume. Uebrig blieben vier, die Bloecke verschwanden, und die + Pruefung meldete "0 Kategorien". + + Sie blieb daraufhin an einem Klick auf einen Block haengen, der gar + nicht da war -- DREI STUNDEN, und mit ihr der ganze Regressionslauf. + Der Fehler war also nicht nur falsch, er war auch teuer. + + Deshalb jetzt zweierlei: + + 1. Die Zeiten liegen RELATIV zu jetzt, nicht auf einer festen + Uhrzeit. Was "heute" heisst, wird nicht behauptet, sondern + gerechnet. + 2. Die Erwartung wird mit DERSELBEN Vorschrift aus den gesaeten + Zeitpunkten abgeleitet, die auch calls.js benutzt -- statt + Zahlen hinzuschreiben, die nur zu einer bestimmten Tageszeit + stimmen. Um 23:58 faellt "in 35 Minuten" auf morgen, und dann + erwartet die Pruefung ihn auch dort. + + Eine Pruefung, die um 17:59 gruen und um 18:01 rot ist, ohne dass + sich am Code etwas geaendert hat, misst den Kalender, nicht die + Software. */ +function callZeit(dat, titel) { + const p = (n) => String(n).padStart(2, "0"); + const beginn = `${dat.getFullYear()}-${p(dat.getMonth() + 1)}-${p(dat.getDate())}` + + `T${p(dat.getHours())}:${p(dat.getMinutes())}`; d.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id, teilnehmer_id, erstellt, erstellt_von) VALUES (?,'call',?,30,?,?,?,?)`) - .run(titel, `${tag(versatz)}T18:00`, idLuna, idLuna, jetzt, idDogi); + .run(titel, beginn, idLuna, idLuna, jetzt, idDogi); + return beginn; } -/* WIE VIELE TAGE bis Sonntag? Dieselbe Rechnung wie in calls.js -- - damit die Prüfung weiss, was sie erwarten darf, statt es zu raten. */ -const heute = new Date(); -const wochentag = (heute.getDay() + 6) % 7; // Mo=0 … So=6 -const bisSonntag = 6 - wochentag; +/** WOERTLICH dieselbe Vorschrift wie zeitraumVon() in calls.js. + * Absichtlich abgeschrieben und nicht importiert: calls.js laeuft im + * Browser und kennt kein export. Waere sie hier anders, pruefte die + * Datei ihre eigene Rechnung statt die der Anwendung -- deshalb steht + * weiter unten eine Gegenprobe, die beide gegeneinander haelt. */ +function zeitraumVon(beginn) { + const dd = new Date(String(beginn).replace(" ", "T")); + const h = new Date(); h.setHours(0, 0, 0, 0); + const t = new Date(dd); t.setHours(0, 0, 0, 0); + const tage = Math.round((t - h) / 86400000); + if (tage <= 0) return "heute"; + const wt = (h.getDay() + 6) % 7; // Mo=0 … So=6 + const bisSo = 6 - wt; + if (tage <= bisSo) return "woche"; + if (tage <= bisSo + 7) return "naechste"; + return "spaeter"; +} -/* Zwei heute. Dann einer, der sicher noch in diese Woche fällt (falls - es sie gibt), einer in die nächste, und drei weit weg. */ -call("Heute A", 0); -call("Heute B", 0); -if (bisSonntag >= 1) call("Diese Woche", 1); -call("Nächste Woche", bisSonntag + 2); -for (let i = 0; i < 3; i++) call(`Später ${i}`, bisSonntag + 20 + i); +const nachher = (min) => new Date(Date.now() + min * 60000); + +/* SIEBEN kuenftige Calls, gestreut. Sieben, weil die Oberflaeche erst + ab fuenf anstehenden unterteilt -- mit vieren pruefte diese Datei + einen Zustand, in dem es die Bloecke gar nicht geben soll. */ +/* Die Titel getrennt, weil weiter unten drei davon wieder geloescht + werden muessen -- ueber genau diese Liste und nicht ueber ein + abgeschriebenes Namensmuster. */ +const GESAET_TITEL = ["Gleich", "Etwas spaeter", "Morgen", "In drei Tagen", + "In neun Tagen", "In dreissig Tagen", "In einunddreissig Tagen"]; +const GESAET = [ + callZeit(nachher(5), "Gleich"), + callZeit(nachher(35), "Etwas spaeter"), + callZeit(nachher(60 * 26), "Morgen"), + callZeit(nachher(60 * 24 * 3), "In drei Tagen"), + callZeit(nachher(60 * 24 * 9), "In neun Tagen"), + callZeit(nachher(60 * 24 * 30), "In dreissig Tagen"), + callZeit(nachher(60 * 24 * 31), "In einunddreissig Tagen"), +]; + +/* Was MUSS also in welchem Block stehen? Gerechnet, nicht behauptet. */ +const ERWARTET = {}; +for (const b of GESAET) { + const z = zeitraumVon(b); + ERWARTET[z] = (ERWARTET[z] || 0) + 1; +} d.close(); const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); @@ -133,26 +204,32 @@ const { ctx, seite } = await callsSeite(); /* DIE ANZAHL IN DIE BEDINGUNG: Ohne Blöcke liefe alles Folgende über eine leere Menge und meldete grün. */ - ok(bloecke.length >= 3, `${bloecke.length} Kategorien (erwartet mindestens 3)`); + const erwarteteRaeume = Object.keys(ERWARTET); + ok(bloecke.length === erwarteteRaeume.length, + `${bloecke.length} Kategorien (gerechnet: ${erwarteteRaeume.length} — ` + + `${erwarteteRaeume.map((r) => `${r}:${ERWARTET[r]}`).join(", ")})`); - const heuteBlock = bloecke.find((b) => b.raum === "heute"); - ok(heuteBlock?.zahl === 2, `"Heute" enthält ${heuteBlock?.zahl} Termine (erwartet 2)`); + /* JEDER gerechnete Zeitraum einzeln. Nicht "mindestens drei": Welche + Bloecke es gibt, haengt vom Wochentag ab (am Sonntag gibt es kein + "Diese Woche" mehr, und das ist richtig). Gerechnet wird es mit + derselben Vorschrift wie in der Anwendung. */ + let stimmen = 0; + for (const [raum, soll] of Object.entries(ERWARTET)) { + const b = bloecke.find((x) => x.raum === raum); + const gut = b?.zahl === soll; + if (gut) stimmen++; + ok(gut, `"${raum}" enthält ${b?.zahl ?? "keinen Block"} (gerechnet: ${soll})`); + } + ok(stimmen === erwarteteRaeume.length && stimmen >= 3, + `${stimmen} von ${erwarteteRaeume.length} Zeitraeumen stimmen (mindestens 3 mussten es sein)`); - const spaeter = bloecke.find((b) => b.raum === "spaeter"); - ok(spaeter?.zahl === 3, `"Später" enthält ${spaeter?.zahl} (erwartet 3)`); - - const naechste = bloecke.find((b) => b.raum === "naechste"); - ok(naechste?.zahl === 1, `"Nächste Woche" enthält ${naechste?.zahl} (erwartet 1)`); - - /* Am Sonntag gibt es kein "Diese Woche" mehr -- das ist richtig und - kein Fehler. Deshalb wird die Erwartung aus dem Wochentag - abgeleitet, nicht festgeschrieben. */ - const diese = bloecke.find((b) => b.raum === "woche"); - const erwarteDiese = bisSonntag >= 1; - ok(!!diese === erwarteDiese, - erwarteDiese - ? `"Diese Woche" ist da (${diese?.zahl}) — heute ist noch ${bisSonntag} Tag(e) bis Sonntag` - : `"Diese Woche" fehlt zu Recht — heute ist Sonntag, danach kommt nichts mehr`); + /* GEGENPROBE zur abgeschriebenen Vorschrift: Die Summe ueber alle + Bloecke muss die Zahl der gesaeten Calls sein. Rechnete diese Datei + anders als die Anwendung, ginge die Summe nicht auf -- und genau + das faellt sonst nicht auf, weil beide Seiten "plausibel" aussehen. */ + const summe = bloecke.reduce((n, b) => n + b.zahl, 0); + ok(summe === GESAET.length, + `alle ${GESAET.length} Calls sind in genau einem Block gelandet (gezaehlt: ${summe})`); /* Die Zahl im Kopf muss zu den Karten darin passen. Eine Zahl, die etwas anderes sagt als der Inhalt, ist schlimmer als keine. */ @@ -211,10 +288,17 @@ melde("\n=== Wenige Termine brauchen keine Schubladen ==="); /* Vier Karten in vier Kategorien wären mehr Arbeit als Nutzen -- man klickt dreimal, um vier Zeilen zu sehen. */ const p = new DatabaseSync(process.env.WORKSPACE_DB); - p.prepare("DELETE FROM termine WHERE titel LIKE 'Später%' OR titel = 'Nächste Woche'").run(); + /* Drei der sieben weg, vier bleiben -- unterhalb der Schwelle, ab der + die Oberflaeche ueberhaupt unterteilt. Geloescht wird ueber die + TITEL AUS DER SAAT oben, nicht ueber Namen von Hand: Als die Saat + am 06.09.2026 umgestellt wurde, traf das alte `LIKE 'Später%'` + nichts mehr, es blieben sieben Termine stehen, und dieser Abschnitt + pruefte ab da einen Zustand, den er selbst nie hergestellt hatte. */ + const WEG = GESAET_TITEL.slice(-3); + p.prepare(`DELETE FROM termine WHERE titel IN (${WEG.map(() => "?").join(",")})`).run(...WEG); const rest = p.prepare("SELECT COUNT(*) AS n FROM termine").get().n; p.close(); - ok(rest <= 4, `nur noch ${rest} Termine übrig`); + ok(rest === 4, `nur noch ${rest} Termine übrig (drei entfernt: ${WEG.join(", ")})`); await seite.reload({ waitUntil: "networkidle" }); await seite.waitForTimeout(1400); @@ -234,10 +318,17 @@ melde("\n=== Gegenprobe ==="); gerade bewiesen, dass sie VERSCHWINDEN -- ohne diesen Nachweis bewiese "0 Blöcke" nur, dass der Selektor nie etwas trifft. */ const p = new DatabaseSync(process.env.WORKSPACE_DB); + const pp = (n) => String(n).padStart(2, "0"); for (let i = 0; i < 6; i++) { + /* Relativ zu jetzt, wie oben. Eine feste Uhrzeit haette hier + denselben Zeitzuender wie in der Saat: Ab 18 Uhr waeren die neuen + Termine schon vorbei und die Gegenprobe bewiese nichts. */ + const t = new Date(Date.now() + (40 + i) * 86400000); + const beginn = `${t.getFullYear()}-${pp(t.getMonth() + 1)}-${pp(t.getDate())}` + + `T${pp(t.getHours())}:${pp(t.getMinutes())}`; p.prepare(`INSERT INTO termine (titel, art, beginn, dauer_min, creator_id, teilnehmer_id, erstellt, erstellt_von) VALUES (?,'call',?,30,?,?,?,?)`) - .run(`Wieder da ${i}`, `${tag(bisSonntag + 30 + i)}T18:00`, idLuna, idLuna, jetzt, idDogi); + .run(`Wieder da ${i}`, beginn, idLuna, idLuna, jetzt, idDogi); } p.close(); diff --git a/server/pruef-chat-optik.mjs b/server/pruef-chat-optik.mjs index 5821dc3f..ed95249b 100644 --- a/server/pruef-chat-optik.mjs +++ b/server/pruef-chat-optik.mjs @@ -30,6 +30,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-chatoptik-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4317, "die Chat-Optikpruefung"); @@ -41,6 +42,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-chat-optik"); await new Promise((r) => setTimeout(r, 1000)); const BASIS = "http://127.0.0.1:4317"; diff --git a/server/pruef-checkliste.mjs b/server/pruef-checkliste.mjs index 9b64337d..a9f76b84 100644 --- a/server/pruef-checkliste.mjs +++ b/server/pruef-checkliste.mjs @@ -27,7 +27,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-checkliste"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4207"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-code.mjs b/server/pruef-code.mjs index 53e3ebf9..bfd41d67 100644 --- a/server/pruef-code.mjs +++ b/server/pruef-code.mjs @@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-code"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4195"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 240_000).unref?.(); diff --git a/server/pruef-content-ansicht.mjs b/server/pruef-content-ansicht.mjs index 958df87d..bc9d8a6b 100644 --- a/server/pruef-content-ansicht.mjs +++ b/server/pruef-content-ansicht.mjs @@ -15,7 +15,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-content-ansicht"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4185"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-dabei-optik.mjs b/server/pruef-dabei-optik.mjs index 875be318..8c78417f 100644 --- a/server/pruef-dabei-optik.mjs +++ b/server/pruef-dabei-optik.mjs @@ -33,6 +33,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-dabei-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4293, "die Optikpruefung der Teilnehmerwahl"); @@ -44,6 +45,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-dabei-optik"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4293"; diff --git a/server/pruef-formulare.mjs b/server/pruef-formulare.mjs index 1bab52a2..2d2cd48f 100644 --- a/server/pruef-formulare.mjs +++ b/server/pruef-formulare.mjs @@ -29,7 +29,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-formulare"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4203"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.(); diff --git a/server/pruef-freie-namen.mjs b/server/pruef-freie-namen.mjs index 1f04a87e..80bced68 100644 --- a/server/pruef-freie-namen.mjs +++ b/server/pruef-freie-namen.mjs @@ -35,7 +35,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-freie-namen"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4192"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 240_000).unref?.(); diff --git a/server/pruef-glocke.mjs b/server/pruef-glocke.mjs index 89411aec..d42b9b80 100644 --- a/server/pruef-glocke.mjs +++ b/server/pruef-glocke.mjs @@ -51,6 +51,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-glocke-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4299, "die Glockenpruefung"); @@ -62,6 +63,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-glocke"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4299"; diff --git a/server/pruef-grosscheck.mjs b/server/pruef-grosscheck.mjs index 612602b0..2234d6de 100644 --- a/server/pruef-grosscheck.mjs +++ b/server/pruef-grosscheck.mjs @@ -53,7 +53,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-grosscheck"); await new Promise((r) => setTimeout(r, 800)); const BASIS = "http://127.0.0.1:4218"; setTimeout(() => { console.log("ABBRUCH — zu lange gelaufen"); process.exit(1); }, 900_000).unref?.(); diff --git a/server/pruef-handy.mjs b/server/pruef-handy.mjs index 43bb47b2..336e8562 100644 --- a/server/pruef-handy.mjs +++ b/server/pruef-handy.mjs @@ -36,7 +36,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-handy"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4198"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 400_000).unref?.(); diff --git a/server/pruef-kalender.mjs b/server/pruef-kalender.mjs index eaade626..557a660b 100644 --- a/server/pruef-kalender.mjs +++ b/server/pruef-kalender.mjs @@ -22,7 +22,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-kalender"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4188"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-kopf-messen.mjs b/server/pruef-kopf-messen.mjs index d9ef1b91..bb55da18 100644 --- a/server/pruef-kopf-messen.mjs +++ b/server/pruef-kopf-messen.mjs @@ -35,7 +35,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-kopf-messen"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4179"; diff --git a/server/pruef-leistung-optik.mjs b/server/pruef-leistung-optik.mjs index 0f1554f9..61a8ffa6 100644 --- a/server/pruef-leistung-optik.mjs +++ b/server/pruef-leistung-optik.mjs @@ -35,6 +35,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-leistoptik-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4337, "die Zahlen-Optikpruefung"); @@ -46,6 +47,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-leistung-optik"); await new Promise((r) => setTimeout(r, 1000)); const BASIS = "http://127.0.0.1:4337"; diff --git a/server/pruef-lesbarkeit.mjs b/server/pruef-lesbarkeit.mjs index 32026380..b40e7511 100644 --- a/server/pruef-lesbarkeit.mjs +++ b/server/pruef-lesbarkeit.mjs @@ -36,7 +36,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-lesbarkeit"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4214"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-manager-sicht.mjs b/server/pruef-manager-sicht.mjs index 95bfa528..e03733fa 100644 --- a/server/pruef-manager-sicht.mjs +++ b/server/pruef-manager-sicht.mjs @@ -47,7 +47,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-manager-sicht"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4199"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-personen-formular.mjs b/server/pruef-personen-formular.mjs index e41e0e8d..6b041875 100644 --- a/server/pruef-personen-formular.mjs +++ b/server/pruef-personen-formular.mjs @@ -33,7 +33,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-personen-formular"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4198"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-personen-liste.mjs b/server/pruef-personen-liste.mjs index f5108f45..9938bb19 100644 --- a/server/pruef-personen-liste.mjs +++ b/server/pruef-personen-liste.mjs @@ -27,7 +27,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-personen-liste"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4187"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-protokoll-klappe.mjs b/server/pruef-protokoll-klappe.mjs index 28b83a6d..be1852ea 100644 --- a/server/pruef-protokoll-klappe.mjs +++ b/server/pruef-protokoll-klappe.mjs @@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-protokoll-klappe"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4180"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-protokoll-loeschen.mjs b/server/pruef-protokoll-loeschen.mjs index 1ec20fac..56d91618 100644 --- a/server/pruef-protokoll-loeschen.mjs +++ b/server/pruef-protokoll-loeschen.mjs @@ -40,7 +40,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-protokoll-loeschen"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4197"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-rollen.mjs b/server/pruef-rollen.mjs index e2a997d5..5871316e 100644 --- a/server/pruef-rollen.mjs +++ b/server/pruef-rollen.mjs @@ -42,7 +42,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-rollen"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4193"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 540_000).unref?.(); diff --git a/server/pruef-schulung.mjs b/server/pruef-schulung.mjs index d97a94d6..1e30b4e4 100644 --- a/server/pruef-schulung.mjs +++ b/server/pruef-schulung.mjs @@ -26,7 +26,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-schulung"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4205"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.(); diff --git a/server/pruef-scout-zuteilung.mjs b/server/pruef-scout-zuteilung.mjs index 41c4e9a2..0c236d35 100644 --- a/server/pruef-scout-zuteilung.mjs +++ b/server/pruef-scout-zuteilung.mjs @@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-scout-zuteilung"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4217"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-serien.mjs b/server/pruef-serien.mjs index 2a40d234..3d726d78 100644 --- a/server/pruef-serien.mjs +++ b/server/pruef-serien.mjs @@ -38,7 +38,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-serien"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4189"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-sicht.mjs b/server/pruef-sicht.mjs index 5d7d3eda..bc65b3ac 100644 --- a/server/pruef-sicht.mjs +++ b/server/pruef-sicht.mjs @@ -34,7 +34,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-sicht"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4215"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-sprung.mjs b/server/pruef-sprung.mjs index 015ba87e..b8ba9cd1 100644 --- a/server/pruef-sprung.mjs +++ b/server/pruef-sprung.mjs @@ -40,7 +40,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-sprung"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4212"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-start-ansicht.mjs b/server/pruef-start-ansicht.mjs index 3fa58ce9..4ba80c98 100644 --- a/server/pruef-start-ansicht.mjs +++ b/server/pruef-start-ansicht.mjs @@ -15,7 +15,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-start-ansicht"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4186"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-steckbrief.mjs b/server/pruef-steckbrief.mjs index 5a1d5a9a..50876d7b 100644 --- a/server/pruef-steckbrief.mjs +++ b/server/pruef-steckbrief.mjs @@ -19,7 +19,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-steckbrief"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4196"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-tagesblick.mjs b/server/pruef-tagesblick.mjs index 1049ff02..45942683 100644 --- a/server/pruef-tagesblick.mjs +++ b/server/pruef-tagesblick.mjs @@ -28,6 +28,7 @@ import { join } from "node:path"; const ordner = mkdtempSync(join(tmpdir(), "ws-tagesblick-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4303, "die Tagesblickpruefung"); @@ -39,6 +40,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-tagesblick"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4303"; diff --git a/server/pruef-team.mjs b/server/pruef-team.mjs index cb0a1a71..5f80b16e 100644 --- a/server/pruef-team.mjs +++ b/server/pruef-team.mjs @@ -38,7 +38,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-team"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4213"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 300_000).unref?.(); diff --git a/server/pruef-tempo-workspace.mjs b/server/pruef-tempo-workspace.mjs index 9f12ae96..836b4a1f 100644 --- a/server/pruef-tempo-workspace.mjs +++ b/server/pruef-tempo-workspace.mjs @@ -37,6 +37,7 @@ const ordner = mkdtempSync(join(tmpdir(), "ws-tempo-")); process.env.WORKSPACE_DB = join(ordner, "workspace.db"); /* Der Port muss frei sein -- sonst wird gegen einen fremden Server gemessen. Begruendung in helfer-port.mjs. */ +import { notbremse } from "./helfer-notbremse.mjs"; const { portMussFreiSein } = await import("./helfer-port.mjs"); await portMussFreiSein(4283, "die Tempopruefung"); @@ -48,6 +49,12 @@ const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-tempo-workspace"); await new Promise((r) => setTimeout(r, 900)); const BASIS = "http://127.0.0.1:4283"; diff --git a/server/pruef-uebersicht-browser.mjs b/server/pruef-uebersicht-browser.mjs index 102cfa6d..1c1f4950 100644 --- a/server/pruef-uebersicht-browser.mjs +++ b/server/pruef-uebersicht-browser.mjs @@ -23,7 +23,14 @@ const echteCookie = express.response.cookie; express.response.cookie = function (name, wert, opt) { return echteCookie.call(this, name, wert, { ...(opt || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(150_000, "pruef-uebersicht-browser"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4177"; @@ -38,11 +45,10 @@ const tagText = (d = new Date()) => let fehler = 0; const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; -const notbremse = setTimeout(() => { - console.log("\nABBRUCH: hing über 150 Sekunden."); - process.exit(1); -}, 150_000); -notbremse.unref?.(); +/* Die eigene Notbremse von hier ist am 06.09.2026 nach + helfer-notbremse.mjs gewandert und wird oben aufgerufen -- sie faengt + jetzt zusaetzlich den unbehandelten Absturz ab, nicht nur das + Zulangsamsein. Die knappe Frist dieser Datei bleibt erhalten. */ /* ---------- Daten ------------------------------------------------------ */ diff --git a/server/pruef-wissen-neu.mjs b/server/pruef-wissen-neu.mjs index d79a60c3..e8aef5f6 100644 --- a/server/pruef-wissen-neu.mjs +++ b/server/pruef-wissen-neu.mjs @@ -23,7 +23,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-wissen-neu"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4201"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 200_000).unref?.(); diff --git a/server/pruef-workspace-seiten.mjs b/server/pruef-workspace-seiten.mjs index 30fe7cd5..34fe1e72 100644 --- a/server/pruef-workspace-seiten.mjs +++ b/server/pruef-workspace-seiten.mjs @@ -18,7 +18,14 @@ const echteCookie = express.response.cookie; express.response.cookie = function (n, w, o) { return echteCookie.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-workspace-seiten"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4178"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 150_000).unref?.(); diff --git a/server/pruef-zustand-ansicht.mjs b/server/pruef-zustand-ansicht.mjs index 7f5e1547..5e219343 100644 --- a/server/pruef-zustand-ansicht.mjs +++ b/server/pruef-zustand-ansicht.mjs @@ -17,7 +17,14 @@ process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; const express = (await import("express")).default; const ec = express.response.cookie; express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +import { notbremse } from "./helfer-notbremse.mjs"; await import("./index.js"); +/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort + zwei Auffangnetze ein, die einen Absturz nur protokollieren -- + richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese + Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der + Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */ +notbremse(480_000, "pruef-zustand-ansicht"); await new Promise((r) => setTimeout(r, 700)); const BASIS = "http://127.0.0.1:4182"; setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 180_000).unref?.(); diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js index 3d5fda3d..640b6e9a 100644 --- a/server/workspace-bereiche.js +++ b/server/workspace-bereiche.js @@ -75,6 +75,43 @@ export const BEREICHE = { bewertung: false, dringlichkeit: true, }, + /* DER SECHSTE BEREICH (06.09.2026). + + Kernprinzip 04 des Konzepts: "Die Agentur bleibt angebunden." Fuenf + Bereiche standen, dieser fehlte -- und damit fehlte der einzige + Ort, an dem steht, welche Kampagne laeuft, welche Schulung ansteht + und wie ein Anliegen an die Agentur ausgegangen ist. Das lief + ueber private Nachrichten: nicht auffindbar, nicht nachvollziehbar, + und beim naechsten Mal fing man wieder von vorn an. + + VIER ARTEN, und die vierte ist der Punkt: + kampagne was laeuft, bis wann, unter welcher Bedingung + schulung Termine und Inhalte, verweist auf die Bibliothek + anliegen der OFFIZIELLE Weg hin -- mit Stand + zustaendig wer wofuer zustaendig ist: wir oder die Agentur + + Die letzte Art ist keine Verlegenheit, sondern das, was das Konzept + ausdruecklich fordert ("Betreuung & Umsetzung ist unsere Seite, + offizielle Wege und Plattformthemen sind Agenturseite"). Solange + das nur im Kopf steht, wird bei jedem Streitfall neu verhandelt, + wer eigentlich haette handeln muessen. + + Dringlichkeit: ja. Ein Anliegen an die Agentur, das drei Wochen + liegt, ist etwas anderes als eines von gestern -- und ohne Stufe + sieht man den Unterschied in einer Liste nicht. + Bewertung: nein. Hier wird nichts benotet; es ist ein + Zusammenarbeits-Bereich, kein Analysebereich. */ + agentur: { + name: "Agentur", + arten: { + kampagne: "Event & Kampagne", + schulung: "Schulung", + anliegen: "Anliegen an die Agentur", + zustaendig: "Zuständigkeit", + }, + bewertung: false, + dringlichkeit: true, + }, }; const DRINGLICHKEITEN = ["hoch", "mittel", "niedrig"]; diff --git a/server/workspace-reports.js b/server/workspace-reports.js index 0ce44beb..ffbb593b 100644 --- a/server/workspace-reports.js +++ b/server/workspace-reports.js @@ -22,6 +22,7 @@ import express from "express"; import { db, protokolliere, echteIp, sitzungLesen, betreuteIds, darfCreator, istLeitung, istDogFather, sichtbareCreatorIds, } from "./workspace.js"; +import { BEREICHE } from "./workspace-bereiche.js"; export const reportRouter = express.Router(); @@ -135,7 +136,12 @@ reportRouter.get("/workspace/api/report", (req, res) => { /* --- Bereiche -------------------------------------------------------- */ const bereiche = {}; - for (const b of ["live", "content", "technik", "community", "schutz"]) { + /* AUS DER EINEN QUELLE, nicht abgeschrieben (06.09.2026). + Hier stand die Liste der fuenf Bereiche noch einmal von Hand. Als + "agentur" dazukam, waere er im Wochenbericht schlicht nicht + vorgekommen -- ohne Fehler, ohne Luecke, einfach nicht da. Genau + so altert eine abgeschriebene Liste. */ + for (const b of Object.keys(BEREICHE)) { bereiche[b] = { neu: zahl( `SELECT COUNT(*) AS n FROM eintraege e WHERE e.bereich = ? AND e.erstellt >= ?${nurE}`, diff --git a/server/workspace-suche.js b/server/workspace-suche.js index f42f2496..d7dbd0cd 100644 --- a/server/workspace-suche.js +++ b/server/workspace-suche.js @@ -30,7 +30,7 @@ import { import { sichtbar as sichtbarAufgaben } from "./workspace-aufgaben.js"; import { sichtbar as sichtbarTermine } from "./workspace-kalender.js"; import { sichtbar as sichtbarDateien } from "./workspace-dateien.js"; -import { sichtbar as sichtbarEintraege } from "./workspace-bereiche.js"; +import { sichtbar as sichtbarEintraege, BEREICHE } from "./workspace-bereiche.js"; export const sucheRouter = express.Router(); @@ -38,10 +38,12 @@ const MIN = 2; const JE_BEREICH = 6; const AUSSCHNITT = 110; -const BEREICHSNAME = { - live: "LIVE-Analyse", content: "Content", technik: "Technik", - community: "Community", schutz: "Schutz", -}; +/* Die Namen kommen aus workspace-bereiche.js, nicht aus einer zweiten + Liste hier. Eine abgeschriebene Liste vergisst den naechsten neuen + Bereich -- und in der Suche hiesse das: Er wird gefunden und traegt + dann keine Ueberschrift. */ +const BEREICHSNAME = Object.fromEntries( + Object.entries(BEREICHE).map(([k, b]) => [k, b.name])); function angemeldet(req, res, next) { const person = sitzungLesen(req); diff --git a/server/workspace.js b/server/workspace.js index d31c33ea..a5a5c606 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -444,6 +444,100 @@ function umstellungen(d) { } } + /* ---- Der sechste Bereich: "agentur" (06.09.2026) ---- + + Kernprinzip 04 des Konzepts lautet woertlich "Die Agentur bleibt + angebunden". Fuenf der sechs Bereiche standen (LIVE, Content, + Technik, Community, Schutz) -- der sechste fehlte vollstaendig. Was + dadurch nirgends stand: welche Kampagne laeuft und bis wann, welche + Schulung ansteht, und vor allem der OFFIZIELLE WEG zur Agentur mit + Stand und Antwort. Das lief bisher ueber private Nachrichten und + war damit weder auffindbar noch nachvollziehbar. + + Und die Trennung, die das Konzept ausdruecklich verlangt und die im + Alltag sonst verschwimmt: Betreuung und Umsetzung sind UNSERE + Seite, offizielle Wege und Plattformthemen die der Agentur. Wer + wofuer zustaendig ist, gehoert auf den Bildschirm, nicht ins + Gedaechtnis. + + Derselbe Umbau wie bei "abgebrochen" darueber -- ein CHECK laesst + sich in SQLite nicht aendern, die Tabelle muss neu gebaut werden: + erst sichern, dann tauschen, Zeilen zaehlen, Verweise pruefen. */ + const eintraegePlan = d.prepare( + "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'eintraege'").get()?.sql || ""; + if (eintraegePlan && !eintraegePlan.includes("'agentur'")) { + const sicherung = `${DB_PFAD}.vor-agentur-${jetztStempel}`; + try { + d.exec(`VACUUM INTO '${sicherung.replace(/'/g, "''")}'`); + console.log("[workspace] Sicherung vor der Umstellung:", sicherung); + } catch (fehler) { + console.error("[workspace] Sicherung fehlgeschlagen, Umstellung abgebrochen:", fehler?.message); + return; + } + + d.exec("PRAGMA foreign_keys = OFF"); + try { + const vorher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n; + d.exec("BEGIN"); + d.exec(` + CREATE TABLE eintraege_neu ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + bereich TEXT NOT NULL + CHECK (bereich IN ('live','content','technik','community','schutz','agentur')), + art TEXT NOT NULL, + titel TEXT NOT NULL, + text TEXT, + datum TEXT NOT NULL, + bewertung INTEGER CHECK (bewertung IS NULL OR (bewertung BETWEEN 1 AND 5)), + dringlichkeit TEXT NOT NULL DEFAULT 'mittel' + CHECK (dringlichkeit IN ('hoch','mittel','niedrig')), + status TEXT NOT NULL DEFAULT 'offen' + CHECK (status IN ('offen','erledigt')), + creator_id INTEGER REFERENCES personen(id) ON DELETE SET NULL, + erstellt TEXT NOT NULL, + erstellt_von INTEGER REFERENCES personen(id) ON DELETE SET NULL, + geaendert TEXT, + hook TEXT, + format TEXT, + saeule_id INTEGER, + geplant TEXT, + creator_extern TEXT + ); + INSERT INTO eintraege_neu + (id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status, + creator_id, erstellt, erstellt_von, geaendert, + hook, format, saeule_id, geplant, creator_extern) + SELECT id, bereich, art, titel, text, datum, bewertung, dringlichkeit, status, + creator_id, erstellt, erstellt_von, geaendert, + hook, format, saeule_id, geplant, creator_extern + FROM eintraege; + DROP TABLE eintraege; + ALTER TABLE eintraege_neu RENAME TO eintraege; + CREATE INDEX IF NOT EXISTS idx_eintraege_bereich ON eintraege (bereich, creator_id); + `); + const nachher = d.prepare("SELECT COUNT(*) AS n FROM eintraege").get().n; + if (nachher !== vorher) { + d.exec("ROLLBACK"); + console.error(`[workspace] Umstellung abgebrochen: ${vorher} Eintraege vorher, ` + + `${nachher} nachher. Sicherung: ${sicherung}`); + } else { + d.exec("COMMIT"); + const kaputt = d.prepare("PRAGMA foreign_key_check").all(); + if (kaputt.length) { + console.error("[workspace] ACHTUNG: nach der Umstellung", kaputt.length, + "verwaiste Verweise. Sicherung liegt unter", sicherung); + } else { + console.log(`[workspace] Bereich 'agentur' freigeschaltet, ${vorher} Eintraege, Verweise geprueft.`); + } + } + } catch (fehler) { + try { d.exec("ROLLBACK"); } catch { /* schon zurueckgerollt */ } + console.error("[workspace] Umstellung 'agentur' fehlgeschlagen:", fehler?.message); + } finally { + d.exec("PRAGMA foreign_keys = ON"); + } + } + /* ---- Rolle "manager" erlauben ---- */ const bauplan = d.prepare( "SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'personen'").get()?.sql || ""; @@ -918,7 +1012,7 @@ export function db() { CREATE TABLE IF NOT EXISTS eintraege ( id INTEGER PRIMARY KEY AUTOINCREMENT, bereich TEXT NOT NULL - CHECK (bereich IN ('live','content','technik','community','schutz')), + CHECK (bereich IN ('live','content','technik','community','schutz','agentur')), art TEXT NOT NULL, titel TEXT NOT NULL, text TEXT, diff --git a/tools/alles-pruefen.mjs b/tools/alles-pruefen.mjs index 4828e5da..46d8f0c3 100644 --- a/tools/alles-pruefen.mjs +++ b/tools/alles-pruefen.mjs @@ -26,19 +26,73 @@ const dateien = readdirSync(ORDNER) console.log(`${dateien.length} Pruefdateien gefunden\n`); +/* ---------- JEDE PRUEFUNG BEKOMMT EINE FRIST (06.09.2026) --------------- + + VIERTER GRUND fuer diesen eigenen Laeufer, und er hat mich an einem + Tag ZWEIMAL erwischt: + + Eine Pruefung, die haengt, bringt den GANZEN Lauf zum Stillstand -- + ohne Fehlermeldung, ohne FEHL, ohne Absturz. Es steht einfach nichts + mehr da. Von aussen sieht "noch nicht fertig" genauso aus wie "haengt + fuer immer", und deshalb faellt es erst Stunden spaeter auf. + + * pruef-alle-wege lief in den Ereignisstrom des Chats und wartete + auf ein Ende, das es nie gibt (behoben in der Datei selbst). + * pruef-call-kategorien blieb DREI STUNDEN stehen. Sie hatte, anders + als pruef-handy, keine eigene Notbremse. + + Einzelne Dateien nachzuruesten hilft genau so lange, bis jemand die + naechste ohne Notbremse schreibt. Deshalb sitzt sie jetzt HIER, wo + sie fuer alle gilt -- auch fuer die, die es noch nicht gibt. + + Und eine ueberschrittene Frist ist ein FEHLER, kein Ueberspringen: Sie + wird gezaehlt, gemeldet und mit der letzten Ausgabe der Datei belegt, + damit man sieht, wo sie stehengeblieben ist. Ein stiller Stillstand + waere das Gegenteil. + + 600 Sekunden sind grosszuegig bemessen: Die langsamste ehrliche + Pruefung im Haus (pruef-barrierefrei-workspace) braucht 190 s. Wer + das Dreifache davon ueberschreitet, wartet nicht -- er haengt. */ +const FRIST_MS = 600_000; + const laufen = (datei) => new Promise((fertig) => { const start = Date.now(); const kind = spawn(process.execPath, [join(ORDNER, datei)], { stdio: ["ignore", "pipe", "pipe"], }); let aus = ""; + let abgewuergt = false; kind.stdout.on("data", (d) => { aus += d; }); kind.stderr.on("data", (d) => { aus += d; }); + + const wecker = setTimeout(() => { + abgewuergt = true; + /* Erst freundlich, dann bestimmt. Ein Playwright-Browser haengt + sonst als Waise weiter und belegt Speicher bis zum Neustart. */ + try { kind.kill("SIGTERM"); } catch { /* schon weg */ } + setTimeout(() => { try { kind.kill("SIGKILL"); } catch { /* schon weg */ } }, 3000); + }, FRIST_MS); + kind.on("close", (code) => { + clearTimeout(wecker); const sek = Math.round((Date.now() - start) / 100) / 10; const einzeln = (aus.match(/^\s{2}(ok|FEHL)\s/gm) || []).length; const schlecht = (aus.match(/^\s{2}FEHL\s/gm) || []).length; - fertig({ datei, code, sek, einzeln, schlecht, aus }); + if (abgewuergt) { + aus += `\n\n FEHL DIE PRUEFUNG HING. Nach ${Math.round(FRIST_MS / 1000)} s kam nichts mehr` + + " -- abgebrochen.\n Letzte Ausgabe davor steht oben; dort ist sie stehengeblieben." + + "\n Haeufigste Ursache: eine Verbindung, die nie endet (Ereignisstrom)," + + "\n oder ein warten auf etwas, das nicht kommt.\n"; + } + fertig({ + datei, code: abgewuergt ? 124 : code, sek, + /* Die abgewuergte Pruefung zaehlt als fehlgeschlagen, nicht als + "keine Pruefung gelaufen" -- das sind zwei verschiedene Sachen, + und beide muessen unterscheidbar bleiben. */ + einzeln: einzeln + (abgewuergt ? 1 : 0), + schlecht: schlecht + (abgewuergt ? 1 : 0), + aus, hing: abgewuergt, + }); }); }); diff --git a/workspace/assets/js/bereich.js b/workspace/assets/js/bereich.js index 448d03a9..896ed85e 100644 --- a/workspace/assets/js/bereich.js +++ b/workspace/assets/js/bereich.js @@ -27,6 +27,11 @@ technik: 'Setups, Fehlerbilder und Lösungen pro Creator sauber dokumentiert.', community: 'Moderation, Aktionen und Konflikte – ruhig statt Drama.', schutz: 'Richtlinien, Vorfälle und was daraus gelernt wurde.', + /* Der Satz nennt die Trennung beim Namen, die das Konzept fordert: + Betreuung und Umsetzung sind unsere Seite, offizielle Wege sind + Agenturseite. Solange das nur im Kopf steht, wird es bei jedem + Streitfall neu verhandelt. */ + agentur: 'Kampagnen, Schulungen und der offizielle Weg zur Agentur – mit Stand und Antwort.', }; let ich = null; @@ -203,7 +208,13 @@ Sinn ergibt: LIVE, Community und Technik. Bei Content sind es eigene Ideen des Creators -- die bewertet niemand mit einer Ampel. */ - if (["live", "community", "technik", "schutz"].includes(bereich) + /* "agentur" ist seit dem 06.09.2026 dabei, und aus demselben + Grund wie die anderen vier: Ein Anliegen an die Agentur hat + einen STAND -- eingereicht, in Klärung, beantwortet. Genau + dafür ist die Ampel da, samt der Nachrichten daran. Ohne sie + wäre der "offizielle Weg" wieder nur eine Liste, in der etwas + steht und niemand weiß, wie es ausgegangen ist. */ + if (["live", "community", "technik", "schutz", "agentur"].includes(bereich) && window.ampelAnhaengen && karten.size) { const stand = await window.ampelnHolen([...karten.keys()]); for (const [id, k] of karten) { diff --git a/workspace/assets/js/bereiche.js b/workspace/assets/js/bereiche.js index 1ac73b83..8d388c0c 100644 --- a/workspace/assets/js/bereiche.js +++ b/workspace/assets/js/bereiche.js @@ -135,6 +135,13 @@ Bericht, sondern um einen VERLAUF. */ zahlen: [ 'M4 16 9.5 11.5 13 14 20 6.5 20 19.5 4 19.5z' + ], + /* Agentur (06.09.2026): ein Aktenkoffer. Bewusst kein + Haendedruck und kein Gebaeude -- beides wird bei 20 Pixeln zu + Matsch. Der Koffer ist die einzige Form, die man bei dieser + Groesse noch als "offizielle Seite" liest. */ + agentur: [ + 'M3.5 8h17v11h-17z' ] }; @@ -257,6 +264,15 @@ fuellung: ['M13.2 3.2 6 13.4h5.2l-.8 7.4 7.6-10.6h-5.2z'], linien: ['M13.2 3.2 6 13.4h5.2l-.8 7.4 7.6-10.6h-5.2z'], }, + agentur: { + /* Der Deckel als Flaeche -- daran erkennt man einen Koffer, so wie + man einen Kalender an seinem Kopf erkennt. */ + fuellung: ['M3.5 8h17v4.6h-17z'], + linien: ['M3.5 8h17v11h-17z', + 'M9 8V6.4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1V8', + 'M3.5 12.6h17', + 'M10.9 11.6h2.2v2h-2.2z'], + }, zahlen: { fuellung: ['M4 16 9.5 11.5 13 14 20 6.5 20 19.5 4 19.5z'], /* Der Linienzug selbst, die Grundlinie darunter und ein Punkt am @@ -368,6 +384,16 @@ rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=technik', szene: 'garage' }, { name: 'Schutz & Regeln', unter: 'Richtlinien und Vorfälle', zeichen: 'schutz', ton: 1, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=schutz', szene: 'halle' }, + /* DER SECHSTE BEREICH (06.09.2026). Kernprinzip 04 des Konzepts + lautet "Die Agentur bleibt angebunden" -- und dafuer gab es + bis heute nichts. Fuenf Bereiche standen, dieser fehlte. + Steht bei den anderen fuenf und nicht unter "Team & System": + Es ist ein Bereich wie LIVE, Technik oder Schutz, nur dass + die andere Seite des Tisches die Agentur ist. + Ton 9 (Orange) -- deutlich von seinen Nachbarn 1 (Blau) und + 14 (Gruen) entfernt. */ + { name: 'Agentur', unter: 'Kampagnen, Schulungen, offizieller Weg', zeichen: 'agentur', ton: 9, + rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'bereich.html?b=agentur', szene: 'halle' }, { name: 'Reports & Review', unter: 'Woche und Entscheidung', zeichen: 'reports', ton: 14, rollen: ['admin', 'manager', 'creator', 'scout'], ziel: 'report.html', szene: 'showbuehne' }, ], diff --git a/workspace/assets/js/report.js b/workspace/assets/js/report.js index ad0cb6af..12f2a78a 100644 --- a/workspace/assets/js/report.js +++ b/workspace/assets/js/report.js @@ -15,7 +15,7 @@ const $ = (id) => document.getElementById(id); const BEREICHSNAME = { live: 'LIVE', content: 'Content', technik: 'Technik', - community: 'Community', schutz: 'Schutz', + community: 'Community', schutz: 'Schutz', agentur: 'Agentur', }; let ich = null; @@ -92,6 +92,7 @@ community: 'bereich.html?b=community', technik: 'bereich.html?b=technik', schutz: 'bereich.html?b=schutz', + agentur: 'bereich.html?b=agentur', content: 'content.html', }; const bereichZiel = (b, zeigen) => { diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 7ea3ca02..9a02910d 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -9,9 +9,9 @@ - - - + + + @@ -211,10 +211,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 076be020..0e886e4b 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -134,10 +134,10 @@ - - - - - + + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 19c0bb40..af14b1f2 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -117,14 +117,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index f6dba9d2..6ed3855b 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -76,11 +76,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index 3c8ac314..58212668 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -9,16 +9,16 @@ - - + + - - + + @@ -154,9 +154,9 @@ - - - - + + + + diff --git a/workspace/content.html b/workspace/content.html index ffaa7073..9f1053f9 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -166,12 +166,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/dateien.html b/workspace/dateien.html index bad00078..dd9fc315 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -95,10 +95,10 @@ - - - - - + + + + + diff --git a/workspace/index.html b/workspace/index.html index e2cd9eef..a912bf76 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -27,7 +27,7 @@ - + @@ -145,6 +145,6 @@

- + diff --git a/workspace/kalender.html b/workspace/kalender.html index 1141b9b3..b9b68faf 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -206,10 +206,10 @@ - - - - - + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index d37395d2..34dacc89 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -199,10 +199,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index 5f902bce..500a1a8c 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -123,10 +123,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index ecfe4d10..0d29707e 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -201,11 +201,11 @@ - - - - - - + + + + + + diff --git a/workspace/report.html b/workspace/report.html index 9f6ed1e0..046ed504 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -93,11 +93,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index 9d61e80c..df953759 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -126,10 +126,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 07c72f13..8014e7d4 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -9,8 +9,8 @@ - - + + @@ -160,10 +160,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index 0d08d848..c3634cd5 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -72,11 +72,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 6e589e41..570e83ee 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -113,10 +113,10 @@ - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index 69e13a6c..8cc8a3f5 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -9,9 +9,9 @@ - - - + + + @@ -82,10 +82,10 @@ - + - - - + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index 0915995f..80f6d8e5 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -9,10 +9,10 @@ - - - - + + + + @@ -177,10 +177,10 @@ - - - - - + + + + +