diff --git a/server/pruef-backstage-import.mjs b/server/pruef-backstage-import.mjs index 4d873826..72897215 100644 --- a/server/pruef-backstage-import.mjs +++ b/server/pruef-backstage-import.mjs @@ -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})`); diff --git a/server/pruef-spicy.mjs b/server/pruef-spicy.mjs index 15b12277..b92483f7 100644 --- a/server/pruef-spicy.mjs +++ b/server/pruef-spicy.mjs @@ -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 diff --git a/server/workspace-leistung.js b/server/workspace-leistung.js index 2321b9e3..165bb3ab 100644 --- a/server/workspace-leistung.js +++ b/server/workspace-leistung.js @@ -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 ------------------------------------------------ */ diff --git a/server/workspace.js b/server/workspace.js index 44c20f5c..3ef53239 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -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);