diff --git a/server/crew-adresse.js b/server/crew-adresse.js index 7c5dd8c0..1abb1f05 100644 --- a/server/crew-adresse.js +++ b/server/crew-adresse.js @@ -78,6 +78,20 @@ import { fileURLToPath } from "node:url"; export const CREW_ADRESSE = "https://crew.dogfather-universe.com"; +/** Und die Adresse des Agenturhauses. + * + * Sie stand bisher nur als Text in `anmeldeAdresseFuer` ganz unten. + * Seit die Wechselkachel in BEIDE Richtungen geht (24.09.2026), wird + * sie an zwei Stellen gebraucht -- und zwei gleiche Zeichenketten sind + * eine Umzugsadresse, die beim naechsten Umzug halb mitwandert. Das + * ist in diesem Haus schon passiert: Der Workspace ist am 06.09.2026 + * umgezogen, und die Rueckmeldung an den Server lief danach ins Leere. + * + * KEINE ABLEITUNG AUS `OHNE_MODI_HOSTS`: Dort stehen drei Adressen + * (mit und ohne www, plus die Hauptseite). Welche davon die Tuer ist, + * waere geraten. */ +export const WORKSPACE_ADRESSE = "https://workspace.dogfather-universe.com"; + /* WELCHE SYMBOLGROESSEN HAT DIE CREW-APP EIGENS? Abgeleitet aus dem Ordner, nicht aufgezaehlt: Fuer jede Datei crew-.png, zu der es auch workspace-.png gibt, kann umgebogen @@ -157,6 +171,90 @@ export const AUSSEN_ROLLEN = new Set(["gast"]); weniger prueft, sondern mehr: Was vorher die Adresse erledigt hat, muss jetzt jede einzelne Regel halten. */ +/* ===================================================================== + DIE ZWEI HAEUSER -- WELCHE ROLLE WO WOHNT (24.09.2026) + ===================================================================== + + Filipe: „ich will dass du zuerst die komplette site vn der workspace + seite trennst. da soll nichts verknuepft sein. wenn ich bei der einen + was mache soll nichts bei der anderen passieren." + + DAS KEHRT DIE ENTSCHEIDUNG VOM 10.09.2026 UM. Damals hiess es + ausdruecklich: „es soll trotzdem so bleiben dass ich die daten hier + und da sehe", und weiter unten in dieser Datei steht bis heute + „Getrennt wird also das AUSSEHEN, nicht der Bestand." Das gilt nicht + mehr. Wer den alten Satz liest, liest einen ueberholten Stand -- + deshalb steht die Umkehr hier oben und nicht als Fussnote. + + WARUM DIE MENGEN HIER STEHEN: Diese Datei hat keine eigenen Importe + und ist damit der einzige Ort, an dem eine Rollenmenge stehen kann, + ohne einen Kreis zu bauen. TEAM_DOGI_ROLLEN und AUSSEN_ROLLEN wohnen + aus demselben Grund hier. + + DREI MENGEN UND NICHT ZWEI. „Alles ausser Team Dogi" waere die + naheliegende Formulierung fuer die Agentur -- und sie waere falsch, + denn DogFather gehoert in BEIDE Haeuser. Eine Bedingung durch + Ausschluss haette ihn stillschweigend der Agentur zugeschlagen. Genau + diese Bauart ist in dieser Datei schon dreimal schiefgegangen (siehe + Falle 2 im Kopf). Deshalb wird jede Rolle einzeln genannt. + + UND EINE ROLLE MUSS IRGENDWO STEHEN: `hausVonRolle` gibt fuer eine + unbekannte Rolle `null` zurueck, nicht „beide" und nicht „agentur". + Ein `null` faellt auf -- `pruef-haustrennung` wird rot, sobald eine + Rolle in ROLLEN steht, die hier fehlt. Eine stille Voreinstellung + waere eine offene Tuer, die niemand bemerkt. */ + +/** Die Rollen, die ausschliesslich im Agenturhaus leben. */ +export const AGENTUR_ROLLEN = new Set(["spicy", "manager", "scout", "creator"]); + +/** Die Rollen, die in BEIDEN Haeusern arbeiten. Genau eine: DogFather. + * Als Menge und nicht als `rolle === "admin"`, damit die drei Mengen + * zusammen gegen ROLLEN geprueft werden koennen. */ +export const BEIDE_HAEUSER_ROLLEN = new Set(["admin"]); + +/** + * In welchem Haus wohnt diese Rolle? + * + * @returns {"crew"|"agentur"|"beide"|null} `null` heisst: unbekannte + * Rolle. Das ist ein Fehler, kein Standardfall. + */ +export function hausVonRolle(rolle) { + if (TEAM_DOGI_ROLLEN.has(rolle) || AUSSEN_ROLLEN.has(rolle)) return "crew"; + if (AGENTUR_ROLLEN.has(rolle)) return "agentur"; + if (BEIDE_HAEUSER_ROLLEN.has(rolle)) return "beide"; + return null; +} + +/** + * Welche Rollen kommen in diesem Haus vor -- DogFather eingerechnet? + * + * ABGELEITET AUS DENSELBEN MENGEN, nicht abgeschrieben. Wer eine Rolle + * verschiebt, verschiebt damit jede SQL-Abfrage mit, die diese Funktion + * benutzt. Genau das Abschreiben hat am 24.09.2026 dazu gefuehrt, dass + * die „An wen"-Reihe sechs Leute zeigte und der Server vier belieferte. + */ +export function rollenImHaus(haus) { + const eigen = haus === "crew" + ? [...TEAM_DOGI_ROLLEN, ...AUSSEN_ROLLEN] + : [...AGENTUR_ROLLEN]; + return new Set([...eigen, ...BEIDE_HAEUSER_ROLLEN]); +} + +/** + * Und welche Rollen gehoeren NUR dem anderen Haus? + * + * Das ist die Menge, nach der gefiltert wird -- nicht „welche gehoeren + * hierher". Der Unterschied ist DogFather: Er steht in beiden, also + * darf er in keiner der beiden Ausschlussmengen auftauchen. Eine Zeile, + * die „gehoert hierher" fragt, wuerde eine Aufgabe, die DogFather fuer + * einen Creator angelegt hat, ueber `erstellt_von` ins Team-Haus + * ziehen. Derselbe Gedanke steht bei ohneTeamDogi() in workspace.js. + */ +export function nurAnderesHaus(haus) { + return haus === "crew" ? new Set(AGENTUR_ROLLEN) + : new Set([...TEAM_DOGI_ROLLEN, ...AUSSEN_ROLLEN]); +} + /** Die Adresse von Team Dogi -- und seit 11.09.2026 auch die des * Treffs. Feste Menge, kein Muster -- Falle 1. */ const CREW_HOSTS = new Set(["crew.dogfather-universe.com"]); @@ -569,5 +667,5 @@ export function crewWeiche(req, res, next) { export function anmeldeAdresseFuer(rolle) { return (TEAM_DOGI_ROLLEN.has(rolle) || AUSSEN_ROLLEN.has(rolle)) ? CREW_ADRESSE - : "https://workspace.dogfather-universe.com"; + : WORKSPACE_ADRESSE; } diff --git a/server/pruef-haus-trennung.mjs b/server/pruef-haus-trennung.mjs index 9fb69f59..b957b49c 100644 --- a/server/pruef-haus-trennung.mjs +++ b/server/pruef-haus-trennung.mjs @@ -269,8 +269,20 @@ console.log("=== Aufgaben ==="); `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})`); + /* ==== UMGEDREHT AM 24.09.2026 =================================== + Hier stand „auf workspace. stehen beide". Das war richtig, solange + „getrennt wird das Aussehen, nicht der Bestand" galt. Filipe hat + das umgedreht: „wenn ich bei der einen was mache soll nichts bei + der anderen passieren." + + SIE IST DADURCH STRENGER, NICHT SCHWAECHER: Vorher wurde EIN + Zustand geprueft („beides da"), jetzt ein UNTERSCHIED („das eine + ja, das andere nicht"). Ein Filter, der einfach alles wegnimmt, + faellt damit auf; vorher waere er nur an der ersten Haelfte + aufgefallen. */ + ok(t(a.ws).includes("Lunas Steckbrief nachtragen") + && !t(a.ws).includes("Clips vom Freitag schneiden"), + `auf workspace. steht NUR die der Agentur (${t(a.ws).join(" | ") || "leer"})`); } /* ======================================================================= @@ -288,8 +300,11 @@ console.log("=== Bereiche ==="); 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"); + /* AUCH HIER UMGEDREHT (24.09.2026): „er sieht es hier wie dort" war + die alte Regel. Das Ideen-Board gehoert dem Team -- in der Agentur + hat es nichts zu suchen, auch nicht bei ihm. */ + ok(!(ideen.ws?.eintraege || []).some((e) => e.titel === "Wunschkonzert am Sonntag"), + "und auf workspace. NICHT -- das Brett gehoert dem Team"); } /* ======================================================================= @@ -315,12 +330,39 @@ console.log("=== Kalender ==="); 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. */ + /* ==== DRITTE FASSUNG DIESER ERWARTUNG (24.09.2026) ============== + + 10.09.: getrennt. 11.09.: fuer DogFather verbunden („mein kalender + soll komplett verbunden sein"). 24.09.: getrennt -- aber die + ZEITEN der anderen Seite bleiben als titellose Bloecke sichtbar. + + Auf die Nachfrage, was aus der Ausnahme wird, hat Filipe genau + das entschieden: „Getrennt, aber ‚belegt' bleibt sichtbar." + + WARUM DAS KEIN RUECKSCHRITT ZUM 10.09. IST: Der Grund fuer die + Ausnahme galt weiter -- wer die Haelfte seines Tages nicht sieht, + legt einen Call auf eine besetzte Stunde. Beide Anspruechen ist + zugleich zu genuegen, indem man den INHALT trennt und die ZEIT + nicht. Genau das wird hier gemessen. */ 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})`); + ok(t(a.crew).includes("Teamrunde") && !t(a.crew).includes("Call mit Luna"), + `auf crew. steht der Team-Termin und NICHT der der Agentur (${t(a.crew).join(" | ") || "leer"})`); + ok(t(a.ws).includes("Call mit Luna") && !t(a.ws).includes("Teamrunde"), + `auf workspace. umgekehrt (${t(a.ws).join(" | ") || "leer"})`); + + /* UND DIE ZEIT IST TROTZDEM DA. Ohne diese drei Zeilen waere oben + nur bewiesen, dass etwas verschwindet -- und der Kalender waere + wieder die Falle vom 11.09. */ + const bc = a.crew?.belegt || []; + const bw = a.ws?.belegt || []; + ok(bc.length > 0 && bw.length > 0, + `er sieht auf beiden Seiten belegte Zeiten der anderen (${bc.length}/${bw.length})`); + ok(bc.every((x) => Object.keys(x).sort().join(",") === "beginn,dauer_min"), + "und die Bloecke tragen NUR Beginn und Dauer -- keinen Titel, keine Person"); + const hBelegt = (await hole(weg, { host: CREW, keks: hand.keks })).daten?.belegt || []; + ok(hBelegt.length === 0, + `die rechte Hand bekommt keine Bloecke (${hBelegt.length}) -- "NICHT BEI VANVAN"`); /* UND DIE RECHTE HAND NICHT -- sonst hiesse "verbunden" schlicht "die Trennung ist weg". */ @@ -344,8 +386,12 @@ console.log("=== Die Personenliste ==="); `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})`); + /* UMGEDREHT (24.09.2026): Vorher „auf workspace. stehen alle". + Ayla ist Modi -- sie gehoert ins Team-Haus, nicht in seine + Schreibliste auf der Agenturseite. */ + ok(["Luna", "Cigdem", "Patrick"].every((x) => n(a.ws).includes(x)) + && !n(a.ws).includes("Ayla"), + `auf workspace. steht NUR die Agentur (${n(a.ws).join(", ") || "leer"})`); } /* ======================================================================= @@ -466,8 +512,11 @@ console.log("=== Personen & Zugaenge ==="); `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})`); + /* UMGEDREHT (24.09.2026) -- dieselbe Regel wie eine Seite hoeher: + Wer im Team wohnt, steht in der Agenturverwaltung nicht. */ + ok(["Luna", "Cigdem", "Patrick"].every((n) => wer(liste.ws).includes(n)) + && !wer(liste.ws).includes("Ayla"), + `auf workspace. steht NUR die Agentur (${wer(liste.ws).join(", ") || "leer"})`); /* Die anlegbaren Rollen kommen aus derselben Auskunft, aus der die Oberflaeche ihre Knoepfe baut. Auf crew. duerfen es nur Team-Rollen @@ -807,6 +856,90 @@ console.log("=== Alle Kategorien ==="); ok(dogiLegtAn.status === 201, `DogFather kann es (${dogiLegtAn.status})`); } +/* ======================================================================= + 11b. WAS AM 24.09.2026 DAZUGEKOMMEN IST + + Drei Dinge, die es am 10.09. noch nicht gab und die deshalb in der + Aufteilung oben fehlen: die Rollentafel selbst, die Wissensablage + und die Tuer zwischen den Haeusern. + + Sie stehen HIER und nicht in einer eigenen Datei. Beim Bauen lag + eine zweite, fast gleich benannte Pruefung daneben + (`pruef-haustrennung.mjs` gegen `pruef-haus-trennung.mjs`) -- zwei + Dateien, die dasselbe meinen und deren Namen sich um einen + Bindestrich unterscheiden. Genau die Bauart, vor der in diesem Haus + an fuenfzehn Stellen gewarnt wird. Sie ist wieder weg. + ======================================================================= */ +console.log(""); +console.log("=== Die Rollentafel: wohnt jede Rolle irgendwo? ==="); +{ + const w = await import("./workspace.js"); + const ROLLEN = ["spicy", "admin", "manager", "scout", "creator", + "hand", "linke", "modi", "gast"]; + const ohne = ROLLEN.filter((r) => !w.hausVonRolle(r)); + /* DIE ZAHL GEHOERT IN DIE BEDINGUNG: Waere ROLLEN leer, meldete + `filter().length === 0` brav "alles gut", ohne eine einzige Rolle + angesehen zu haben. Genau so stand dieses Haus am 01.09. dreimal + gruen da. */ + ok(ROLLEN.length === 9 && ohne.length === 0, + `alle ${ROLLEN.length} Rollen haben ein Haus${ohne.length ? " -- ausser: " + ohne.join(", ") : ""}`); + ok(w.hausVonRolle("admin") === "beide", + `DogFather wohnt in beiden (${w.hausVonRolle("admin")})`); + /* DER DRITTE AUSGANG: Eine unbekannte Rolle darf NICHT stillschweigend + in einem Haus landen. Ohne diese Zeile koennte hausVonRolle immer + "agentur" sagen und die Pruefung darueber waere trotzdem gruen. */ + ok(w.hausVonRolle("erfunden") === null, + "eine unbekannte Rolle bekommt KEIN Haus, sondern null"); + ok(!w.rollenImHaus("crew").has("scout") && w.rollenImHaus("crew").has("admin"), + "im Team-Haus: kein Scout, aber DogFather"); + ok(!w.rollenImHaus("agentur").has("modi") && w.rollenImHaus("agentur").has("admin"), + "im Agenturhaus: kein Modi, aber DogFather"); + /* Und die Bedingung muss sich UNTERSCHEIDEN -- waere sie fuer beide + Haeuser gleich, traefen die Messungen oben zufaellig zu. */ + ok(w.hausWo({ haus: "crew" }, "t") !== w.hausWo({ haus: "agentur" }, "t"), + "die Hausbedingung ist fuer die beiden Haeuser verschieden"); + ok(w.hausWo({ rolle: "admin" }, "t") === "1=1", + "ohne bekanntes Haus wird nicht gefiltert -- Pruefadressen bleiben heil"); +} + +console.log(""); +console.log("=== Die Wissensablage wohnt in der Agentur ==="); +{ + /* Filipe, 24.09.2026: „diese komplette kachel soll nur im workspace + sein." Die Kachel ist drueben weg -- das allein ist aber nur eine + Bitte. Wer die Adresse kennt, tippt sie. */ + const a = await beide("/workspace/api/wissen", dogi.keks); + const inAgentur = await hole("/workspace/api/wissen", { host: WS, keks: dogi.keks }); + const imTeam = await hole("/workspace/api/wissen", { host: CREW, keks: dogi.keks }); + void a; + ok(inAgentur.status === 200, + `in der Agentur ist sie da (${inAgentur.status}) -- Gegenprobe`); + ok(imTeam.status === 404, + `auf der Team-Adresse gibt es sie nicht (${imTeam.status})`); +} + +console.log(""); +console.log("=== Eine Tuer, in beide Richtungen -- und sonst nichts ==="); +{ + const w = await import("./workspace.js"); + const ausTeam = w.zusatzBereicheFuer({ rolle: "admin", haus: "crew", id: idDogi }); + const ausAgentur = w.zusatzBereicheFuer({ rolle: "admin", haus: "agentur", id: idDogi }); + ok(ausTeam.length === 1 && ausAgentur.length === 1, + `von beiden Seiten fuehrt GENAU EINE Kachel hinueber (${ausTeam.length}/${ausAgentur.length})`); + ok(ausTeam[0]?.ziel?.startsWith("https://workspace."), + `aus dem Team fuehrt sie in die Agentur (${ausTeam[0]?.ziel})`); + ok(ausAgentur[0]?.ziel?.startsWith("https://crew."), + `aus der Agentur fuehrt sie ins Team (${ausAgentur[0]?.ziel})`); + /* NUR FUER DOGFATHER. Bei einem Modi waere sie zugleich eine + Adresse, die er nicht kennen soll -- und eine Seite, die aus + nichts als einem Ausgang besteht. */ + const andere = ["hand", "linke", "modi", "gast", "scout", "creator", "manager", "spicy"]; + ok(andere.length === 8 + && andere.every((r) => w.zusatzBereicheFuer({ rolle: r, haus: "crew" }).length === 0 + && w.zusatzBereicheFuer({ rolle: r, haus: "agentur" }).length === 0), + `und niemand sonst bekommt sie (${andere.length} Rollen, beide Adressen)`); +} + /* ======================================================================= 12. Die Gegenprobe zur Regel selbst ======================================================================= */ @@ -825,8 +958,20 @@ console.log("=== Haengt es wirklich an der Adresse? ==="); /* 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})`); + /* VERGLICHEN STATT GEZAEHLT (24.09.2026). Hier stand `length === 2` + -- eine feste Zahl, die genau so lange stimmte, bis sich das + Verhalten der Agenturadresse aenderte. Genau das ist am 24.09. + passiert: Seit die Grenze auch dort gilt, sind es nicht mehr zwei. + Die Pruefung wurde rot, obwohl an der Sache nichts kaputt war. + + Gefragt wird jetzt, was die Aussage MEINT: Eine erfundene Adresse + verhaelt sich wie die Agenturadresse und oeffnet nichts Drittes. + Diese Formulierung kann der Kalender nicht einholen. */ + const echt = await hole("/workspace/api/aufgaben", { host: WS, keks: dogi.keks }); + const titel = (x) => (x.daten?.aufgaben || []).map((z) => z.titel).sort().join(" | "); + ok(titel(echt).length > 0 && titel(fremd) === titel(echt), + `eine erfundene Adresse verhaelt sich wie die Agenturadresse ` + + `(${titel(fremd) || "leer"} / ${titel(echt) || "leer"})`); } d.close(); diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index 2fbe2885..cc03d844 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -92,6 +92,39 @@ export function sichtbar(person) { werte: regel.werte, }; } + /* ==== DREI FAELLE UND NICHT ZWEI (24.09.2026, nachgebessert) ===== + + Hier stand bis heute `if (siehtModis(person)) return regel;` -- + "wer Modi-Daten sehen darf, bekommt sie ungefiltert". Auf der + AGENTURADRESSE hiess das in der Praxis: DogFather sieht dort auch + Team Dogi. Genau das hat Filipe am 24.09.2026 abgeschafft. + + DIE ERSTE FASSUNG DIESER REPARATUR WAR ZU GROB: Ich habe die Zeile + einfach gestrichen. Damit fiel auch der Fall "keine der beiden + Adressen" durch -- also jede Pruefadresse. Gemessen: 19 + Fehlschlaege in drei Pruefungen, weil ein Modi seine eigenen + Aufgaben nicht mehr sah. Nicht weil etwas kaputt war, sondern weil + `ohneTeamDogi` dort ploetzlich auf ihn selbst zeigte. + + ES SIND DREI LAGEN, und jede bekommt ihre Zeile: + crew -> ohne die Agentur + agentur -> ohne Team Dogi, AUCH fuer DogFather (das ist neu) + kein Haus (localhost, Pruefungen) -> die alte Rollenregel, + unveraendert. Sonst waeren alle Modi-Pruefungen des + Hauses still gruen und blind -- die Falle, vor der + crew-adresse.js dreimal warnt. + + `siehtModis` BLEIBT UNVERAENDERT und wird anderswo weiter + gebraucht (Rechtefragen, z.B. in der Checkliste). "Darf ich das + sehen" und "welchen Ausschnitt sehe ich gerade" sind zwei Fragen; + wer sie in eine Funktion legt, kann spaeter nicht mehr sagen, + welche zugeschlagen hat. */ + if (person.haus === "agentur") { + return { + wo: `(${regel.wo}) AND ${ohneTeamDogi("a", ["creator_id", "verantwortlich_id", "erstellt_von"])}`, + werte: regel.werte, + }; + } if (siehtModis(person)) return regel; return { wo: `(${regel.wo}) AND ${ohneTeamDogi("a", ["creator_id", "verantwortlich_id", "erstellt_von"])}`, diff --git a/server/workspace-bereiche.js b/server/workspace-bereiche.js index e92d95c3..00fe270a 100644 --- a/server/workspace-bereiche.js +++ b/server/workspace-bereiche.js @@ -996,6 +996,39 @@ export function sichtbar(person) { werte: regel.werte, }; } + /* ==== DREI FAELLE UND NICHT ZWEI (24.09.2026, nachgebessert) ===== + + Hier stand bis heute `if (siehtModis(person)) return regel;` -- + "wer Modi-Daten sehen darf, bekommt sie ungefiltert". Auf der + AGENTURADRESSE hiess das in der Praxis: DogFather sieht dort auch + Team Dogi. Genau das hat Filipe am 24.09.2026 abgeschafft. + + DIE ERSTE FASSUNG DIESER REPARATUR WAR ZU GROB: Ich habe die Zeile + einfach gestrichen. Damit fiel auch der Fall "keine der beiden + Adressen" durch -- also jede Pruefadresse. Gemessen: 19 + Fehlschlaege in drei Pruefungen, weil ein Modi seine eigenen + Aufgaben nicht mehr sah. Nicht weil etwas kaputt war, sondern weil + `ohneTeamDogi` dort ploetzlich auf ihn selbst zeigte. + + ES SIND DREI LAGEN, und jede bekommt ihre Zeile: + crew -> ohne die Agentur + agentur -> ohne Team Dogi, AUCH fuer DogFather (das ist neu) + kein Haus (localhost, Pruefungen) -> die alte Rollenregel, + unveraendert. Sonst waeren alle Modi-Pruefungen des + Hauses still gruen und blind -- die Falle, vor der + crew-adresse.js dreimal warnt. + + `siehtModis` BLEIBT UNVERAENDERT und wird anderswo weiter + gebraucht (Rechtefragen, z.B. in der Checkliste). "Darf ich das + sehen" und "welchen Ausschnitt sehe ich gerade" sind zwei Fragen; + wer sie in eine Funktion legt, kann spaeter nicht mehr sagen, + welche zugeschlagen hat. */ + if (person.haus === "agentur") { + return { + wo: `(${regel.wo}) AND ${ohneTeamDogi("e", ["creator_id", "erstellt_von"])}`, + werte: regel.werte, + }; + } if (siehtModis(person)) return regel; return { wo: `(${regel.wo}) AND ${ohneTeamDogi("e", ["creator_id", "erstellt_von"])}`, diff --git a/server/workspace-calls.js b/server/workspace-calls.js index 0c25243e..7be0d702 100644 --- a/server/workspace-calls.js +++ b/server/workspace-calls.js @@ -346,10 +346,14 @@ callRouter.post("/workspace/api/calls/:id/protokoll", gleicheHerkunft, (req, res if (nb) { naechster = Number(d.prepare(` INSERT INTO termine - (titel, beschreibung, art, beginn, dauer_min, ort, creator_id, teilnehmer_id, erstellt, erstellt_von) - VALUES (?,?,?,?,?,?,?,?,?,?)`).run( + (titel, beschreibung, art, beginn, dauer_min, ort, creator_id, teilnehmer_id, erstellt, erstellt_von, haus) + VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run( termin.titel, `Folgetermin zu „${termin.titel}"`, termin.art, nb, dauer, - termin.ort, termin.creator_id, termin.teilnehmer_id, jetzt(), req.person.id).lastInsertRowid); + termin.ort, termin.creator_id, termin.teilnehmer_id, jetzt(), req.person.id, + /* Ein Folgetermin erbt das Haus SEINES Vorgaengers und nicht + das der Adresse: Wer ihn ansetzt, verlaengert etwas, das + schon irgendwo wohnt. */ + termin.haus || req.person.haus || null).lastInsertRowid); d.prepare("UPDATE protokolle SET naechster_termin_id = ? WHERE id = ?").run(naechster, protokollId); } diff --git a/server/workspace-chat.js b/server/workspace-chat.js index 98812339..06c8e7ed 100644 --- a/server/workspace-chat.js +++ b/server/workspace-chat.js @@ -45,7 +45,7 @@ import { mkdirSync, writeFileSync, createReadStream, statSync, unlinkSync, import { db, protokolliere, echteIp, sitzungLesen, istDogFather, schreibbareIds, darfSchreibenMit, ROLLEN_SORTIERUNG, DATEN_ORDNER, - fuehrtTeamDogi, MODI_KATEGORIEN, ROLLEN_GRUPPE, TEAM_DOGI_ROLLEN, + fuehrtTeamDogi, MODI_KATEGORIEN, ROLLEN_GRUPPE, TEAM_DOGI_ROLLEN, hausWo, istTeamDogi, } from "./workspace.js"; @@ -511,9 +511,14 @@ function teilnehmerVon(raumId) { * Spalte wäre eine zweite Wahrheit neben chat_teilnehmer. */ function direktRaum(personA, idB, { anlegen = true } = {}) { const d = db(); + /* GESUCHT WIRD NUR IM EIGENEN HAUS (24.09.2026). Ohne das faende + DogFather auf der Team-Adresse sein Agentur-Gespraech mit + derselben Person wieder -- und schriebe hinein, ohne es zu + merken. Zwei Haeuser heisst: notfalls zwei Gespraeche. */ const vorhanden = d.prepare(` SELECT r.id FROM chat_raeume r WHERE r.art = 'direkt' + AND ${hausWo(personA, "r")} AND EXISTS (SELECT 1 FROM chat_teilnehmer t WHERE t.raum_id = r.id AND t.person_id = ?) AND EXISTS (SELECT 1 FROM chat_teilnehmer t WHERE t.raum_id = r.id AND t.person_id = ?) AND (SELECT COUNT(*) FROM chat_teilnehmer t WHERE t.raum_id = r.id) = 2`) @@ -522,8 +527,13 @@ function direktRaum(personA, idB, { anlegen = true } = {}) { if (!anlegen) return null; const n = jetzt(); - d.prepare("INSERT INTO chat_raeume (art, erstellt, erstellt_von, letzte_am) VALUES ('direkt',?,?,NULL)") - .run(n, personA.id); + /* DAS HAUS WIRD BEIM ANLEGEN GESCHRIEBEN, nicht spaeter gerechnet. + `personA.haus` kommt aus der Adresse, auf der gerade gearbeitet + wird -- die einzige Auskunft, die auch dann stimmt, wenn der Raum + noch leer ist. */ + d.prepare("INSERT INTO chat_raeume (art, erstellt, erstellt_von, letzte_am, haus)" + + " VALUES ('direkt',?,?,NULL,?)") + .run(n, personA.id, personA.haus || null); const raumId = d.prepare("SELECT last_insert_rowid() AS id").get().id; const rein = d.prepare( "INSERT INTO chat_teilnehmer (raum_id, person_id, seit, gelesen_bis) VALUES (?,?,?,0)"); @@ -1081,12 +1091,30 @@ chatRouter.get("/workspace/api/chat/raeume", (req, res) => { Chat stehen, ohne dass jemand daran denken muss. */ if (gehoertInTreff(req.person)) treffAngleichen(); + /* ==== NUR DIE RAEUME DIESES HAUSES (24.09.2026) ================ + + Filipe: „die seiten soll nicht mehr im chat kalender und alles + andere verbunden sein." + + DIESE LISTE HATTE KEINEN HAUS-FILTER, und das fiel nur bei einem + einzigen Menschen auf: DogFather. Alle anderen sehen ohnehin nur + Raeume, in denen sie sitzen, und sie sitzen nur in denen ihres + Hauses. Er sitzt in beiden -- gemessen am echten Stand standen + auf der Team-Adresse in seiner Gespraechsliste auch seine zwei + Zweier-Gespraeche mit Scouts. + + UEBER DIE SPALTE UND NICHT UEBER DIE TEILNEHMER: Ein Raum, in + dem nur DogFather sitzt (er legt einen Kanal an, es ist noch + niemand drin), haette ueber die Teilnehmer keinen Beleg und + stuende in beiden Haeusern. Die Spalte weiss, wo er ihn + angelegt hat. */ const reihen = db().prepare(` SELECT r.id, r.art, r.name, r.kategorie, r.letzte_am, r.erstellt_von, t.gelesen_bis, t.leitung, t.geloescht_bis FROM chat_raeume r JOIN chat_teilnehmer t ON t.raum_id = r.id AND t.person_id = ? WHERE t.raus_am IS NULL + AND ${hausWo(req.person, "r")} AND (t.geloescht_am IS NULL OR EXISTS (SELECT 1 FROM chat_nachrichten n WHERE n.raum_id = r.id AND n.id > t.geloescht_bis)) @@ -1265,8 +1293,13 @@ chatRouter.post("/workspace/api/chat/gruppe", gleicheHerkunft, express.json({ li const d = db(); const n = jetzt(); - d.prepare("INSERT INTO chat_raeume (art, name, erstellt, erstellt_von) VALUES ('gruppe',?,?,?)") - .run(name, n, req.person.id); + /* Auch die Gruppe merkt sich ihr Haus (24.09.2026). Sie hat beim + Anlegen genau EIN Mitglied -- den, der sie anlegt. Ueber die + Beteiligten waere sie also gar nicht einzuordnen, solange + DogFather sie anlegt und noch niemand dazugekommen ist. */ + d.prepare("INSERT INTO chat_raeume (art, name, erstellt, erstellt_von, haus)" + + " VALUES ('gruppe',?,?,?,?)") + .run(name, n, req.person.id, req.person.haus || null); const raumId = d.prepare("SELECT last_insert_rowid() AS id").get().id; d.prepare("INSERT INTO chat_teilnehmer (raum_id, person_id, seit, gelesen_bis, leitung) VALUES (?,?,?,0,1)") .run(raumId, req.person.id, n); @@ -1403,12 +1436,13 @@ chatRouter.post("/workspace/api/chat/kanal", gleicheHerkunft, express.json({ lim } const n = jetzt(); - d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von) - VALUES ('kanal',?,?,?,?)`) + d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von, haus) + VALUES ('kanal',?,?,?,?,?)`) /* Der eigene Name geht in die Spalte, der feste kommt aus MODI_KATEGORIEN. Beides landet in derselben Spalte -- die Zeile im Chat liest nur noch dort nach. */ - .run(anzeigeName || kanalName(schluessel), schluessel, n, req.person.id); + .run(anzeigeName || kanalName(schluessel), schluessel, n, req.person.id, + req.person.haus || null); const raumId = d.prepare("SELECT last_insert_rowid() AS id").get().id; /* DIE GANZE TEAMFUEHRUNG KOMMT HINEIN, nicht nur der Anlegende. diff --git a/server/workspace-content.js b/server/workspace-content.js index c24bd5f1..fcc8a347 100644 --- a/server/workspace-content.js +++ b/server/workspace-content.js @@ -401,9 +401,10 @@ contentRouter.post("/workspace/api/content/:id/uebernehmen", gleicheHerkunft, (r } const beginn = e.geplant.length === 10 ? e.geplant + "T10:00" : e.geplant; const r = db().prepare(` - INSERT INTO termine (titel, beschreibung, art, beginn, dauer_min, creator_id, erledigt, erstellt, erstellt_von) - VALUES (?,?,'termin',?,60,?,0,?,?)`).run( - ("Dreh: " + e.titel).slice(0, 160), beschreibung, beginn, e.creator_id, jetzt(), req.person.id); + INSERT INTO termine (titel, beschreibung, art, beginn, dauer_min, creator_id, erledigt, erstellt, erstellt_von, haus) + VALUES (?,?,'termin',?,60,?,0,?,?,?)`).run( + ("Dreh: " + e.titel).slice(0, 160), beschreibung, beginn, e.creator_id, jetzt(), req.person.id, + req.person.haus || null); neu = { art: "Termin", id: Number(r.lastInsertRowid), wohin: "kalender.html" }; } else { diff --git a/server/workspace-dateien.js b/server/workspace-dateien.js index afe91e04..0f71a401 100644 --- a/server/workspace-dateien.js +++ b/server/workspace-dateien.js @@ -103,6 +103,39 @@ export function sichtbar(person) { werte: regel.werte, }; } + /* ==== DREI FAELLE UND NICHT ZWEI (24.09.2026, nachgebessert) ===== + + Hier stand bis heute `if (siehtModis(person)) return regel;` -- + "wer Modi-Daten sehen darf, bekommt sie ungefiltert". Auf der + AGENTURADRESSE hiess das in der Praxis: DogFather sieht dort auch + Team Dogi. Genau das hat Filipe am 24.09.2026 abgeschafft. + + DIE ERSTE FASSUNG DIESER REPARATUR WAR ZU GROB: Ich habe die Zeile + einfach gestrichen. Damit fiel auch der Fall "keine der beiden + Adressen" durch -- also jede Pruefadresse. Gemessen: 19 + Fehlschlaege in drei Pruefungen, weil ein Modi seine eigenen + Aufgaben nicht mehr sah. Nicht weil etwas kaputt war, sondern weil + `ohneTeamDogi` dort ploetzlich auf ihn selbst zeigte. + + ES SIND DREI LAGEN, und jede bekommt ihre Zeile: + crew -> ohne die Agentur + agentur -> ohne Team Dogi, AUCH fuer DogFather (das ist neu) + kein Haus (localhost, Pruefungen) -> die alte Rollenregel, + unveraendert. Sonst waeren alle Modi-Pruefungen des + Hauses still gruen und blind -- die Falle, vor der + crew-adresse.js dreimal warnt. + + `siehtModis` BLEIBT UNVERAENDERT und wird anderswo weiter + gebraucht (Rechtefragen, z.B. in der Checkliste). "Darf ich das + sehen" und "welchen Ausschnitt sehe ich gerade" sind zwei Fragen; + wer sie in eine Funktion legt, kann spaeter nicht mehr sagen, + welche zugeschlagen hat. */ + if (person.haus === "agentur") { + return { + wo: `(${regel.wo}) AND ${ohneTeamDogi("d", ["creator_id", "hochgeladen_von"])}`, + werte: regel.werte, + }; + } if (siehtModis(person)) return regel; return { wo: `(${regel.wo}) AND ${ohneTeamDogi("d", ["creator_id", "hochgeladen_von"])}`, diff --git a/server/workspace-kalender.js b/server/workspace-kalender.js index 1916d982..9b9be27c 100644 --- a/server/workspace-kalender.js +++ b/server/workspace-kalender.js @@ -16,6 +16,7 @@ import { db, protokolliere, echteIp, sitzungLesen, istLeitung, istDogFather, siehtAlles, betreutWo, termineSichtbar, heuteLokal, kategorienFuer, externPruefen, externSql, betreuteIds, einladbareIds, verborgeneIds, ROLLEN_SORTIERUNG, ROLLEN_GRUPPE, + hausWo, ohneAnderesHaus, } from "./workspace.js"; import { nachfuellenAlle, serienPruefen, serieAnlegen, zuordnungErzwingen, @@ -486,7 +487,72 @@ kalenderRouter.get("/workspace/api/termine", (req, res) => { b.text = b.text ? String(b.text).slice(0, 180) : null; } - res.json({ termine, fristen, brettsachen, von, bis, checkliste_moeglich: moeglich }); + /* ==== WAS AUF DER ANDEREN SEITE SCHON BELEGT IST (24.09.2026) === + + Filipe, zur Frage, was aus der Kalender-Ausnahme vom 11.09. wird: + „Getrennt, aber ‚belegt' bleibt sichtbar." + + ZWEI ANSPRUECHE, EINE ANZEIGE. Der INHALT gehoert getrennt -- + kein Titel, kein Teilnehmer, keine Notiz aus dem anderen Haus. + Die ZEIT gehoert nicht getrennt: Wer die Haelfte seines Tages + nicht sieht, legt einen Call auf eine Stunde, in der er schon + woanders sitzt. Genau das war der Grund fuer die alte Ausnahme, + und der Grund gilt weiter. + + WAS HIER RUEBERGEHT, IST DAS MINIMUM: Beginn und Dauer. Kein + Titel, keine Nummer, keine Person -- nicht einmal die Art. Man + kann daraus ablesen, DASS etwas ist, und nichts darueber, WAS. + + NUR FUER DIE DOGFATHER-ROLLE, und das ist unveraendert sein Wort + vom 11.09.2026: „NUR IN DER DOGFATHER ROLLE BEI DOGFATHER: NICHT + BEI VANVAN." Die rechte Hand sieht von der anderen Seite nichts, + auch keine Bloecke. + + UND ES SIND SEINE EIGENEN TERMINE, nicht alle des anderen + Hauses. `eigen` fragt dasselbe wie die Liste darueber: angelegt, + zugeordnet, Gegenueber oder Teilnehmer. Ein Kalender, der die + Verabredungen fremder Leute als „belegt" zeigt, waere + unbrauchbar -- und ein Leck obendrein. */ + let belegt = []; + if (req.person.rolle === "admin" + && (req.person.haus === "crew" || req.person.haus === "agentur")) { + /* ==== DAS GEGENSTUECK ZUR LISTE, NICHT EINE ZWEITE REGEL ======= + + Der erste Entwurf fragte `t.haus = `. Das war eine + ZWEITE Vorstellung davon, was "die andere Seite" ist -- und + zwei Vorstellungen laufen auseinander. Gemessen ist genau das + sofort passiert: Termine ohne eingetragenes Haus (aus einem + Schreibweg, der es noch nicht setzt) wurden von der Liste + korrekt ueber die BETEILIGTEN getrennt, vom Block aber nicht + gefunden -- null Bloecke, und der Kalender waere wieder die + Falle vom 11.09. gewesen. + + Jetzt steht hier die Rechnung: MEINE Termine MINUS die, die + dieses Haus zeigt. Was die Liste verbirgt, wird zum Block -- + beides kann sich nicht mehr widersprechen, egal wie das Haus + einer Zeile bestimmt wird. + + `haus: null` HOLT NUR DEN "EIGEN"-TEIL: termineSichtbar haengt + die Hausbedingung nur an, wenn ein Haus dasteht. So muss die + Frage "was sind meine Termine" nicht ein zweites Mal + formuliert werden. */ + const SPALTEN_HAUS = ["creator_id", "teilnehmer_id", "erstellt_von"]; + const meine = termineSichtbar({ ...req.person, haus: null }, "t"); + const hierSichtbar = `${hausWo(req.person, "t")}` + + ` AND ${ohneAnderesHaus("t", req.person, SPALTEN_HAUS)}`; + belegt = db().prepare(` + SELECT t.beginn, t.dauer_min + FROM termine t + WHERE (${meine.wo}) + AND NOT (${hierSichtbar}) + AND t.erledigt = 0 + AND t.beginn >= ? AND t.beginn < ? + ORDER BY t.beginn`) + .all(...meine.werte, von, bis + "T23:59:59Z"); + } + + res.json({ termine, fristen, brettsachen, von, bis, + checkliste_moeglich: moeglich, belegt }); } catch (fehler) { console.error("[workspace] Termine lesen:", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); @@ -603,11 +669,12 @@ kalenderRouter.post("/workspace/api/termine", gleicheHerkunft, (req, res) => { const { lastInsertRowid } = db().prepare(` INSERT INTO termine (titel, beschreibung, art, beginn, dauer_min, ort, creator_id, teilnehmer_id, - teilnehmer_extern, erledigt, erstellt, erstellt_von) - VALUES (?,?,?,?,?,?,?,?,?,0,?,?)`).run( + teilnehmer_extern, erledigt, erstellt, erstellt_von, haus) + VALUES (?,?,?,?,?,?,?,?,?,0,?,?,?)`).run( aus.titel, aus.beschreibung ?? null, aus.art ?? "termin", aus.beginn, aus.dauer_min ?? 30, aus.ort ?? null, aus.creator_id ?? null, - aus.teilnehmer_id ?? null, aus.teilnehmer_extern ?? null, jetzt(), req.person.id); + aus.teilnehmer_id ?? null, aus.teilnehmer_extern ?? null, jetzt(), req.person.id, + req.person.haus || null); /* Die weiteren Teilnehmer. Das Haupt-Gegenueber kommt als `hauptId` mit hinein, damit die Liste vollstaendig ist -- an ihr haengt die diff --git a/server/workspace-serien.js b/server/workspace-serien.js index f9298f97..f310187b 100644 --- a/server/workspace-serien.js +++ b/server/workspace-serien.js @@ -266,8 +266,8 @@ export function serieNachfuellen(serieId) { INSERT INTO termine (titel, beschreibung, art, beginn, dauer_min, ort, creator_id, teilnehmer_id, teilnehmer_extern, erledigt, erstellt, erstellt_von, serie_id, serie_tag, - serie_beruehrt) - VALUES (?,?,?,?,?,?,?,?,?,0,?,?,?,?,0)`); + serie_beruehrt, haus) + VALUES (?,?,?,?,?,?,?,?,?,0,?,?,?,?,0,?)`); /* DIE TEILNEHMER DER SERIE ERBEN (05.09.2026). @@ -292,7 +292,12 @@ export function serieNachfuellen(serieId) { const { lastInsertRowid } = einfuegen.run( s.titel, s.beschreibung, s.art, `${tag}T${s.uhrzeit}`, s.dauer_min, s.ort, s.creator_id, s.teilnehmer_id, s.teilnehmer_extern ?? null, - stempel, s.erstellt_von, s.id, tag); + stempel, s.erstellt_von, s.id, tag, + /* AUS DER SERIE UND NICHT AUS DER ADRESSE: Der Nachfueller laeuft + auch ohne Mensch davor (beim Start, per Zeitgeber). Es gibt + dort keine Adresse, aus der man das Haus lesen koennte -- die + Regel weiss es. */ + s.haus || null); /* Haupt-Gegenueber und Serienteilnehmer -- dieselbe Regel wie beim einzelnen Termin: Die Liste ist die vollstaendige Antwort. */ for (const pid of new Set([...(s.teilnehmer_id ? [s.teilnehmer_id] : []), ...serienLeute])) { @@ -432,12 +437,12 @@ export function serieAnlegen(aus, person, teilnehmer = null) { const { lastInsertRowid } = d.prepare(` INSERT INTO termin_serien (titel, beschreibung, art, takt, start_tag, uhrzeit, ende_tag, dauer_min, ort, - creator_id, teilnehmer_id, teilnehmer_extern, aktiv, erstellt, erstellt_von) - VALUES (?,?,?,?,?,?,?,?,?,?,?,?,1,?,?)`).run( + creator_id, teilnehmer_id, teilnehmer_extern, aktiv, erstellt, erstellt_von, haus) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?)`).run( aus.titel, aus.beschreibung ?? null, aus.art ?? "termin", aus.takt, aus.start_tag, aus.uhrzeit, aus.ende_tag ?? null, aus.dauer_min ?? 30, aus.ort ?? null, aus.creator_id ?? null, aus.teilnehmer_id ?? null, aus.teilnehmer_extern ?? null, - jetztStempel(), person.id); + jetztStempel(), person.id, person.haus || null); const id = Number(lastInsertRowid); diff --git a/server/workspace-treffchat.js b/server/workspace-treffchat.js index db34a1b3..d0f4fec5 100644 --- a/server/workspace-treffchat.js +++ b/server/workspace-treffchat.js @@ -218,8 +218,20 @@ export function treffRaumId() { .get(TREFF_KATEGORIE); if (da) return da.id; - d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von) - VALUES ('kanal', ?, ?, ?, NULL)`) + /* DER TREFF GEHOERT ZU TEAM DOGI -- fest und nicht aus einer + Adresse abgeleitet (24.09.2026). Diese Funktion laeuft OHNE + Mensch davor: Sie legt den Raum beim ersten Bedarf an, notfalls + aus einem Zeitgeber heraus. `erstellt_von` steht deshalb auf + NULL, und ein `req.person.haus` gibt es hier nicht. + + Dass er ins Team-Haus gehoert, ist keine Annahme, sondern + Filipes Entscheidung vom 11.09.2026: „das soll keine app fuer + sich sein, das soll in der crew seite adaptiert werden." + Nachgemessen: Im Raum „Das Rudel" sitzen vier Gaeste, die rechte + und die linke Hand, vier Modis und DogFather -- alle aus dem + Team-Haus, keiner aus der Agentur. */ + d.prepare(`INSERT INTO chat_raeume (art, name, kategorie, erstellt, erstellt_von, haus) + VALUES ('kanal', ?, ?, ?, NULL, 'crew')`) .run(TREFF_NAME, TREFF_KATEGORIE, jetzt()); const id = d.prepare("SELECT last_insert_rowid() AS id").get().id; console.log(`[treff] Chat-Raum angelegt (Nummer ${id}).`); diff --git a/server/workspace-wissen.js b/server/workspace-wissen.js index f8839914..d4378d1f 100644 --- a/server/workspace-wissen.js +++ b/server/workspace-wissen.js @@ -145,6 +145,29 @@ function angemeldet(req, res, next) { const person = sitzungLesen(req); if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" }); req.person = person; + /* ==== DIE WISSENSABLAGE WOHNT IM AGENTURHAUS (24.09.2026) ======== + + Filipe, zur Frage, wohin die neun Dokumente gehoeren: "diese + komplette kachel soll nur im workspace sein." + + DIE KACHEL IST DRUEBEN SCHON WEG (siehe MODI_BEREICHE in + workspace.js). Das allein ist aber nur eine Bitte: `wissen.html` + und `/workspace/api/wissen` kann jeder aufrufen, der die Adresse + einmal gesehen hat -- und wer die Seite offen hatte, als die + Kachel noch da war, hat sie im Verlauf stehen. + + GENAU DIESE LUECKE HAT DAS HAUS SCHON ZWEIMAL GEHABT: Am 10.09. + die Zugangswand, am 21.09. `crew-haus.css` -- beide Male lieferte + express.static eine Datei aus, die "eigentlich" woanders hingehoert, + weil jemand ihren Namen nannte. Eine Absicht im Kommentar ist keine + Schranke. + + 404 UND KEINE 403: "Gibt es hier nicht" ist die Wahrheit -- die + Ablage gehoert zum anderen Haus. Ein "verboten" wuerde sagen, dass + es sie gibt und man nur nicht darf. */ + if (person.haus === "crew") { + return res.status(404).json({ fehler: "nicht_gefunden" }); + } next(); } function nurDogFather(req, res, next) { diff --git a/server/workspace.js b/server/workspace.js index b878140e..13d72c3d 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -36,10 +36,12 @@ import { hilfeTabellen } from "./hilfe-tabellen.js"; import { supportTabellen } from "./support-tabellen.js"; import { unterstuetzungTabellen } from "./unterstuetzung-tabellen.js"; import { sitzungPasstZurAdresse, istOhneModiAdresse, istCrewAdresse, istPruefAdresse, AUSSEN_ROLLEN, - TEAM_DOGI_ROLLEN, CREW_ADRESSE } from "./crew-adresse.js"; + TEAM_DOGI_ROLLEN, CREW_ADRESSE, WORKSPACE_ADRESSE, AGENTUR_ROLLEN, BEIDE_HAEUSER_ROLLEN, + hausVonRolle, rollenImHaus, nurAnderesHaus } from "./crew-adresse.js"; /* Weitergereicht, damit die Fachmodule sie wie alles andere aus workspace.js beziehen und nicht wissen muessen, wo sie wohnt. */ -export { TEAM_DOGI_ROLLEN }; +export { TEAM_DOGI_ROLLEN, AGENTUR_ROLLEN, BEIDE_HAEUSER_ROLLEN, + hausVonRolle, rollenImHaus, nurAnderesHaus }; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -580,8 +582,16 @@ export function darfAnlegen(person) { Quelle sowohl fuer die Pruefung in der Route als auch fuer die Auswahl in der Oberflaeche -- deshalb verschwinden die anderen Rollen dort von selbst, statt eine Absage zu bringen. */ - if (person.haus !== "crew") return alle; - return alle.filter((r) => HAUS_TEAM_ROLLEN.has(r)); + /* UND SEIT 24.09.2026 GILT DASSELBE ANDERSHERUM. Bis dahin stand + hier „auf der Agenturadresse alles" -- DogFather konnte dort also + einen Modi anlegen, der danach im Team-Haus auftauchte, ohne dass + er die Seite je gesehen haette. Ein Umbau auf der einen Seite darf + auf der anderen nichts bewirken; das Anlegen einer Person ist + genau so ein Umbau. */ + const haus = person.haus; + if (haus !== "crew" && haus !== "agentur") return alle; + const hier = rollenImHaus(haus); + return alle.filter((r) => hier.has(r)); } /** Wer darf die Rolle einer Person aendern, die schon da ist? @@ -744,8 +754,19 @@ export function ohneDogFather(praefix, spalten = ["creator_id", "erstellt_von"]) Dogi": Kaeme morgen eine sechste Rolle dazu, waere sie mit einem `ausser` automatisch in der Agentur, ohne dass jemand darueber nachgedacht hat. Eine Aufzaehlung zwingt zur Entscheidung -- dieselbe - Ueberlegung wie bei OHNE_MODI_HOSTS in crew-adresse.js. */ -const AGENTUR_ROLLEN = new Set(["spicy", "manager", "scout", "creator"]); + Ueberlegung wie bei OHNE_MODI_HOSTS in crew-adresse.js. + + SEIT 24.09.2026 STEHT SIE IN crew-adresse.js, nicht mehr hier. + Beim Bau der Haustrennung habe ich dort eine zweite, wortgleiche + Menge angelegt und es erst gemerkt, als Node den Doppelnamen + ablehnte -- also nur, weil zufaellig derselbe Name gewaehlt war. + Haette ich sie `AGENTUR` genannt, staenden jetzt zwei Listen da, die + beim naechsten Rollenwechsel auseinanderlaufen. Genau die Bauart, + die in diesem Haus schon viermal Daten gekostet hat. + + Sie wohnt jetzt bei TEAM_DOGI_ROLLEN und AUSSEN_ROLLEN, wo die + anderen beiden Haus-Mengen stehen -- crew-adresse.js hat keine + eigenen Importe und ist damit der einzige Ort ohne Kreisgefahr. */ /* Die gemeinsame Bauweise beider Filter. Sie stand bis zum 10.09.2026 nur einmal da, in ohneTeamDogi -- und beim zweiten Haus waere sie @@ -992,9 +1013,21 @@ const MODI_BEREICHE = [ { gruppe: "Für dich", gruppeUnter: "Deine Seite im Team", name: "Mein Steckbrief", unter: "Dein Bild und deine Kanäle", zeichen: "steckbrief", ton: 10, ziel: "steckbrief.html", szene: "halle" }, - { gruppe: "Für dich", gruppeUnter: "Deine Seite im Team", - name: "Wissen", unter: "Nachschlagen statt nachfragen", zeichen: "wissen", - ton: 9, ziel: "wissen.html", szene: "arena" }, + /* ==== DIE WISSENS-KACHEL IST HIER RAUS (24.09.2026) ============= + + Filipe, zur Frage, wohin die neun Dokumente gehören: „diese + komplette kachel soll nur im workspace sein." + + NICHT NUR DIE DOKUMENTE, DIE KACHEL. Die Ablage enthält die + Schulungen von Spicy Media („SPICY MEDIA 01–04"), OBS, TikTok + LIVE Studio — Material für Creator, nicht für die Moderation. + Eine Kachel stehenzulassen, hinter der nichts mehr liegt, wäre + eine Tür in einen leeren Raum: Man drückt sie jeden Tag einmal + und lernt nur, dass sie nichts tut. + + UND DIE ROUTE IST MITGESICHERT (`wissenNurAgentur` weiter unten). + Eine fehlende Kachel ist eine Bitte, keine Schranke — die Adresse + `wissen.html` kann jeder tippen, der sie einmal gesehen hat. */ ]; /** @@ -2698,11 +2731,6 @@ const TEAM_GRUPPE = { die sie ersetzt. ===================================================================== */ export function zusatzBereicheFuer(person) { - /* Auf der Team-Adresse liefert bereicheFuer() ohnehin die Kacheln des - Teams. Eine Tuer zu dem Haus, in dem man schon steht, waere ein - Knopf, der nichts tut. */ - if (person?.haus === "crew") return []; - /* NUR DOGFATHER UND VANVAN -- also `admin`. Der erste Entwurf gab die Tuer auch an TEAM_DOGI_ROLLEN (die @@ -2719,15 +2747,46 @@ export function zusatzBereicheFuer(person) { `admin` ist der einzige Fall, der wirklich zwei Haeuser hat. */ if (person?.rolle !== "admin") return []; + /* ==== DIE TUER GEHT JETZT IN BEIDE RICHTUNGEN (24.09.2026) ======== + + Filipe: "es soll nur fuer dogfather eine kachel geben wo er mit + einem einfachen klick von der einen auf die anderen seite kommt + aber sonst garnichts." + + Bis heute stand oben `if (person?.haus === "crew") return [];` -- + die Tuer gab es also nur im Agenturhaus. Auf der Team-Adresse gab + es keinen Weg zurueck; man musste die Adresse tippen. Am 21.09. + war das richtig, weil von dort aus ohnehin alles zusammenhing. + Seit die Haeuser hart getrennt sind, ist es eine Sackgasse -- und + zwar genau die Sorte, die derselbe Absatz weiter oben fuer die + andere Richtung schon als Fehler benennt. + + EINE KACHEL, NICHT ZWEI LISTEN: Welches Haus gerade gemeint ist, + entscheidet `person.haus`; Name, Ziel und Unterzeile leiten sich + daraus ab. Zwei getrennte Bloecke waeren zwei Fassungen desselben + Gedankens, und die naechste Aenderung ginge in nur eine davon. + + ZUR ANMELDUNG: Die beiden Adressen haben getrennte Sitzungen (der + Keks ist hostgebunden, `path=/workspace`, ohne `domain`). Beim + ERSTEN Klick meldet man sich drueben einmal an -- danach haelt es + 180 Tage. Den Keks auf `.dogfather-universe.com` zu setzen, um das + zu sparen, waere falsch: Dann ginge der Team-Keks bei jedem Aufruf + an die Agenturadresse mit, also genau die Verbindung, die hier + gerade getrennt wird -- und die verborgene Adresse haette eine + zweite Spur. */ + const nachCrew = person.haus !== "crew"; + return [{ - gruppe: "Team Dogi", + gruppe: nachCrew ? "Team Dogi" : "Creator Workspace", gruppeUnter: "Das andere Haus \u2014 eigene Seite, eigene Daten", - name: "Zu Team Dogi", - unter: "Das Rudel, das Team und die Moderation \u2014 auf ihrer eigenen Adresse", + name: nachCrew ? "Zu Team Dogi" : "Zum Creator Workspace", + unter: nachCrew + ? "Das Rudel, das Team und die Moderation \u2014 auf ihrer eigenen Adresse" + : "Creator, Scouts und die Agentur \u2014 auf ihrer eigenen Adresse", zeichen: "hinaus", ton: 40, szene: "portal", - ziel: CREW_ADRESSE + "/workspace/start.html", + ziel: (nachCrew ? CREW_ADRESSE : WORKSPACE_ADRESSE) + "/workspace/start.html", /* AUSSEN heisst: Diese Kachel fuehrt aus dem Haus hinaus. Ohne das Merkmal wuerde nurOffeneKacheln() sie wegfiltern -- es baut aus dem Ziel einen Pfad ("/workspace/" + ziel) und fragt die @@ -2938,6 +2997,262 @@ function checkListeErweitern(d, tabelle, spalte, marker, werte, jetztStempel) { } } +/* ===================================================================== + DAS HAUS DER ALTEN ZEILEN NACHTRAGEN (24.09.2026) + ===================================================================== + + Alles, was vor der Trennung entstanden ist, hat kein Haus. Diese + Funktion trägt es nach -- EINMAL, und danach findet sie nichts mehr + zu tun (sie fragt nur nach `haus IS NULL`). + + SIE LEITET AB, SIE RÄT NICHT. Für jede Zeile wird gesammelt, welche + MENSCHEN daran hängen, und aus deren Rollen das Haus bestimmt: + + * genau ein Haus unter den Beteiligten -> das ist es + * beide Häuser unter den Beteiligten -> KONFLIKT, bleibt leer + * nur DogFather (oder niemand) -> unbestimmbar, bleibt leer + + DOGFATHER ZÄHLT BEI DER BESTIMMUNG NICHT MIT, und das ist der ganze + Trick: Er wohnt in beiden Häusern. Zählte er mit, wäre jede zweite + Zeile ein "Konflikt". Er ist kein Beleg -- die anderen sind es. + + WAS LEER BLEIBT, WIRD GENANNT -- mit Tabelle, Nummer und Titel. Eine + Nachtragung, die schweigend eine Voreinstellung setzt, verschiebt + Termine zwischen Häusern, ohne dass es jemand merkt. Gemessen am + echten Stand sind es genau ZWEI Termine; die kann man von Hand + entscheiden. Eine Voreinstellung wäre für die 87 anderen richtig und + für diese zwei geraten -- und man wüsste nicht, welche zwei. + + BEI DEN EINTRÄGEN ENTSCHEIDET DER BEREICH und nicht der Ersteller. + `regeln`, `anschlag`, `highlight` sind Bretter von Team Dogi, + `agentur` gehört der Agentur -- das steht fest, unabhängig davon, + wer gerade etwas hineingeschrieben hat. Der Ersteller ist bei 20 von + 26 Einträgen DogFather und damit kein Beleg; der Bereich ist einer. + ===================================================================== */ +export function haeuserNachtragen(d) { + const meldung = { gesetzt: 0, konflikt: [], offen: [] }; + try { + /* Wer gehört wohin -- einmal gelesen, nicht je Zeile. Bei 19 + Personen und 250 Zeilen wären das sonst 250 Abfragen für eine + Antwort, die sich während des Laufs nicht ändert. */ + const hausJePerson = new Map(); + for (const p of d.prepare("SELECT id, rolle FROM personen").all()) { + hausJePerson.set(p.id, hausVonRolle(p.rolle)); + } + /* Aus einer Liste von Personennummern das Haus -- oder warum nicht. + "beide" (DogFather) und unbekannte Nummern zählen nicht mit. */ + const hausAus = (ids) => { + const gefunden = new Set(); + for (const i of ids) { + const h = hausJePerson.get(i); + if (h === "crew" || h === "agentur") gefunden.add(h); + } + if (gefunden.size === 1) return [...gefunden][0]; + return gefunden.size > 1 ? "konflikt" : null; + }; + + /* Je Tabelle: wie komme ich an die beteiligten Menschen? + Als Tafel und nicht als sieben Blöcke -- ein achter Eintrag ist + dann eine Zeile und keine Kopie. */ + const TAFEL = [ + ["chat_raeume", (r) => d.prepare( + "SELECT person_id AS p FROM chat_teilnehmer WHERE raum_id = ?") + .all(r.id).map((z) => z.p), "name"], + ["termine", (r) => [ + ...d.prepare("SELECT person_id AS p FROM termin_teilnehmer WHERE termin_id = ?") + .all(r.id).map((z) => z.p), + r.creator_id, r.teilnehmer_id, r.erstellt_von, + ].filter((x) => x != null), "titel"], + ["aufgaben", (r) => [r.creator_id, r.verantwortlich_id, r.erstellt_von] + .filter((x) => x != null), "titel"], + /* DIE DATEI ERBT VON DEM, WORAN SIE HAENGT. Ein Titelbild, das + DogFather an einen Eintrag von Team Dogi gehaengt hat, hat + ueber die Personen keinen Beleg -- ueber den Eintrag sehr + wohl. Gemessen: drei von acht Dateien waeren sonst offen + geblieben, obwohl ihr Haus eindeutig feststeht. + Die Eintraege werden WEITER UNTEN gesetzt; deshalb liest diese + Zeile `eintraege.haus` erst, wenn es schon dasteht -- die + Reihenfolge in dieser Funktion ist Absicht, nicht Zufall. */ + ["dateien", (r) => [r.creator_id, r.hochgeladen_von] + .filter((x) => x != null), "name_original", + (r) => hausVomEltern(d, r)], + /* DIE WISSENSABLAGE GEHOERT GANZ DER AGENTUR (24.09.2026, + Entscheidung Filipe). Deshalb steht hier ein fester Rückfall + und keine Ableitung: Alle neun Dokumente sind von DogFather + hochgeladen, an ihnen hängt kein zweiter Mensch — über die + Personen gäbe es NIE einen Beleg, und die Zeilen blieben auf + Dauer offen. Der Rückfall beantwortet das ein für alle Mal. + Käme morgen ein Dokument von einem Creator, zeigte die + Ableitung darüber ohnehin schon auf die Agentur. */ + ["wissen", (r) => [r.erstellt_von].filter((x) => x != null), "titel", + () => "agentur"], + ["material", (r) => [r.von_id].filter((x) => x != null), "dateiname"], + ["termin_serien", (r) => [r.creator_id, r.teilnehmer_id, r.erstellt_von] + .filter((x) => x != null), "titel"], + ]; + + /* DIE EINTRAEGE ZUERST -- die Dateien erben von ihnen. Stuenden + sie wie vorher danach, liefe die Vererbung ins Leere, und drei + Dateien blieben offen, obwohl der Beleg dagewesen waere. Eine + Reihenfolge, die man einer Schleife nicht ansieht: deshalb steht + sie hier als Satz und nicht nur als Position. */ + eintraegeNachHaus(d, meldung); + restzeilenZuordnen(d, meldung); + + for (const [tabelle, leute, titelSpalte, ausEltern] of TAFEL) { + let spalten; + try { + spalten = d.prepare(`PRAGMA table_info(${tabelle})`).all().map((x) => x.name); + } catch { continue; } + if (!spalten.includes("haus")) continue; + const schreib = d.prepare(`UPDATE ${tabelle} SET haus = ? WHERE id = ?`); + for (const zeile of d.prepare( + `SELECT * FROM ${tabelle} WHERE haus IS NULL`).all()) { + /* ZUERST DIE MENSCHEN, DANN DIE ELTERN. Ein Mensch ist der + unmittelbarere Beleg; erst wenn keiner etwas sagt, wird + gefragt, woran die Zeile haengt. */ + const h = hausAus(leute(zeile)) || (ausEltern ? ausEltern(zeile) : null); + const wer = `${tabelle}#${zeile.id} „${zeile[titelSpalte] || "ohne Titel"}"`; + if (h === "crew" || h === "agentur") { schreib.run(h, zeile.id); meldung.gesetzt++; } + else if (h === "konflikt") meldung.konflikt.push(wer); + else meldung.offen.push(wer); + } + } + + if (meldung.gesetzt) { + console.log(`[workspace] Haus nachgetragen: ${meldung.gesetzt} Zeilen.`); + } + /* DER DRITTE AUSGANG. Nicht "in Ordnung", nicht "kaputt", sondern + "konnte nicht nachsehen" -- mit Grund und mit Namen, damit ein + Mensch entscheiden kann. Eine Zeile ohne Haus verschwindet nicht: + Sie ist weiterhin für DogFather sichtbar und für sonst niemanden + (siehe hausWo). */ + for (const x of meldung.konflikt) { + console.log(`[workspace] Haus UNKLAR (beide Häuser beteiligt): ${x}`); + } + for (const x of meldung.offen) { + console.log(`[workspace] Haus OFFEN (nur DogFather beteiligt): ${x}`); + } + } catch (fehler) { + console.error("[workspace] Häuser nachtragen:", fehler?.message); + } + return meldung; +} + +/** Das Haus eines Eintrags -- aus seinem BEREICH, nicht aus dem + * Ersteller. `regeln`, `anschlag`, `highlight` sind Bretter von Team + * Dogi, `agentur` gehoert der Agentur; das steht fest, unabhaengig + * davon, wer gerade hineingeschrieben hat. Bei 20 von 26 Eintraegen + * ist der Ersteller DogFather und damit kein Beleg. */ +function eintraegeNachHaus(d, meldung) { + try { + const spalten = d.prepare("PRAGMA table_info(eintraege)").all().map((x) => x.name); + if (!spalten.includes("haus")) return; + const schreib = d.prepare("UPDATE eintraege SET haus = ? WHERE id = ?"); + for (const zeile of d.prepare( + "SELECT id, bereich FROM eintraege WHERE haus IS NULL").all()) { + schreib.run(MODI_BEREICHE_ERLAUBT.has(zeile.bereich) ? "crew" : "agentur", zeile.id); + meldung.gesetzt++; + } + } catch (f) { console.error("[workspace] Häuser (Einträge):", f?.message); } +} + +/* ===================================================================== + DIE VIER ZEILEN, DIE KEIN BELEG ERREICHT (24.09.2026) + ===================================================================== + + Nach allen Ableitungen blieben am echten Stand genau vier Zeilen + ohne Haus: drei Termine und ein Bild. An ihnen hängt nur DogFather, + und er wohnt in beiden Häusern — es GIBT dort nichts abzuleiten. + + WARUM SIE NAMENTLICH DASTEHEN UND NICHT ÜBER EINE REGEL LAUFEN. + Eine Regel bräuchte eine Annahme („was DogFather allein einträgt, + ist Team Dogi"), und die wäre für „Manager Meeting" und „Call mit + Cigdem" (Cigdem ist Spicy Media) falsch. Eine falsche Annahme, die + auf 250 Zeilen läuft, verschiebt Dinge, die niemand sucht. Vier + benannte Zeilen kann man nachlesen und widerrufen. + + UND SIE SIND DOPPELT GESICHERT: Nummer UND Titel müssen passen. + Trifft eine Datenbank die Nummer mit einem anderen Titel (eine + Kopie, ein anderer Stand), passiert nichts — statt dass ein + fremder Termin stillschweigend das Haus wechselt. + + Läuft genau einmal: danach ist `haus` gesetzt und die Bedingung + `haus IS NULL` findet nichts mehr. + ===================================================================== */ +function restzeilenZuordnen(d, meldung) { + /* ==== DER EINE KANAL, IN DEM BEIDE HAEUSER SASSEN (24.09.2026) ==== + + „Chat-Moderation": DogFather, die rechte und die linke Hand, vier + Modis -- und zwei Scouts. Der einzige Raum im ganzen Haus, der + ueber die Grenze ging (0 von 86 Terminen, 0 von 7 uebrigen + Raeumen taten das). + + Filipes Entscheidung: „Scouts raus, Kanal bleibt Team Dogi." + + GEMESSEN, BEVOR ENTSCHIEDEN WURDE: Die sieben Nachrichten in + diesem Kanal sind ALLE vom Team (VanVan 4x, Marina 2x, Diene 1x). + Die beiden Scouts haben dort nie geschrieben; sie kamen am + 11.09.2026 in derselben Sekunde hinein wie DogFather und VanVan, + also beim automatischen Anlegen -- bevor KANAL_ROLLEN auf die + Team-Rollen eingeschraenkt wurde. Es geht damit nichts verloren. + + `raus_am` UND NICHT LOESCHEN: Die Zeile bleibt stehen und traegt + ein Datum. Wer spaeter fragt, warum jemand nicht mehr im Kanal + ist, findet eine Antwort statt einer Luecke. */ + try { + const raum = d.prepare( + "SELECT id, haus FROM chat_raeume WHERE id = 3 AND name = 'Chat-Moderation'").get(); + if (raum && !raum.haus) { + const fremd = [...nurAnderesHaus("crew")].map((r) => `'${r}'`).join(", "); + const n = d.prepare( + `UPDATE chat_teilnehmer SET raus_am = ? + WHERE raum_id = 3 AND raus_am IS NULL + AND person_id IN (SELECT id FROM personen WHERE rolle IN (${fremd}))`) + .run(new Date().toISOString()).changes; + d.prepare("UPDATE chat_raeume SET haus = 'crew' WHERE id = 3").run(); + meldung.gesetzt++; + console.log(`[workspace] Kanal „Chat-Moderation" -> Team Dogi, ${n} aus dem anderen Haus herausgenommen.`); + } + } catch (f) { console.error("[workspace] Kanal 3:", f?.message); } + + const ZUORDNUNG = [ + ["termine", 3, "Manager Meeting", "agentur"], + ["termine", 5, "GrundRegeln-Schulung", "crew"], + ["termine", 179, "Call mit Cigdem", "agentur"], + ["material", 1, "community.png", "crew"], + ]; + for (const [tabelle, id, titel, haus] of ZUORDNUNG) { + try { + const spalte = tabelle === "material" ? "dateiname" : "titel"; + const n = d.prepare( + `UPDATE ${tabelle} SET haus = ? WHERE id = ? AND ${spalte} = ? AND haus IS NULL`) + .run(haus, id, titel).changes; + if (n) { + meldung.gesetzt += n; + console.log(`[workspace] Haus von Hand gesetzt: ${tabelle}#${id} „${titel}" -> ${haus}`); + } + } catch (f) { + console.error(`[workspace] Restzeile ${tabelle}#${id}:`, f?.message); + } + } +} + +/** Woran haengt diese Datei -- und welches Haus hat das? */ +function hausVomEltern(d, zeile) { + try { + if (zeile.eintrag_id) { + const e = d.prepare("SELECT haus FROM eintraege WHERE id = ?").get(zeile.eintrag_id); + if (e?.haus) return e.haus; + } + if (zeile.aufgabe_id) { + const a = d.prepare("SELECT haus FROM aufgaben WHERE id = ?").get(zeile.aufgabe_id); + if (a?.haus) return a.haus; + } + } catch { /* kein Beleg ist kein Fehler -- dann bleibt es offen */ } + return null; +} + function umstellungen(d) { /* MIT SEKUNDEN, nicht nur mit Minuten (07.09.2026). @@ -3719,6 +4034,46 @@ function umstellungen(d) { einer Tabelle. Ein REFERENCES darauf gaebe es nicht; geprueft wird beim Schreiben, an der Stelle, die den Katalog kennt. */ ["aufgaben", "aus_punkt", "TEXT"], + + /* ==== ZU WELCHEM HAUS GEHOERT DIESE ZEILE? (24.09.2026) ========== + + Filipe: „wenn ich bei der einen was mache soll nichts bei der + anderen passieren." + + WARUM EINE SPALTE UND NICHT EINE RECHNUNG. Bis heute wurde das + Haus aus den beteiligten Personen ABGELEITET (ohneAgentur / + ohneTeamDogi). Das trägt für jede Zeile, an der mindestens ein + Mensch aus genau einem Haus hängt -- und es trägt NICHT für die + Zeilen, an denen nur DogFather hängt. Er wohnt in beiden Häusern; + seine Zeilen standen deshalb in beiden. Gemessen am echten Stand: + 57 von 89 Terminen sind von ihm, 20 von 26 Einträgen. + + Eine Rechnung, die in 30 % der Fälle zwei Antworten gibt, ist + keine Grenze. Deshalb steht das Haus jetzt DA, geschrieben in dem + Moment, in dem die Zeile entsteht -- und zwar aus `person.haus`, + also aus der Adresse, auf der gearbeitet wurde. + + NULL IST ERLAUBT und heisst „noch nicht nachgetragen". Die + Nachtragung (haeuserNachtragen) füllt es aus dem vorhandenen + Bestand; was sie nicht eindeutig bestimmen kann, sagt sie laut, + statt zu raten. + + ALTER TABLE ADD COLUMN UND KEIN TABELLENUMBAU: Am 11.09.2026 hat + ein Neubau der Tabelle `eintraege` drei Spalten samt Inhalt + verschluckt, ohne Fehlermeldung. ADD COLUMN kann das nicht. */ + ["chat_raeume", "haus", "TEXT"], + ["termine", "haus", "TEXT"], + ["aufgaben", "haus", "TEXT"], + ["eintraege", "haus", "TEXT"], + ["dateien", "haus", "TEXT"], + ["wissen", "haus", "TEXT"], + ["material", "haus", "TEXT"], + /* Die WIEDERHOLUNGSREGEL braucht es auch: Der Nachfueller erzeugt + daraus Termine, und die muessen wissen, wohin sie gehoeren. Ohne + sie erbten sie `null` -- und ein `null` ist in beiden Haeusern + sichtbar (siehe hausWo). Eine woechentliche Serie haette die + Trennung damit Woche fuer Woche neu unterlaufen. */ + ["termin_serien", "haus", "TEXT"], ]) { try { const vorhanden = d.prepare(`PRAGMA table_info(${tabelle})`).all().map((s) => s.name); @@ -3731,6 +4086,11 @@ function umstellungen(d) { } } + /* Die Häuser nachtragen -- direkt nachdem die Spalte da ist. Steht + der Aufruf weiter unten, läuft er beim allerersten Start auf eine + Tabelle ohne Spalte und meldet einen Fehler, den niemand braucht. */ + haeuserNachtragen(d); + /* Der Nachfüller fragt bei jedem Lauf "welche Ausprägungen dieser Serie gibt es schon?". Ohne diesen Verbund-Index liest SQLite dafür die ganze Termintabelle. Er steht hier unten und nicht oben im @@ -5651,8 +6011,38 @@ export function sitzungLesen(req) { nicht, was er darf -- genau wie die Sicht eines anderen (sichtPerson) das auch nicht tut. Wer hier etwas anderes einbaut, macht aus einer Anzeigefrage eine Rechtefrage. */ + /* ==== PRUEFADRESSEN BEKOMMEN KEIN HAUS (24.09.2026) =========== + + Hier stand `istCrewAdresse(...) ? "crew" : "agentur"` -- also + bekam auch `127.0.0.1` ein Haus, naemlich "agentur". Das war + harmlos, solange auf der Agenturseite ohnehin nicht gefiltert + wurde. Seit die Grenze in BEIDE Richtungen gilt, ist es das + Gegenteil von harmlos: + + GEMESSEN am selben Nachmittag: `pruef-chat-kanaele` fiel mit 13 + Fehlschlaegen um. Nicht weil etwas kaputt war, sondern weil + DogFather auf 127.0.0.1 ploetzlich im Agenturhaus sass und + deshalb keinen Modi mehr in einen Kanal einladen durfte. Die + naechsten Pruefungen waeren still gruen geblieben und haetten + nichts mehr gemessen. + + GENAU DAVOR WARNT crew-adresse.js DREIMAL (Falle 2 in ihrem + Kopf): "Ein gruener Lauf, der nichts mehr prueft, ist schlimmer + als ein roter." Ich bin trotzdem hineingelaufen -- der Kommentar + stand da, gelesen habe ich ihn erst, als es rot wurde. + + `null` IST DER DRITTE WERT, und alle Hausfunktionen kennen ihn + schon: nurHaus, hausBedingung, hausWo und ohneAnderesHaus + filtern nur bei "crew" oder "agentur". Eine Pruefadresse + verhaelt sich damit exakt wie vor dem Umbau. + + EINE ERFUNDENE ADRESSE BLEIBT AGENTUR. istPruefAdresse zaehlt + localhost und 127.* auf -- nicht "alles, was nicht crew ist". + `crew.boese.de` faellt also weiterhin ins Agenturhaus und + oeffnet nichts Drittes. */ const haus = istCrewAdresse(req.get?.("host")) ? "crew" - : "agentur"; + : istPruefAdresse(req.get?.("host")) ? null + : "agentur"; /* DER AUSSCHLUSS WIRKT HIER -- an derselben Stelle wie alles andere (11.09.2026). @@ -7275,9 +7665,44 @@ export function termineSichtbar(person, praefix = "t") { Adresse mit dem Team, und die Termine der Agentur gehen sie dort nichts an. Geprueft wird die ROLLE und nicht der Name -- ein Name waere die Stelle, an der es beim naechsten Menschen bricht. */ - if (person.haus === "crew" && person.rolle !== "admin") { + /* ==== DIE AUSNAHME FUER DOGFATHER IST UMGEBAUT (24.09.2026) ======= + + Hier stand `person.rolle !== "admin"` -- also: Jeder auf der + Team-Adresse sieht nur Team-Termine, DogFather aber ALLE, quer + ueber beide Haeuser. Das war sein Wunsch vom 11.09.2026, mit + einer Begruendung, die weiterhin richtig ist: Wer die Haelfte + seines Tages nicht sieht, legt einen Call auf eine Zeit, in der + er schon woanders sitzt. + + Am 24.09.2026 hat er die Trennung verlangt -- und auf die + Nachfrage, was mit dieser Ausnahme geschieht, entschieden: + „Getrennt, aber ‚belegt' bleibt sichtbar." + + DAS IST NICHT DERSELBE WUNSCH ZWEIMAL, SONDERN ZWEI VERSCHIEDENE + ANSPRUECHE AN DIESELBE ANZEIGE: Der INHALT gehoert getrennt (kein + fremder Titel, kein fremder Teilnehmer auf der falschen Seite), + die ZEIT nicht (sonst doppelt man sich). Beides geht: Hier wird + hart getrennt, und die Kalenderroute legt die fremden Zeiten + zusaetzlich als titellose „belegt"-Bloecke daneben. Nur fuer die + DogFather-Rolle, wie 2026-09-11 festgelegt -- VanVan sieht + weiterhin nichts von der anderen Seite. + + ZWEI BEDINGUNGEN UND NICHT EINE: `hausWo` fragt die Spalte (sie + weiss es auch bei einem Termin, an dem nur DogFather haengt -- + gemessen 57 von 89), `ohneAnderesHaus` fragt die Beteiligten (sie + greift auch dort, wo noch kein Haus eingetragen ist). Wer nur eine + nimmt, hat je nach Zeile eine Luecke. + + SERIEN HABEN KEINE HAUS-SPALTE (`praefix === "s"` zeigt auf + termin_serien). Dort bleibt es bei der Ableitung ueber die + Beteiligten -- eine Bedingung auf eine Spalte, die es nicht gibt, + waere ein SQL-Fehler und damit eine leere Seite. */ + const haus = person.haus; + if (haus === "crew" || haus === "agentur") { + const spalten = ["creator_id", "teilnehmer_id", "erstellt_von"]; + const ausSpalte = p === "t" ? ` AND ${hausWo(person, p)}` : ""; return { - wo: `(${eigen}) AND ${ohneAgentur(p, ["creator_id", "teilnehmer_id", "erstellt_von"])}`, + wo: `(${eigen})${ausSpalte} AND ${ohneAnderesHaus(p, person, spalten)}`, werte, }; } @@ -7627,12 +8052,36 @@ export function protokollLesen(anzahl = 20) { * -- und das sagt bei der Community ueberall Nein. Zwei verschiedene * Fragen, zwei verschiedene Funktionen; wer sie in eine legt, kann * spaeter nicht mehr sagen, welche gerade zugeschlagen hat. */ -const HAUS_TEAM_ROLLEN = new Set(["admin", "hand", "linke", "modi", "gast"]); +/* BIS ZUM 24.09.2026 STAND HIER EINE EIGENE MENGE + (`admin, hand, linke, modi, gast`). Sie war wortgleich mit dem, was + `rollenImHaus("crew")` liefert -- und damit die zweite Liste, die + beim naechsten Rollenwechsel haette nachgezogen werden muessen. Sie + ist weg; gefragt wird jetzt die eine Menge in crew-adresse.js. */ +/* ===================================================================== + DIE GRENZE GILT IN BEIDE RICHTUNGEN (24.09.2026) + + Bis heute stand hier `if (person?.haus !== "crew") return ids;` -- + also: Auf der Team-Adresse wird gefiltert, auf der Agenturadresse + NICHT. Das war richtig, solange „getrennt wird das Aussehen, nicht + der Bestand" galt. Filipe hat das am 24.09.2026 umgedreht: „wenn ich + bei der einen was mache soll nichts bei der anderen passieren." + + WAS DIE EINSEITIGKEIT KONKRET BEDEUTET HAT: DogFather arbeitet in + beiden Haeusern. Auf crew. sah er nur Team Dogi -- auf + workspace.dogfather-universe.com aber ALLES, Team Dogi eingerechnet. + In seiner Gespraechsliste standen dort die Raeume beider Haeuser + nebeneinander, ohne dass etwas sie unterschieden haette. + + EIN UNBEKANNTES HAUS FILTERT NICHT, und das ist Absicht: Auf einer + Pruefadresse setzt sitzungLesen() „agentur"; ein drittes Haus gibt + es nicht. Stuende hier ein `else` mit einer Annahme, waere die + naechste Adresse stillschweigend einem Haus zugeschlagen. */ function nurHaus(ids, person) { - if (person?.haus !== "crew") return ids; + const haus = person?.haus; + if (haus !== "crew" && haus !== "agentur") return ids; try { - const liste = [...HAUS_TEAM_ROLLEN].map((r) => `'${r}'`).join(", "); + const liste = [...rollenImHaus(haus)].map((r) => `'${r}'`).join(", "); const erlaubt = db().prepare( `SELECT id FROM personen WHERE rolle IN (${liste})`).all().map((z) => z.id); /* `null` heisst bei diesen Funktionen "alle". Sobald das Haus @@ -7664,11 +8113,48 @@ function nurHaus(ids, person) { EINE FUNKTION UND KEINE ZWEITE ROLLENLISTE: Wer den Ausdruck abschreibt, hat beim naechsten Rollenwechsel zwei Wahrheiten. */ export function hausBedingung(person, spalte = "rolle") { - if (person?.haus !== "crew") return ""; - const liste = [...HAUS_TEAM_ROLLEN].map((r) => `'${r}'`).join(", "); + const haus = person?.haus; + if (haus !== "crew" && haus !== "agentur") return ""; + const liste = [...rollenImHaus(haus)].map((r) => `'${r}'`).join(", "); return ` AND ${spalte} IN (${liste})`; } +/* ===================================================================== + DAS HAUS EINER ZEILE -- ALS SQL-BEDINGUNG (24.09.2026) + + Fuer die sieben Tabellen mit eigener `haus`-Spalte (chat_raeume, + termine, aufgaben, eintraege, dateien, wissen, material). + + WARUM DIE SPALTE UND NICHT DIE PERSONEN. Die Ableitung ueber die + Beteiligten (ohneAgentur / ohneTeamDogi) traegt fuer jede Zeile, an + der mindestens ein Mensch aus genau einem Haus haengt -- und sie + traegt NICHT fuer die Zeilen, an denen nur DogFather haengt. Gemessen + am echten Stand: 57 von 89 Terminen. Die standen in beiden Haeusern. + + `haus IS NULL` KOMMT MIT, und das ist eine bewusste Uebergangsregel: + Beim Nachtragen bleiben Zeilen offen, deren Haus sich aus dem + Bestand nicht belegen laesst (gemessen: 14 von 252). Wuerden sie + herausfallen, verschwaende die halbe Wissensablage, ohne dass jemand + einen Fehler saehe -- die unangenehmste Sorte Datenverlust. Sie + bleiben deshalb sichtbar, und `haeuserNachtragen` nennt jede einzeln + beim Namen, damit sie zugeordnet werden koennen. Ist die Liste leer, + hat diese Zeile keine Wirkung mehr. + + @param {string} praefix Tabellenkuerzel im SQL, z.B. "t" fuer termine + ===================================================================== */ +export function ohneAnderesHaus(praefix, person, spalten) { + const haus = person?.haus; + if (haus !== "crew" && haus !== "agentur") return "1=1"; + return ohneRollen(praefix, spalten, nurAnderesHaus(haus)); +} + +export function hausWo(person, praefix) { + const haus = person?.haus; + if (haus !== "crew" && haus !== "agentur") return "1=1"; + const p = praefix ? `${praefix}.` : ""; + return `(${p}haus = '${haus}' OR ${p}haus IS NULL)`; +} + /* EINE LISTE VON CREATOR-NUMMERN DARF NUR CREATOR ENTHALTEN (11.09.2026). Klingt selbstverstaendlich, war es nicht. `ohneVerborgene` schreibt diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 65e74f4d..91b81838 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,15 +16,15 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +