Filipe, mit dem Bildschirmfoto: "rechte hand soll immer ueber modi stehen bitte." DIE REIHENFOLGE STAND AN ZWEI STELLEN, und die zweite war falsch herum: ROLLEN_REIHE hatte die Rechte Hand laengst vor den Modis (und die SQL-Sortierung leitet sich daraus ab) -- aber `zusatzrollen` in workspace-personen.js schrieb sie von Hand, und dort kam Modi zuerst. Genau diese Liste baut die Abschnitte auf der Personenseite und die zusaetzlichen Knoepfe im Anlegen-Formular. Das ist an diesem Tag das VIERTE Mal dieselbe Sache: eine Aussage, zwei Listen, und die zweite ist die, die abweicht. Vorher: der Zeitraum-Schutz (an einer von drei Stellen), die Dauer-Einheit (dieselbe), das Recht zum Aufloesen (Liste kannte es, Nachrichtenweg nicht). Deshalb wird jetzt SORTIERT und nicht geschrieben: Die Liste geht durch ROLLEN_REIHE. Wer die Reihenfolge aendern will, aendert sie dort -- und alles andere zieht nach, statt nachgezogen werden zu muessen. Was in ROLLEN_REIHE nicht vorkommt (heute "gast"/Community), wandert ans Ende statt stillschweigend nach vorn. pruef-haus-trennung 63 -> 66. GEPRUEFT WIRD DIE REIHENFOLGE, NICHT DIE ANWESENHEIT: "beide sind da" waere auch dann gruen gewesen, wenn sie vertauscht sind -- und genau das war der Fall. Dazu eine Zeile, die nachweist, dass die Reihenfolge wirklich aus ROLLEN_REIHE kommt und nicht nur zufaellig stimmt; sonst haette jemand sie auch von Hand andersherum schreiben koennen: richtig im Ergebnis, falsch im Aufbau, und beim naechsten Mal wieder auseinander. Co-Authored-By: Claude Opus 5 <[email protected]>
672 lines
34 KiB
JavaScript
672 lines
34 KiB
JavaScript
/* ZWEI HÄUSER, EINE DATENBANK (10.09.2026)
|
|
|
|
Filipe: "die daten von dieser seite sollen nichts mit den daten am
|
|
hut haben von der workspace seite bitte, die hier soll ihre eigene
|
|
daten haben und komplett von der anderen getrennt sein. dogfather
|
|
soll die daten auch auf der anderen seite sehen in der team dogi
|
|
kategorie aber auch nur er und vanvan."
|
|
|
|
---------------------------------------------------------------------
|
|
WAS HIER GEMESSEN WIRD -- UND WARUM ES ZWEI RICHTUNGEN BRAUCHT
|
|
|
|
Die Trennung hängt an EINEM Wert: `person.haus`, gesetzt in
|
|
sitzungLesen() aus dem Hostnamen. Von dort reist er durch jede
|
|
Sichtbarkeitsregel im Haus. Ein Wert, der so weit trägt, hat zwei
|
|
Arten, falsch zu sein:
|
|
|
|
ER GREIFT NICHT -- dann steht die Agentur weiterhin auf der
|
|
Team-Seite, und niemand merkt es, weil alles funktioniert.
|
|
|
|
ER GREIFT ZU WEIT -- dann verschwindet auf der AGENTURSEITE etwas,
|
|
das dort hingehört. Das ist der gefährlichere Fall: Eine Liste, die
|
|
zu kurz ist, sieht aus wie "nichts zu tun".
|
|
|
|
Deshalb steht hinter JEDER Messung auf crew. dieselbe Messung auf
|
|
workspace., mit DERSELBEN Sitzung. Der einzige Unterschied zwischen
|
|
den beiden Aufrufen ist der Host-Kopf -- alles andere ist gleich.
|
|
|
|
`fetch` kann diesen Kopf nicht setzen (undici setzt ihn aus der
|
|
Adresse; nachgemessen, nicht vermutet). Deshalb node:http.
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request } from "node:http";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-haus-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
const { portMussFreiSein } = await import("./helfer-port.mjs");
|
|
await portMussFreiSein(4381, "die Haustrennung");
|
|
|
|
process.env.PORT = "4381";
|
|
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 }); };
|
|
await import("./index.js");
|
|
await new Promise((r) => setTimeout(r, 1000));
|
|
|
|
for (const art of ["uncaughtException", "unhandledRejection"]) {
|
|
process.on(art, (f) => { console.error(""); console.error("ABBRUCH:", f?.stack || f); process.exit(1); });
|
|
}
|
|
setTimeout(() => { console.error(""); console.error("ABBRUCH: 150 s vorbei."); process.exit(1); },
|
|
150000).unref?.();
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const WS = "workspace.dogfather-universe.com";
|
|
|
|
function hole(pfad, { host, methode = "GET", rumpf = null, keks = "" } = {}) {
|
|
return new Promise((fertig, schief) => {
|
|
const kopf = { Host: host };
|
|
if (rumpf) { kopf["Content-Type"] = "application/json"; kopf["Content-Length"] = Buffer.byteLength(rumpf); }
|
|
if (keks) kopf.Cookie = keks;
|
|
const a = request({ host: "127.0.0.1", port: 4381, path: pfad, method: methode, headers: kopf },
|
|
(antwort) => {
|
|
let text = "";
|
|
antwort.setEncoding("utf8");
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => {
|
|
let daten = null;
|
|
try { daten = JSON.parse(text); } catch { /* kein JSON */ }
|
|
fertig({ status: antwort.statusCode, text, daten,
|
|
keks: (antwort.headers["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ") });
|
|
});
|
|
});
|
|
a.on("error", schief);
|
|
a.setTimeout(15000, () => { a.destroy(new Error("Zeitueberschreitung")); });
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
/* ---- Bestand -------------------------------------------------------- */
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
await hole("/workspace/api/anmelden", { host: WS, methode: "POST",
|
|
rumpf: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
|
|
for (let i = 0; i < 60; i++) {
|
|
try {
|
|
const p = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
|
|
} catch { await new Promise((r) => setTimeout(r, 250)); }
|
|
}
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
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, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
const idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
const idHand = anlegen("VanVan", "hand", "CODE-HAND-0001");
|
|
const idModi = anlegen("Ayla", "modi", "CODE-TEAM-0001");
|
|
const idMana = anlegen("Cigdem", "manager", "CODE-MANA-0001");
|
|
const idScout = anlegen("Patrick", "scout", "CODE-SCOU-0001");
|
|
const idCrea = anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idCrea, idScout, jetzt);
|
|
|
|
/* Zwei Aufgaben: eine der Agentur, eine des Teams. */
|
|
d.prepare(`INSERT INTO aufgaben (titel, status, creator_id, verantwortlich_id, erstellt, erstellt_von)
|
|
VALUES ('Lunas Steckbrief nachtragen','offen',?,?,?,?)`).run(idCrea, idScout, jetzt, idDogi);
|
|
d.prepare(`INSERT INTO aufgaben (titel, status, verantwortlich_id, erstellt, erstellt_von)
|
|
VALUES ('Clips vom Freitag schneiden','offen',?,?,?)`).run(idModi, jetzt, idDogi);
|
|
|
|
/* Zwei Eintraege: einer im Bereich eines Creators, einer im Ideen-Board. */
|
|
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, creator_id, erstellt, erstellt_von)
|
|
VALUES ('live','auswertung','Lunas Live vom Montag','2026-09-08',?,?,?)`)
|
|
.run(idCrea, jetzt, idDogi);
|
|
d.prepare(`INSERT INTO eintraege (bereich, art, titel, datum, erstellt, erstellt_von)
|
|
VALUES ('ideen','community','Wunschkonzert am Sonntag','2026-09-08',?,?)`)
|
|
.run(jetzt, idModi);
|
|
|
|
/* Zwei Termine: einer mit einem Creator, einer mit dem Team. */
|
|
d.prepare(`INSERT INTO termine (titel, beginn, dauer_min, creator_id, erstellt, erstellt_von)
|
|
VALUES ('Call mit Luna','2026-09-12T18:00:00.000Z',60,?,?,?)`).run(idCrea, jetzt, idDogi);
|
|
d.prepare(`INSERT INTO termine (titel, beginn, dauer_min, teilnehmer_id, erstellt, erstellt_von)
|
|
VALUES ('Teamrunde','2026-09-12T20:00:00.000Z',30,?,?,?)`).run(idModi, jetzt, idDogi);
|
|
/* ZWEI TERMINE FUER DIE RECHTE HAND, und beide braucht die Pruefung:
|
|
|
|
Ohne einen eigenen Termin saehe sie GAR NICHTS -- jeder sieht nur,
|
|
woran er beteiligt ist -- und "sie sieht den Agenturtermin nicht"
|
|
waere ein gruener Haken ueber einer Null. Erst mit einem sichtbaren
|
|
daneben sagt die Messung etwas: Das eine kommt an, das andere nicht. */
|
|
d.prepare(`INSERT INTO termine (titel, beginn, dauer_min, creator_id, erstellt, erstellt_von)
|
|
VALUES ('Lunas Abnahme','2026-09-13T17:00:00.000Z',45,?,?,?)`).run(idCrea, jetzt, idHand);
|
|
d.prepare(`INSERT INTO termine (titel, beginn, dauer_min, teilnehmer_id, erstellt, erstellt_von)
|
|
VALUES ('Schichtuebergabe','2026-09-13T19:00:00.000Z',20,?,?,?)`).run(idModi, jetzt, idHand);
|
|
|
|
async function anmelden(rolle, code, wand) {
|
|
const a = await hole("/workspace/api/anmelden", { host: wand, methode: "POST",
|
|
rumpf: JSON.stringify({ rolle, code }) });
|
|
return { ok: a.status === 200, keks: a.keks };
|
|
}
|
|
const dogi = await anmelden("admin", "CODE-DOGI-0001", CREW);
|
|
const hand = await anmelden("hand", "CODE-HAND-0001", CREW);
|
|
const mana = await anmelden("manager", "CODE-MANA-0001", WS);
|
|
ok(dogi.ok && hand.ok && mana.ok, "angemeldet");
|
|
|
|
/* Beide Aufrufe mit DERSELBEN Sitzung -- nur der Host unterscheidet sie. */
|
|
const beide = async (pfad, keks) => ({
|
|
crew: (await hole(pfad, { host: CREW, keks })).daten,
|
|
ws: (await hole(pfad, { host: WS, keks })).daten,
|
|
});
|
|
|
|
/* =======================================================================
|
|
1. Die Kacheln
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Was DogFather zu sehen bekommt ===");
|
|
{
|
|
const a = await beide("/workspace/api/ich", dogi.keks);
|
|
const namen = (x) => [...(x?.bereiche || []), ...(x?.bereiche_zusatz || [])].map((k) => k.name);
|
|
const aufCrew = namen(a.crew);
|
|
const aufWs = namen(a.ws);
|
|
|
|
ok(aufCrew.includes("Rückmeldung") && aufCrew.includes("Aufgaben"),
|
|
`auf crew. stehen die Kacheln des Teams (${aufCrew.length})`);
|
|
ok(!aufCrew.some((n) => ["Creator-Profile", "Zahlen", "Agentur", "Scout-Pipeline"].includes(n)),
|
|
`und keine der Agentur (${aufCrew.join(", ")})`);
|
|
|
|
/* GEGENPROBE, und sie ist schaerfer als sie aussieht.
|
|
|
|
Auf der Agenturadresse liefert der Server `bereiche: null` -- das
|
|
heisst "nimm die vollstaendige Liste aus assets/js/bereiche.js".
|
|
Nur auf crew. schickt er eine eigene. Der Unterschied zwischen
|
|
"null" und "einer Liste" IST die Trennung; wer hier bloss Namen
|
|
zaehlt, misst am Mechanismus vorbei.
|
|
|
|
(Beim ersten Anlauf stand hier genau das: eine Suche nach
|
|
"Creator-Profile" in der Antwort. Sie schlug fehl -- nicht weil
|
|
die Kachel fehlt, sondern weil sie dort gar nicht ueber die
|
|
Schnittstelle kommt. Die Pruefung hatte unrecht, nicht der Code.) */
|
|
ok(a.ws?.bereiche === null,
|
|
`auf workspace. bleibt die Kachelliste die der Datei (${a.ws?.bereiche === null ? "null" : "eigene Liste"})`);
|
|
ok(Array.isArray(a.crew?.bereiche) && a.crew.bereiche.length > 0,
|
|
`auf crew. schickt der Server eine eigene (${a.crew?.bereiche?.length})`);
|
|
ok(aufWs.includes("Rückmeldung"),
|
|
"und die Team-Kachel steht dort ebenfalls -- er sieht seine Daten auch dort");
|
|
|
|
/* Und keine doppelt: bereicheFuer liefert auf crew. schon die
|
|
Team-Liste, zusatzBereicheFuer muss dort schweigen. */
|
|
ok(new Set(aufCrew).size === aufCrew.length,
|
|
`keine Kachel steht zweimal da (${aufCrew.length})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. Aufgaben
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Aufgaben ===");
|
|
{
|
|
const a = await beide("/workspace/api/aufgaben", dogi.keks);
|
|
const t = (x) => (x?.aufgaben || []).map((z) => z.titel);
|
|
ok(t(a.crew).includes("Clips vom Freitag schneiden"),
|
|
`auf crew. steht die Aufgabe des Teams (${t(a.crew).join(" | ") || "leer"})`);
|
|
ok(!t(a.crew).includes("Lunas Steckbrief nachtragen"),
|
|
"und die der Agentur nicht");
|
|
ok(t(a.ws).includes("Lunas Steckbrief nachtragen") && t(a.ws).includes("Clips vom Freitag schneiden"),
|
|
`auf workspace. stehen beide (${t(a.ws).length})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. Bereiche
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Bereiche ===");
|
|
{
|
|
const live = await beide("/workspace/api/bereich/live", dogi.keks);
|
|
ok((live.crew?.eintraege || []).length === 0,
|
|
`auf crew. ist der Creator-Bereich leer (${(live.crew?.eintraege || []).length})`);
|
|
ok((live.ws?.eintraege || []).some((e) => e.titel === "Lunas Live vom Montag"),
|
|
"auf workspace. steht der Eintrag da");
|
|
|
|
const ideen = await beide("/workspace/api/bereich/ideen", dogi.keks);
|
|
ok((ideen.crew?.eintraege || []).some((e) => e.titel === "Wunschkonzert am Sonntag"),
|
|
"das Ideen-Board des Teams steht auf crew.");
|
|
ok((ideen.ws?.eintraege || []).some((e) => e.titel === "Wunschkonzert am Sonntag"),
|
|
"und auf workspace. ebenfalls -- er sieht es hier wie dort");
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. Der Kalender
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Kalender ===");
|
|
{
|
|
const weg = "/workspace/api/termine?von=2026-09-01&bis=2026-09-30";
|
|
/* DER KALENDER IST DIE EINE AUSNAHME (11.09.2026).
|
|
|
|
Filipe: "mein kalender (dogfather) auf dieser seite hier soll
|
|
komplett verbunden sein mit dem kalender in der workspace seite
|
|
ABER NUR IN DER DOGFATHER ROLLE BEI DOGFATHER: NICHT BEI VANVAN."
|
|
|
|
Bei Aufgaben, Bereichen und Dateien ist die Trennung eine Hilfe --
|
|
man will das andere Haus dort gerade nicht sehen. Beim Kalender
|
|
waere sie eine Falle: Wer auf der Team-Seite einen Termin eintraegt
|
|
und die Haelfte seines Tages nicht sieht, legt ihn auf eine Zeit,
|
|
in der er schon woanders sitzt.
|
|
|
|
Diese Erwartung hat sich damit gedreht -- vorher verlangte sie das
|
|
Gegenteil. Sie ist dadurch nicht schwaecher: Sie prueft jetzt eine
|
|
UNTERSCHEIDUNG (er ja, sie nein) statt einer einzelnen Antwort, und
|
|
das ist mehr, nicht weniger. */
|
|
const a = await beide(weg, dogi.keks);
|
|
const t = (x) => (x?.termine || []).map((z) => z.titel);
|
|
ok(t(a.crew).includes("Teamrunde") && t(a.crew).includes("Call mit Luna"),
|
|
`DogFather sieht auf crew. seinen GANZEN Kalender (${t(a.crew).join(" | ") || "leer"})`);
|
|
ok(t(a.ws).includes("Call mit Luna") && t(a.ws).includes("Teamrunde"),
|
|
`und auf workspace. denselben (${t(a.ws).length})`);
|
|
|
|
/* UND DIE RECHTE HAND NICHT -- sonst hiesse "verbunden" schlicht
|
|
"die Trennung ist weg". */
|
|
const h = (await hole(weg, { host: CREW, keks: hand.keks })).daten;
|
|
const th = (h?.termine || []).map((z) => z.titel);
|
|
ok(th.includes("Schichtuebergabe"),
|
|
`die rechte Hand sieht ihren Team-Termin (${th.join(" | ") || "leer"})`);
|
|
ok(!th.includes("Lunas Abnahme"),
|
|
"und ihren eigenen Agenturtermin dort NICHT -- die Trennung gilt fuer sie weiter");
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. Mit wem man schreiben kann
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Die Personenliste ===");
|
|
{
|
|
const a = await beide("/workspace/api/chat/partner", dogi.keks);
|
|
const n = (x) => (x?.personen || []).map((p) => p.name);
|
|
ok(n(a.crew).includes("Ayla") && n(a.crew).includes("VanVan"),
|
|
`auf crew. steht das Team (${n(a.crew).join(", ") || "leer"})`);
|
|
ok(!n(a.crew).some((x) => ["Luna", "Cigdem", "Patrick"].includes(x)),
|
|
"und niemand aus der Agentur");
|
|
ok(["Luna", "Cigdem", "Patrick", "Ayla"].every((x) => n(a.ws).includes(x)),
|
|
`auf workspace. stehen alle (${n(a.ws).length})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. Die rechte Hand -- dieselbe Trennung
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== VanVan ===");
|
|
{
|
|
const a = await beide("/workspace/api/aufgaben", hand.keks);
|
|
const t = (x) => (x?.aufgaben || []).map((z) => z.titel);
|
|
ok(t(a.crew).includes("Clips vom Freitag schneiden"), "sie sieht die Team-Aufgabe auf crew.");
|
|
ok(!t(a.crew).includes("Lunas Steckbrief nachtragen"), "die der Agentur nicht");
|
|
/* Auf der Agenturadresse kommt sie gar nicht erst herein -- das ist
|
|
die Regel vom 10.09., und sie muss weiter gelten. */
|
|
const ws = await hole("/workspace/api/aufgaben", { host: WS, keks: hand.keks });
|
|
ok(ws.status === 401, `und auf workspace. ist sie niemand (${ws.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. Die Agentur bleibt unberuehrt
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Und der Manager? ===");
|
|
{
|
|
const a = await hole("/workspace/api/aufgaben", { host: WS, keks: mana.keks });
|
|
const t = (a.daten?.aufgaben || []).map((z) => z.titel);
|
|
ok(a.status === 200, `er kommt herein (${a.status})`);
|
|
/* Er sieht die Aufgabe seines Creators nicht -- Luna gehoert Patrick,
|
|
nicht ihm. Das ist die alte Regel, und sie darf sich nicht
|
|
geaendert haben. Wichtig ist hier nur: Die Team-Aufgabe sieht er
|
|
ganz sicher nicht. */
|
|
ok(!t.includes("Clips vom Freitag schneiden"),
|
|
`und sieht die Aufgabe des Teams nicht (${t.join(" | ") || "leer"})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. Die Zahlen aus dem Bildschirmfoto
|
|
=======================================================================
|
|
|
|
Genau die beiden Stellen, auf die Filipe gezeigt hat: der Ring der
|
|
Zentrale ("9 IM TEAM", obwohl das Team drei Leute hat) und die
|
|
Hinweiszeile darunter ("Creator-Profile sind noch leer").
|
|
|
|
Beide gehen ABSICHTLICH nicht durch die zentralen Listenfunktionen --
|
|
im Quelltext der Zentrale steht das sogar ausdruecklich. Genau
|
|
deshalb greift die Haustrennung dort nicht von selbst, und genau
|
|
deshalb muessen sie hier einzeln stehen.
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Der Ring und die Hinweise ===");
|
|
{
|
|
const z = await beide("/workspace/api/zentrale", dogi.keks);
|
|
/* Der Ring heisst in der Antwort `segmente` -- ein Segment je Person.
|
|
Beim ersten Anlauf stand hier `team.leute`; die Pruefung meldete
|
|
"leer" auf BEIDEN Adressen und haette damit fast bestaetigt, was
|
|
sie widerlegen sollte. Deshalb steht unten eine Zeile, die
|
|
verlangt, dass ueberhaupt jemand gefunden wird. */
|
|
const leute = (x) => (x?.segmente || []).map((p) => p.name);
|
|
const aufCrew = leute(z.crew);
|
|
const aufWs = leute(z.ws);
|
|
ok(aufCrew.length > 0, `der Ring ist auf crew. nicht leer (${aufCrew.join(", ") || "leer"})`);
|
|
ok(!aufCrew.some((n) => ["Luna", "Cigdem", "Patrick"].includes(n)),
|
|
"und es steht niemand aus der Agentur darin");
|
|
ok(aufWs.some((n) => ["Luna", "Cigdem", "Patrick"].includes(n)),
|
|
`auf workspace. steht das ganze Haus darin (${aufWs.length})`);
|
|
|
|
const h = await beide("/workspace/api/hinweise", dogi.keks);
|
|
const texte = (x) => (x?.hinweise || []).map((w) => w.text || w.titel || "");
|
|
ok(!texte(h.crew).some((w) => /Creator/i.test(w)),
|
|
`keine Creator-Hinweise auf crew. (${texte(h.crew).length} Hinweise)`);
|
|
ok(texte(h.ws).some((w) => /Creator/i.test(w)),
|
|
`auf workspace. stehen sie sehr wohl (${texte(h.ws).length} Hinweise)`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
9. Personen & Zugaenge -- auf der Team-Seite nur das Team
|
|
=======================================================================
|
|
|
|
Filipe: "die kategorie personen & zugaenge fehlt also muss das
|
|
hinzugefuegt werden und bitte nur basiert auf diese seite."
|
|
|
|
Sie fehlte, weil sie mit den Agenturkacheln verschwunden ist -- und
|
|
ausgerechnet sie ist die, mit der man jemandem eine Rolle gibt.
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Personen & Zugaenge ===");
|
|
{
|
|
const a = await beide("/workspace/api/ich", dogi.keks);
|
|
const namen = (x) => (x?.bereiche || []).map((k) => k.name);
|
|
ok(namen(a.crew).includes("Personen & Zugänge"),
|
|
`die Kachel steht auf crew. (${namen(a.crew).length} Kacheln)`);
|
|
|
|
/* UND BEI DER RECHTEN HAND EBENFALLS -- diese Erwartung hat sich
|
|
gedreht, und das gehoert hierhergeschrieben.
|
|
|
|
Zuerst stand hier das Gegenteil: Die Kachel durfte bei ihr NICHT
|
|
stehen, weil die Seite serverseitig an `nurAdmin` hing und ein
|
|
Knopf, der eine Absage bringt, schlimmer ist als kein Knopf. Das
|
|
war richtig -- solange sie die Seite nicht durfte.
|
|
|
|
Filipe danach: "ich will dass die rechte hand auch alle sieht."
|
|
Also darf sie sie, lesend. Damit stimmt der Knopf wieder.
|
|
|
|
Die Pruefung ist deshalb nicht schwaecher geworden: Sie verlangt
|
|
weiterhin, dass Kachel und Zugang DASSELBE sagen -- nur sagen sie
|
|
jetzt beide ja statt beide nein. Der Fall, den sie verhindert, ist
|
|
unveraendert: ein Knopf ohne Raum dahinter. */
|
|
const h = await hole("/workspace/api/ich", { host: CREW, keks: hand.keks });
|
|
ok((h.daten?.bereiche || []).map((k) => k.name).includes("Personen & Zugänge"),
|
|
"die Kachel steht auch bei der rechten Hand");
|
|
const hZugriff = await hole("/workspace/api/verwaltung/personen", { host: CREW, keks: hand.keks });
|
|
ok(hZugriff.status === 200,
|
|
`und die Seite laesst sie herein (${hZugriff.status}) -- Kachel und Zugang sagen dasselbe`);
|
|
|
|
const liste = await beide("/workspace/api/verwaltung/personen", dogi.keks);
|
|
const wer = (x) => (x?.personen || []).map((p) => p.name);
|
|
ok(wer(liste.crew).includes("Ayla") && wer(liste.crew).includes("VanVan"),
|
|
`auf crew. steht das Team (${wer(liste.crew).join(", ") || "leer"})`);
|
|
ok(!wer(liste.crew).some((n) => ["Luna", "Cigdem", "Patrick"].includes(n)),
|
|
"und niemand aus der Agentur");
|
|
ok(["Luna", "Cigdem", "Patrick", "Ayla"].every((n) => wer(liste.ws).includes(n)),
|
|
`auf workspace. stehen alle (${wer(liste.ws).length})`);
|
|
|
|
/* Die anlegbaren Rollen kommen aus derselben Auskunft, aus der die
|
|
Oberflaeche ihre Knoepfe baut. Auf crew. duerfen es nur Team-Rollen
|
|
sein -- sonst legt man dort versehentlich einen Creator an und
|
|
findet ihn erst auf der anderen Seite wieder. */
|
|
const neuCrew = await hole("/workspace/api/verwaltung/personen", { host: CREW,
|
|
methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({ name: "Testcreator", rolle: "creator" }) });
|
|
ok(neuCrew.status === 403,
|
|
`ein Creator laesst sich auf crew. nicht anlegen (${neuCrew.status})`);
|
|
/* GEGENPROBE: Auf der Agenturadresse geht genau das. Ohne sie hiesse
|
|
die 403 oben auch dann gruen, wenn Anlegen ueberhaupt kaputt waere. */
|
|
const neuWs = await hole("/workspace/api/verwaltung/personen", { host: WS,
|
|
methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({ name: "Testcreator", rolle: "creator" }) });
|
|
ok(neuWs.status === 201, `auf workspace. geht es (${neuWs.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
9b. DIE RECHTE HAND STEHT UEBER DEN MODIS (14.09.2026)
|
|
=======================================================================
|
|
Filipe, mit dem Bildschirmfoto: "rechte hand soll immer ueber modi
|
|
stehen bitte."
|
|
|
|
Die Reihenfolge stand an ZWEI Stellen: in ROLLEN_REIHE (richtig) und
|
|
von Hand in `zusatzrollen` (falsch herum). Dasselbe Muster wie heute
|
|
schon dreimal -- eine Aussage, zwei Listen, und die zweite ist die
|
|
unvollstaendige. Jetzt wird sortiert, nicht geschrieben.
|
|
|
|
GEPRUEFT WIRD DIE REIHENFOLGE, NICHT DIE ANWESENHEIT: "beide sind
|
|
da" waere auch dann gruen, wenn sie vertauscht sind -- und genau das
|
|
war der Fall. */
|
|
{
|
|
const antwort = await hole("/workspace/api/verwaltung/personen",
|
|
{ host: CREW, keks: dogi.keks });
|
|
const zusatz = (antwort.daten?.zusatzrollen || []).map((z) => z.wert);
|
|
ok(zusatz.includes("hand") && zusatz.includes("modi"),
|
|
`beide Rollen stehen in der Liste (${zusatz.join(", ")})`);
|
|
ok(zusatz.indexOf("hand") < zusatz.indexOf("modi"),
|
|
`die Rechte Hand steht VOR den Modis (${zusatz.join(" -> ")})`);
|
|
|
|
/* UND SIE FOLGT DER EINEN LISTE. Ohne diese Zeile haette jemand die
|
|
Reihenfolge auch von Hand andersherum schreiben koennen -- richtig
|
|
im Ergebnis, falsch im Aufbau, und beim naechsten Mal wieder
|
|
auseinander. */
|
|
const { ROLLEN_REIHE } = await import("./workspace.js");
|
|
const erwartet = [...zusatz].sort((a, b) => {
|
|
const p = (r) => (ROLLEN_REIHE.indexOf(r) < 0 ? ROLLEN_REIHE.length : ROLLEN_REIHE.indexOf(r));
|
|
return p(a) - p(b);
|
|
});
|
|
ok(JSON.stringify(zusatz) === JSON.stringify(erwartet),
|
|
`die Reihenfolge folgt ROLLEN_REIHE (${zusatz.join(", ")})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
10. Eine Rolle aendern -- es gab die Funktion gar nicht
|
|
=======================================================================
|
|
|
|
Beim Suchen nach "wie gebe ich VanVan die Rolle Rechte Hand" kam
|
|
heraus: Es gibt im ganzen Server keine Stelle, die `personen.rolle`
|
|
aendert. Anlegen ja, sperren ja, loeschen ja -- aendern nirgends. Wer
|
|
jemandem eine andere Aufgabe geben wollte, musste ihn loeschen und
|
|
neu anlegen, und daran haengen Aufgaben, Nachrichten und Eintraege.
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Die Rolle aendern ===");
|
|
{
|
|
const weg = "/workspace/api/verwaltung/personen";
|
|
|
|
/* Erst die Sicherungen, dann der Erfolgsfall -- sonst pruefe ich sie
|
|
auf einem Bestand, den der Erfolgsfall schon veraendert hat. */
|
|
const selbst = await hole(`${weg}/${idDogi}/rolle`, { host: CREW, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "hand" }) });
|
|
ok(selbst.status === 400, `die eigene Rolle laesst sich nicht aendern (${selbst.status})`);
|
|
|
|
const erfunden = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "gibtsnicht" }) });
|
|
ok(erfunden.status === 400, `eine erfundene Rolle wird abgelehnt (${erfunden.status})`);
|
|
|
|
const gleich = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "modi" }) });
|
|
ok(gleich.status === 400, `dieselbe Rolle noch einmal: abgelehnt (${gleich.status})`);
|
|
|
|
/* Auf der Team-Adresse laesst sich niemand zum Creator machen -- die
|
|
Rollen kommen aus derselben Auskunft wie beim Anlegen. */
|
|
const fremd = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "creator" }) });
|
|
ok(fremd.status === 403, `auf crew. auch nicht zum Creator (${fremd.status})`);
|
|
|
|
/* SEIT DEM 11.09.2026 LAESST SICH DIE ROLLE NICHT MEHR VERGEBEN.
|
|
|
|
Filipe: "dogfather soll man nicht auswaehlen koennen. das ist die
|
|
einzige die man nicht auswaehlen kann bitte." Hier stand bis dahin
|
|
ein Anlegen ueber die Schnittstelle -- das geht jetzt nicht mehr,
|
|
und das ist der erste Prueffall geworden statt eines Hilfsschritts. */
|
|
const versuch = await hole(weg, { host: WS, methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({ name: "Zweitzugang", rolle: "admin" }) });
|
|
ok(versuch.status === 403 || versuch.status === 400,
|
|
`ein zweiter DogFather laesst sich NICHT anlegen (${versuch.status})`);
|
|
|
|
/* DER LETZTE DOGFATHER -- und dafuer braucht es trotzdem zwei.
|
|
|
|
Der zweite entsteht jetzt direkt im Bestand, nicht ueber die
|
|
Schnittstelle. Das ist kein Trick an der Regel vorbei, sondern ihr
|
|
Gegenteil: Die Regel sagt "nicht ueber die Oberflaeche", und genau
|
|
das wird eine Zeile darueber nachgewiesen. Fuer die FOLGEfrage --
|
|
darf der vorletzte herabgestuft werden? -- braucht die Pruefung
|
|
einen Bestand, den die Oberflaeche nicht mehr herstellen kann.
|
|
|
|
Ohne diesen Umweg waere die Sicherung "nie den letzten DogFather"
|
|
ab heute ungeprueft. Eine Pruefung wegzulassen, weil ihre
|
|
Voraussetzung schwerer herzustellen ist, waere der bequeme Weg --
|
|
und die Sicherung steht zwischen einem Versehen und einem Haus
|
|
ohne Zugang. */
|
|
const idZweit = anlegen("Zweitzugang", "admin", "CODE-ZWEI-0001");
|
|
ok(Number.isInteger(idZweit) && idZweit > 0,
|
|
`ein zweiter DogFather-Zugang im Bestand (#${idZweit})`);
|
|
|
|
const runter = await hole(`${weg}/${idZweit}/rolle`, { host: WS, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "scout" }) });
|
|
ok(runter.status === 200, `solange es zwei gibt, darf einer wechseln (${runter.status})`);
|
|
|
|
const letzter = await hole(`${weg}/${idDogi}/rolle`, { host: WS, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "scout" }) });
|
|
ok(letzter.status === 400, `der letzte nicht mehr (${letzter.status})`);
|
|
|
|
/* DER ERFOLGSFALL, und was daran haengt. */
|
|
/* SIE MUSS ANGEMELDET SEIN, sonst prueft "die Sitzungen sind
|
|
beendet" gar nichts. Beim ersten Lauf stand hier `>= 0` und eine
|
|
Null -- ein gruener Haken ueber einem leeren Bestand. Genau die
|
|
Sorte Pruefung, die immer bestaetigt und deshalb nichts
|
|
bestaetigt. */
|
|
await anmelden("modi", "CODE-TEAM-0001", CREW);
|
|
const sitzungenVorher = d.prepare(
|
|
"SELECT COUNT(*) AS n FROM sitzungen WHERE person_id = ?").get(idModi).n;
|
|
ok(sitzungenVorher > 0, `Ayla ist angemeldet (${sitzungenVorher} Sitzung(en))`);
|
|
|
|
const um = await hole(`${weg}/${idModi}/rolle`, { host: CREW, methode: "PUT",
|
|
keks: dogi.keks, rumpf: JSON.stringify({ rolle: "hand" }) });
|
|
ok(um.status === 200, `aus dem Teammitglied wird die rechte Hand (${um.status})`);
|
|
const jetztRolle = d.prepare("SELECT rolle FROM personen WHERE id = ?").get(idModi)?.rolle;
|
|
ok(jetztRolle === "hand", `und es steht so in der Datenbank (${jetztRolle})`);
|
|
|
|
/* SICHERUNG 4: Die Sitzungen sind weg. Eine Sitzung gehoert zu einer
|
|
ADRESSE, und welche das ist, entscheidet die Rolle. */
|
|
const nachher = d.prepare(
|
|
"SELECT COUNT(*) AS n FROM sitzungen WHERE person_id = ?").get(idModi).n;
|
|
ok(nachher === 0, `ihre Sitzungen sind beendet (${nachher})`);
|
|
|
|
/* SICHERUNG 5: Der Code bleibt. Er haengt am Menschen, nicht an der
|
|
Rolle -- ihn mitzutauschen hiesse, jemanden auszusperren, ohne es
|
|
zu merken. */
|
|
const code = d.prepare("SELECT code_hash FROM personen WHERE id = ?").get(idModi)?.code_hash;
|
|
ok(!!code && code.length > 20, "der Zugangscode ist unveraendert");
|
|
|
|
/* Und es steht im Protokoll. Eine Rechteaenderung ohne Spur waere
|
|
genau die, nach der spaeter niemand mehr fragen kann. */
|
|
const spur = d.prepare(
|
|
"SELECT detail FROM protokoll WHERE aktion = 'rolle_geaendert' ORDER BY id DESC LIMIT 1").get();
|
|
ok(/modi -> hand/.test(spur?.detail || ""),
|
|
`das Protokoll nennt beide Rollen (${spur?.detail || "kein Eintrag"})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
11. ALLE KATEGORIEN -- und niemand faellt lautlos weg
|
|
=======================================================================
|
|
|
|
Filipe, mit Bildschirmfoto der Personenseite: "ich muss alle
|
|
kategorien da sehen. und ich will dass die rechte hand auch alle
|
|
sieht."
|
|
|
|
Auf dem Bild stand genau EIN Abschnitt: DogFather. VanVan war in
|
|
derselben Minute zur rechten Hand geworden -- und damit aus der
|
|
Liste verschwunden. Nicht aus der Antwort des Servers: Der schickte
|
|
sie mit. Der Browser gruppierte nach einer Liste mit fuenf Rollen,
|
|
und wer dort nicht steht, wurde nicht gezeichnet.
|
|
|
|
DIESE PRUEFUNG VERGLEICHT DESHALB BEIDE SEITEN: was der Server
|
|
liefert, und was am Ende auf dem Bildschirm steht. Eine Pruefung, die
|
|
nur die Antwort ansieht, waere hier gruen gewesen.
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Alle Kategorien ===");
|
|
{
|
|
const liste = (await hole("/workspace/api/verwaltung/personen",
|
|
{ host: CREW, keks: dogi.keks })).daten;
|
|
const rollen = new Set((liste?.personen || []).map((p) => p.rolle));
|
|
ok(rollen.has("admin") && rollen.has("hand"),
|
|
`der Server liefert mehrere Rollen (${[...rollen].join(", ")})`);
|
|
|
|
/* Die Ueberschriften fuer die Rollen, die im Browser nicht stehen
|
|
duerfen, kommen mit der Antwort. Ohne sie faellt die Oberflaeche
|
|
auf den Rollennamen zurueck -- lesbar, aber roh. */
|
|
const zusatz = (liste?.zusatzrollen || []).map((r) => r.wert);
|
|
ok(zusatz.includes("hand") && zusatz.includes("modi"),
|
|
`und die Ueberschriften dazu (${zusatz.join(", ") || "keine"})`);
|
|
|
|
/* Und dieselbe Auskunft an die rechte Hand -- sonst saehe sie
|
|
dieselbe leere Seite wie DogFather vorhin. */
|
|
const beiHand = (await hole("/workspace/api/verwaltung/personen",
|
|
{ host: CREW, keks: hand.keks })).daten;
|
|
ok(Array.isArray(beiHand?.personen) && beiHand.personen.length > 0,
|
|
`die rechte Hand bekommt die Liste (${beiHand?.personen?.length || 0} Menschen)`);
|
|
ok((beiHand?.zusatzrollen || []).length > 0,
|
|
"samt der Ueberschriften");
|
|
|
|
/* SIE LIEST NUR. Jeder schreibende Weg bleibt zu -- gemessen, nicht
|
|
angenommen, denn genau hier waere aus "sehen" versehentlich
|
|
"verwalten" geworden. */
|
|
const codeVersuch = await hole(`/workspace/api/verwaltung/personen/${idModi}/code`,
|
|
{ host: CREW, methode: "POST", keks: hand.keks, rumpf: "{}" });
|
|
ok(codeVersuch.status === 404, `sie kann keinen Code neu setzen (${codeVersuch.status})`);
|
|
const rolleVersuch = await hole(`/workspace/api/verwaltung/personen/${idModi}/rolle`,
|
|
{ host: CREW, methode: "PUT", keks: hand.keks, rumpf: JSON.stringify({ rolle: "modi" }) });
|
|
ok(rolleVersuch.status === 404, `und keine Rolle vergeben (${rolleVersuch.status})`);
|
|
const anlegen = await hole("/workspace/api/verwaltung/personen",
|
|
{ host: CREW, methode: "POST", keks: hand.keks,
|
|
rumpf: JSON.stringify({ name: "Nochwer", rolle: "modi" }) });
|
|
ok(anlegen.status === 404, `und niemanden anlegen (${anlegen.status})`);
|
|
|
|
/* GEGENPROBE: DogFather darf all das sehr wohl -- sonst hiessen die
|
|
drei 404 oben auch dann gruen, wenn die Wege ueberhaupt kaputt
|
|
waeren. Der Rollenwechsel weiter oben hat das schon gezeigt; hier
|
|
genuegt das Anlegen. */
|
|
const dogiLegtAn = await hole("/workspace/api/verwaltung/personen",
|
|
{ host: CREW, methode: "POST", keks: dogi.keks,
|
|
rumpf: JSON.stringify({ name: "Neues Teammitglied", rolle: "modi" }) });
|
|
ok(dogiLegtAn.status === 201, `DogFather kann es (${dogiLegtAn.status})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
12. Die Gegenprobe zur Regel selbst
|
|
======================================================================= */
|
|
console.log("");
|
|
console.log("=== Haengt es wirklich an der Adresse? ===");
|
|
{
|
|
/* Auf localhost aendert sich NICHTS -- das ist Absicht: Alle
|
|
Pruefungen im Haus laufen dort, und eine stille Einschraenkung
|
|
haette sie alle blind gemacht, ohne dass irgendwo etwas rot wird.
|
|
Genau diese Falle steht schon in crew-adresse.js beschrieben. */
|
|
const lokal = await hole("/workspace/api/aufgaben", { host: "127.0.0.1:4381", keks: dogi.keks });
|
|
const t = (lokal.daten?.aufgaben || []).map((z) => z.titel);
|
|
ok(t.includes("Lunas Steckbrief nachtragen"),
|
|
`auf 127.0.0.1 bleibt alles wie bisher (${t.length} Aufgaben)`);
|
|
|
|
/* Und eine erfundene Adresse gilt als Agentur, nicht als Team --
|
|
wer sich einen Hostnamen ausdenkt, bekommt keine Sonderbehandlung. */
|
|
const fremd = await hole("/workspace/api/aufgaben", { host: "crew.boese.de", keks: dogi.keks });
|
|
ok((fremd.daten?.aufgaben || []).length === 2,
|
|
`eine erfundene Adresse oeffnet kein drittes Haus (${(fremd.daten?.aufgaben || []).length})`);
|
|
}
|
|
|
|
d.close();
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
console.log("");
|
|
console.log(`${fehler === 0 ? "BESTANDEN" : "FEHLGESCHLAGEN"} -- ${geprueft} geprueft, ${fehler} Fehler`);
|
|
process.exit(fehler ? 1 : 0);
|