Filipe hat den Bauplan "Willkommen & Anleitung -- Einweisung je Rolle"
(27 Seiten, Stand 06.10.2026) geschickt: pruefen, perfektionieren und
auf der Crew-Seite umsetzen.
AUSGANGSMESSUNG, bevor etwas angefasst wurde:
agentur creator 18 · manager 22 · scout 21 · spicy 23 Karten
crew 0 Karten -- die Schnittstelle antwortete 404 "nicht_hier"
Die Seite lud also, war aber leer. Baulich vorbereitet war sie schon:
Alle drei Inhaltstabellen tragen `haus` mit UNIQUE (haus, rolle,
kachel), und die Aussaat nimmt das Haus als Parameter.
WAS JETZT DA IST
gast 14 Karten (5 Muss, 4 Regelmaessig, 5 Bedarf)
modi 29 Karten (7 / 7 / 15)
linke 29 Karten (7 / 7 / 15)
hand 34 Karten (8 / 7 / 19)
Jede Zahl entspricht Kapitel 13 des Bauplans. Dazu je Rolle
Leitgedanke, drei Saetze, fuenf Erste Schritte, "Nicht deins",
Rhythmus und Fragen -- woertlich aus Kapitel 06 bis 09.
VOR DEM SCHREIBEN ABGEGLICHEN, nicht danach: Ein Skript prueft jede
Karte gegen die echte Kachelliste der Rolle. Ergebnis fuer alle vier
Fassungen: Kartenzahl wie im Plan, jede Karte trifft eine Kachel,
jede Kachel hat eine Karte, keine doppelt.
ZWEI KACHELN AUF EINER SEITE. Auf der Crew-Adresse fuehren "Chat"
(das Team) und "Rudel-Chat" (`?raum=treff`) beide auf chat.html.
`schluesselVonZiel` behielt nur `b` -- beide ergaben denselben
Schluessel, und damit konnten sie keine eigenen Karten haben, obwohl
der Bauplan ihnen verschiedene Einordnungen gibt (fuer die rechte
Hand: Team-Chat Muss, Rudel-Chat nur bei Bedarf). Jetzt wird auch
`raum` unterschieden. Auf der Agenturadresse gibt es nur eine
Chat-Kachel; dort aendert sich nichts.
DAS HAUS KOMMT AUS DER ADRESSE, nicht aus der angesehenen Rolle.
`const HAUS = "agentur"` ist einer Funktion gewichen. Nimmt DogFather
die Sicht einer anderen Rolle ein, wechselt er die ROLLE, nicht die
ADRESSE -- wer hier `req.sicht` naehme, bekaeme auf der Crew-Adresse
Agenturinhalte, sobald eine Sicht gesetzt ist.
`FASSUNGEN` stand an zehn Stellen fest und haette die Crew ueberall
abgewiesen. Jetzt `fassungenFuer(haus)` -- eine Funktion statt zweier
Listen an zehn Stellen.
Neue Zeilenart `vorweg` fuer "Drei Dinge vorweg" (nur die Community,
Bauplan Kapitel 04). Fehlt die Liste bei einer Rolle, wird nichts
eingetragen -- kein Sonderfall noetig.
Eigene Datei fuer die Crew-Inhalte: anleitung-tabellen.js traegt
schon die Agenturfassungen und ist 856 Zeilen lang. Getrennte Dateien
sind hier dasselbe Prinzip wie getrennte Haeuser.
pruef-anleitung 125 -> 127. Fuenf Zeilen darin verteidigten den alten
Zustand ("ein Modi bekommt die Anleitung nicht, erwartet 404") -- sie
sind mitgewandert statt stehenzubleiben. Dazu eine neue Gegenprobe
zur Haustrennung: In der Crew-Fassung darf keine Agenturkachel
vorkommen.
Datenbank vorher gesichert und zurueckgelesen (integrity_check,
84 Karten).
Co-Authored-By: Claude Opus 5 <[email protected]>
140 lines
6.1 KiB
JavaScript
140 lines
6.1 KiB
JavaScript
/* =====================================================================
|
|
WAS SEHEN DIE VIER CREW-ROLLEN HEUTE AUF willkommen.html?
|
|
|
|
Ausgangsmessung vom 06.10.2026, bevor der Bauplan auf der
|
|
Crew-Seite umgesetzt wird. Ohne sie waere hinterher nicht zu
|
|
sagen, was die Arbeit bewirkt hat -- und ob die Seite vorher
|
|
wirklich leer war oder nur anders.
|
|
|
|
Gemessen wird am HOSTNAMEN, nicht an der Rolle: Erst der
|
|
Hostname macht `person.haus` zu "crew", und genau daran haengt
|
|
die Schranke im Servermodul.
|
|
===================================================================== */
|
|
|
|
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-anlcrew-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
const { eigenerPort } = await import("./helfer-port.mjs");
|
|
const PORT = await eigenerPort(import.meta, "mess-anleitung-crew");
|
|
process.env.PORT = `${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 }); };
|
|
await import("./index.js");
|
|
notbremse(120_000, "mess-anleitung-crew");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
|
|
/** Ein Aufruf mit frei waehlbarem Host-Kopf -- `fetch` laesst `Host`
|
|
* nicht setzen, deshalb node:http. */
|
|
function ruf(pfad, { keks = "", methode = "GET", rumpf = null } = {}) {
|
|
return new Promise((fertig) => {
|
|
const kopf = { Host: CREW };
|
|
if (keks) kopf.Cookie = keks;
|
|
if (rumpf) { kopf["Content-Type"] = "application/json"; }
|
|
const a = request({ host: "127.0.0.1", port: PORT, path: pfad, method: methode, headers: kopf },
|
|
(r) => {
|
|
let t = "";
|
|
r.on("data", (c) => { t += c; });
|
|
r.on("end", () => fertig({
|
|
status: r.statusCode,
|
|
keks: (r.headers["set-cookie"] || []).map((k) => k.split(";")[0]).join("; "),
|
|
text: t,
|
|
json: (() => { try { return JSON.parse(t); } catch { return null; } })(),
|
|
}));
|
|
});
|
|
a.on("error", () => fertig({ status: 0, text: "", json: null, keks: "" }));
|
|
if (rumpf) a.write(rumpf);
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
await ruf("/workspace/api/anmelden", { methode: "POST", rumpf: JSON.stringify({ rolle: "admin", code: "x" }) });
|
|
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 ROLLEN = [["gast", "Community"], ["modi", "Modi"], ["linke", "Linke Hand"], ["hand", "Rechte Hand"]];
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
for (const [rolle] of ROLLEN) {
|
|
const salt = randomBytes(16).toString("hex");
|
|
const code = `CODE-${rolle.toUpperCase()}-0001`;
|
|
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(rolle, rolle, hash, salt, 32768, new Date().toISOString());
|
|
}
|
|
d.close();
|
|
}
|
|
|
|
console.log("\n=== Was die Crew-Rollen heute auf willkommen.html bekommen ===");
|
|
for (const [rolle, wie] of ROLLEN) {
|
|
/* DIE ALTERSBESTAETIGUNG IST EIN EIGENER SCHRITT -- nur fuer den
|
|
Gast. Ohne sie antwortet der Server mit 400, und die Messung
|
|
haette "Community kommt nicht herein" gemeldet, ohne dass etwas
|
|
kaputt waere. */
|
|
const an = await ruf("/workspace/api/anmelden", {
|
|
methode: "POST",
|
|
rumpf: JSON.stringify({ rolle, code: `CODE-${rolle.toUpperCase()}-0001`, alter_ok: true }),
|
|
});
|
|
if (an.status !== 200) { console.log(` ${wie.padEnd(12)} kommt nicht herein (${an.status})`); continue; }
|
|
const seite = await ruf("/workspace/willkommen.html", { keks: an.keks });
|
|
const api = await ruf("/workspace/api/anleitung", { keks: an.keks });
|
|
const karten = api.json?.karten?.length ?? "-";
|
|
console.log(` ${wie.padEnd(12)} Seite ${seite.status} · Schnittstelle ${api.status}`
|
|
+ ` · Karten ${karten}`
|
|
+ (api.json?.fehler ? ` · "${api.json.fehler}"` : ""));
|
|
}
|
|
|
|
/* ==== FUEHREN DIE KACHELN DER LINKEN HAND INS LEERE? ================
|
|
|
|
Der Bauplan spricht der Linken Hand vier Kacheln ab (Talente,
|
|
Personen & Zugaenge, Wer sieht was, Vertraulich melden). Die
|
|
Kachelliste gibt sie ihr trotzdem. Entweder irrt der Plan -- oder
|
|
sie sieht vier Kacheln, die auf Seiten fuehren, die sie nicht
|
|
oeffnen darf. Gefragt wird der laufende Server, nicht die Tabelle. */
|
|
{
|
|
console.log("\n=== Linke Hand: fuehren ihre Kacheln irgendwohin? ===");
|
|
const an = await ruf("/workspace/api/anmelden", {
|
|
methode: "POST", rumpf: JSON.stringify({ rolle: "linke", code: "CODE-LINKE-0001" }),
|
|
});
|
|
for (const pfad of ["/workspace/talente.html", "/workspace/personen.html",
|
|
"/workspace/rechte.html", "/workspace/hilfe.html", "/workspace/aufgaben.html"]) {
|
|
const a2 = await ruf(pfad, { keks: an.keks });
|
|
const wohin = a2.status >= 300 && a2.status < 400 ? " -> umgeleitet" : "";
|
|
console.log(` ${pfad.replace("/workspace/", "").padEnd(18)} ${a2.status}${wohin}`);
|
|
}
|
|
}
|
|
|
|
/* Und was steht in der Datenbank -- je Haus. */
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
console.log("\n=== Inhalte in der Datenbank ===");
|
|
for (const r of d.prepare(
|
|
"SELECT haus, rolle, COUNT(*) n FROM anl_karte GROUP BY haus, rolle ORDER BY haus, rolle").all()) {
|
|
console.log(` ${r.haus.padEnd(8)} ${r.rolle.padEnd(10)} ${r.n} Karten`);
|
|
}
|
|
const crew = d.prepare("SELECT COUNT(*) n FROM anl_karte WHERE haus = 'crew'").get().n;
|
|
console.log(` -> fuer die Crew: ${crew} Karten`);
|
|
d.close();
|
|
}
|
|
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
process.exit(0);
|