Spicy Media sieht die Zahlen wieder -- und eine Creator-Liste enthaelt nur Creator

Zwei Dinge, das zweite habe ich nur gefunden, weil das erste eine
Pruefung rot gemacht hat.

1) DIE SPICY-SPERRE IST AUFGEHOBEN.
Filipe: "jetzt soll jede rolle diese kachel sehen. spicy, dogfather,
manager, scout und creator ... der manager scout dogfather oder spicy
koennen eintragen."

Kachel und Tuer hatte ich schon geoeffnet -- es reichte nicht. In
workspace-leistung.js sass eine dritte Schranke, die Spicy Media mit
404 abwies (Rest des Wunsches vom 07.09.). Folge: Die Seite lud, das
Auswahlfeld blieb leer, und es sah aus wie ein Fehler. Drei Schichten
mussten zustimmen, und die dritte stand woanders als die beiden
ersten.

Nebenwirkung, ausdruecklich: Damit stehen im Dashboard wieder
Diamanten, LIVE-Tage und Verweildauer je Creator-Karte. Das war der
zweite Teil der damaligen Entscheidung und faellt mit ihr weg.

Die Pruefung in pruef-spicy wurde nicht geloescht, sondern GEDREHT --
sie schlaegt jetzt an, wenn jemand die Sperre versehentlich wieder
einbaut.

2) EINE LISTE VON CREATOR-NUMMERN ENTHIELT KEINE CREATOR.
Beim Drehen fiel auf: Spicy Media bekam "Agentur, Filipe, Luna, Max,
NeuerCreator, NeuerManager" -- DogFather nur "Luna, NeuerCreator".

Ursache: `ohneVerborgene` schreibt ein `null` ("sieht alles") zu einer
echten Liste aus, sobald es etwas zu verbergen gibt -- zur Liste ALLER
Personen, weil sie nicht wissen kann, wovon "alles" gerade handelt.
Bei DogFather greift das nie (er verbirgt nichts vor sich selbst), bei
Spicy Media schon. Der Aufrufer baut daraus ein `IN (...)` OHNE
Rollenfilter, und damit wurden Manager und Scouts zu Creators.

Repariert an der Wurzel, nicht beim Aufrufer: Es gibt zehn Aufrufer,
und neun richtig plus einen vergessen sieht man nie. Der Name der
Funktion ist das Versprechen -- es wird jetzt dort eingeloest, wo der
Name steht.

AUFGEFALLEN IST ES, WEIL EINE PRUEFUNG DIE BEIDEN LISTEN VERGLICHEN
HAT, statt bei jeder einzeln "ist nicht leer" zu sagen. Genau dieser
Unterschied steht jetzt als eigene Aussage drin.

Gemessen: pruef-backstage-import 50 -> 63 (Manager und Spicy Media
kamen dazu, inklusive der Aussage, dass Spicy Media MEHR Creator sieht
als ein Manager), pruef-spicy 60 -> 62. Dazu gruen: betreuung,
manager-sicht, verborgen, haus-trennung, sicht, aufgabenbrett,
schulung, steckbrief, uebersicht, leistung, tiktok-datei,
fremde-sicht, personen-liste, creator-anlegen, ampel, tagesblick.

