=== screen6: „An alle" === Filipe: „bei an ween fehlt noch die option alle neben den namen allen." Der Knopf steht VORN, nicht hinten: Wer etwas an das ganze Team geben will, soll nicht erst an sechs Namen vorbeilesen. Er traegt die Anzahl der Leute und faerbt sich wie die Stufe „Alle" -- beide bedeuten „keine Einschraenkung", und zwei Farben dafuer waeren zwei Aussagen fuer einen Gedanken. Ab zwei Personen; bei einer waere „alle" derselbe Handgriff wie ihr Name. EINE ANFRAGE, NICHT SECHS. Der Browser koennte fuer jede Person einzeln fragen -- und beim dritten von sechs Aufrufen die Verbindung verlieren. Dann haette die Haelfte des Teams die Aufgabe und die andere nicht, und niemand saehe, wo es abgebrochen ist. DER DUPLIKAT-SCHUTZ WANDERTE IN DIE SCHLEIFE. Er fragt, was EINE Person schon liegen hat; stuende er davor, bekaeme nur die erste ihre Pruefung und alle anderen die Vorlage doppelt -- genau so entstanden am 22.09. aus zwei Klicks 112 Aufgaben. Gemessen: erster Klick 36 Aufgaben (12 mal 3 Modis), zweiter Klick 0 angelegt und 36 uebersprungen. Ein gesperrter Modi bekommt nichts, ein Creator auch nicht, und wer gar nicht verteilen darf, kommt ueber „alle" ebenfalls nicht weiter. === screen4: kein anderer Creator === Filipe: „in dieser app gibt es keinen und wird es niemals einen anderen creator geben wie mich." Auf der Aufgabenseite war eine Filterreihe mit „Alle Creator" beschriftet -- und darunter standen Modis. Auf der Team-Adresse gibt es keine Creator und wird es nie geben. Sie heisst dort jetzt „Für wen". DAS HAUS KOMMT VOM SERVER. `/api/ich` schickt jetzt `haus`; es stand schon an der Sitzung und wurde nie mitgeschickt. Eine Rollenliste im Browser waere die naechste zweite Wahrheit -- und falsch fuer DogFather, der in beiden Haeusern arbeitet. NEUN WEITERE „BEFUNDE" WAREN MEINE MESSFEHLER. Meine erste Messung lief ueber 127.0.0.1 und meldete unter anderem „CREATOR WORKSPACE" in der Kopfleiste JEDER Seite. Auf der echten Adresse steht dort „Team Dogi" -- `person.haus` wird aus dem Host abgeleitet, und auf 127.0.0.1 ist das Haus nun einmal die Agentur. Nachgemessen mit gesetztem Host: crew. -> haus="crew", marke="Team Dogi". Die Uebersichtsseite mit ihren Creator-Texten steht auf crew. gar nicht erst in den Kacheln. Gemessen: pruef-an-alle, 19 Pruefungen, 0 Fehler. Gruen: pruef-aufgabenbrett, pruef-aufgaben-vorlagen, pruef-vorlagen, pruef-entwicklung (48), pruef-css-klassen. Co-Authored-By: Claude Opus 5 <[email protected]>
232 lines
9.8 KiB
JavaScript
232 lines
9.8 KiB
JavaScript
/* =====================================================================
|
|
„AN ALLE" BEI „AN WEN" (24.09.2026)
|
|
|
|
Filipe: „bei an ween fehlt noch die option alle neben den namen
|
|
allen."
|
|
|
|
DREI FRAGEN, und die dritte ist die, an der es schiefgehen kann:
|
|
|
|
1. Trifft „alle" wirklich JEDEN -- oder nur den ersten?
|
|
2. Bleibt der Duplikat-Schutz erhalten? Er fragt, was EINE Person
|
|
schon hat; bei „alle" muss er je Person neu gestellt werden.
|
|
Sonst bekaeme nur die erste ihre Pruefung und alle anderen
|
|
die Vorlage doppelt -- genau so entstanden am 22.09. aus zwei
|
|
Klicks 112 Aufgaben.
|
|
3. Kommt jemand darueber an Leute heran, die ihn nichts angehen?
|
|
„alle" darf nicht mehr treffen als die Namen daneben.
|
|
===================================================================== */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-aa-"));
|
|
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, "die An-alle-Pruefung");
|
|
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
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(240_000, "pruef-an-alle");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const melde = (t) => console.log(t);
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
const ks = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", ks, jetzt);
|
|
function anlegen(name, rolle, code, aktiv = 1) {
|
|
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 (?,?,?,?,?,?,?,?)")
|
|
.run(name, rolle, hash, salt, 32768,
|
|
createHmac("sha256", ks).update(code).digest("hex"), aktiv, jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
}
|
|
anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
const modis = [
|
|
anlegen("Miss", "modi", "CODE-MODI-0001"),
|
|
anlegen("Ghost", "modi", "CODE-MODI-0002"),
|
|
anlegen("Marina", "modi", "CODE-MODI-0003"),
|
|
];
|
|
/* EIN GESPERRTER MODI. Er darf NICHT mitbekommen -- ein Zugang, der
|
|
nicht hereinkommt, braucht keine Aufgabenliste. */
|
|
const gesperrt = anlegen("Diene", "modi", "CODE-MODI-0004", 0);
|
|
/* Und ein Creator: Er steht gar nicht in dieser Auswahl und darf
|
|
auch ueber „alle" nichts bekommen. */
|
|
const creator = anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
d.close();
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const { request } = await import("node:http");
|
|
function roh(pfad, keks, koerper, methode) {
|
|
return new Promise((fertig, schief) => {
|
|
const daten = koerper ? JSON.stringify(koerper) : null;
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: pfad,
|
|
method: methode || (daten ? "POST" : "GET"),
|
|
headers: {
|
|
Host: CREW, Origin: `https://${CREW}`,
|
|
...(keks ? { Cookie: keks } : {}),
|
|
...(daten ? { "Content-Type": "application/json",
|
|
"Content-Length": Buffer.byteLength(daten) } : {}),
|
|
},
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text }));
|
|
});
|
|
a.on("error", schief);
|
|
if (daten) a.write(daten);
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (r) => { try { return JSON.parse(r.text); } catch { return {}; } };
|
|
|
|
async function anmelden(rolle, code) {
|
|
const r = await new Promise((fertig, schief) => {
|
|
const daten = JSON.stringify({ rolle, code, alter_ok: true });
|
|
const a = request({
|
|
host: "127.0.0.1", port: PORT, path: "/workspace/api/anmelden",
|
|
method: "POST",
|
|
headers: { Host: CREW, Origin: `https://${CREW}`,
|
|
"Content-Type": "application/json", "Content-Length": Buffer.byteLength(daten) },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (t) => { text += t; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
a.write(daten); a.end();
|
|
});
|
|
if (r.code !== 200) return null;
|
|
return [].concat(r.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
}
|
|
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001");
|
|
const kModi = await anmelden("modi", "CODE-MODI-0001");
|
|
ok(!!kDogi && !!kModi, "DogFather und ein Modi sind angemeldet");
|
|
|
|
/* Wie viele Aufgaben hat wer? Direkt aus der Datenbank -- die Antwort
|
|
der Route zu glauben hiesse, sie mit sich selbst zu pruefen. */
|
|
function zaehle(personId) {
|
|
const db2 = new DatabaseSync(process.env.WORKSPACE_DB, { readOnly: true });
|
|
const { n } = db2.prepare(
|
|
"SELECT COUNT(*) AS n FROM aufgaben WHERE verantwortlich_id = ? AND vorlage IS NOT NULL")
|
|
.get(personId);
|
|
db2.close();
|
|
return n;
|
|
}
|
|
|
|
/* Welche Kategorie gibt es ueberhaupt? Aus dem Katalog, den die Seite
|
|
auch bekommt -- eine erfundene Gruppe waere eine Pruefung, die
|
|
nichts trifft. */
|
|
const katalog = json(await roh("/workspace/api/vorlagen", kDogi));
|
|
const ersteGruppe = (katalog.katalog?.gruppen || [])[0]?.schluessel
|
|
|| (katalog.katalog?.aufgaben || [])[0]?.gruppe || "";
|
|
ok(!!ersteGruppe, `es gibt eine Kategorie zum Verteilen (${ersteGruppe})`);
|
|
|
|
melde("\n=== 1. alle trifft jeden ===");
|
|
{
|
|
const vorher = modis.map(zaehle);
|
|
ok(vorher.every((x) => x === 0), `vorher hat niemand etwas (${vorher.join(", ")})`);
|
|
|
|
const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi,
|
|
{ art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" });
|
|
ok(a.code === 200, `die Anfrage geht durch (${a.code})`);
|
|
const r = json(a);
|
|
ok((r.angelegt || 0) > 0, `es wurde etwas angelegt (${r.angelegt})`);
|
|
|
|
const nachher = modis.map(zaehle);
|
|
ok(nachher.every((x) => x > 0),
|
|
`JEDER der drei Modis hat jetzt Aufgaben (${nachher.join(", ")})`);
|
|
ok(new Set(nachher).size === 1,
|
|
"und alle gleich viele -- sonst hat die Schleife jemanden ausgelassen");
|
|
ok((r.angelegt || 0) === nachher[0] * modis.length,
|
|
`die Zahl in der Antwort passt zur Datenbank (${r.angelegt} = ${nachher[0]} x ${modis.length})`);
|
|
}
|
|
|
|
melde("\n=== 2. Der Duplikat-Schutz gilt je Person ===");
|
|
{
|
|
const vorher = modis.map(zaehle);
|
|
const a = await roh("/workspace/api/vorlagen/uebernehmen", kDogi,
|
|
{ art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" });
|
|
ok(a.code === 200, `ein zweiter Klick geht durch (${a.code})`);
|
|
const r = json(a);
|
|
ok((r.angelegt || 0) === 0,
|
|
`und legt NICHTS mehr an (${r.angelegt}) -- genau das war der Fehler vom 22.09.`);
|
|
/* `uebersprungen` ist eine ZAHL, kein Feld -- nachgesehen in
|
|
workspace-vorlagen.js (`uebersprungen: uebersprungen.length`).
|
|
Mein erster Versuch rief `.length` darauf auf und bekam
|
|
`undefined`; ein Messfehler, der wie eine fehlende Antwort
|
|
aussah. */
|
|
ok(Number(r.uebersprungen) > 0,
|
|
`stattdessen sagt die Antwort, wie viele schon lagen (${r.uebersprungen})`);
|
|
ok(Number(r.uebersprungen) === 36,
|
|
`und zwar alle 36 -- 12 Vorlagen mal 3 Modis (${r.uebersprungen})`);
|
|
const nachher = modis.map(zaehle);
|
|
ok(nachher.join() === vorher.join(),
|
|
`niemand hat etwas doppelt (${nachher.join(", ")})`);
|
|
}
|
|
|
|
melde("\n=== 3. alle trifft nicht mehr als die Namen ===");
|
|
{
|
|
ok(zaehle(gesperrt) === 0,
|
|
`ein GESPERRTER Modi bekommt nichts (${zaehle(gesperrt)})`);
|
|
ok(zaehle(creator) === 0,
|
|
`ein Creator erst recht nicht (${zaehle(creator)})`);
|
|
|
|
/* Und wer gar nicht verteilen darf, kommt auch ueber „alle" nicht
|
|
weiter -- sonst waere der neue Wert ein Weg an der Rechtefrage
|
|
vorbei. */
|
|
const alsModi = await roh("/workspace/api/vorlagen/uebernehmen", kModi,
|
|
{ art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle" });
|
|
ok(alsModi.code >= 400,
|
|
`ein Modi kann nicht an alle verteilen (${alsModi.code})`);
|
|
}
|
|
|
|
melde("\n=== 4. Gegenproben ===");
|
|
{
|
|
/* EINE EINZELNE PERSON GEHT WEITERHIN -- sonst hiesse „alle
|
|
funktioniert" nur, dass der alte Weg kaputt ist. */
|
|
const einzeln = await roh("/workspace/api/vorlagen/uebernehmen", kDogi,
|
|
{ art: "katalog", nr: 0, gruppe: ersteGruppe, person_id: modis[0] });
|
|
ok(einzeln.code === 200 || einzeln.code === 400,
|
|
`der Weg an EINE Person antwortet noch (${einzeln.code})`);
|
|
|
|
/* Ein erfundener Wert ist kein "alle". */
|
|
const quatsch = await roh("/workspace/api/vorlagen/uebernehmen", kDogi,
|
|
{ art: "katalog_alle", gruppe: ersteGruppe, stufe: "", person_id: "alle-zusammen" });
|
|
ok(quatsch.code >= 400,
|
|
`alle-zusammen ist nicht alle (${quatsch.code})`);
|
|
|
|
/* Und ohne Person weiterhin die alte Absage. */
|
|
const ohne = await roh("/workspace/api/vorlagen/uebernehmen", kDogi,
|
|
{ art: "katalog_alle", gruppe: ersteGruppe, stufe: "" });
|
|
ok(ohne.code === 400, `ohne Person immer noch 400 (${ohne.code})`);
|
|
}
|
|
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
|
|
console.log("");
|
|
console.log(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
console.log(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
process.exit(fehler ? 1 : 0);
|