Filipe, 22.09.2026: "ersetze die zentrale durch, Die IrrenAnstalt.
genau so geschrieben bitte. und alles was treff heißt oder wo treff
steht soll durch Rudel ersetzt werden bitte."
Die Schreibweise "IrrenAnstalt" mit grossem A in der Mitte ist so
gewollt. Das steht als Hinweis daneben, damit sie beim naechsten Mal
niemand "korrigiert".
WAS UMBENANNT WURDE: alles, was jemand LIEST -- Kachelnamen,
Ueberschriften, Markenzeilen, Saetze, Meldungen, Aufgabenvorlagen.
116 Zeilen in 42 Dateien.
WAS BLEIBT: Adressen (treff-regeln.html), Bezeichner (TREFF_ROLLEN),
Datenbankwerte (bereich = 'treff'), Abfrageteile (b=treff). Eine
Adresse umzubenennen bricht jedes Lesezeichen, und ein Datenbankwert
umzuschreiben waere eine Umstellung ohne Gegenwert. Dieselbe
Entscheidung wie heute frueh bei Dogi-Media, wo material.html auch
material.html geblieben ist.
DREI GRAMMATIKFEHLER IM EIGENEN ENTWURF, alle vor dem Ausliefern
gefunden -- ein blindes Ersetzen reicht hier nicht:
1. "der Treff" ist maennlich, "das Rudel" saechlich. Ohne Tabelle
waere ueberall "Der Rudel" gestanden. (Und "Treffer" waere zu
"Rudeler" geworden, "Treffen" zu "Rudelen" -- deshalb greift die
Regel nur bei Wortgrenze und nie vor einem Kleinbuchstaben.)
2. BINDESTRICH-ZUSAMMENSETZUNGEN. In "der Treff-Chat" gehoert der
Artikel zu "Chat", nicht zu "Treff". Der erste Durchlauf machte
daraus "das Rudel-Chat", "das Rudel-Kacheln" und "ein Rudel-Raum".
Jetzt greift die Artikelregel nur, wenn das Wort allein steht.
3. GESCHUETZTE LEERZEICHEN. In den Markenzeilen steht
`Der Treff`, damit die zwei Woerter nicht umbrechen. Das
Muster hat daran vorbeigegriffen: "Der Rudel", auf fuenf
Seiten. Jetzt wird das Trennzeichen mitgefasst und unveraendert
wieder eingesetzt.
Gefunden wurden alle drei, weil jede einzelne der 116 Zeilen vor dem
Schreiben als ALT/NEU ausgegeben und gelesen wurde -- und danach
gezielt nach falschen Artikeln gesucht ("den Rudel", "der Rudel",
"einen Rudel"). Uebrig blieben sechs Treffer, und die sind alle
richtig: "einen Rudel-Raum", "der Rudel-Chat", "den Rudel-Regeln" --
Zusammensetzungen, bei denen der Artikel zum letzten Wort gehoert.
UND ZWEI PRUEFUNGEN, die die Umbenennung nicht mitbekommen haetten:
`/Treff/.test(...)` und `/Treff/i.test(...)`. Ein regulaerer Ausdruck
ist keine Zeichenkette -- sie haetten ab sofort nach einem Wort
gesucht, das die Seite nicht mehr sagt, und waeren still rot geworden,
ohne dass etwas kaputt ist.
Geprueft, alle gruen: pruef-treff, pruef-treffchat,
pruef-deutsche-texte, pruef-uebernahme, pruef-crew-adresse,
pruef-willkommen, pruef-wege-nach-draussen, pruef-vorlagen,
pruef-start-ansicht, pruef-struktur.
Co-Authored-By: Claude Opus 5 <[email protected]>
966 lines
49 KiB
JavaScript
966 lines
49 KiB
JavaScript
/* =====================================================================
|
|
DER CREW — die Community und ihre sieben Bretter (11.09.2026)
|
|
|
|
Filipe:
|
|
|
|
"eine community rolle und community kategorie. wo die community
|
|
auch dan nur die sieht und die modis rechte hand und ich also
|
|
dogfather sehen die aber auch. die community aber nur die dann."
|
|
|
|
"wieso nur eine kachel fuer die community ich will dass die
|
|
mehrere kacheln haben. mit mehreren optionen."
|
|
|
|
---------------------------------------------------------------------
|
|
DIESE PRUEFUNG MISST IN BEIDE RICHTUNGEN, UND DAS IST DER GANZE PUNKT
|
|
|
|
Eine Trennung kann auf zwei Arten falsch sein, und die zweite ist die
|
|
gefaehrlichere:
|
|
|
|
1. SIE GREIFT NICHT -- die Community sieht Dinge des Hauses. Das
|
|
faellt auf, wenn jemand hinsieht.
|
|
2. SIE GREIFT ZU WEIT -- die Community sieht ihren eigenen Raum
|
|
nicht. Das faellt NICHT auf: Ein leeres Brett sieht aus wie
|
|
"hier ist nichts los", nicht wie ein Fehler. Genau so ist am
|
|
01.09. ein Manager vor einem leeren Aufgabenbrett gestanden und
|
|
hat es nicht gemeldet.
|
|
|
|
Deshalb steht zu jeder Zeile "sieht es nicht" eine Zeile "und der da
|
|
sieht es sehr wohl". Ohne die zweite waere die erste auch dann gruen,
|
|
wenn schlicht gar nichts mehr geht.
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM node:http UND NICHT fetch
|
|
|
|
Das Team und die Community melden sich an verschiedenen Waenden an,
|
|
und welche Wand gilt, entscheidet der Host-Kopf. `fetch` in Node
|
|
setzt den selbst und ignoriert einen gesetzten -- die Anmeldung ginge
|
|
dann an die falsche Wand und scheiterte, ohne dass man saehe warum.
|
|
Dieselbe Falle wie am 10.09. bei den Kategorie-Kanaelen.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync, readFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request as httpAnfrage } from "node:http";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-treff");
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-treff-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(PORT);
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
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");
|
|
notbremse(240_000, "pruef-treff");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const HAUS = "workspace.dogfather-universe.com";
|
|
const HAUPT = "dogfather-universe.com";
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const w = await import("./workspace.js");
|
|
const treffModul = await import("./workspace-treff.js");
|
|
const { BEREICHE } = await import("./workspace-bereiche.js");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
const schluessel = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", schluessel, jetzt);
|
|
function anlegen(name, rolle, code) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const hash = scryptSync(code, salt, 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, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
anlegen("Rieke", "hand", "CODE-HAND-0001");
|
|
anlegen("Frida", "modi", "CODE-MODI-0001");
|
|
anlegen("Mara", "manager", "CODE-MANA-0001");
|
|
anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
const idGast = anlegen("Kessi", "gast", "CODE-GAST-0001");
|
|
|
|
/* EIN EINTRAG IN JEDEM DER SIEBEN BRETTER. Ohne Daten waere jede Zeile
|
|
"sieht nichts" trivial wahr -- der gruene Haken ueber der Null. */
|
|
const rein = d.prepare(
|
|
"INSERT INTO eintraege (bereich, art, titel, text, datum, erstellt, erstellt_von)"
|
|
+ " VALUES (?,?,?,?,?,?,?)");
|
|
const BRETTER = ["anschlag", "ansteht", "treff", "wunsch", "highlight", "regeln", "mitmachen"];
|
|
for (const b of BRETTER) rein.run(b, Object.keys((await import("./workspace-bereiche.js")).BEREICHE[b].arten)[0],
|
|
`CREW-PROBE-${b.toUpperCase()}`, "", jetzt.slice(0, 10), jetzt, idDogi);
|
|
/* Und einer im Haus, den die Community NICHT sehen darf. */
|
|
rein.run("ideen", "content", "HAUS-GEHEIM-4711", "", jetzt.slice(0, 10), jetzt, idDogi);
|
|
d.close();
|
|
|
|
/* Die Methode ist ein eigenes Argument, seit es Wege gibt, die PUT
|
|
oder DELETE heissen. Vorher wurde sie aus "gibt es einen Koerper?"
|
|
erraten -- das ging, solange nur GET und POST vorkamen, und haette
|
|
beim ersten PUT stillschweigend ein POST geschickt. Der Server
|
|
haette mit 404 geantwortet, und man sucht den Fehler im Server. */
|
|
function roh(pfad, host, kopf = {}, koerper = null, methode = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad,
|
|
method: methode || (koerper ? "POST" : "GET"),
|
|
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
if (koerper) a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
async function anmelden(rolle, code, host) {
|
|
/* Die Community bestaetigt beim ersten Mal ihr Alter (seit 15.09.2026).
|
|
Nur fuer 'gast' -- fuer alle anderen muss der Server das Feld gar
|
|
nicht erst sehen. */
|
|
const r = await roh("/workspace/api/anmelden", host, {},
|
|
rolle === "gast" ? { rolle, code, alter_ok: true } : { rolle, code });
|
|
const kekse = [].concat(r.kopf?.["set-cookie"] || []);
|
|
return { keks: kekse.map((c) => c.split(";")[0]).join("; "), code: r.code };
|
|
}
|
|
|
|
/* =======================================================================
|
|
1. DIE WAND: die Community kommt auf genau eine Adresse
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Eine Rolle, eine Wand ===");
|
|
const gast = await anmelden("gast", "CODE-GAST-0001", CREW);
|
|
ok(gast.code === 200 && gast.keks.length > 0,
|
|
`die Community kommt auf crew. herein (${gast.code})`);
|
|
|
|
/* AUF DEN ALTEN ADRESSEN NICHT -- und zwar mit derselben Antwort wie
|
|
ein erfundener Code. Ein anderer Fehlercode waere die Auskunft
|
|
"diesen Zugang gibt es, nur nicht hier", und wer das liest, sucht
|
|
weiter. */
|
|
for (const [name, host] of [["workspace.", HAUS], ["der Hauptseite", HAUPT]]) {
|
|
const a = await anmelden("gast", "CODE-GAST-0001", host);
|
|
const b = await anmelden("gast", "VOELLIG-ERFUNDEN-XYZ", host);
|
|
/* NICHT AUF EINEN BESTIMMTEN CODE FESTLEGEN (gemessen 11.09.2026).
|
|
|
|
Hier stand `=== 401`. Auf workspace. stimmt das; auf der
|
|
Hauptseite antwortet der Server 410 -- der Workspace ist von dort
|
|
weggezogen, und "Gone" ist die richtige Antwort fuer JEDEN, der es
|
|
dort noch versucht. Die Pruefung war rot, obwohl genau das Richtige
|
|
passierte.
|
|
|
|
Gemessen wird jetzt, was die Zeile eigentlich behaupten will: Er
|
|
kommt nicht herein, und er kann an der Antwort nicht ablesen, dass
|
|
es seinen Zugang gibt. */
|
|
ok(a.code !== 200 && a.keks === "", `auf ${name} nicht (${a.code})`);
|
|
ok(a.code === b.code, `und zwar mit derselben Antwort wie ein erfundener Code (${a.code}/${b.code})`);
|
|
}
|
|
{
|
|
/* DIE GEGENPROBE: Auf derselben Wand kommt auch das Team herein --
|
|
sonst waere "die Community kommt auf crew. herein" auch dann
|
|
gruen, wenn dort ueberhaupt niemand mehr durchkaeme. */
|
|
const a = await anmelden("admin", "CODE-DOGI-0001", CREW);
|
|
const b = await anmelden("modi", "CODE-MODI-0001", CREW);
|
|
ok(a.code === 200 && b.code === 200,
|
|
`und das Team ebenfalls -- eine Wand fuer beide (${a.code}/${b.code})`);
|
|
|
|
/* UND DIE AGENTUR WEITERHIN NICHT. Dass der Treff hier eingezogen
|
|
ist, hat daran nichts geaendert -- und genau das ist die Stelle,
|
|
an der ein Umbau so etwas versehentlich aufmacht. */
|
|
const c = await anmelden("manager", "CODE-MANA-0001", CREW);
|
|
const d = await anmelden("creator", "CODE-CREA-0001", CREW);
|
|
ok(c.code !== 200 && d.code !== 200,
|
|
`eine Managerin und eine Creatorin aber nicht (${c.code}/${d.code})`);
|
|
}
|
|
|
|
const kDogi = (await anmelden("admin", "CODE-DOGI-0001", HAUS)).keks;
|
|
const kModi = (await anmelden("creator", "CODE-MODI-0001", CREW)).keks;
|
|
const kCrea = (await anmelden("creator", "CODE-CREA-0001", HAUS)).keks;
|
|
const kMana = (await anmelden("manager", "CODE-MANA-0001", HAUS)).keks;
|
|
ok([kDogi, kModi, kCrea, kMana].every((k) => k.length > 0), "die vier Vergleichsrollen sind angemeldet");
|
|
|
|
/* =======================================================================
|
|
1b. EINE WAND FÜR BEIDE — und keine dritte mehr
|
|
|
|
Bis zum Mittag des 11.09.2026 hatte der Treff eine eigene Adresse,
|
|
eine eigene Wand und ein eigenes Manifest. Filipe hat das verworfen:
|
|
*"das soll keine app für sich sein, das soll in der crew seite
|
|
adaptiert werden."*
|
|
|
|
Was dieser Abschnitt jetzt misst, ist deshalb das Gegenteil von
|
|
vorher — und er misst es, weil ein halber Rückbau die gefährlichste
|
|
Lage wäre: eine Wand, die es nicht mehr geben soll, die aber noch
|
|
als Datei herumliegt und von express.static ausgeliefert wird.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1b. Eine Wand für beide ===");
|
|
{
|
|
/* DIE DATEIEN DER DRITTEN ADRESSE SIND WEG. Nicht "nicht mehr
|
|
verlinkt" -- weg. Eine Zugangswand, die niemand mehr meint, die
|
|
aber unter ihrem Dateinamen weiterhin herumliegt, ist genau die
|
|
Sorte Rest, die man ein halbes Jahr spaeter findet.
|
|
|
|
GEFRAGT WIRD DIE PLATTE, NICHT DER SERVER (gemessen 11.09.2026).
|
|
Ueber HTTP antwortet eine geloeschte Seite mit 302 auf die
|
|
Startseite -- weil die Schranke davor greift, bevor express.static
|
|
ueberhaupt nachsieht. Das ist im Betrieb genau richtig (eine Seite,
|
|
die es nicht gibt, verhaelt sich wie eine, die man nicht darf),
|
|
macht die Frage "gibt es die Datei noch?" per HTTP aber
|
|
unbeantwortbar: 302 kommt in beiden Faellen. Wer hier 404 erwartet,
|
|
prueft die Schranke und nennt es Dateipruefung. */
|
|
{
|
|
const { existsSync } = await import("node:fs");
|
|
const { fileURLToPath } = await import("node:url");
|
|
/* fileURLToPath statt .pathname mit Handarbeit: Unter Windows
|
|
liefert .pathname "/C:/..." -- mit einem fuehrenden Schraegstrich,
|
|
den existsSync nicht mag. Wer den mit einem Muster wegschneidet,
|
|
hat eine Zeile, die auf Linux nichts tut und auf Windows alles
|
|
entscheidet. Der eingebaute Weg kennt beide Faelle. */
|
|
const ordner = fileURLToPath(new URL("../workspace/", import.meta.url));
|
|
const reste = ["treff-index.html", "treff.webmanifest",
|
|
"assets/img/buehne/treff-gate-1280.webp"]
|
|
.filter((f) => existsSync(ordner + f));
|
|
ok(reste.length === 0,
|
|
`von der dritten Adresse liegt nichts mehr herum${reste.length ? " -- REST: " + reste.join(", ") : ""}`);
|
|
/* GEGENPROBE: Die Pruefung sieht ueberhaupt an der richtigen
|
|
Stelle nach. Ohne sie waere die Zeile darueber auch bei einem
|
|
falschen Pfad gruen -- der gruene Haken ueber der Null. */
|
|
ok(existsSync(ordner + "crew-index.html"),
|
|
"und die Wand von Team Dogi liegt dort, wo gesucht wurde");
|
|
}
|
|
|
|
/* DIE WAND ZEIGT VIER KACHELN. Die Zahl steht nicht als Zahl da,
|
|
sondern kommt aus der Rollenliste des Servers -- wer eine fünfte
|
|
Rolle auf diese Wand lässt, ohne eine Kachel zu bauen, bekommt
|
|
hier eine rote Zeile statt eines stummen Lochs. */
|
|
const wand = await roh("/workspace/", CREW);
|
|
ok(wand.code === 200 && /Team Dogi|Team Dogi/.test(wand.text),
|
|
`die Wand von Team Dogi steht (${wand.code})`);
|
|
const kacheln = [...wand.text.matchAll(/data-rolle="([a-z]+)"/g)].map((m) => m[1]);
|
|
/* ABGELEITET, NICHT GEZAEHLT (berichtigt 21.09.2026).
|
|
----------------------------------------------------------------
|
|
Hier stand `kacheln.length === 4`. Am 21.09. kam die linke Hand
|
|
dazu, und die Zeile wurde rot, obwohl die Wand richtig war.
|
|
|
|
Die eigentliche Aussage ist auch eine andere und wiegt schwerer:
|
|
Die Anmeldung sucht `WHERE rolle = ?`. Eine Rolle OHNE Kachel
|
|
kommt damit ueberhaupt nicht herein -- der Zugang existiert, und
|
|
niemand kann ihn benutzen. Geprueft wird deshalb, dass jede Rolle
|
|
dieser Adresse ihre Kachel hat. Das kann nicht veralten. */
|
|
const sollRollen = [...w.TREFF_ROLLEN];
|
|
const ohneKachel = sollRollen.filter((r) => !kacheln.includes(r));
|
|
ok(ohneKachel.length === 0, ohneKachel.length === 0
|
|
? `jede Rolle dieser Adresse hat ihre Kachel: ${kacheln.join(", ")}`
|
|
: `OHNE KACHEL und damit unerreichbar: ${ohneKachel.join(", ")}`);
|
|
ok(kacheln.includes("gast"), "die Community ist eine davon");
|
|
ok(kacheln[kacheln.length - 1] === "gast",
|
|
"und sie steht zuletzt -- wer täglich kommt, findet oben, was er täglich braucht");
|
|
|
|
/* DER TEXT AUF DER WAND MUSS FÜR BEIDE STIMMEN. Er stand auf
|
|
"Privater Arbeitsbereich für DogFather und die Modis" -- das liest
|
|
ab heute auch die Community, und für sie ist es schlicht falsch. */
|
|
ok(!/Arbeitsbereich für DogFather und die Modis/.test(wand.text),
|
|
"und der Untertitel behauptet nicht mehr, das hier sei nur für das Team");
|
|
|
|
/* DAS MANIFEST GILT JE ADRESSE und kann sich nicht nach der Rolle
|
|
richten. Filipes Entscheidung: ein Name, der für beide passt. */
|
|
const man = await roh("/workspace/app.webmanifest", CREW);
|
|
let name = "(kein JSON)";
|
|
try { name = JSON.parse(man.text).name; } catch { /* bleibt stehen */ }
|
|
ok(name === "DogFather Universe", `die App heißt "${name}" -- für Team und Community`);
|
|
const manHaus = await roh("/workspace/app.webmanifest", HAUS);
|
|
let nameHaus = "(kein JSON)";
|
|
try { nameHaus = JSON.parse(manHaus.text).name; } catch { /* bleibt stehen */ }
|
|
ok(nameHaus !== name, `und auf der Agenturadresse weiterhin etwas anderes ("${nameHaus}")`);
|
|
|
|
/* DIE WAND VON TEAM DOGI BLEIBT AUF DEN ALTEN ADRESSEN UNSICHTBAR.
|
|
404 und keine Weiterleitung: "Gibt es nicht" verrät nichts. */
|
|
const fremd = await roh("/workspace/crew-index.html", HAUS);
|
|
const schraeg = await roh("/workspace//crew-index.html", HAUS);
|
|
ok(fremd.code === 404 && schraeg.code === 404,
|
|
`die Wand gibt es auf workspace. nicht -- auch mit doppeltem Schrägstrich (${fremd.code}/${schraeg.code})`);
|
|
|
|
/* NICHT INDIZIEREN -- als Kopf und nicht nur als <meta>, damit er
|
|
auch für Manifest, Bilder und jede Antwort ohne HTML gilt. */
|
|
ok(/noindex/i.test(wand.kopf?.["x-robots-tag"] || ""),
|
|
`der Kopf sagt noindex (${wand.kopf?.["x-robots-tag"]})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. DIE KACHELN: sieben, und sonst nichts
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Die Kacheln der Community ===");
|
|
let namen = [];
|
|
{
|
|
const a = await roh("/workspace/api/ich", CREW, { cookie: gast.keks });
|
|
const j = JSON.parse(a.text);
|
|
/* ==== ABGELEITET STATT ABGESCHRIEBEN (19.09.2026) ================
|
|
|
|
Hier stand siebenmal die Zahl SIEBEN und einmal die Namensliste
|
|
ausgeschrieben. Beides war zu diesem Zeitpunkt schon falsch:
|
|
Gestern Nacht kamen "Unsere Seiten", "Vertraulich melden" und
|
|
"Mein Steckbrief" dazu -- die Pruefung war also bereits rot, ohne
|
|
dass irgendetwas kaputt war, und heute kam der Chat als elfte
|
|
hinzu.
|
|
|
|
Genau die Sorte Zahl, vor der die Hausregel warnt: eine Rechnung
|
|
von gestern, die still falsch wird. Gezaehlt wird ab jetzt gegen
|
|
die Liste, die der Server wirklich fuehrt. Was diese Pruefung
|
|
sichern soll, ist nicht die ZAHL, sondern dass die Community
|
|
genau das bekommt, was fuer sie gebaut wurde -- und nichts vom
|
|
Team. */
|
|
const { bereicheFuer } = await import("./workspace.js");
|
|
const erwartet = bereicheFuer({ rolle: "gast" }).map((k) => k.name);
|
|
ok(Array.isArray(j.bereiche) && j.bereiche.length === erwartet.length,
|
|
`die Community bekommt ${erwartet.length} Kacheln (${j.bereiche?.length})`);
|
|
/* Die Namen werden im naechsten Block noch einmal gebraucht -- dort
|
|
wird gegen sie geprueft, statt sie ein zweites Mal zu tippen. Zwei
|
|
Abschriften derselben sieben Namen waeren zwei Gelegenheiten,
|
|
einen davon zu aendern und den anderen zu vergessen. */
|
|
namen = (j.bereiche || []).map((k) => k.name);
|
|
/* DIE REIHENFOLGE IST TEIL DER AUSSAGE, nicht Zufall (17.09.2026).
|
|
"Das Rudel" steht seit heute VORNE, und zwar aus zwei Gruenden:
|
|
|
|
1. Das Raster hat drei Spalten, seine Kachel ist doppelt breit.
|
|
An dritter Stelle passte sie nicht mehr in die erste Reihe und
|
|
riss ein Loch hinein -- genau das, was Filipe auf dem
|
|
Bildschirmfoto gesehen hat. Eine breite Kachel gehoert an den
|
|
Anfang; steht sie hinten, faellt die Luecke in die Mitte.
|
|
2. Der Treff IST das Herz dieses Bereichs. Die Reihenfolge
|
|
stimmt jetzt auch inhaltlich.
|
|
|
|
Wer sie wieder aendert, aendert beides. `pruef-kachelraster`
|
|
rechnet zusaetzlich nach, ob daraus ein Loch entsteht. */
|
|
ok(namen.join(" | ") === erwartet.join(" | "),
|
|
`und zwar diese: ${namen.join(", ")}`);
|
|
/* UEBER DIE EIGENSCHAFT, NICHT UEBER DEN NAMEN (berichtigt
|
|
21.09.2026). Hier stand `namen[0] === "Das Rudel"`. Am 21.09.
|
|
wurde aus der Kachel die Willkommensseite, und die Zeile wurde
|
|
rot, obwohl das Raster stimmte. Gemeint war nie der Name, sondern:
|
|
die DOPPELT BREITE Kachel steht vorne. Genau das wird jetzt
|
|
gefragt. */
|
|
const breite = bereicheFuer({ rolle: "gast" }).filter((k) => k.gross);
|
|
ok(breite.length === 1, `genau eine Kachel ist doppelt breit (${breite.length})`);
|
|
ok(namen[0] === breite[0]?.name,
|
|
`und sie steht vorne (${namen[0]})`);
|
|
/* NICHTS VOM TEAM. Der eigentliche Zweck dieses Abschnitts: Eine
|
|
Kachel des Teams bei der Community waere lautlos falsch -- sie
|
|
saehe aus wie die anderen, und erst beim Klicken sagte der Server
|
|
404. */
|
|
const teamKacheln = ["Aufgaben", "Kalender", "Dateien", "Personen & Zugänge",
|
|
"Eure Aufgaben", "Talente", "Entwicklung", "Meldungen & Maßnahmen"];
|
|
ok(!namen.some((n) => teamKacheln.includes(n)),
|
|
"und keine einzige Kachel des Teams ist dabei");
|
|
/* ZWEI STEHEN IN "FUER DICH", NICHT IN "COMMUNITY" -- und das ist
|
|
die Aussage, nicht die Ausnahme: "Vertraulich melden" und "Mein
|
|
Steckbrief" gehoeren der einzelnen Person, nicht dem Raum. Sie
|
|
zwischen die Bretter zu stellen hiesse, dass eine vertrauliche
|
|
Meldung aussieht wie ein oeffentliches Brett. */
|
|
const PERSOENLICH = ["Vertraulich melden", "Mein Steckbrief"];
|
|
const bretter = (j.bereiche || []).filter((k) => !PERSOENLICH.includes(k.name));
|
|
ok(bretter.every((k) => k.gruppe === "Community"),
|
|
`die ${bretter.length} Bretter stehen in der Gruppe „Community“`);
|
|
ok((j.bereiche || []).filter((k) => PERSOENLICH.includes(k.name))
|
|
.every((k) => k.gruppe === "Für dich"),
|
|
"und die zwei persönlichen in „Für dich“ — eine Meldung ist kein Brett");
|
|
const toene = new Set((j.bereiche || []).map((k) => k.ton));
|
|
ok(toene.size === (j.bereiche || []).length,
|
|
`${toene.size} Kacheln, ${toene.size} verschiedene Farben (${[...toene].join(", ")})`);
|
|
ok(j.rolle_name === "Community", `die Rolle heißt „${j.rolle_name}“`);
|
|
}
|
|
{
|
|
/* GEGENPROBE: Das Team sieht sie mit -- sonst waere "sieben Kacheln"
|
|
auch dann gruen, wenn sie sonst niemand bekommt.
|
|
|
|
UND ES SIEHT EINE MEHR (11.09.2026): "Meldungen & Massnahmen". Sie
|
|
ist ausdruecklich KEIN Brett und steht deshalb nicht in
|
|
TREFF_BEREICHE -- sonst waere sie bei der Community gelandet, und
|
|
zwar lautlos: Sie saehe dort aus wie die anderen sieben, und erst
|
|
beim Klicken haette der Server 404 gesagt.
|
|
|
|
Geprueft wird deshalb nicht eine Zahl, sondern die BEZIEHUNG: Die
|
|
sieben der Community sind eine echte Teilmenge dessen, was das Team
|
|
sieht -- und der Unterschied ist genau die Moderationskachel. Eine
|
|
Zahl waere beim naechsten Umbau wieder rot, ohne dass etwas kaputt
|
|
ist; diese Bedingung nicht. */
|
|
const MOD = "Meldungen & Ma\u00dfnahmen";
|
|
const a = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: kModi })).text);
|
|
const beimModi = (a.bereiche || []).filter((k) => k.gruppe === "Community").map((k) => k.name);
|
|
/* ==== ZWEI KACHELN SIEHT EIN MODI AUSDRUECKLICH NICHT ============
|
|
|
|
Bis zum 18.09. stimmte "ein Modi sieht alle Kacheln der Community
|
|
mit". Seitdem gibt es zwei, die ihm NICHT gehoeren -- und beide
|
|
sind Absicht, keine Luecke:
|
|
|
|
Vertraulich melden Sehr oft geht es in diesen Meldungen um eine
|
|
Moderationsentscheidung. Wer beteiligt ist,
|
|
darf die Beschwerde ueber sich nicht lesen.
|
|
Mein Steckbrief Jeder hat seinen eigenen. "Der von jemand
|
|
anderem" ist hier gar nicht gemeint.
|
|
|
|
Die Pruefung war dadurch seit gestern rot, ohne dass etwas kaputt
|
|
war. Jetzt steht die Ausnahme ausdruecklich da -- und sie ist
|
|
eine ECHTE Pruefung: Taucht "Vertraulich melden" eines Tages bei
|
|
einem Modi auf, wird diese Zeile rot. */
|
|
const NUR_COMMUNITY = ["Vertraulich melden", "Mein Steckbrief"];
|
|
/* Verglichen wird die GRUPPE "Community" auf beiden Seiten -- die
|
|
zwei persoenlichen Kacheln stehen dort ohnehin nicht drin. */
|
|
const sollteSehen = namen.filter((n) => !NUR_COMMUNITY.includes(n));
|
|
const fehlt = sollteSehen.filter((n) => !beimModi.includes(n));
|
|
ok(fehlt.length === 0,
|
|
`ein Modi sieht die ${sollteSehen.length} Bretter der Community mit`
|
|
+ `${fehlt.length ? " -- FEHLT: " + fehlt.join(", ") : ""}`);
|
|
const zuViel = NUR_COMMUNITY.filter((n) => beimModi.includes(n));
|
|
ok(zuViel.length === 0,
|
|
`aber NICHT den vertraulichen Meldeweg${zuViel.length ? " -- DURCH: " + zuViel.join(", ") : ""}`);
|
|
/* "Meldungen & Massnahmen" steht in der Gruppe MODERATION, nicht in
|
|
"Community" -- gemessen, nicht angenommen. Gesucht wird deshalb in
|
|
seiner ganzen Liste. Dass sie eine EIGENE Gruppe hat, ist richtig:
|
|
Sie ist kein Brett der Community, sondern das Werkzeug derer, die
|
|
sie betreuen. */
|
|
const modiAlles = [...(a.bereiche || []), ...(a.bereiche_zusatz || [])]
|
|
.map((k) => k.name);
|
|
ok(modiAlles.includes(MOD) && !namen.includes(MOD),
|
|
`und genau eine mehr, die die Community NICHT hat: ${MOD}`);
|
|
|
|
/* AUF DER TEAM-ADRESSE GEFRAGT, NICHT AUF DER AGENTUR- (21.09.2026).
|
|
|
|
Hier stand `HAUS`, also workspace. -- und die Zeile war richtig,
|
|
solange DogFather dort auch die Bretter des Treffs bekam.
|
|
|
|
Seit dem 21.09. bekommt er sie dort nicht mehr. Filipe: "ich will
|
|
dass du im workspace alles von team dogi weg nimmst ... ich will
|
|
keine mischung mehr von kacheln." Der Treff steht nur noch auf
|
|
seiner eigenen Adresse; von der Agenturseite fuehrt genau EINE
|
|
Tuer hinueber.
|
|
|
|
Die Aussage bleibt dieselbe -- "DogFather sieht dieselben Bretter
|
|
wie die Community" --, sie wird nur dort gemessen, wo diese
|
|
Bretter zuhause sind. Auf der Agenturadresse waere sie ab jetzt
|
|
eine Frage nach etwas, das dort nicht sein soll. */
|
|
const b = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: kDogi })).text);
|
|
/* AUF crew. STEHEN SIE IN `bereiche`, NICHT IN `bereiche_zusatz`:
|
|
Dort ist der Treff sein Zuhause und keine Zugabe. Gesucht wird
|
|
deshalb in beiden Listen -- so misst die Zeile dasselbe, egal von
|
|
welcher Adresse sie kommt. */
|
|
const beiDogi = [...(b.bereiche || []), ...(b.bereiche_zusatz || [])]
|
|
.filter((k) => k.gruppe === "Community").map((k) => k.name);
|
|
/* DogFather sieht den Meldeweg sehr wohl -- er beantwortet ihn. */
|
|
/* Auch hier: MOD steht in der Gruppe "Moderation", nicht in
|
|
"Community". `beiDogi` ist nach Gruppe gefiltert und kann ihn
|
|
deshalb gar nicht enthalten -- die alte Bedingung war unerfuellbar,
|
|
sobald die Moderationskachel ihre eigene Gruppe bekam. */
|
|
const dogiAlles = [...(b.bereiche || []), ...(b.bereiche_zusatz || [])]
|
|
.map((k) => k.name);
|
|
ok(sollteSehen.every((n) => beiDogi.includes(n)),
|
|
`und DogFather sieht auf seiner Adresse dieselben ${beiDogi.length} Bretter`);
|
|
ok(dogiAlles.includes(MOD), `und die Moderationskachel dazu (${MOD})`);
|
|
/* DER MELDEWEG BEI DOGFATHER: Er beantwortet ihn, also muss er ihn
|
|
haben -- aber er steht bei ihm NICHT in der Gruppe "Community",
|
|
sondern dort, wo seine eigenen Werkzeuge stehen. Gesucht wird
|
|
deshalb in seiner GANZEN Liste, nicht nur in einer Gruppe. */
|
|
/* DER MELDEWEG LEBT AUF DER TEAM-ADRESSE, nicht auf der Agentur-.
|
|
Gemessen: Auf crew. hat DogFather ihn in der Gruppe "Fuer dich";
|
|
auf workspace. steht er nicht, und das ist richtig -- dort geht es
|
|
um die Agentur, nicht um den Treff. Geprueft wird deshalb dort, wo
|
|
er hingehoert. */
|
|
const cDogi = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: kDogi })).text);
|
|
const dogiAufCrew = [...(cDogi.bereiche || []), ...(cDogi.bereiche_zusatz || [])]
|
|
.map((k) => k.name);
|
|
ok(dogiAufCrew.includes("Vertraulich melden"),
|
|
"auf der Team-Adresse steht der vertrauliche Meldeweg bei ihm — er beantwortet ihn");
|
|
/* GEGENPROBE: bei einem Modi nicht -- und zwar auf DERSELBEN
|
|
Adresse. Ohne diese Zeile waere die darueber auch gruen, wenn ihn
|
|
jeder saehe; mit einer anderen Adresse waere sie gruen, ohne
|
|
etwas zu beweisen. */
|
|
ok(!modiAlles.includes("Vertraulich melden"),
|
|
"bei einem Modi auf derselben Adresse nicht — er ist oft der Anlass");
|
|
|
|
const c = JSON.parse((await roh("/workspace/api/ich", HAUS, { cookie: kMana })).text);
|
|
const beiMana = (c.bereiche_zusatz || []).filter((k) => k.gruppe === "Community");
|
|
ok(beiMana.length === 0, `eine Managerin nicht (${beiMana.length})`);
|
|
|
|
/* UND DIE MODERATIONSKACHEL IST BEI DER COMMUNITY WIRKLICH NICHT DA
|
|
-- nicht nur "nicht in dieser Liste". Die Kachel ist ein Weg; wenn
|
|
der Weg zu ist, muss auch die Tuer zu sein. */
|
|
const d = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: gast.keks })).text);
|
|
const alleNamen = [...(d.bereiche || []), ...(d.bereiche_zusatz || [])].map((k) => k.name);
|
|
ok(!alleNamen.includes(MOD), `die Community hat keine Moderationskachel (${alleNamen.length} Kacheln)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. DIE BRETTER: was die Community sieht — und was nicht
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Die sieben Bretter ===");
|
|
async function brett(b, host, keks) {
|
|
const a = await roh(`/workspace/api/bereich/${b}`, host, { cookie: keks });
|
|
let titel = [];
|
|
try { titel = (JSON.parse(a.text).eintraege || []).map((e) => e.titel); } catch { /* kein JSON */ }
|
|
return { code: a.code, titel };
|
|
}
|
|
{
|
|
let gefunden = 0;
|
|
for (const b of BRETTER) {
|
|
const r = await brett(b, CREW, gast.keks);
|
|
if (r.code === 200 && r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) gefunden++;
|
|
}
|
|
/* FUENF VON SIEBEN, UND DAS IST DAS ERGEBNIS -- kein Mangel.
|
|
|
|
Termine und Highlights sind fuer die Community erst da, wenn
|
|
jemand aus dem Team sie freigegeben hat (TREFF_FREIGABE_BRETTER).
|
|
Die Zahl steht deshalb nicht als 7 hier, sondern wird aus der
|
|
Tafel gerechnet: Wer ein drittes Freigabe-Brett anlegt, bekommt
|
|
hier keine rote Zeile fuer etwas Richtiges. */
|
|
const OFFENE = BRETTER.filter((b) => !treffModul.TREFF_FREIGABE_BRETTER.includes(b));
|
|
ok(gefunden === OFFENE.length,
|
|
`die Community sieht die ${OFFENE.length} freien Bretter mit Inhalt (${gefunden}/${OFFENE.length})`);
|
|
|
|
/* DIE GEGENPROBE IN BEIDE RICHTUNGEN. Ohne sie waere die Zeile oben
|
|
auch dann gruen, wenn die Freigabe gar nicht wirkt und die beiden
|
|
Bretter aus einem anderen Grund leer sind. */
|
|
let vorher = 0;
|
|
for (const b of treffModul.TREFF_FREIGABE_BRETTER) {
|
|
const r = await brett(b, CREW, gast.keks);
|
|
if (r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) vorher++;
|
|
}
|
|
ok(vorher === 0, `ohne Freigabe sieht die Community Termine und Highlights nicht (${vorher})`);
|
|
|
|
/* Jetzt freigeben -- ueber den echten Weg, nicht in der Datenbank. */
|
|
let freigegeben = 0;
|
|
for (const b of treffModul.TREFF_FREIGABE_BRETTER) {
|
|
const beiDogi2 = await roh(`/workspace/api/bereich/${b}`, HAUS, { cookie: kDogi });
|
|
const eintrag = (JSON.parse(beiDogi2.text).eintraege || [])
|
|
.find((e) => e.titel === `CREW-PROBE-${b.toUpperCase()}`);
|
|
if (!eintrag) continue;
|
|
const a = await roh(`/workspace/api/treff/eintrag/${eintrag.id}/freigeben`, HAUS,
|
|
{ cookie: kDogi }, {}, "PUT");
|
|
if (a.code === 200) freigegeben++;
|
|
}
|
|
ok(freigegeben === treffModul.TREFF_FREIGABE_BRETTER.length,
|
|
`DogFather gibt beide frei (${freigegeben})`);
|
|
|
|
let nachher = 0;
|
|
for (const b of treffModul.TREFF_FREIGABE_BRETTER) {
|
|
const r = await brett(b, CREW, gast.keks);
|
|
if (r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) nachher++;
|
|
}
|
|
ok(nachher === treffModul.TREFF_FREIGABE_BRETTER.length,
|
|
`nach der Freigabe sieht sie beide (${nachher})`);
|
|
|
|
/* Und ein Modi darf nicht freigeben, was er nicht sehen darf --
|
|
hier: dass die Route ueberhaupt Rechte prueft. Eine Creatorin
|
|
bekommt 404, nicht 403: den Treff gibt es fuer sie nicht. */
|
|
const fremd = await roh("/workspace/api/treff/eintrag/1/freigeben", HAUS,
|
|
{ cookie: kCrea }, {}, "PUT");
|
|
ok(fremd.code === 404, `eine Creatorin kann nichts freigeben (${fremd.code})`);
|
|
}
|
|
{
|
|
/* KEIN EINZIGES BRETT DES HAUSES. Ueber die Adresszeile probiert,
|
|
nicht ueber die Kacheln -- eine Regel, die nur im Formular gilt,
|
|
ist keine Regel. */
|
|
const fremde = ["ideen", "angebote", "rueckmeldung", "entwicklung", "talente", "agentur", "live", "content"];
|
|
const durch = [];
|
|
for (const b of fremde) {
|
|
const r = await brett(b, CREW, gast.keks);
|
|
if (r.code === 200) durch.push(b);
|
|
}
|
|
ok(durch.length === 0, `und kein einziges des Hauses${durch.length ? " -- DURCH: " + durch.join(", ") : ""}`);
|
|
|
|
/* DIE SUCHE SPIEGELT DIE FRAGE ZURUECK (Feld `frage`). Wer den
|
|
Rohtext nach dem Suchwort durchsucht, findet deshalb IMMER einen
|
|
Treffer -- und meldet ein Leck, das es nicht gibt. Genau darauf
|
|
bin ich am 10.09. schon einmal hereingefallen und haette fast ein
|
|
Loch repariert, das keines war. Gezaehlt wird, was die Suche
|
|
WIRKLICH gefunden hat. */
|
|
const suche = await roh("/workspace/api/suche?q=HAUS-GEHEIM-4711", CREW, { cookie: gast.keks });
|
|
let treffer = -1;
|
|
try { treffer = JSON.parse(suche.text).gesamt; } catch { treffer = -1; }
|
|
ok(treffer === 0, `auch die Suche findet nichts aus dem Haus (${treffer} Treffer)`);
|
|
}
|
|
{
|
|
/* UND DIE AGENTUR SIEHT DEN CREW NICHT. Beide Richtungen, beide
|
|
ueber die Adresszeile. */
|
|
const durch = [];
|
|
for (const b of BRETTER) {
|
|
const r = await brett(b, HAUS, kCrea);
|
|
if (r.code === 200 && r.titel.some((t) => t.startsWith("CREW-PROBE"))) durch.push(b);
|
|
}
|
|
ok(durch.length === 0, `eine Creatorin sieht kein Brett des Rudels${durch.length ? " -- DURCH: " + durch.join(", ") : ""}`);
|
|
|
|
const durchM = [];
|
|
for (const b of BRETTER) {
|
|
const r = await brett(b, HAUS, kMana);
|
|
if (r.code === 200 && r.titel.some((t) => t.startsWith("CREW-PROBE"))) durchM.push(b);
|
|
}
|
|
ok(durchM.length === 0, `eine Managerin auch nicht${durchM.length ? " -- DURCH: " + durchM.join(", ") : ""}`);
|
|
|
|
/* GEGENPROBE: Der Modi sieht sie sehr wohl. Ohne diese Zeile waeren
|
|
die beiden darueber auch gruen, wenn die Bretter fuer NIEMANDEN
|
|
lesbar waeren. */
|
|
let sieht = 0;
|
|
for (const b of BRETTER) {
|
|
const r = await brett(b, CREW, kModi);
|
|
if (r.code === 200 && r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) sieht++;
|
|
}
|
|
ok(sieht === 7, `ein Modi sieht alle sieben (${sieht}/7)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. DIE SEITEN: die aus der Tafel, und keine einzige mehr
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Nur die Seiten aus der Tafel ===");
|
|
{
|
|
/* DIE LISTE WIRD NICHT GETIPPT, SIE WIRD GEHOLT (11.09.2026).
|
|
|
|
Hier standen zwei Seiten und daneben "und keine dritte". Beim
|
|
Anlegen der Regelseite wurde daraus eine dritte -- die Prüfung
|
|
war rot, obwohl nichts kaputt war, und die naheliegende Reaktion
|
|
wäre gewesen, die Zahl von zwei auf drei zu setzen. Beim vierten
|
|
Mal wieder.
|
|
|
|
Gemessen wird jetzt das, was die Zeile eigentlich behaupten will:
|
|
Die Tafel und die Wirklichkeit stimmen überein — jede Seite, die
|
|
in rechte.js für "gast" steht, geht auf; jede andere ausgelieferte
|
|
Seite geht NICHT auf. Das ist strenger als die alte Fassung (die
|
|
elf Namen waren von Hand gewählt) und altert nicht. */
|
|
const rechte = await import("./rechte.js");
|
|
const { readdirSync } = await import("node:fs");
|
|
const ordner = new URL("../workspace/", import.meta.url).pathname
|
|
.replace(/^\/([A-Za-z]:)/, "$1");
|
|
const alleDateien = readdirSync(ordner).filter((f) => f.endsWith(".html"))
|
|
.map((f) => "/workspace/" + f);
|
|
|
|
const erlaubt = rechte.seitenFuer("gast");
|
|
const verboten = alleDateien
|
|
.filter((p) => !erlaubt.includes(p) && !rechte.OHNE_ANMELDUNG.includes(p));
|
|
|
|
ok(erlaubt.length > 0 && verboten.length > 0,
|
|
`die Tafel gibt der Community ${erlaubt.length} Seiten und verbietet ${verboten.length}`);
|
|
|
|
const zu = [];
|
|
for (const p of erlaubt) {
|
|
const a = await roh(p, CREW, { cookie: gast.keks });
|
|
if (a.code !== 200) zu.push(`${p.split("/").pop()} (${a.code})`);
|
|
}
|
|
ok(zu.length === 0,
|
|
`alle ${erlaubt.length} erlaubten öffnen sich${zu.length ? " -- ZU: " + zu.join(", ") : ""}`);
|
|
|
|
const durch = [];
|
|
for (const p of verboten) {
|
|
const a = await roh(p, CREW, { cookie: gast.keks });
|
|
if (a.code === 200) durch.push(p.split("/").pop());
|
|
}
|
|
ok(durch.length === 0,
|
|
`und keine der ${verboten.length} anderen${durch.length ? " -- DURCH: " + durch.join(", ") : ""}`);
|
|
|
|
/* UND DIE MODERATIONSSEITE AUSDRÜCKLICH NICHT. Sie steht zwar in der
|
|
Liste oben mit drin -- aber sie ist die eine, bei der ein Fehler
|
|
wirklich weh täte: Dort stehen die Namen derer, die gemeldet
|
|
haben. Eine eigene Zeile dafür, damit der Befund einen Namen hat
|
|
und nicht in "eine von 20" untergeht. */
|
|
const mod = await roh("/workspace/treff-moderation.html", CREW, { cookie: gast.keks });
|
|
ok(mod.code !== 200, `die Moderationsseite bleibt zu (${mod.code})`);
|
|
const modApi = await roh("/workspace/api/treff/meldungen", CREW, { cookie: gast.keks });
|
|
ok(modApi.code === 404, `und die Meldungen dahinter auch (${modApi.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DAS HAUS BLEIBT ZU: Aufgaben, Termine, Dateien, Personen
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Vom Haus sieht sie nichts ===");
|
|
for (const [name, pfad] of [
|
|
["Aufgaben", "/workspace/api/aufgaben"],
|
|
["Termine", "/workspace/api/termine"],
|
|
["Dateien", "/workspace/api/dateien"],
|
|
["Personen", "/workspace/api/personen"],
|
|
/* "Gespräche" IST HIER HERAUSGENOMMEN (19.09.2026).
|
|
|
|
Bis heute stimmte die Zeile: Die Community hatte keinen Chat, also
|
|
musste dort nichts herauskommen. Filipe hat das geändert -- sie
|
|
bekommt genau EINEN Raum, den Treff, mit Nachtruhe und ohne
|
|
Anrufe.
|
|
|
|
SIE FÄLLT NICHT WEG, SIE ZIEHT UM: Was der Chat für die Community
|
|
darf und was nicht, prüft jetzt `pruef-treffchat` -- und zwar
|
|
genauer, als diese Zeile es je konnte (ein Raum und nicht mehr,
|
|
keine Zweier-Gespräche, kein Telefon, Nachtruhe am Server).
|
|
Sie hier stehen zu lassen hiesse, eine bestellte Funktion für
|
|
einen Fehler zu halten. */
|
|
]) {
|
|
const a = await roh(pfad, CREW, { cookie: gast.keks });
|
|
let leer = a.code !== 200;
|
|
if (a.code === 200) {
|
|
try {
|
|
const j = JSON.parse(a.text);
|
|
/* EINE WORTLISTE IST KEIN LECK (15.09.2026).
|
|
|
|
Diese Pruefung fragte "enthaelt die Antwort IRGENDEIN gefuelltes
|
|
Feld" -- und wurde rot, als das Aufgabenbrett am 15.09. die drei
|
|
Aufwandsstufen mitzuliefern begann (klein/mittel/gross). Die sind
|
|
fuer jeden Menschen dieselben, sie sagen ueber niemanden etwas,
|
|
und sie kommen mit, damit im Browser keine zweite Liste gepflegt
|
|
werden muss.
|
|
|
|
Ab da meldete die Pruefung bei JEDEM Lauf einen Befund, der keiner
|
|
war -- und eine Warnung, die immer kommt, wird ueberlesen. Genau
|
|
deshalb steht sie hier jetzt praeziser, statt lauter.
|
|
|
|
WAS EINE WORTLISTE VON DATEN UNTERSCHEIDET: Ein Datensatz in
|
|
diesem Haus hat eine `id`. Ein Vokabular hat einen `schluessel`
|
|
und keine. Das ist keine Auslegung, sondern die Bauweise -- jede
|
|
Tabelle vergibt id, jede Wortliste in workspace-*.js nicht. Ein
|
|
echter Eintrag kann dadurch nicht versehentlich als Vokabular
|
|
durchgehen. */
|
|
const istWortliste = (f) => f.length > 0 && f.every((x) => x
|
|
&& typeof x === "object" && typeof x.schluessel === "string" && x.id === undefined);
|
|
const felder = Object.values(j).filter(Array.isArray).filter((f) => !istWortliste(f));
|
|
leer = felder.every((f) => f.length === 0);
|
|
/* Die eigene Person in einer Personenliste ist kein Leck. */
|
|
if (!leer && pfad.endsWith("personen")) {
|
|
leer = felder.every((f) => f.every((p) => p.id === idGast));
|
|
}
|
|
} catch { leer = true; }
|
|
}
|
|
ok(leer, `${name}: nichts (${a.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. SCHREIBEN — und wo es ankommt
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Schreiben ===");
|
|
{
|
|
/* DIE ERSTE WOCHE IST EINE SANDKISTE. Wer eben erst dazugekommen
|
|
ist, liest und stimmt ab -- mehr nicht. Das wird von BEIDEN
|
|
Seiten der Grenze gemessen, sonst beweist es nichts: Eine
|
|
Schreibsperre, die immer sperrt, ist keine Stufe. */
|
|
const neu = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks },
|
|
{ art: "hallo", titel: "GAST-ZU-FRUEH-0001", text: "Hallo zusammen.", datum: jetzt.slice(0, 10) });
|
|
ok(neu.code === 403, `wer neu ist, schreibt noch nicht (${neu.code})`);
|
|
|
|
const lage0 = JSON.parse((await roh("/workspace/api/treff/lage", CREW, { cookie: gast.keks })).text);
|
|
ok(lage0.stufe === "neu", `und die Lage sagt es ihm auch: ${lage0.stufe}`);
|
|
ok(lage0.schreiben?.treff === false, "der Knopf waere aus");
|
|
|
|
/* Acht Tage zurueckdatieren -- genau EIN Tag ueber der Grenze. Eine
|
|
Grenze, die nur von weit weg geprueft wird, koennte um Tage
|
|
danebenliegen, ohne dass es auffaellt. */
|
|
const dd = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
dd.prepare("UPDATE personen SET erstellt = ? WHERE id = ?")
|
|
.run(new Date(Date.now() - 8 * 86400000).toISOString(), idGast);
|
|
dd.close();
|
|
|
|
const a = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks },
|
|
{ art: "hallo", titel: "GAST-SCHREIBT-8899", text: "Hallo zusammen.", datum: jetzt.slice(0, 10) });
|
|
ok(a.code === 200 || a.code === 201, `nach acht Tagen schon (${a.code})`);
|
|
|
|
/* UND SECHS TAGE REICHEN NICHT. Die andere Seite derselben Grenze --
|
|
ohne sie waere "acht Tage gehen" auch dann gruen, wenn die Stufe
|
|
gar nicht geprueft wird. */
|
|
const dd2 = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
dd2.prepare("UPDATE personen SET erstellt = ? WHERE id = ?")
|
|
.run(new Date(Date.now() - 6 * 86400000).toISOString(), idGast);
|
|
dd2.close();
|
|
const knapp = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks },
|
|
{ art: "hallo", titel: "GAST-ZU-FRUEH-0002", text: "Und?", datum: jetzt.slice(0, 10) });
|
|
ok(knapp.code === 403, `sechs Tage reichen nicht (${knapp.code})`);
|
|
const dd3 = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
dd3.prepare("UPDATE personen SET erstellt = ? WHERE id = ?")
|
|
.run(new Date(Date.now() - 8 * 86400000).toISOString(), idGast);
|
|
dd3.close();
|
|
|
|
/* Und auf ein Brett des Teams kommt er auch als "Dabei" nicht. */
|
|
const brettDesTeams = await roh("/workspace/api/bereich/anschlag", CREW, { cookie: gast.keks },
|
|
{ art: Object.keys(BEREICHE.anschlag.arten)[0], titel: "GAST-AM-ANSCHLAG-0001",
|
|
text: "Ansage", datum: jetzt.slice(0, 10) });
|
|
ok(brettDesTeams.code === 403, `aufs Anschlagbrett schreibt nur das Team (${brettDesTeams.code})`);
|
|
|
|
const beiDogi = await brett("treff", HAUS, kDogi);
|
|
ok(beiDogi.titel.includes("GAST-SCHREIBT-8899"), "DogFather sieht den Beitrag");
|
|
const beiModi = await brett("treff", CREW, kModi);
|
|
ok(beiModi.titel.includes("GAST-SCHREIBT-8899"), "der Modi auch");
|
|
const beiCrea = await brett("treff", HAUS, kCrea);
|
|
ok(!beiCrea.titel.includes("GAST-SCHREIBT-8899"), "die Creatorin nicht");
|
|
}
|
|
{
|
|
/* UND NICHT IN EIN BRETT DES HAUSES. */
|
|
const a = await roh("/workspace/api/bereich/ideen", CREW, { cookie: gast.keks },
|
|
{ art: "content", titel: "GAST-IM-HAUS-0000", text: "", datum: jetzt.slice(0, 10) });
|
|
ok(a.code !== 200 && a.code !== 201, `aber nicht in ein Brett des Hauses (${a.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. WEM EIN EINTRAG GILT, SAGT DER SERVER -- NICHT DIE OBERFLAECHE
|
|
|
|
Filipe, 11.09.2026: "auf dieser seite soll nichts stehen von agentur,
|
|
diese seite hier ist nur fuer mich meine modis und meine community."
|
|
|
|
Er hatte recht, und der Fehler war ein fest eingetragenes Wort: In
|
|
bereich.js stand woertlich "Agentur" -- richtig auf der
|
|
Agenturadresse, falsch auf jeder anderen.
|
|
|
|
GEPRUEFT WIRD MIT EINEM EINZIGEN MENSCHEN AUF ZWEI ADRESSEN. Dieselbe
|
|
Person, dasselbe Brett, zwei Waende: Wandert das Wort mit, kommt es
|
|
vom Haus. Waere es an der Rolle festgemacht, stuende hier zweimal
|
|
dasselbe -- und das ist genau der Fehler, den es zu finden gilt.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Das Wort für „Gilt für“ kommt vom Server ===");
|
|
{
|
|
const kCrew = (await anmelden("admin", "CODE-DOGI-0001", CREW)).keks;
|
|
const kHaus = (await anmelden("admin", "CODE-DOGI-0001", HAUS)).keks;
|
|
ok(!!kCrew && !!kHaus, "DogFather ist an beiden Waenden angemeldet");
|
|
|
|
const lies = async (brett, host, keks) => {
|
|
const a = await roh(`/workspace/api/bereich/${brett}`, host, { cookie: keks });
|
|
let g = null;
|
|
try { g = JSON.parse(a.text).gehoert; } catch { /* bleibt null */ }
|
|
return { code: a.code, gehoert: g };
|
|
};
|
|
|
|
const treff = await lies("treff", CREW, kCrew);
|
|
ok(treff.code === 200 && treff.gehoert === "Das Rudel",
|
|
`ein Rudel-Brett gehoert dem Rudel (${treff.gehoert})`);
|
|
|
|
const crew = await lies("ideen", CREW, kCrew);
|
|
ok(crew.code === 200 && crew.gehoert === "Team Dogi",
|
|
`dasselbe Haus-Brett auf crew. gehoert Team Dogi (${crew.gehoert})`);
|
|
|
|
/* GEGENPROBE, und ohne sie beweist die Zeile darueber nichts: Auf der
|
|
Agenturadresse MUSS weiterhin "Agentur" stehen. Ein Wort, das
|
|
ueberall dasselbe sagt, sagt nichts. */
|
|
const haus = await lies("ideen", HAUS, kHaus);
|
|
ok(haus.code === 200 && haus.gehoert === "Agentur",
|
|
`und auf workspace. weiterhin Agentur (${haus.gehoert})`);
|
|
ok(crew.gehoert !== haus.gehoert,
|
|
"dieselbe Person, dasselbe Brett, zwei Woerter -- es haengt am Haus");
|
|
|
|
/* UND DIE OBERFLAECHE SCHREIBT ES NICHT MEHR SELBST HIN. */
|
|
const js = readFileSync(new URL("../workspace/assets/js/bereich.js", import.meta.url), "utf8");
|
|
const ohneKommentare = js.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
ok(!/['"`]Agentur/.test(ohneKommentare),
|
|
"in bereich.js steht das Wort „Agentur“ in keinem Text mehr");
|
|
ok(/gehoert/.test(ohneKommentare),
|
|
"stattdessen fragt sie den Server danach");
|
|
/* Gegenprobe zum Sucher selbst: Er MUSS ein hingeschriebenes
|
|
"Agentur" auch wirklich finden, sonst ist die Zeile darueber nur
|
|
gruen, weil sie blind ist. */
|
|
ok(/['"`]Agentur/.test("const x = 'Agentur-Eintraege';"),
|
|
"und der Sucher findet so ein Wort auch wirklich");
|
|
}
|
|
|
|
/* =====================================================================
|
|
KEIN BRETT DARF ZWISCHEN DIE REGELN FALLEN (21.09.2026)
|
|
=====================================================================
|
|
|
|
Dreimal derselbe Fehler, dreimal derselbe Hergang:
|
|
|
|
19.09. Die Kachel "Regeln & Hilfe" bekam ein neues Ziel.
|
|
`regeln` fiel aus TREFF_BRETTER -- und war fuer die
|
|
Community 404, obwohl treff-regeln.html darauf verweist.
|
|
20.09. Dasselbe noch einmal, bei MODI_BEREICHE_ERLAUBT.
|
|
21.09. Die Kachel "Das Rudel" wurde zur Willkommensseite.
|
|
`treff` fiel heraus -- und diesmal ging es in die andere
|
|
Richtung: Die Schranke laesst eine AGENTURROLLE unbesehen
|
|
durch, wenn ein Brett in keiner Liste steht. Eine
|
|
Creatorin konnte das Brett des Treffs lesen.
|
|
|
|
DER GEMEINSAME KERN: Die Schranke kennt drei Toere -- Treff-Bretter,
|
|
Bretter mit eigener Kachel, Bretter hinter einer Seite. Ein Brett,
|
|
das durch KEINES davon abgedeckt ist, faellt nicht etwa heraus,
|
|
sondern DURCH: Fuer eine Agenturrolle endet der Weg bei
|
|
`return next()`.
|
|
|
|
Genau das wird hier gezaehlt. Nicht "ist treff drin" -- das waere
|
|
die Reparatur von gestern -- sondern: faellt IRGENDEIN Brett durch
|
|
alle Regeln? Wer morgen eine Kachel umleitet, bekommt es damit
|
|
gesagt, statt es zu verschenken.
|
|
===================================================================== */
|
|
melde("\n--- Kein Brett faellt zwischen die Regeln ---");
|
|
{
|
|
const bModul = await import("./workspace-bereiche.js");
|
|
const alleBretter = Object.keys(bModul.BEREICHE || {});
|
|
ok(alleBretter.length > 10,
|
|
`${alleBretter.length} Bretter angesehen`);
|
|
|
|
/* Die Tore der Schranke, aus dem Code geholt statt abgeschrieben. */
|
|
const durchTreff = new Set(w.TREFF_BRETTER);
|
|
const durchKachel = w.MODI_BEREICHE_ERLAUBT;
|
|
|
|
/* DIE EHRLICHE REGEL, GEMESSEN STATT GERATEN.
|
|
----------------------------------------------------------------
|
|
Mein erster Entwurf fragte "haengt dieses Brett an irgendeiner
|
|
Regel" -- und meldete sofort `content` und `schutz`. Beides sind
|
|
AGENTURBRETTER ("Content-Ideen", "Schutz & Regeln"), die eine
|
|
Agenturrolle zu Recht liest. Die Zeile war also zu breit und
|
|
haette zwei Fehlalarme dauerhaft festgeschrieben. Eine Pruefung,
|
|
die zu viel meldet, wird abgeschaltet -- dann haelt sie gar
|
|
nichts mehr.
|
|
|
|
Nachgemessen ist die Trennung exakt und in BEIDE Richtungen
|
|
deckungsgleich: Die Bretter mit `fuerAlle` sind genau die des
|
|
Treffs, plus die Agenturablage. Das ist eine Eigenschaft des
|
|
BRETTS und wandert nicht, wenn jemand eine Kachel umleitet --
|
|
genau der Unterschied, an dem es dreimal gescheitert ist. */
|
|
const fuerAlle = Object.entries(bModul.BEREICHE)
|
|
.filter(([, v]) => v && v.fuerAlle).map(([k]) => k);
|
|
ok(fuerAlle.length >= 7, `${fuerAlle.length} Bretter stehen allen offen`);
|
|
|
|
/* Die Agenturablage ist der eine gewollte Fall: Sie steht allen
|
|
offen, aber im ANDEREN Haus. Sie namentlich auszunehmen ist etwas
|
|
anderes als eine Liste zu pflegen -- es ist eine Ausnahme, die
|
|
man sieht. */
|
|
const GEMEINSCHAFT = fuerAlle.filter((b) => b !== "agentur");
|
|
const ungeschuetzt = GEMEINSCHAFT.filter((b) => !durchTreff.has(b));
|
|
ok(ungeschuetzt.length === 0, ungeschuetzt.length === 0
|
|
? `alle ${GEMEINSCHAFT.length} Bretter des Rudels stehen in TREFF_BRETTER`
|
|
: `NICHT GESCHUETZT -- eine Agenturrolle kaeme hinein: ${ungeschuetzt.join(", ")}`);
|
|
|
|
/* UND DIE ANDERE RICHTUNG: Nichts darf in TREFF_BRETTER stehen, das
|
|
dort nicht hingehoert -- sonst spannte die Liste einen Schutz ueber
|
|
ein Brett, das der Agentur gehoert, und sperrte sie aus. */
|
|
const zuviel = [...durchTreff].filter((b) => !GEMEINSCHAFT.includes(b));
|
|
ok(zuviel.length === 0, zuviel.length === 0
|
|
? "und nichts Fremdes steht darin"
|
|
: `ZUVIEL in TREFF_BRETTER: ${zuviel.join(", ")}`);
|
|
|
|
/* UND EIN MODI MUSS SIE AUCH OEFFNEN KOENNEN. Dieselbe Falle, zweite
|
|
Liste: `MODI_BEREICHE_ERLAUBT` wird ebenfalls aus Kachelzielen
|
|
abgeleitet und verliert ein Brett, sobald eine Kachel umzieht. */
|
|
const fuerModiZu = GEMEINSCHAFT.filter((b) => !durchKachel.has(b));
|
|
ok(fuerModiZu.length === 0, fuerModiZu.length === 0
|
|
? "und ein Modi kommt in jedes davon hinein"
|
|
: `fuer einen Modi gesperrt: ${fuerModiZu.join(", ")}`);
|
|
|
|
/* GEGENPROBE: Wuerden diese Zeilen ein ungeschuetztes Brett
|
|
ueberhaupt sehen? Ein erfundener Name steht in keiner Menge und
|
|
muss deshalb auffallen. */
|
|
const erfunden = "brett-das-es-nicht-gibt";
|
|
ok(!durchTreff.has(erfunden) && !durchKachel.has(erfunden),
|
|
"und ein Brett ohne Schutz wuerde hier wirklich auffallen");
|
|
}
|
|
|
|
melde("");
|
|
console.log(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
console.log(fehler === 0 ? "ALLES IN ORDNUNG" : "NICHT IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest, kein Befund */ }
|
|
process.exit(fehler === 0 ? 0 : 1);
|