NICHT von mir: pruef-agentur meldet 31 Fehler (HTTP 503). Gegen den
Stand ohne meine Aenderungen nachgemessen -- dort dieselben 31. Ein
aelterer, eigener Befund, unangetastet.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-11 14:19:43 +02:00
co-authored by Claude Opus 5
parent 8d3a79ed2a
commit 915a4ae07c
4 changed files with 138 additions and 34 deletions
+60 -1
View File
@@ -72,7 +72,7 @@ await starten();
kind.kill();
await new Promise((r) => setTimeout(r, 600));
let idNova, idKiro, idLumi, idSam;
let idNova, idKiro, idLumi, idSam, idMira;
{
const d = new DatabaseSync(DB);
const anlegen = (name, rolle, code, tiktok) => {
@@ -85,6 +85,12 @@ let idNova, idKiro, idLumi, idSam;
};
anlegen("Filipe", "admin", "CODE-DOGI-0001");
idSam = anlegen("Sam", "scout", "CODE-SAM-00001");
/* Seit dem 11.09.2026 kommen auch Manager und Spicy Media auf diese
Seite. Beide sehen andere Creator als ein Scout -- Spicy Media
alle (siehtAlles), ein Manager nur seine eigenen und die seiner
Scouts. Ohne beide im Bestand waere das ungeprueft. */
idMira = anlegen("Mira", "manager", "CODE-MIRA-0001");
anlegen("Sasha", "spicy", "CODE-SASHA-0001");
/* Nova hat ein Handle, Kiro NICHT -- er wird ueber den Namen
gefunden. Lumi hat ein Handle mit anderer Schreibweise als in der
Tabelle: Genau daran zeigt sich, ob normalisiert wird. */
@@ -98,6 +104,11 @@ let idNova, idKiro, idLumi, idSam;
einem Zufall. */
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)")
.run(idNova, idSam, jetzt);
/* Und Sam gehoert zu Mira -- damit der Manager ueber seinen Scout
an Nova kommt. Sonst saehe er eine leere Seite, und die Pruefung
unten wuerde messen, dass nichts da ist, statt dass es stimmt. */
d.prepare("INSERT INTO scout_zuteilung (scout_id, manager_id, seit) VALUES (?,?,?)")
.run(idSam, idMira, jetzt);
d.close();
}
await starten();
@@ -275,6 +286,21 @@ console.log("\n=== Gegenproben ===");
ok(status === 200, `heute als Tag geht dagegen durch (HTTP ${status})`);
}
/* WER SIEHT WEN -- direkt an der Schnittstelle gemessen, bevor der
Browser ins Spiel kommt. Wird der Auswahlliste auf der Seite etwas
fehlen, steht hier, ob es schon hier fehlte. */
for (const [rolle, code, wer] of [
["manager", "CODE-MIRA-0001", "über seinen Scout"],
["spicy", "CODE-SASHA-0001", "alle"],
]) {
const keks = await anmelden(rolle, code);
const a = await fetch(BASIS + "/workspace/api/leistung", { headers: { Cookie: keks } });
const d = await a.json().catch(() => ({}));
const namen = (d.creator || []).map((c) => c.name).join(", ");
ok(a.status === 200 && namen.length > 0,
`${rolle} bekommt Creator (${wer}): ${namen || `LEER, HTTP ${a.status}`}`);
}
/* Vorbedingung fuer die Gegenprobe weiter unten: Sam MUSS Nova sehen.
Sonst waere "der Scout sieht den Backstage-Satz nicht" nur die
Auskunft, dass seine Seite leer ist. */
@@ -444,6 +470,39 @@ console.log("\n=== Die Seite ===");
ok(scoutBild.aendern > 0,
`er kann einen Tag eintragen (${scoutBild.aendern} Knöpfe in der Liste)`);
/* MANAGER UND SPICY MEDIA -- die beiden, die ich beinahe nur
mitgedacht haette. Sie gehoeren zur Leitung, also sollten sie
alles koennen; "sollte" ist aber kein Messwert. Sie sehen
ausserdem UNTERSCHIEDLICH viele Creator: Spicy Media alle
(siehtAlles), ein Manager nur seine eigenen und die seiner
Scouts. Genau das wird hier verglichen -- eine Pruefung, die bei
beiden nur "sieht etwas" sagt, haette den Unterschied nie
bemerkt. */
for (const [rolle, code, mindestens] of [
["manager", "CODE-MIRA-0001", 1],
["spicy", "CODE-SASHA-0001", 3],
]) {
const b = await rollenBild(rolle, code);
ok(b.kachel === true && /leistung\.html$/.test(b.weg),
`${rolle}: findet die Kachel und kommt auf die Seite`);
ok(b.fehler === "", `${rolle}: kein Skriptfehler (${b.fehler || "keiner"})`);
ok(b.netz === false && b.tt === false && b.import === false && b.ziele === false,
`${rolle}: alle vier Wege sind da -- auch die Backstage-Tabelle`);
ok(b.sNetz === false && b.sTt === false,
`${rolle}: und beide Erklärungen dazu`);
ok(b.wahl >= mindestens,
`${rolle}: sieht ${b.wahl} Creator zur Auswahl (mindestens ${mindestens})`);
}
/* Und der Unterschied selbst, als eigene Aussage: Spicy Media sieht
MEHR als der Manager. Waeren beide gleich, waere oben alles gruen
und die Haustrennung trotzdem kaputt. */
{
const m = await rollenBild("manager", "CODE-MIRA-0001");
const sp = await rollenBild("spicy", "CODE-SASHA-0001");
ok(sp.wahl > m.wahl,
`Spicy Media sieht mehr Creator als der Manager (${sp.wahl} zu ${m.wahl})`);
}
const creatorBild = await rollenBild("creator", "CODE-NOVA-0001");
ok(creatorBild.kachel === true && /leistung\.html$/.test(creatorBild.weg),
`der Creator findet die Kachel und kommt auf die Seite (${creatorBild.weg})`);
+22 -9
View File
@@ -455,20 +455,33 @@ const ruf = async (art, weg, keks, koerper) => {
`DogFather sieht weiterhin beide (${dTitel.length})`);
}
{
/* DIE ZAHLEN SIEHT SIE NIRGENDS (07.09.2026).
/* DIE ZAHLEN SIEHT SIE WIEDER (11.09.2026) -- umgedreht.
Zuerst waren nur Kachel und Seite geschlossen -- die Creator-Zahlen
standen damit weiterhin im Dashboard, weil das sie ueber eine
eigene Schnittstelle holt. Filipe hat entschieden, dass auch das
weg soll.
Vom 07.09. bis heute stand hier das Gegenteil: Die Schnittstelle
antwortete ihr mit 404, nach Filipes Wunsch "bei der spicy rolle,
die zahlen diese kategorie nicht sehen". Am 11.09. hat er das
aufgehoben: "jetzt soll jede rolle diese kachel sehen. spicy,
dogfather, manager, scout und creator."
Geprueft wird die SCHNITTSTELLE und nicht die Oberflaeche: Eine
ausgeblendete Zeile ist keine Sperre. Und mit Gegenprobe, sonst
bewiese die Zeile nur, dass es den Weg gar nicht gibt. */
DIE PRUEFUNG WURDE NICHT GELOESCHT, SONDERN GEDREHT. Eine
entfernte Pruefung senkt die Zahl und beweist nichts; eine
gedrehte haelt fest, dass die Entscheidung bewusst war -- und
schlaegt an, wenn jemand die Sperre versehentlich wieder
einbaut.
Geprueft wird die SCHNITTSTELLE und nicht die Oberflaeche: Ein
eingeblendeter Knopf ist kein Zugang. Und mit Gegenprobe, sonst
bewiese die Zeile nur, dass irgendetwas antwortet. */
const beiIhr = await ruf("GET", "/workspace/api/leistung", keksSpicy);
ok(beiIhr.status === 404, `die Zahlen antworten ihr mit 404 (${beiIhr.status})`);
ok(beiIhr.status === 200, `die Zahlen antworten ihr wieder (${beiIhr.status})`);
ok((beiIhr.daten?.creator || []).length > 0,
`und sie bekommt auch Creator dabei (${(beiIhr.daten?.creator || []).length})`);
const beiIhm = await ruf("GET", "/workspace/api/leistung", keksDogi);
ok(beiIhm.status === 200, `DogFather bekommt sie weiterhin (${beiIhm.status})`);
const ihre = (beiIhr.daten?.creator || []).map((c) => c.name).sort().join(",");
const seine = (beiIhm.daten?.creator || []).map((c) => c.name).sort().join(",");
ok(ihre === seine,
`und beide sehen dieselben -- Spicy Media sieht alles, wie DogFather (sie: ${ihre || "keine"} | er: ${seine || "keine"})`);
/* UND DAS DASHBOARD BLEIBT FUER SIE STEHEN. Das ist der eigentliche
Prueffall: Die Uebersicht holt die Zahlen in einer zusaetzlichen
+24 -23
View File
@@ -44,7 +44,7 @@
import express from "express";
import {
db, protokolliere, echteIp, sitzungLesen, istLeitung, istSpicy,
db, protokolliere, echteIp, sitzungLesen, istLeitung,
sichtbareCreatorIds, ROLLEN_SORTIERUNG, heuteLokal,
} from "./workspace.js";
import { kandidatenFinden, listeAmPfad, zuTagen } from "./workspace-tiktok-datei.js";
@@ -74,32 +74,33 @@ function gleicheHerkunft(req, res, next) {
leistungRouter.use("/workspace/api/leistung", angemeldet);
/* SPICY MEDIA SIEHT DIE ZAHLEN NIRGENDS (07.09.2026).
/* ---------- Die Spicy-Sperre von 07.09.2026 ist aufgehoben ----------
(11.09.2026) Filipe, mit der Zahlen-Kachel im Bild: "jetzt soll jede
rolle diese kachel sehen. spicy, dogfather, manager, scout und
creator ... der manager scout dogfather oder spicy koennen
eintragen."
Filipe: "bei der spicy rolle, die zahlen diese kategorie nicht
sehen." Zuerst habe ich woertlich nur die KATEGORIE geschlossen --
Kachel und Seite. Auf Nachfrage, was noch offen sei, habe ich selbst
darauf hingewiesen, dass die Creator-Zahlen damit weiterhin im
Dashboard stehen (Diamanten, LIVE-Tage, Verweildauer je Karte);
Filipe hat entschieden, dass auch das weg soll.
Hier stand bis heute eine Sperre, die Spicy Media mit 404
abgewiesen hat -- der Rest des Wunsches vom 07.09. ("bei der spicy
rolle, die zahlen diese kategorie nicht sehen"). Sie sass
ausdruecklich am Server und nicht in der Oberflaeche, und genau
deshalb reichte es heute NICHT, Kachel und Tuer zu oeffnen: Die
Seite lud, das Auswahlfeld blieb leer, und es sah aus wie ein
Fehler.
DIE SPERRE SITZT AM SERVER UND NICHT IN DER OBERFLAECHE. Eine
ausgeblendete Zeile ist keine Sperre: Wer die Adresse kennt, ruft
die Schnittstelle direkt auf. Hier kommt sie gar nicht mehr an
Daten.
GEFUNDEN HAT DAS EINE MESSUNG, NICHT DAS LESEN. Ich hatte Kachel
und Tuer geaendert und haette es dabei belassen; erst die Pruefung
mit Spicy-Zugang zeigte "0 Creator zur Auswahl" und dahinter das
404. Drei Schichten mussten zustimmen, und die dritte stand
woanders als die beiden ersten.
404 statt 403, wie ueberall in diesem Haus: Wer nicht hierher
gehoert, muss nicht erfahren, dass es diesen Bereich gibt.
NEBENWIRKUNG, ausdruecklich: Damit stehen auch im Dashboard wieder
Diamanten, LIVE-Tage und Verweildauer je Creator-Karte. Das war der
zweite Teil der damaligen Entscheidung und faellt mit ihr weg.
DAS DASHBOARD BRICHT DADURCH NICHT. Es holt die Zahlen in EINER
zusaetzlichen Abfrage und faengt ihren Fehlschlag ausdruecklich ab
("die Karten stehen auch ohne", uebersicht.js) -- bei ihr fehlen
also die drei Zeilen, alles andere bleibt. Genau deshalb ist diese
eine Zeile hier der ganze Eingriff. */
leistungRouter.use("/workspace/api/leistung", (req, res, next) => {
if (istSpicy(req.person)) return res.status(404).json({ fehler: "nicht_gefunden" });
next();
});
Was die Zeile NICHT war: eine Rechteregel. Wessen Zahlen Spicy
Media sieht, entscheidet weiterhin siehtAlles() -- und das sagt
seit jeher "alle". */
/* ---------- Wer darf was ------------------------------------------------ */
+32 -1
View File
@@ -4750,7 +4750,38 @@ export function hausBedingung(person, spalte = "rolle") {
return ` AND ${spalte} IN (${liste})`;
}
export const sichtbareCreatorIds = (person) => nurHaus(ohneVerborgene(sichtbareCreatorIdsRoh(person), person), person);
/* EINE LISTE VON CREATOR-NUMMERN DARF NUR CREATOR ENTHALTEN (11.09.2026).
Klingt selbstverstaendlich, war es nicht. `ohneVerborgene` schreibt
ein `null` ("sieht alles") zu einer echten Liste aus, sobald es etwas
zu verbergen gibt -- und zwar zur Liste ALLER Personen, weil sie
nicht wissen kann, wovon "alles" gerade handelt. Fuer DogFather
greift das nie (er verbirgt nichts vor sich selbst), fuer Spicy
Media schon: Bei ihr kam die Liste mit Admins, Managern und Scouts
darin zurueck.
GEMESSEN, NICHT VERMUTET: Am 11.09.2026 bekam Spicy Media auf der
Zahlen-Seite "Agentur, Filipe, Luna, Max, NeuerCreator,
NeuerManager" zur Auswahl -- DogFather dagegen nur "Luna,
NeuerCreator". Aufgefallen ist es, weil eine Pruefung die beiden
Listen VERGLICHEN hat, statt bei jeder einzeln "ist nicht leer" zu
sagen.
WARUM DIE REPARATUR HIER STEHT UND NICHT BEIM AUFRUFER: Es gibt
zehn Aufrufer. Jeder von ihnen baut daraus ein `IN (...)`, und jeder
haette die Rolle selbst danebenschreiben muessen -- neun Stellen
richtig und eine vergessen sieht man nie. Der Name der Funktion ist
das Versprechen; es gehoert dorthin eingeloest, wo der Name steht. */
const nurCreatorIds = (ids) => {
if (ids === null || !ids.length) return ids;
try {
return db().prepare(
`SELECT id FROM personen WHERE rolle = 'creator' AND id IN (${ids.map(() => "?").join(",")})`)
.all(...ids).map((z) => z.id);
} catch { return ids; }
};
export const sichtbareCreatorIds = (person) => nurCreatorIds(nurHaus(ohneVerborgene(sichtbareCreatorIdsRoh(person), person), person));
export const sichtbarePersonenIds = (person) => nurHaus(ohneVerborgene(sichtbarePersonenIdsRoh(person), person), person);
export const betreuerIds = (person) => nurHaus(ohneVerborgene(betreuerIdsRoh(person), person), person);
export const einladbareIds = (person) => nurHaus(ohneVerborgene(einladbareIdsRoh(person), person), person);