diff --git a/server/crew-adresse.js b/server/crew-adresse.js index d6a52ccd..720728c2 100644 --- a/server/crew-adresse.js +++ b/server/crew-adresse.js @@ -105,14 +105,36 @@ export const TEAM_DOGI_ROLLEN = new Set(["hand", "modi"]); bekommt nur, was ausdruecklich fuer sie da ist. */ export const AUSSEN_ROLLEN = new Set(["gast"]); -/* Die Adresse des Treffs. Eigene Wand, eigenes Haus -- und ausdruecklich - NICHT crew.: Diese Adresse ist absichtlich verborgen (kein Eintrag bei - Suchmaschinen, keine Rollennamen in ausgelieferten Dateien, Codes nur - persoenlich). Dutzende Zuschauer heben jede dieser Eigenschaften auf. - Nicht durch einen Fehler, sondern durch die Nutzung. */ -const TREFF_HOSTS = new Set(["treff.dogfather-universe.com"]); +/* DER TREFF HAT KEINE EIGENE ADRESSE (Entscheidung Filipe, 11.09.2026: + "das soll keine app fuer sich sein, das soll in der crew seite + adaptiert werden"). -/** Die Adresse der Modi-App. Feste Menge, kein Muster -- Falle 1. */ + Am Vormittag stand hier treff.dogfather-universe.com -- eine dritte + Tuer mit eigener Wand, eigenem Manifest, eigenen Buehnenbildern. Sie + ist wieder weg, und das gehoert aufgeschrieben, weil die Gruende + dafuer weiterhin gelten und beim naechsten Mal jemand dieselbe Idee + haben wird: + + WAS DAFUER SPRACH: Auf einem Ursprung laesst sich genau EINE App + installieren. Und zwischen einem Mitglied der Community und den + Daten der Agentur stand damit ein Schloss mehr. + + WAS DAGEGEN ENTSCHIEDEN HAT: Es waere ein drittes Haus gewesen -- + mit eigener Wand, eigenem Namen, eigener App neben Team Dogi. Der + Treff ist aber kein drittes Haus, sondern ein Raum in Dogis. + + WAS DER VERZICHT KOSTET, ehrlich benannt: Die Community steht jetzt + vor derselben Wand wie das Team und liest dort die Namen der drei + Team-Rollen (Filipes Entscheidung: vierte Kachel "Community" statt + gar keiner Kacheln). Und sie ist auf demselben Ursprung angemeldet + wie die Modis -- die Trennung haengt damit allein an der + Rechtetafel und an sitzungLesen(), nicht mehr zusaetzlich an der + Adresse. Das ist der Grund, warum pruef-treff seit heute NICHT + weniger prueft, sondern mehr: Was vorher die Adresse erledigt hat, + muss jetzt jede einzelne Regel halten. */ + +/** 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"]); /** Die echten Adressen, auf denen ein Modi nichts verloren hat. @@ -155,11 +177,6 @@ export function istPruefAdresse(hostKopfzeile) { || h === "::1" || h === "[::1]"; } -/** Ist das die Adresse des Treffs? */ -export function istTreffAdresse(hostKopfzeile) { - return TREFF_HOSTS.has(nurHost(hostKopfzeile)); -} - /** Gehoert dieser Pfad zum Workspace-Teil? Genau "/workspace" oder * alles darunter -- aber NICHT /workspaceXYZ (Falle 3). */ export function istWorkspacePfad(pfad) { @@ -197,9 +214,12 @@ export function istWorkspacePfad(pfad) { die jede Wand im Browser ansehen, keine mehr oeffnen -- sie blieben gruen und wuerden nichts mehr messen. Ein gruener Lauf, der nichts mehr prueft, ist schlimmer als ein roter. */ +/* EINE benannte Wand, seit der Treff keine eigene mehr hat. Die Tafel + bleibt trotzdem eine Tafel und wird nicht wieder zum Sonderfall: Sie + ist der Ort, an dem die naechste Wand eingetragen wird, ohne dass + jemand drei Bedingungen umbauen muss. */ const BENANNTE_WAENDE = [ { datei: "/workspace/crew-index.html", zuhause: istCrewAdresse }, - { datei: "/workspace/treff-index.html", zuhause: istTreffAdresse }, ]; /** Wird hier eine Wand verlangt, die zu einer ANDEREN Tuer gehoert? */ @@ -212,8 +232,7 @@ export function istFremdeWand(hostKopfzeile, pfad) { const wand = BENANNTE_WAENDE.find((w) => w.datei === p); if (!wand) return false; if (istPruefAdresse(hostKopfzeile)) return false; - const benannt = istOhneModiAdresse(hostKopfzeile) - || istCrewAdresse(hostKopfzeile) || istTreffAdresse(hostKopfzeile); + const benannt = istOhneModiAdresse(hostKopfzeile) || istCrewAdresse(hostKopfzeile); if (!benannt) return false; return !wand.zuhause(hostKopfzeile); } @@ -234,18 +253,37 @@ export function sitzungPasstZurAdresse(rolle, hostKopfzeile) { /* AUSSENROLLEN GEHOEREN AUF GENAU EINE ADRESSE (11.09.2026). Und die Bedingung steht in BEIDE Richtungen: Die Community kommt - nur auf treff. herein, und auf treff. kommt NUR sie herein. Das - Team sieht den Treff auf seiner eigenen Adresse -- es hat dort - nichts zu suchen, und ein Konto, das an zwei Waenden funktioniert, - ist eine Tuer, die man beim naechsten Umbau uebersieht. */ + NUR auf crew. herein (dort liegt der Treff), und auf den alten + Adressen kommt sie gar nicht herein. Ein Konto, das an zwei + Waenden funktioniert, ist eine Tuer, die man beim naechsten Umbau + uebersieht. + + SEIT DEM 11.09.2026 IST DIESE ADRESSE crew. UND NICHT treff. + (Filipe: "das soll keine app fuer sich sein"). Die Richtung der + Regel hat sich dadurch umgedreht, die Regel selbst nicht: Es gibt + genau eine Adresse, auf der die Community angemeldet sein darf. */ const aussen = AUSSEN_ROLLEN.has(rolle); - if (istTreffAdresse(hostKopfzeile)) return aussen; - if (aussen) return false; - /* crew.: das Team -- und DogFather, der in beiden Welten arbeitet. */ - if (istCrewAdresse(hostKopfzeile)) return team || rolle === "admin"; - /* Die echten alten Adressen: alle ausser dem Team. */ - if (istOhneModiAdresse(hostKopfzeile)) return !team; - /* localhost, Pruefadressen: unberuehrt. */ + /* crew.: das Team, die Community -- und DogFather, der in beiden + Welten arbeitet. */ + if (istCrewAdresse(hostKopfzeile)) return team || aussen || rolle === "admin"; + /* Die echten alten Adressen: alle ausser dem Team und der Community. */ + if (istOhneModiAdresse(hostKopfzeile)) return !team && !aussen; + /* localhost, Pruefadressen: unberuehrt. + + UND DAS GILT AUCH FUER DIE COMMUNITY (gemessen 11.09.2026). Der + erste Entwurf hatte hier ein "wer von aussen ist, kommt sonst + nirgends herein" stehen -- eine Bedingung durch AUSSCHLUSS, und sie + traf damit auch localhost. Ergebnis: Jede Pruefung des Treffs haette + ab sofort gemessen, dass ein Mitglied nirgends hereinkommt, und + waere gruen geblieben, ohne noch irgendetwas zu pruefen. Genau die + Falle, vor der oben in diesem Kopf Nummer 2 warnt -- und ich bin + am selben Tag zum dritten Mal hineingelaufen. + + Jetzt nennt jede Zeile die Adresse, FUER DIE sie gilt. Wer eine + vierte baut, faellt hier unten heraus und bekommt "erlaubt" -- + das ist auf einer Pruefadresse richtig und auf einer echten faellt + es sofort auf, weil pruef-crew-adresse jede echte Adresse + einzeln aufzaehlt. */ return true; } diff --git a/server/index.js b/server/index.js index bc656499..f0fe7bd5 100644 --- a/server/index.js +++ b/server/index.js @@ -13,7 +13,6 @@ import { fileURLToPath } from "node:url"; import { gateMiddleware } from "./gate.js"; import { workspaceUmzug } from "./workspace-umzug.js"; import { crewWeiche } from "./crew-adresse.js"; -import { treffWeiche } from "./treff-adresse.js"; import { webdesignGate } from "./webdesign-gate.js"; import { inhaltsrichtlinie } from "./inhaltsrichtlinie.js"; import { workspaceRouter, sichtSetzen } from "./workspace.js"; @@ -129,21 +128,6 @@ app.use(workspaceUmzug); gehen. Zwei Orte fuer dieselbe Regel waeren einer zu viel. */ app.use(crewWeiche); -/* Die eigene Adresse des Treffs (11.09.2026): treff.dogfather-universe.com. - Begruendung in treff-adresse.js, geprueft in pruef-treff-adresse.mjs. - - DIREKT HINTER crewWeiche UND NICHT DAVOR. Die Reihenfolge ist keine - Geschmacksfrage: crewWeiche beantwortet ueber die Wandtafel fuer JEDE - Adresse die Frage "wird hier eine fremde Zugangswand verlangt?" -- - auch fuer treff. Stuende treffWeiche davor, wuerde sie - /workspace/crew-index.html erst auf die Treff-Wand umbiegen und die - Tafel kaeme nie zum Zug. Ein Mitglied der Community saehe dann keine - 404, sondern seine eigene Wand -- und niemandem waere aufgefallen, - dass die Sperre nicht mehr greift. - - Auch sie vergibt KEINE Rechte. Wer auf welcher Adresse angemeldet - sein darf, entscheidet sitzungLesen() in workspace.js. */ -app.use(treffWeiche); /* Inhaltsrichtlinie (26.08.2026). Steht bewusst HIER -- vor jeder Schranke und vor express.static: Auch die Zugangswand und die diff --git a/server/pruef-crew-adresse.mjs b/server/pruef-crew-adresse.mjs index 3086be45..51075f9a 100644 --- a/server/pruef-crew-adresse.mjs +++ b/server/pruef-crew-adresse.mjs @@ -320,7 +320,22 @@ try { mc = JSON.parse(manifestCrew.text); } catch { /* bleibt leer */ } try { mw = JSON.parse(manifestWs.text); } catch { /* bleibt leer */ } ok(manifestCrew.status === 200 && manifestWs.status === 200, "beide Adressen liefern ein Manifest"); -ok(mc.name === "Team Dogi", `crew.: die App heisst "Team Dogi" (gelesen: ${mc.name || "?"})`); +/* DER NAME GILT SEIT DEM 11.09.2026 FUER BEIDE (Entscheidung Filipe). + + Er hiess "Team Dogi". Seit der Treff hier wohnt, wuerde ein Mitglied + der Community diesen Namen auf seinem Startbildschirm haben -- den + Namen eines Bereichs, zu dem es nicht gehoert. Das Manifest gilt je + ADRESSE und kann sich nicht nach der Rolle richten; also einer, der + fuer beide stimmt. + + GEPRUEFT WIRD NICHT NUR DER NAME, SONDERN DER UNTERSCHIED. Ein + fester Name allein waere beim naechsten Umbenennen wieder rot, ohne + dass etwas kaputt ist. Dass die beiden Adressen sich UNTERSCHEIDEN, + ist die Eigenschaft, um die es geht -- sonst waere es eine App. */ +ok(mc.name === "DogFather Universe", + `crew.: die App heisst "DogFather Universe" (gelesen: ${mc.name || "?"})`); +ok(mc.name && mw.name && mc.name !== mw.name, + `und anders als auf der Agenturadresse ("${mw.name}")`); ok(mw.name === "Creator Workspace — Spicy & Dogi", `workspace.: dort heisst sie unveraendert (gelesen: ${mw.name || "?"})`); ok(Array.isArray(mc.icons) && mc.icons.length > 0 @@ -367,13 +382,43 @@ ok(/Team Dogi|Team Dogi/.test(wandCrew.text), "crew.: dort steht Team Dogi" ok(/DogFather/.test(wandCrew.text), "crew.: und DogFather daneben"); ok(!/Spicy/i.test(wandCrew.text), "crew.: von Spicy Media steht dort nichts mehr"); ok(!/Creator/i.test(wandCrew.text), "crew.: und vom Creator Workspace auch nicht"); -ok(/die Modis/.test(wandCrew.text), "crew.: der Satz nennt DogFather und die Modis"); +/* DER SATZ AUF DER WAND MUSS FUER BEIDE STIMMEN (11.09.2026). -/* Die Kachelreihe ist weg -- auf einer Adresse mit genau einer Sorte - Zugang waere sie eine Bedienhuerde, keine Auswahl. */ + Hier stand `/die Modis/` -- und der Satz lautete "Privater Bereich + fuer DogFather und die Modis". Seit die Community durch dieselbe Wand + kommt, liest sie ihn auch, und fuer sie ist er schlicht falsch. + + UND DIE ALTE PRUEFUNG WAERE TROTZDEM GRUEN GEBLIEBEN: Sie durchsuchte + den QUELLTEXT, und dort steht "die Modis" weiterhin in einem + Kommentar. Sie haette also bestaetigt, dass ein Satz dasteht, den es + auf dem Bildschirm gar nicht mehr gibt. Gemessen wird deshalb die + sichtbare Unterzeile, nicht die Datei. */ +{ + const unter = /
([^<]*)<\/p>/.exec(wandCrew.text)?.[1] || ""; + ok(/Team\s*Dogi/.test(unter) && /Treff/.test(unter), + `crew.: die Unterzeile nennt Team Dogi und den Treff ("${unter.trim()}")`); + ok(!/Arbeitsbereich/.test(unter), + "und behauptet nicht mehr, das hier sei nur zum Arbeiten"); +} + +/* DIE KACHELREIHE HAT SEIT DEM 11.09.2026 VIER EINTRAEGE. + + Die Geschichte dieser Zeile ist lehrreich und steht deshalb hier: + Erst gab es keine Kachelreihe (eine Sorte Zugang, eine Reihe waere + eine Huerde gewesen). Dann drei ("3 rollen. dogfather. rechte hand + und modis"). Jetzt vier, weil der Treff hier eingezogen ist. + + GEPRUEFT WIRD NICHT DIE ZAHL, SONDERN WELCHE. Eine Zahl war schon + zweimal rot, ohne dass etwas kaputt war. Die NAMEN dagegen sagen + etwas: Steht dort eines Tages "manager", ist das ein Befund und kein + Umbau. */ +const rollenAufWand = [...wandCrew.text.matchAll(/data-rolle="([a-z]+)"/g)].map((m) => m[1]); +ok(JSON.stringify(rollenAufWand) === JSON.stringify(["admin", "hand", "modi", "gast"]), + `crew.: die vier Kacheln in dieser Reihenfolge (${rollenAufWand.join(", ")})`); const kachelnCrew = (wandCrew.text.match(/class="rolle"/g) || []).length; const kachelnWs = (wandWs.text.match(/class="rolle"/g) || []).length; -ok(kachelnCrew === 3, `crew.: drei Rollenkacheln (gezaehlt: ${kachelnCrew})`); +ok(kachelnCrew === rollenAufWand.length, + `crew.: ${kachelnCrew} Rollenkacheln -- so viele, wie es Rollen auf der Wand gibt`); for (const [rolle, name] of [["admin", "DogFather"], ["hand", "Rechte Hand"], ["modi", "Modi"]]) { ok(wandCrew.text.includes(`data-rolle="${rolle}"`) && wandCrew.text.includes(`>${name}<`), `crew.: die Kachel "${name}" steht da`); diff --git a/server/pruef-treff-werkzeuge.mjs b/server/pruef-treff-werkzeuge.mjs index bee17cc2..30045cb9 100644 --- a/server/pruef-treff-werkzeuge.mjs +++ b/server/pruef-treff-werkzeuge.mjs @@ -64,9 +64,9 @@ let fehler = 0, geprueft = 0; const melde = (t) => console.log(t); const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; -const TREFF = "treff.dogfather-universe.com"; const CREW = "crew.dogfather-universe.com"; const HAUS = "workspace.dogfather-universe.com"; +const HAUPT = "dogfather-universe.com"; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); @@ -153,8 +153,8 @@ async function anmelden(rolle, code, host) { const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS); const kModi = await anmelden("modi", "CODE-MODI-0001", CREW); -const kKessi = await anmelden("gast", "CODE-GAST-0001", TREFF); -const kBo = await anmelden("gast", "CODE-GAST-0002", TREFF); +const kKessi = await anmelden("gast", "CODE-GAST-0001", CREW); +const kBo = await anmelden("gast", "CODE-GAST-0002", CREW); ok(!!kDogi && !!kModi && !!kKessi && !!kBo, "alle vier sind angemeldet"); /* ======================================================================= @@ -164,24 +164,24 @@ melde(""); melde("=== 1. Will ich auch ==="); { const weg = `/workspace/api/treff/eintrag/${wunsch1}/willich`; - const a = await roh(weg, TREFF, { cookie: kKessi }, {}, "PUT"); + const a = await roh(weg, CREW, { cookie: kKessi }, {}, "PUT"); ok(a.code === 200 && json(a).stimmen === 1, `Kessi stimmt ab (${json(a).stimmen})`); /* ZWEIMAL DRUECKEN IST EINMAL. Das ist die eigentliche Regel: Der zusammengesetzte Schluessel laesst gar keine zweite Zeile zu. */ - const b = await roh(weg, TREFF, { cookie: kKessi }, {}, "PUT"); + const b = await roh(weg, CREW, { cookie: kKessi }, {}, "PUT"); ok(b.code === 200 && json(b).stimmen === 1, `zweimal druecken aendert nichts (${json(b).stimmen})`); - const c = await roh(weg, TREFF, { cookie: kBo }, {}, "PUT"); + const c = await roh(weg, CREW, { cookie: kBo }, {}, "PUT"); ok(c.code === 200 && json(c).stimmen === 2, `Bo auch (${json(c).stimmen})`); - const e = await roh(weg, TREFF, { cookie: kKessi }, {}, "DELETE"); + const e = await roh(weg, CREW, { cookie: kKessi }, {}, "DELETE"); ok(e.code === 200 && json(e).stimmen === 1 && json(e).meine === false, `Kessi nimmt sie zurueck (${json(e).stimmen})`); /* Und die Zahl kommt am Brett an -- sonst waere sie eine Zahl, die nur die Route kennt. */ - const brett = await roh("/workspace/api/bereich/wunsch", TREFF, { cookie: kBo }); + const brett = await roh("/workspace/api/bereich/wunsch", CREW, { cookie: kBo }); const zeile = (json(brett).eintraege || []).find((x) => x.id === wunsch1); ok(zeile?.stimmen === 1 && zeile?.meine_stimme === 1, `am Brett steht sie auch (${zeile?.stimmen}, meine ${zeile?.meine_stimme})`); @@ -190,7 +190,7 @@ melde("=== 1. Will ich auch ==="); diese Zeile waere "es geht" auch dann gruen, wenn es ueberall ginge -- und dann waere die Wunschliste keine Abstimmung mehr, sondern ein Nebeneffekt. */ - const f = await roh(`/workspace/api/treff/eintrag/${bosBeitrag}/willich`, TREFF, + const f = await roh(`/workspace/api/treff/eintrag/${bosBeitrag}/willich`, CREW, { cookie: kKessi }, {}, "PUT"); ok(f.code === 400, `im Gespraech gibt es kein "Will ich auch" (${f.code})`); } @@ -225,7 +225,7 @@ melde("=== 2. Anheften ==="); ok(nochmal.code === 200, `nach dem Abnehmen geht die vierte (${nochmal.code})`); /* Und die Community heftet nichts an. */ - const gast = await roh(`/workspace/api/treff/eintrag/${anschlag[1]}/anheften`, TREFF, + const gast = await roh(`/workspace/api/treff/eintrag/${anschlag[1]}/anheften`, CREW, { cookie: kKessi }, {}, "PUT"); ok(gast.code === 404, `ein Mitglied kann nichts anheften (${gast.code})`); @@ -241,13 +241,13 @@ melde(""); melde("=== 3. Melden ==="); { const weg = `/workspace/api/treff/eintrag/${bosBeitrag}/melden`; - const ohne = await roh(weg, TREFF, { cookie: kKessi }, { grund: "" }); + const ohne = await roh(weg, CREW, { cookie: kKessi }, { grund: "" }); ok(ohne.code === 400, `ohne Grund geht es nicht (${ohne.code})`); - const kurz = await roh(weg, TREFF, { cookie: kKessi }, { grund: "doof" }); + const kurz = await roh(weg, CREW, { cookie: kKessi }, { grund: "doof" }); ok(kurz.code === 400, `"doof" ist kein Grund (${kurz.code})`); - const mit = await roh(weg, TREFF, { cookie: kKessi }, + const mit = await roh(weg, CREW, { cookie: kKessi }, { grund: "Da steht die Handynummer von jemandem drin." }); ok(mit.code === 200, `mit Grund geht es (${mit.code})`); @@ -260,7 +260,7 @@ melde("=== 3. Melden ==="); /* Die Community sieht die Meldungen NICHT -- sonst waere ablesbar, wer wen gemeldet hat. */ - const gast = await roh("/workspace/api/treff/meldungen", TREFF, { cookie: kBo }); + const gast = await roh("/workspace/api/treff/meldungen", CREW, { cookie: kBo }); ok(gast.code === 404, `ein Mitglied sieht die Liste nicht (${gast.code})`); /* Entscheiden braucht ebenfalls einen Satz. */ @@ -366,7 +366,7 @@ melde("=== 6. Ausschluss ==="); /* "dabei" und nicht "stamm": Kessi ist 40 Tage dabei, hat aber keinen eigenen Beitrag geschrieben. Beide Bedingungen muessen erfuellt sein -- genau das misst diese Zeile nebenbei mit. */ - const vorher = await roh("/workspace/api/treff/lage", TREFF, { cookie: kKessi }); + const vorher = await roh("/workspace/api/treff/lage", CREW, { cookie: kKessi }); ok(vorher.code === 200 && json(vorher).stufe === "dabei", `Kessi ist drin, Stufe ${json(vorher).stufe}`); @@ -378,24 +378,24 @@ melde("=== 6. Ausschluss ==="); /* DIE SITZUNG IST WEG, nicht nur das Schreiben. Ein Ausgeschlossener, der weiter mitlesen kann, ist bei Belaestigung genau der Teil, der weh tut. */ - const lage = await roh("/workspace/api/treff/lage", TREFF, { cookie: kKessi }); + const lage = await roh("/workspace/api/treff/lage", CREW, { cookie: kKessi }); ok(lage.code === 401, `ihre Sitzung gilt nicht mehr (${lage.code})`); - const brett = await roh("/workspace/api/bereich/treff", TREFF, { cookie: kKessi }); + const brett = await roh("/workspace/api/bereich/treff", CREW, { cookie: kKessi }); ok(brett.code === 401, `und sie liest auch nichts mehr (${brett.code})`); /* Und sie kommt auch nicht wieder herein -- gar nicht erst. Ohne diese Zeile waere die Anmeldung gelungen, das Plaetzchen gesetzt, und dahinter eine Seite, auf der nichts geht. Das sieht aus wie ein kaputtes Programm, nicht wie eine Entscheidung. */ - const neu = await anmelden("gast", "CODE-GAST-0001", TREFF); + const neu = await anmelden("gast", "CODE-GAST-0001", CREW); ok(!neu, "und sie kann sich nicht neu anmelden"); /* GEGENPROBE: Bo hat nur eine PAUSE -- kein Ausschluss. Er darf weiter lesen, aber nicht schreiben. Ohne diese Zeile waeren die vier darueber auch dann gruen, wenn jede Massnahme jeden aussperrt. */ - const boLage = await roh("/workspace/api/treff/lage", TREFF, { cookie: kBo }); + const boLage = await roh("/workspace/api/treff/lage", CREW, { cookie: kBo }); ok(boLage.code === 200, `Bo liest weiter (${boLage.code})`); ok(json(boLage).sperre?.art === "pause" && !!json(boLage).sperre?.bis, `und sieht seine Pause mit Enddatum (${json(boLage).sperre?.bis?.slice(0, 10)})`); @@ -403,10 +403,10 @@ melde("=== 6. Ausschluss ==="); /* Und Pim, sieben Tage ausgeschlossen, kommt gar nicht mehr herein -- ein befristeter Ausschluss ist ein Ausschluss, keine Pause. */ - const pim = await anmelden("gast", "CODE-GAST-0003", TREFF); + const pim = await anmelden("gast", "CODE-GAST-0003", CREW); ok(!pim, "ein befristeter Ausschluss sperrt ebenfalls aus"); - const versuch = await roh("/workspace/api/bereich/treff", TREFF, { cookie: kBo }, + const versuch = await roh("/workspace/api/bereich/treff", CREW, { cookie: kBo }, { art: ersteArt("treff"), titel: "BO-TROTZDEM", text: "Hallo?", datum: heute }); ok(versuch.code === 403, `und es geht auch wirklich nicht (${versuch.code})`); } @@ -431,17 +431,89 @@ melde("=== 7. Die Bruecke zu den Talenten ==="); /* Die Community kommt an die Talente nicht heran -- sonst waere die Bruecke ein Fenster. */ - const gast = await roh("/workspace/api/bereich/talente", TREFF, { cookie: kBo }); + const gast = await roh("/workspace/api/bereich/talente", CREW, { cookie: kBo }); ok(gast.code === 404, `ein Mitglied sieht die Talente nicht (${gast.code})`); } +/* ======================================================================= + 7b. DIE COMMUNITY STEHT IN KEINER ARBEITSLISTE — aber in der Verwaltung + + Seit der Treff in Team Dogi eingezogen ist, sind Mitglieder und Team + auf derselben Adresse. Was vorher die Adresse getrennt hat, muss + jetzt jede einzelne Liste halten. + + DREI LISTEN, ZWEI ANTWORTEN. Einladen und Chatten: nein, und zwar + auch für DogFather, der sonst überall "alle" bekommt. Verwaltung: ja + — sonst legt er einen Zugang an und der verschwindet im selben + Moment aus der Liste. + + MIT GEGENPROBE IN BEIDE RICHTUNGEN: Jede "steht nicht drin"-Zeile + steht neben einer, die beweist, dass die Liste überhaupt jemanden + enthält. Eine leere Liste erfüllt sonst jede Nein-Bedingung. + ======================================================================= */ +melde(""); +melde("=== 7b. Keine Arbeitsliste, aber die Verwaltung ==="); +{ + const namen = (a) => { + try { + const j = JSON.parse(a.text); + const feld = Object.values(j).find(Array.isArray) || []; + return feld.map((x) => x?.name).filter(Boolean); + } catch { return []; } + }; + + for (const [was, weg] of [ + ["die Einladungsliste", "/workspace/api/einladbar"], + ["die Chat-Partner", "/workspace/api/chat/partner"], + ]) { + const a = await roh(weg, HAUS, { cookie: kDogi }); + const liste = namen(a); + /* DIE ZAHL GEHÖRT IN DIE BEDINGUNG: Wäre die Liste leer, wäre + "Kessi steht nicht drin" trivial wahr. */ + ok(a.code === 200 && liste.length > 0, + `${was} hat überhaupt Einträge (${liste.length})`); + ok(!liste.includes("Kessi") && !liste.includes("Bo"), + `${was} enthält niemanden aus der Community${ + liste.filter((n) => n === "Kessi" || n === "Bo").join(", ")}`); + ok(liste.includes("Frida"), + `aber den Modi — ${was} filtert nicht einfach alles weg`); + } + + /* UND DIE VERWALTUNG ZEIGT SIE. Auf der Team-Adresse, denn dort + wohnt der Treff jetzt. */ + const verwaltung = await roh("/workspace/api/personen", CREW, { cookie: kDogi }); + const inVerwaltung = namen(verwaltung); + ok(verwaltung.code === 200 && inVerwaltung.includes("Kessi"), + `in "Personen & Zugänge" auf crew. steht die Community (${inVerwaltung.length} Personen)`); + ok(inVerwaltung.includes("Frida"), + "und das Team ebenfalls — eine Liste, nicht zwei"); + + /* Die Rolle lässt sich dort auch VERGEBEN. Ein Knopf, den es nicht + gibt, macht die Liste nutzlos: Ohne ihn kann DogFather nirgends + einen Zugang zum Treff anlegen. */ + const ich = await roh("/workspace/api/ich", CREW, { cookie: kDogi }); + let darf = []; + try { darf = JSON.parse(ich.text).darf_anlegen || []; } catch { /* bleibt leer */ } + ok(darf.includes("gast"), + `und DogFather darf dort Community-Zugänge anlegen (${darf.join(", ")})`); + + /* GEGENPROBE: Ein Modi darf das nicht — Zugänge vergibt DogFather. + Ohne diese Zeile wäre die darüber auch dann grün, wenn es jeder + dürfte. */ + const ichModi = await roh("/workspace/api/ich", CREW, { cookie: kModi }); + let darfModi = []; + try { darfModi = JSON.parse(ichModi.text).darf_anlegen || []; } catch { /* leer */ } + ok(!darfModi.includes("gast"), + `ein Modi nicht (${darfModi.length ? darfModi.join(", ") : "gar nichts"})`); +} + /* ======================================================================= 8. DIE REGELN NENNEN DIE ZAHLEN, DIE WIRKLICH GELTEN ======================================================================= */ melde(""); melde("=== 8. Der Regeltext ==="); { - const a = await roh("/workspace/api/treff/regeln", TREFF, { cookie: kBo }); + const a = await roh("/workspace/api/treff/regeln", CREW, { cookie: kBo }); const r = json(a); ok(a.code === 200 && r.mindestalter === treffModul.MINDESTALTER, `Mindestalter ${r.mindestalter} -- dieselbe Zahl wie im Code`); diff --git a/server/pruef-treff.mjs b/server/pruef-treff.mjs index 4c20126b..9493e83d 100644 --- a/server/pruef-treff.mjs +++ b/server/pruef-treff.mjs @@ -1,5 +1,5 @@ /* ===================================================================== - DER TREFF — die Community und ihre sieben Bretter (11.09.2026) + DER CREW — die Community und ihre sieben Bretter (11.09.2026) Filipe: @@ -66,9 +66,9 @@ let fehler = 0, geprueft = 0; const melde = (t) => console.log(t); const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; -const TREFF = "treff.dogfather-universe.com"; const CREW = "crew.dogfather-universe.com"; const HAUS = "workspace.dogfather-universe.com"; +const HAUPT = "dogfather-universe.com"; const { DatabaseSync } = await import("node:sqlite"); const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); @@ -103,7 +103,7 @@ const rein = d.prepare( + " VALUES (?,?,?,?,?,?,?)"); const BRETTER = ["anschlag", "ansteht", "treff", "wunsch", "highlight", "regeln", "mitmachen"]; for (const b of BRETTER) rein.run(b, Object.keys((await import("./workspace-bereiche.js")).BEREICHE[b].arten)[0], - `TREFF-PROBE-${b.toUpperCase()}`, "", jetzt.slice(0, 10), jetzt, idDogi); + `CREW-PROBE-${b.toUpperCase()}`, "", jetzt.slice(0, 10), jetzt, idDogi); /* Und einer im Haus, den die Community NICHT sehen darf. */ rein.run("ideen", "content", "HAUS-GEHEIM-4711", "", jetzt.slice(0, 10), jetzt, idDogi); d.close(); @@ -140,23 +140,47 @@ async function anmelden(rolle, code, host) { ======================================================================= */ melde(""); melde("=== 1. Eine Rolle, eine Wand ==="); -const gast = await anmelden("gast", "CODE-GAST-0001", TREFF); -ok(gast.code === 200 && gast.keks.length > 0, `die Community kommt auf treff. herein (${gast.code})`); +const gast = await anmelden("gast", "CODE-GAST-0001", CREW); +ok(gast.code === 200 && gast.keks.length > 0, + `die Community kommt auf crew. herein (${gast.code})`); -for (const [name, host] of [["crew.", CREW], ["workspace.", HAUS]]) { +/* AUF DEN ALTEN ADRESSEN NICHT -- und zwar mit derselben Antwort wie + ein erfundener Code. Ein anderer Fehlercode waere die Auskunft + "diesen Zugang gibt es, nur nicht hier", und wer das liest, sucht + weiter. */ +for (const [name, host] of [["workspace.", HAUS], ["der Hauptseite", HAUPT]]) { const a = await anmelden("gast", "CODE-GAST-0001", host); const b = await anmelden("gast", "VOELLIG-ERFUNDEN-XYZ", host); - ok(a.code === 401 && a.keks === "", `auf ${name} nicht (${a.code})`); + /* NICHT AUF EINEN BESTIMMTEN CODE FESTLEGEN (gemessen 11.09.2026). + + Hier stand `=== 401`. Auf workspace. stimmt das; auf der + Hauptseite antwortet der Server 410 -- der Workspace ist von dort + weggezogen, und "Gone" ist die richtige Antwort fuer JEDEN, der es + dort noch versucht. Die Pruefung war rot, obwohl genau das Richtige + passierte. + + Gemessen wird jetzt, was die Zeile eigentlich behaupten will: Er + kommt nicht herein, und er kann an der Antwort nicht ablesen, dass + es seinen Zugang gibt. */ + ok(a.code !== 200 && a.keks === "", `auf ${name} nicht (${a.code})`); ok(a.code === b.code, `und zwar mit derselben Antwort wie ein erfundener Code (${a.code}/${b.code})`); } { - /* UND DIE GEGENRICHTUNG: Auf der Wand des Treffs kommt sonst niemand - herein -- auch DogFather nicht. Er hat dort nichts zu suchen; er - sieht den Treff auf seiner eigenen Adresse. */ - const a = await anmelden("admin", "CODE-DOGI-0001", TREFF); - const b = await anmelden("modi", "CODE-MODI-0001", TREFF); - ok(a.code !== 200 && b.code !== 200, - `auf treff. kommt sonst niemand herein -- auch DogFather nicht (${a.code}/${b.code})`); + /* DIE GEGENPROBE: Auf derselben Wand kommt auch das Team herein -- + sonst waere "die Community kommt auf crew. herein" auch dann + gruen, wenn dort ueberhaupt niemand mehr durchkaeme. */ + const a = await anmelden("admin", "CODE-DOGI-0001", CREW); + const b = await anmelden("modi", "CODE-MODI-0001", CREW); + ok(a.code === 200 && b.code === 200, + `und das Team ebenfalls -- eine Wand fuer beide (${a.code}/${b.code})`); + + /* UND DIE AGENTUR WEITERHIN NICHT. Dass der Treff hier eingezogen + ist, hat daran nichts geaendert -- und genau das ist die Stelle, + an der ein Umbau so etwas versehentlich aufmacht. */ + const c = await anmelden("manager", "CODE-MANA-0001", CREW); + const d = await anmelden("creator", "CODE-CREA-0001", CREW); + ok(c.code !== 200 && d.code !== 200, + `eine Managerin und eine Creatorin aber nicht (${c.code}/${d.code})`); } const kDogi = (await anmelden("admin", "CODE-DOGI-0001", HAUS)).keks; @@ -166,108 +190,96 @@ const kMana = (await anmelden("manager", "CODE-MANA-0001", HAUS)).keks; ok([kDogi, kModi, kCrea, kMana].every((k) => k.length > 0), "die vier Vergleichsrollen sind angemeldet"); /* ======================================================================= - 1b. DIE WEICHE: drei Adressen, drei Türen, keine Verwechslung + 1b. EINE WAND FÜR BEIDE — und keine dritte mehr + + Bis zum Mittag des 11.09.2026 hatte der Treff eine eigene Adresse, + eine eigene Wand und ein eigenes Manifest. Filipe hat das verworfen: + *"das soll keine app für sich sein, das soll in der crew seite + adaptiert werden."* + + Was dieser Abschnitt jetzt misst, ist deshalb das Gegenteil von + vorher — und er misst es, weil ein halber Rückbau die gefährlichste + Lage wäre: eine Wand, die es nicht mehr geben soll, die aber noch + als Datei herumliegt und von express.static ausgeliefert wird. ======================================================================= */ melde(""); -melde("=== 1b. Die Weiche des Treffs ==="); +melde("=== 1b. Eine Wand für beide ==="); { - /* DIE WAND. Ohne die Weiche stünde auf der Adresse der Community die - Wand der Agentur: Spicy-Media-Logo, "Creator Workspace", fünf - Rollenkacheln mit den Namen Admin, Manager, Scout, Creator -- die - komplette Struktur des Unternehmens, VOR der Anmeldung, für jeden, - der die Adresse hat. Das ist das eigentliche Leck, gegen das - treffWeiche gebaut ist. */ - for (const pfad of ["/workspace/", "/workspace/index.html"]) { - const a = await roh(pfad, TREFF); - const ist = a.code === 200 && /Der Treff|Der Treff/.test(a.text); - ok(ist, `${pfad} zeigt die Wand des Treffs (${a.code})`); - ok(!/Creator\s*Workspace|Spicy/i.test(a.text), - `und nennt weder Workspace noch Spicy (${pfad})`); - } + /* DIE DATEIEN DER DRITTEN ADRESSE SIND WEG. Nicht "nicht mehr + verlinkt" -- weg. Eine Zugangswand, die niemand mehr meint, die + aber unter ihrem Dateinamen weiterhin herumliegt, ist genau die + Sorte Rest, die man ein halbes Jahr spaeter findet. - /* KEINE ROLLENNAMEN DES HAUSES in der ausgelieferten Datei. Die - beiden anderen Wände nennen ihre Rollen offen -- dort ist das - richtig, denn dort steht nur, wer ohnehin dazugehört. Hier steht - ein Mensch, der NICHT zum Team gehört. */ + GEFRAGT WIRD DIE PLATTE, NICHT DER SERVER (gemessen 11.09.2026). + Ueber HTTP antwortet eine geloeschte Seite mit 302 auf die + Startseite -- weil die Schranke davor greift, bevor express.static + ueberhaupt nachsieht. Das ist im Betrieb genau richtig (eine Seite, + die es nicht gibt, verhaelt sich wie eine, die man nicht darf), + macht die Frage "gibt es die Datei noch?" per HTTP aber + unbeantwortbar: 302 kommt in beiden Faellen. Wer hier 404 erwartet, + prueft die Schranke und nennt es Dateipruefung. */ { - const a = await roh("/workspace/treff-index.html", TREFF); - const verraten = ["manager", "scout", "creator", "modi", "rechte hand"] - .filter((w) => new RegExp(w, "i").test(a.text)); - ok(verraten.length === 0, - `die Wand nennt keine Rolle des Hauses${verraten.length ? " -- FUND: " + verraten.join(", ") : ""}`); + const { existsSync } = await import("node:fs"); + const { fileURLToPath } = await import("node:url"); + /* fileURLToPath statt .pathname mit Handarbeit: Unter Windows + liefert .pathname "/C:/..." -- mit einem fuehrenden Schraegstrich, + den existsSync nicht mag. Wer den mit einem Muster wegschneidet, + hat eine Zeile, die auf Linux nichts tut und auf Windows alles + entscheidet. Der eingebaute Weg kennt beide Faelle. */ + const ordner = fileURLToPath(new URL("../workspace/", import.meta.url)); + const reste = ["treff-index.html", "treff.webmanifest", + "assets/img/buehne/treff-gate-1280.webp"] + .filter((f) => existsSync(ordner + f)); + ok(reste.length === 0, + `von der dritten Adresse liegt nichts mehr herum${reste.length ? " -- REST: " + reste.join(", ") : ""}`); + /* GEGENPROBE: Die Pruefung sieht ueberhaupt an der richtigen + Stelle nach. Ohne sie waere die Zeile darueber auch bei einem + falschen Pfad gruen -- der gruene Haken ueber der Null. */ + ok(existsSync(ordner + "crew-index.html"), + "und die Wand von Team Dogi liegt dort, wo gesucht wurde"); } - /* DAS MANIFEST MACHT DIE APP. Drei Adressen, drei Namen -- sonst - hieße die App auf dem Startbildschirm eines Mitglieds "Team Dogi", - der Name eines Bereichs, zu dem es nicht gehört. */ - const namen = {}; - for (const [was, host] of [["treff", TREFF], ["crew", CREW], ["haus", HAUS]]) { - const a = await roh("/workspace/app.webmanifest", host); - try { namen[was] = JSON.parse(a.text).name; } catch { namen[was] = `(kein JSON: ${a.code})`; } - } - ok(namen.treff === "Der Treff", `das Manifest heißt "${namen.treff}"`); - ok(namen.crew === "Team Dogi" && namen.treff !== namen.crew, - `und auf crew. weiterhin "${namen.crew}"`); - ok(new Set(Object.values(namen)).size === 3, - `drei Adressen, drei Namen (${Object.values(namen).join(" · ")})`); + /* DIE WAND ZEIGT VIER KACHELN. Die Zahl steht nicht als Zahl da, + sondern kommt aus der Rollenliste des Servers -- wer eine fünfte + Rolle auf diese Wand lässt, ohne eine Kachel zu bauen, bekommt + hier eine rote Zeile statt eines stummen Lochs. */ + const wand = await roh("/workspace/", CREW); + ok(wand.code === 200 && /Team Dogi|Team Dogi/.test(wand.text), + `die Wand von Team Dogi steht (${wand.code})`); + const kacheln = [...wand.text.matchAll(/data-rolle="([a-z]+)"/g)].map((m) => m[1]); + ok(kacheln.length === 4 && kacheln.includes("gast"), + `vier Kacheln, eine davon die Community: ${kacheln.join(", ")}`); + ok(kacheln[kacheln.length - 1] === "gast", + "und sie steht zuletzt -- wer täglich kommt, findet oben, was er täglich braucht"); - /* JEDE WAND GEHÖRT ZU GENAU EINER TÜR. Beide Richtungen, sonst - beweist es nichts: Die eigene Wand MUSS erreichbar sein. */ - const waende = [ - ["/workspace/crew-index.html", CREW, TREFF], - ["/workspace/treff-index.html", TREFF, CREW], - ["/workspace/treff-index.html", TREFF, HAUS], - ]; - for (const [datei, zuhause, fremd] of waende) { - const a = await roh(datei, zuhause); - const b = await roh(datei, fremd); - ok(a.code === 200, `${datei} gibt es zu Hause (${a.code})`); - ok(b.code === 404, `und auf der fremden Adresse nicht (${b.code})`); - } - /* Auch mit doppeltem Schrägstrich -- express.static zieht sie später - selbst zusammen und liefert die Datei aus. Genau daran ist am - 04.09.2026 die Schranke des Workspace vorbeigelaufen. */ - const schraeg = await roh("/workspace//treff-index.html", CREW); - ok(schraeg.code === 404, `auch mit doppeltem Schrägstrich nicht (${schraeg.code})`); + /* DER TEXT AUF DER WAND MUSS FÜR BEIDE STIMMEN. Er stand auf + "Privater Arbeitsbereich für DogFather und die Modis" -- das liest + ab heute auch die Community, und für sie ist es schlicht falsch. */ + ok(!/Arbeitsbereich für DogFather und die Modis/.test(wand.text), + "und der Untertitel behauptet nicht mehr, das hier sei nur für das Team"); + + /* DAS MANIFEST GILT JE ADRESSE und kann sich nicht nach der Rolle + richten. Filipes Entscheidung: ein Name, der für beide passt. */ + const man = await roh("/workspace/app.webmanifest", CREW); + let name = "(kein JSON)"; + try { name = JSON.parse(man.text).name; } catch { /* bleibt stehen */ } + ok(name === "DogFather Universe", `die App heißt "${name}" -- für Team und Community`); + const manHaus = await roh("/workspace/app.webmanifest", HAUS); + let nameHaus = "(kein JSON)"; + try { nameHaus = JSON.parse(manHaus.text).name; } catch { /* bleibt stehen */ } + ok(nameHaus !== name, `und auf der Agenturadresse weiterhin etwas anderes ("${nameHaus}")`); + + /* DIE WAND VON TEAM DOGI BLEIBT AUF DEN ALTEN ADRESSEN UNSICHTBAR. + 404 und keine Weiterleitung: "Gibt es nicht" verrät nichts. */ + const fremd = await roh("/workspace/crew-index.html", HAUS); + const schraeg = await roh("/workspace//crew-index.html", HAUS); + ok(fremd.code === 404 && schraeg.code === 404, + `die Wand gibt es auf workspace. nicht -- auch mit doppeltem Schrägstrich (${fremd.code}/${schraeg.code})`); /* NICHT INDIZIEREN -- als Kopf und nicht nur als , damit er auch für Manifest, Bilder und jede Antwort ohne HTML gilt. */ - const kopf = await roh("/workspace/", TREFF); - ok(/noindex/i.test(kopf.kopf?.["x-robots-tag"] || ""), - `der Kopf sagt noindex (${kopf.kopf?.["x-robots-tag"]})`); - - /* NUR DER WORKSPACE. Sonst läge die komplette Dogfather-Website ein - zweites Mal unter einer zweiten Adresse. */ - const haupt = await roh("/", TREFF); - ok(haupt.code === 302 && haupt.kopf?.location === "/workspace/", - `die Hauptseite gibt es hier nicht (${haupt.code} → ${haupt.kopf?.location})`); - - /* DAS HAUS. haus.css ist auf der Agenturadresse leer; auf treff. - muss dahinter das Farbhaus von Team Dogi stehen -- sonst käme die - Seite grau an und färbte sich erst im Browser um. */ - const haus = await roh("/workspace/assets/css/haus.css?v=1", TREFF); - ok(haus.code === 200 && /--sternenfeld/.test(haus.text), - `haus.css bringt das Farbhaus mit (${haus.text.length} Zeichen)`); - - /* DIE BÜHNEN INNEN. Die Motive der Agentur zeigen das - Spicy-Media-Logo selbst -- teils vielfach nebeneinander. */ - const a1 = await roh("/workspace/assets/img/buehne-lounge-breit.webp", TREFF); - const a2 = await roh("/workspace/assets/img/crew-buehne-lounge-breit.webp", TREFF); - const a3 = await roh("/workspace/assets/img/buehne-lounge-breit.webp", HAUS); - ok(a1.code === 200 && a1.text.length === a2.text.length, - `die Bühne kommt von Team Dogi (${a1.text.length} = ${a2.text.length})`); - ok(a1.text.length !== a3.text.length, - `und ist eine andere als die der Agentur (${a1.text.length} ≠ ${a3.text.length})`); - - /* DAS ZEICHEN IN DER KOPFLEISTE. start.css setzt dort eine Chili -- - als CSS-Hintergrund, auf jeder Seite, den ganzen Tag. */ - const m1 = await roh("/workspace/assets/img/marke-spicy.webp", TREFF); - const m2 = await roh("/workspace/assets/img/marke-husky.webp", TREFF); - const m3 = await roh("/workspace/assets/img/marke-spicy.webp", HAUS); - ok(m1.code === 200 && m1.text.length === m2.text.length, - `in der Kopfleiste steht der Husky (${m1.text.length})`); - ok(m1.text.length !== m3.text.length, - `und nicht die Chili (${m1.text.length} ≠ ${m3.text.length})`); + ok(/noindex/i.test(wand.kopf?.["x-robots-tag"] || ""), + `der Kopf sagt noindex (${wand.kopf?.["x-robots-tag"]})`); } /* ======================================================================= @@ -277,7 +289,7 @@ melde(""); melde("=== 2. Sieben Kacheln — und keine achte ==="); let namen = []; { - const a = await roh("/workspace/api/ich", TREFF, { cookie: gast.keks }); + const a = await roh("/workspace/api/ich", CREW, { cookie: gast.keks }); const j = JSON.parse(a.text); ok(Array.isArray(j.bereiche) && j.bereiche.length === 7, `die Community bekommt sieben Kacheln (${j.bereiche?.length})`); @@ -330,7 +342,7 @@ let namen = []; /* UND DIE MODERATIONSKACHEL IST BEI DER COMMUNITY WIRKLICH NICHT DA -- nicht nur "nicht in dieser Liste". Die Kachel ist ein Weg; wenn der Weg zu ist, muss auch die Tuer zu sein. */ - const d = JSON.parse((await roh("/workspace/api/ich", TREFF, { cookie: gast.keks })).text); + const d = JSON.parse((await roh("/workspace/api/ich", CREW, { cookie: gast.keks })).text); const alleNamen = [...(d.bereiche || []), ...(d.bereiche_zusatz || [])].map((k) => k.name); ok(!alleNamen.includes(MOD), `die Community hat keine Moderationskachel (${alleNamen.length} Kacheln)`); } @@ -349,8 +361,8 @@ async function brett(b, host, keks) { { let gefunden = 0; for (const b of BRETTER) { - const r = await brett(b, TREFF, gast.keks); - if (r.code === 200 && r.titel.includes(`TREFF-PROBE-${b.toUpperCase()}`)) gefunden++; + const r = await brett(b, CREW, gast.keks); + if (r.code === 200 && r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) gefunden++; } /* FUENF VON SIEBEN, UND DAS IST DAS ERGEBNIS -- kein Mangel. @@ -368,8 +380,8 @@ async function brett(b, host, keks) { Bretter aus einem anderen Grund leer sind. */ let vorher = 0; for (const b of treffModul.TREFF_FREIGABE_BRETTER) { - const r = await brett(b, TREFF, gast.keks); - if (r.titel.includes(`TREFF-PROBE-${b.toUpperCase()}`)) vorher++; + const r = await brett(b, CREW, gast.keks); + if (r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) vorher++; } ok(vorher === 0, `ohne Freigabe sieht die Community Termine und Highlights nicht (${vorher})`); @@ -378,7 +390,7 @@ async function brett(b, host, keks) { for (const b of treffModul.TREFF_FREIGABE_BRETTER) { const beiDogi2 = await roh(`/workspace/api/bereich/${b}`, HAUS, { cookie: kDogi }); const eintrag = (JSON.parse(beiDogi2.text).eintraege || []) - .find((e) => e.titel === `TREFF-PROBE-${b.toUpperCase()}`); + .find((e) => e.titel === `CREW-PROBE-${b.toUpperCase()}`); if (!eintrag) continue; const a = await roh(`/workspace/api/treff/eintrag/${eintrag.id}/freigeben`, HAUS, { cookie: kDogi }, {}, "PUT"); @@ -389,8 +401,8 @@ async function brett(b, host, keks) { let nachher = 0; for (const b of treffModul.TREFF_FREIGABE_BRETTER) { - const r = await brett(b, TREFF, gast.keks); - if (r.titel.includes(`TREFF-PROBE-${b.toUpperCase()}`)) nachher++; + const r = await brett(b, CREW, gast.keks); + if (r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) nachher++; } ok(nachher === treffModul.TREFF_FREIGABE_BRETTER.length, `nach der Freigabe sieht sie beide (${nachher})`); @@ -409,7 +421,7 @@ async function brett(b, host, keks) { const fremde = ["ideen", "angebote", "rueckmeldung", "entwicklung", "talente", "agentur", "live", "content"]; const durch = []; for (const b of fremde) { - const r = await brett(b, TREFF, gast.keks); + const r = await brett(b, CREW, gast.keks); if (r.code === 200) durch.push(b); } ok(durch.length === 0, `und kein einziges des Hauses${durch.length ? " -- DURCH: " + durch.join(", ") : ""}`); @@ -420,25 +432,25 @@ async function brett(b, host, keks) { bin ich am 10.09. schon einmal hereingefallen und haette fast ein Loch repariert, das keines war. Gezaehlt wird, was die Suche WIRKLICH gefunden hat. */ - const suche = await roh("/workspace/api/suche?q=HAUS-GEHEIM-4711", TREFF, { cookie: gast.keks }); + const suche = await roh("/workspace/api/suche?q=HAUS-GEHEIM-4711", CREW, { cookie: gast.keks }); let treffer = -1; try { treffer = JSON.parse(suche.text).gesamt; } catch { treffer = -1; } ok(treffer === 0, `auch die Suche findet nichts aus dem Haus (${treffer} Treffer)`); } { - /* UND DIE AGENTUR SIEHT DEN TREFF NICHT. Beide Richtungen, beide + /* UND DIE AGENTUR SIEHT DEN CREW NICHT. Beide Richtungen, beide ueber die Adresszeile. */ const durch = []; for (const b of BRETTER) { const r = await brett(b, HAUS, kCrea); - if (r.code === 200 && r.titel.some((t) => t.startsWith("TREFF-PROBE"))) durch.push(b); + if (r.code === 200 && r.titel.some((t) => t.startsWith("CREW-PROBE"))) durch.push(b); } ok(durch.length === 0, `eine Creatorin sieht kein Brett des Treffs${durch.length ? " -- DURCH: " + durch.join(", ") : ""}`); const durchM = []; for (const b of BRETTER) { const r = await brett(b, HAUS, kMana); - if (r.code === 200 && r.titel.some((t) => t.startsWith("TREFF-PROBE"))) durchM.push(b); + if (r.code === 200 && r.titel.some((t) => t.startsWith("CREW-PROBE"))) durchM.push(b); } ok(durchM.length === 0, `eine Managerin auch nicht${durchM.length ? " -- DURCH: " + durchM.join(", ") : ""}`); @@ -448,7 +460,7 @@ async function brett(b, host, keks) { let sieht = 0; for (const b of BRETTER) { const r = await brett(b, CREW, kModi); - if (r.code === 200 && r.titel.includes(`TREFF-PROBE-${b.toUpperCase()}`)) sieht++; + if (r.code === 200 && r.titel.includes(`CREW-PROBE-${b.toUpperCase()}`)) sieht++; } ok(sieht === 7, `ein Modi sieht alle sieben (${sieht}/7)`); } @@ -488,7 +500,7 @@ melde("=== 4. Nur die Seiten aus der Tafel ==="); const zu = []; for (const p of erlaubt) { - const a = await roh(p, TREFF, { cookie: gast.keks }); + const a = await roh(p, CREW, { cookie: gast.keks }); if (a.code !== 200) zu.push(`${p.split("/").pop()} (${a.code})`); } ok(zu.length === 0, @@ -496,7 +508,7 @@ melde("=== 4. Nur die Seiten aus der Tafel ==="); const durch = []; for (const p of verboten) { - const a = await roh(p, TREFF, { cookie: gast.keks }); + const a = await roh(p, CREW, { cookie: gast.keks }); if (a.code === 200) durch.push(p.split("/").pop()); } ok(durch.length === 0, @@ -507,9 +519,9 @@ melde("=== 4. Nur die Seiten aus der Tafel ==="); wirklich weh täte: Dort stehen die Namen derer, die gemeldet haben. Eine eigene Zeile dafür, damit der Befund einen Namen hat und nicht in "eine von 20" untergeht. */ - const mod = await roh("/workspace/treff-moderation.html", TREFF, { cookie: gast.keks }); + const mod = await roh("/workspace/treff-moderation.html", CREW, { cookie: gast.keks }); ok(mod.code !== 200, `die Moderationsseite bleibt zu (${mod.code})`); - const modApi = await roh("/workspace/api/treff/meldungen", TREFF, { cookie: gast.keks }); + const modApi = await roh("/workspace/api/treff/meldungen", CREW, { cookie: gast.keks }); ok(modApi.code === 404, `und die Meldungen dahinter auch (${modApi.code})`); } @@ -525,7 +537,7 @@ for (const [name, pfad] of [ ["Personen", "/workspace/api/personen"], ["Gespräche", "/workspace/api/chat/raeume"], ]) { - const a = await roh(pfad, TREFF, { cookie: gast.keks }); + const a = await roh(pfad, CREW, { cookie: gast.keks }); let leer = a.code !== 200; if (a.code === 200) { try { @@ -551,11 +563,11 @@ melde("=== 6. Schreiben ==="); ist, liest und stimmt ab -- mehr nicht. Das wird von BEIDEN Seiten der Grenze gemessen, sonst beweist es nichts: Eine Schreibsperre, die immer sperrt, ist keine Stufe. */ - const neu = await roh("/workspace/api/bereich/treff", TREFF, { cookie: gast.keks }, + const neu = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks }, { art: "hallo", titel: "GAST-ZU-FRUEH-0001", text: "Hallo zusammen.", datum: jetzt.slice(0, 10) }); ok(neu.code === 403, `wer neu ist, schreibt noch nicht (${neu.code})`); - const lage0 = JSON.parse((await roh("/workspace/api/treff/lage", TREFF, { cookie: gast.keks })).text); + const lage0 = JSON.parse((await roh("/workspace/api/treff/lage", CREW, { cookie: gast.keks })).text); ok(lage0.stufe === "neu", `und die Lage sagt es ihm auch: ${lage0.stufe}`); ok(lage0.schreiben?.treff === false, "der Knopf waere aus"); @@ -567,7 +579,7 @@ melde("=== 6. Schreiben ==="); .run(new Date(Date.now() - 8 * 86400000).toISOString(), idGast); dd.close(); - const a = await roh("/workspace/api/bereich/treff", TREFF, { cookie: gast.keks }, + const a = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks }, { art: "hallo", titel: "GAST-SCHREIBT-8899", text: "Hallo zusammen.", datum: jetzt.slice(0, 10) }); ok(a.code === 200 || a.code === 201, `nach acht Tagen schon (${a.code})`); @@ -578,7 +590,7 @@ melde("=== 6. Schreiben ==="); dd2.prepare("UPDATE personen SET erstellt = ? WHERE id = ?") .run(new Date(Date.now() - 6 * 86400000).toISOString(), idGast); dd2.close(); - const knapp = await roh("/workspace/api/bereich/treff", TREFF, { cookie: gast.keks }, + const knapp = await roh("/workspace/api/bereich/treff", CREW, { cookie: gast.keks }, { art: "hallo", titel: "GAST-ZU-FRUEH-0002", text: "Und?", datum: jetzt.slice(0, 10) }); ok(knapp.code === 403, `sechs Tage reichen nicht (${knapp.code})`); const dd3 = new DatabaseSync(process.env.WORKSPACE_DB); @@ -587,7 +599,7 @@ melde("=== 6. Schreiben ==="); dd3.close(); /* Und auf ein Brett des Teams kommt er auch als "Dabei" nicht. */ - const brettDesTeams = await roh("/workspace/api/bereich/anschlag", TREFF, { cookie: gast.keks }, + const brettDesTeams = await roh("/workspace/api/bereich/anschlag", CREW, { cookie: gast.keks }, { art: Object.keys(BEREICHE.anschlag.arten)[0], titel: "GAST-AM-ANSCHLAG-0001", text: "Ansage", datum: jetzt.slice(0, 10) }); ok(brettDesTeams.code === 403, `aufs Anschlagbrett schreibt nur das Team (${brettDesTeams.code})`); @@ -601,7 +613,7 @@ melde("=== 6. Schreiben ==="); } { /* UND NICHT IN EIN BRETT DES HAUSES. */ - const a = await roh("/workspace/api/bereich/ideen", TREFF, { cookie: gast.keks }, + const a = await roh("/workspace/api/bereich/ideen", CREW, { cookie: gast.keks }, { art: "content", titel: "GAST-IM-HAUS-0000", text: "", datum: jetzt.slice(0, 10) }); ok(a.code !== 200 && a.code !== 201, `aber nicht in ein Brett des Hauses (${a.code})`); } diff --git a/server/rechte.js b/server/rechte.js index 7f22f906..097c9979 100644 --- a/server/rechte.js +++ b/server/rechte.js @@ -300,9 +300,6 @@ export const SEITEN = { export const OHNE_ANMELDUNG = [ "/workspace/index.html", "/workspace/crew-index.html", - /* Die Wand des Treffs (11.09.2026). Dritte Adresse, dritte Wand -- - und die erste, vor der jemand steht, der NICHT zum Team gehoert. */ - "/workspace/treff-index.html", ]; /* ===================================================================== diff --git a/server/treff-adresse.js b/server/treff-adresse.js deleted file mode 100644 index de87588d..00000000 --- a/server/treff-adresse.js +++ /dev/null @@ -1,171 +0,0 @@ -/* ===================================================================== - Die eigene Adresse des Treffs (11.09.2026) - - treff.dogfather-universe.com - - Auftrag (Filipe, 11.09.2026): *"eine community rolle und community - kategorie, wo die community auch dann nur die sieht."* - - --------------------------------------------------------------------- - WARUM EINE DRITTE ADRESSE UND NICHT EINE DRITTE KACHEL - - Drei Gruende, und jeder allein wuerde schon reichen: - - 1. EINE APP JE URSPRUNG. Auf einem Ursprung laesst sich genau EINE - App installieren (w3c/manifest Nr. 1180: verschachtelte Bereiche - sind "strongly not recommended", Chrome haelt sich daran). Team - Dogi hat seine App, der Workspace hat seine. Ohne eigene Adresse - haette die Community gar keine. - - 2. EINE FALSCHE ANMELDUNG DARF NICHT REICHEN. Waere der Treff eine - Kachel auf der Wand des Workspace, traefe jeder Tippfehler in - der Rechteprueung sofort echte Zuschauer. So steht zwischen - einem Mitglied der Community und den Daten der Agentur nicht nur - eine Rollenabfrage, sondern eine Adresse, ein Zwischenschritt - und die Adressregel in sitzungLesen(). Drei Schloesser -- faellt - eines, halten zwei. - - 3. DIE COMMUNITY IST DER ERSTE KREIS VON AUSSEN. Bisher hatte jede - Rolle in diesem Haus einen Vertrag, eine Rechnung oder eine - Absprache. Ein Mitglied des Treffs hat nur einen Code. Was diese - Menschen sehen, ist damit die einzige Stelle, an der ein Fehler - wirklich nach draussen geht. - - --------------------------------------------------------------------- - WAS SICH NICHT AENDERT: der Pfad /workspace/, der Dienst (4100), die - Datenbank, der Code. Getrennt wird ueber den HOSTNAMEN, so wie bei - Workspace und Team Dogi auch. - - --------------------------------------------------------------------- - WAS DIESE DATEI AUSDRUECKLICH NICHT TUT: Rechte vergeben. - - Wer auf welcher Adresse angemeldet sein darf, entscheidet - sitzungPasstZurAdresse() -- aufgerufen in sitzungLesen(), durch das - alle 26 Fachmodule gehen. Was eine Rolle sehen darf, entscheidet die - Rechtetafel in rechte.js. Zwei Orte fuer dieselbe Regel waeren einer - zu viel; der zweite waere der, den beim naechsten Umbau niemand - anfasst. - - --------------------------------------------------------------------- - DASS DER TREFF WIE TEAM DOGI AUSSIEHT, IST ENTSCHIEDEN - - Er benutzt crew-haus.css, die Buehnen von Team Dogi und deren - App-Symbol. Gemessen gaebe es durchaus einen eigenen Farbton - (Tannengruen liegt 60 Lab-Einheiten vom Marineblau entfernt, also - klar unterscheidbar). Er wuerde den Treff aber zu einem DRITTEN Haus - machen, neben Spicy Media und Team Dogi. Er ist kein drittes Haus, - sondern ein zweiter Raum in demselben -- "lila und babyblau ueberall" - (Filipe, 10.09.2026). - - Unterschieden werden die Tueren durch das WORT darauf: Die Buehne der - Zugangswand traegt "DER TREFF" statt "TEAM DOGI" (tools/crew-buehne.mjs, - zwei Werte, ein Werkzeug), und die App heisst "Der Treff". - ===================================================================== */ - -import { - istTreffAdresse, istWorkspacePfad, -} from "./crew-adresse.js"; - -/** - * Express-Zwischenschritt fuer die Adresse des Treffs. - * - * Er laeuft NACH crewWeiche und tut dort dasselbe wie diese auf ihrer - * Adresse -- mit drei Unterschieden: eigenes Manifest, eigene Wand, - * und die Buehnen kommen von Team Dogi statt von der Agentur. - * - * DIE FREMDEN WAENDE SPERRT ER NICHT SELBST. Das macht bereits - * crewWeiche fuer jede Adresse ueber die Wandtafel (istFremdeWand). - * Eine zweite Sperre hier waere dieselbe Regel an zwei Orten. - */ -export function treffWeiche(req, res, next) { - if (!istTreffAdresse(req.get("host"))) return next(); - - /* NICHT INDIZIEREN. Der Kopf steht hier und nicht nur als im - HTML, weil er dann auch fuer Manifest, Symbole, Bilder und jede - Antwort ohne HTML gilt. Bei dieser Adresse wiegt das schwerer als - bei den anderen beiden: Sie wird an Dutzende Menschen - weitergegeben, die nichts unterschrieben haben. Ein Eintrag bei - einer Suchmaschine waere nicht rueckholbar. */ - res.setHeader("X-Robots-Tag", "noindex, nofollow"); - - /* DAS MANIFEST MACHT DIE APP. Alle drei Adressen liefern dieselben - HTML-Dateien aus, in jeder steht dieselbe Manifest-Zeile. Hier - liegt dahinter ein anderes: Name "Der Treff". Sonst hiesse die App - auf dem Startbildschirm eines Mitglieds "Team Dogi" -- der Name - eines Bereichs, zu dem es nicht gehoert. - - Ueber die Adresse und nicht ueber eine zweite Zeile in 30 - HTML-Dateien: Eine zweite Zeile muesste jemand bei jeder neuen - Seite mitschreiben, und wer sie vergisst, bekommt eine Seite, die - still zur falschen App gehoert. */ - if (req.path === "/workspace/app.webmanifest") { - req.url = "/workspace/treff.webmanifest"; - return next(); - } - - /* DIE ZUGANGSWAND. Ohne diese Zeile stuende auf der Adresse der - Community die Wand der Agentur: Spicy-Media-Logo, "Creator - Workspace", fuenf Rollenkacheln mit den Namen Admin, Manager, - Scout, Creator -- die komplette Struktur des Unternehmens, vor der - Anmeldung, fuer jeden, der die Adresse hat. - - ZWEI PFADE, EINE DATEI: "/workspace/" und "/workspace/index.html" - fuehren beide zur Wand, also muessen beide umgebogen werden. Den - zweiten zu vergessen faellt im Alltag nicht auf -- ihn tippt - niemand -- und waere trotzdem genau das Leck von oben. */ - if (req.path === "/workspace/" || req.path === "/workspace/index.html") { - req.url = "/workspace/treff-index.html"; - return next(); - } - - /* DIE BUEHNEN INNEN. Die neun Motive der Agentur zeigen nicht bloss - rotes Licht, sondern das Spicy-Media-Logo selbst -- in `halle` und - `arena` sogar vielfach nebeneinander. Die Fassungen von Team Dogi - liegen laengst da und sind genau dafuer gebaut worden. - - UND ES FLACKERT NICHT: Das Bild wird beim ERSTEN Abruf schon als - das richtige geliefert. Eine Umschaltung per JavaScript haette - erst die Spicy-Buehne gezeigt und dann getauscht. */ - const buehne = /^\/workspace\/assets\/img\/(buehne-[a-z]+-(?:uhd|breit|schmal)\.webp)$/ - .exec(req.path); - if (buehne) { - req.url = `/workspace/assets/img/crew-${buehne[1]}`; - return next(); - } - - /* DAS ZEICHEN IN DER KOPFLEISTE. start.css setzt links neben den - Namen eine Chili -- als CSS-Hintergrund, nicht als Bild im HTML. - Ohne diese Zeile stuende oben links auf jeder Seite des Treffs das - Zeichen von Spicy Media. */ - if (req.path === "/workspace/assets/img/marke-spicy.webp") { - req.url = "/workspace/assets/img/marke-husky.webp"; - return next(); - } - - /* DAS HAUS. Jede Seite laedt als LETZTE Stilvorlage haus.css; auf der - Agenturadresse ist die leer, hier steht dahinter das Farbhaus von - Team Dogi. - - Der Dateiname reist mit einer Fassungsnummer (`?v=…`); die - Bedingung sieht deshalb nur auf den PFAD, nicht auf die ganze - Adresse. Genau daran waere sie sonst gescheitert -- lautlos, denn - ein nicht umgebogener Pfad liefert weiterhin eine gueltige Datei. */ - if (req.path === "/workspace/assets/css/haus.css") { - req.url = "/workspace/assets/css/crew-haus.css" - + (req.url.includes("?") ? req.url.slice(req.url.indexOf("?")) : ""); - return next(); - } - - /* NUR DER WORKSPACE. Auf treff. gibt es die Hauptseite nicht -- sonst - laege die komplette Dogfather-Website ein zweites Mal unter einer - zweiten Adresse (doppelter Inhalt, und ein Weg, die Adresse ueber - eine Suchmaschine zu finden). Alles ausserhalb von /workspace und - /assets geht zur Zugangswand. - - Caddy tut dasselbe bereits mit seiner eigenen Weiche. Dass es hier - ein zweites Mal steht, ist Absicht: Eine Schranke, die nur in einer - Datei ausserhalb des Projekts existiert, faellt beim naechsten - Serverumbau lautlos weg. */ - if (istWorkspacePfad(req.path) || req.path.startsWith("/assets/")) return next(); - return res.redirect(302, "/workspace/"); -} diff --git a/server/workspace.js b/server/workspace.js index 1bda7905..812e6fab 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -32,7 +32,7 @@ import { darfSeite, OHNE_ANMELDUNG, SEITEN, ALLE_ROLLEN } from "./rechte.js"; obwohl workspace-treff.js seinerseits aus dieser Datei importiert. Begruendung im Kopf von treff-tabellen.js. */ import { treffTabellen, treffSperreLesen } from "./treff-tabellen.js"; -import { sitzungPasstZurAdresse, istOhneModiAdresse, istCrewAdresse, istTreffAdresse, istPruefAdresse, AUSSEN_ROLLEN, +import { sitzungPasstZurAdresse, istOhneModiAdresse, istCrewAdresse, istPruefAdresse, AUSSEN_ROLLEN, TEAM_DOGI_ROLLEN } from "./crew-adresse.js"; /* Weitergereicht, damit die Fachmodule sie wie alles andere aus workspace.js beziehen und nicht wissen muessen, wo sie wohnt. */ @@ -100,8 +100,22 @@ const ROLLEN_KACHEL = new Set(["spicy", "admin", "manager", "scout", "creator"]) * * DogFather steht auf BEIDEN Waenden. Er ist die einzige Person, die * in beiden Welten arbeitet; das ist keine Ausnahme von der Trennung, - * sondern ihr Sinn. */ -const CREW_KACHEL = new Set(["admin", "hand", "modi"]); + * sondern ihr Sinn. + * + * VIER, SEIT DER TREFF HIER WOHNT (Entscheidung Filipe, 11.09.2026: + * "das soll keine app fuer sich sein, das soll in der crew seite + * adaptiert werden" -- und auf die Frage, wie die Community durch + * diese Wand kommt: "vierte Kachel Community"). + * + * WAS DAS KOSTET, und es gehoert hierhin und nicht in eine Fussnote: + * Ein Mitglied der Community liest vor seiner Anmeldung die Namen der + * drei Team-Rollen. Die Alternative waere gewesen, die Kachelreihe + * ganz abzuschaffen und allein den Code entscheiden zu lassen -- dann + * haette niemand eine Rolle gesehen, aber Filipes Entscheidung vom + * 10.09. waere wieder umgeworfen worden. Er hat sich fuer die vierte + * Kachel entschieden; der Preis ist damit bewusst bezahlt und nicht + * uebersehen. */ +const CREW_KACHEL = new Set(["admin", "hand", "modi", "gast"]); /** Die Rollen mit einer Kachel auf der Zugangswand von treff. * @@ -114,7 +128,7 @@ const CREW_KACHEL = new Set(["admin", "hand", "modi"]); * Die Menge steht trotzdem hier: Die Anmeldung prueft gegen sie, und * eine Wand ohne Kacheln darf nicht heissen, dass jede Rolle * durchkommt. */ -const TREFF_KACHEL = new Set(["gast"]); + /* Als SQL-Ausdruck fuer ORDER BY. "ORDER BY rolle" waere alphabetisch (admin, creator, manager, scout, spicy) -- also fast genau falsch @@ -3288,7 +3302,7 @@ export function sitzungLesen(req) { (sichtPerson) das auch nicht tut. Wer hier etwas anderes einbaut, macht aus einer Anzeigefrage eine Rechtefrage. */ const haus = istCrewAdresse(req.get?.("host")) ? "crew" - : istTreffAdresse(req.get?.("host")) ? "treff" : "agentur"; + : "agentur"; /* DER AUSSCHLUSS WIRKT HIER -- an derselben Stelle wie alles andere (11.09.2026). @@ -3676,9 +3690,7 @@ workspaceRouter.post("/workspace/api/anmelden", (req, res) => { Wand gerade dran ist. Ein Manager, der crew. erraet und seinen Code eintippt, faellt dadurch in genau dieselbe Antwort wie jemand mit einer erfundenen Rolle. */ - const kachelSatz = istTreffAdresse(req.get("host")) ? TREFF_KACHEL - : istCrewAdresse(req.get("host")) ? CREW_KACHEL - : ROLLEN_KACHEL; + const kachelSatz = istCrewAdresse(req.get("host")) ? CREW_KACHEL : ROLLEN_KACHEL; if (!kachelSatz.has(rolle) || !codeBrauchbar) { db().prepare("INSERT INTO versuche (ip, zeitpunkt) VALUES (?,?)").run(ip, jetzt()); @@ -4865,7 +4877,21 @@ export function protokollLesen(anzahl = 20) { Die Reihenfolge ist egal (beide verkleinern nur), die Trennung nicht. ===================================================================== */ -const HAUS_TEAM_ROLLEN = new Set(["admin", "hand", "modi"]); +/* WER AUF DER TEAM-ADRESSE UEBERHAUPT VORKOMMT. + * + * Die Community steht seit dem 11.09.2026 mit drin (Filipe: "das soll + * keine app fuer sich sein, das soll in der crew seite adaptiert + * werden"). Der Treff wohnt jetzt hier, also muessen seine Mitglieder + * hier auch verwaltet werden koennen -- sonst legt DogFather einen + * Zugang an und er verschwindet im selben Moment aus der Liste. + * + * DAS MACHT SIE NICHT ZU KOLLEGEN. Diese Menge beantwortet nur "wer + * kommt auf dieser Adresse vor". Ob jemand in eine Einladung, eine + * Aufgabe oder einen Chat darf, beantwortet ohneAussen() weiter unten + * -- 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", "modi", "gast"]); function nurHaus(ids, person) { if (person?.haus !== "crew") return ids; @@ -4971,10 +4997,66 @@ function nurEigene(ids, person) { return person?.id ? [person.id] : []; } -export const sichtbareCreatorIds = (person) => nurEigene(nurCreatorIds(nurHaus(ohneVerborgene(sichtbareCreatorIdsRoh(person), person), person)), person); +/* ===================================================================== + WER VON AUSSEN IST, STEHT IN KEINER ARBEITSLISTE (11.09.2026) + ===================================================================== + + nurEigene() beantwortet "was sieht ein Mitglied der Community?". + Diese Huelle beantwortet die andere Richtung: "steht ein Mitglied der + Community in der Liste, die jemand ANDERS bekommt?" + + DER ANLASS: Seit es die Rolle gibt, taucht sie in jeder Liste auf, + die "alle" bedeutet -- und fuer DogFather bedeutet fast jede Liste + "alle" (siehtAlles). Ein Mitglied der Community stand damit in der + Auswahl, wen man zu einem Termin einlaedt, wem man eine Aufgabe gibt + und mit wem man einen Chat anfaengt. Nichts davon ist vorgesehen, es + gab dafuer nie eine Entscheidung, und aufgefallen waere es zum ersten + Mal, wenn jemand versehentlich einen Zuschauer zu einer internen + Besprechung einlaedt. + + WARUM ALS HUELLE UND NICHT IN DEN VIER FUNKTIONEN: Weil sie dann auch + fuer die Listen gilt, die es noch nicht gibt. Dieselbe Ueberlegung + steht schon bei nurHaus() und bei der Modi-Huelle in + workspace-bereiche.js -- und beide Male war der erste Anlauf ein + einzelner geflickter Fall, der den naechsten offen liess. + + NULL HEISST "ALLE" -- und sobald hier etwas herausfaellt, darf es das + nicht mehr heissen. Die Liste wird deshalb AUSGESCHRIEBEN, so wie + ohneVerborgene() und nurHaus() es auch tun. + + EIN FEHLSCHLAG SCHLIESST. Bei einer Datenbankstoerung eine + ungefilterte Liste zurueckzugeben hiesse, die Community genau dann in + die Einladungsauswahl zu setzen, wenn ohnehin gerade etwas nicht + stimmt. Eine leere Liste ist sichtbar falsch, eine zu volle nicht. + + AUSDRUECKLICH NICHT ANGEWENDET auf sichtbarePersonenIds(): Das ist + die Verwaltungsliste, in der DogFather Zugaenge anlegt und Codes neu + setzt. Dort MUSS die Community stehen. Die Einladungsliste leitet + sich zwar davon ab -- aber sie bekommt diese Huelle aussen herum, + und damit ist die Verwaltung offen und die Zusammenarbeit zu. */ +function ohneAussen(ids) { + const rollen = [...AUSSEN_ROLLEN]; + if (!rollen.length) return ids; + try { + const platz = rollen.map(() => "?").join(", "); + const aussen = db().prepare( + `SELECT id FROM personen WHERE rolle IN (${platz})`).all(...rollen).map((z) => z.id); + if (!aussen.length) return ids; + if (ids === null) { + return db().prepare( + `SELECT id FROM personen WHERE rolle NOT IN (${platz})`).all(...rollen).map((z) => z.id); + } + const menge = new Set(aussen); + return ids.filter((i) => !menge.has(i)); + } catch { + return []; + } +} + +export const sichtbareCreatorIds = (person) => ohneAussen(nurEigene(nurCreatorIds(nurHaus(ohneVerborgene(sichtbareCreatorIdsRoh(person), person), person)), person)); export const sichtbarePersonenIds = (person) => nurEigene(nurHaus(ohneVerborgene(sichtbarePersonenIdsRoh(person), person), person), person); -export const betreuerIds = (person) => nurEigene(nurHaus(ohneVerborgene(betreuerIdsRoh(person), person), person), person); -export const einladbareIds = (person) => nurEigene(nurHaus(ohneVerborgene(einladbareIdsRoh(person), person), person), person); +export const betreuerIds = (person) => ohneAussen(nurEigene(nurHaus(ohneVerborgene(betreuerIdsRoh(person), person), person), person)); +export const einladbareIds = (person) => ohneAussen(nurEigene(nurHaus(ohneVerborgene(einladbareIdsRoh(person), person), person), person)); /* SICH SELBST NICHT -- UND ZWAR AUCH DANN NICHT, WENN DIE LISTE AUSGESCHRIEBEN WIRD (10.09.2026). @@ -4996,7 +5078,7 @@ export const einladbareIds = (person) => nurEigene(nurHaus(ohneVerborgene(einlad seit es ueberhaupt jemanden zu verbergen gibt; vorher konnte es ihn nicht geben. */ export const schreibbareIds = (person) => { - const ids = nurHaus(ohneVerborgene(schreibbareIdsRoh(person), person), person); + const ids = ohneAussen(nurHaus(ohneVerborgene(schreibbareIdsRoh(person), person), person)); return ids === null ? null : ids.filter((i) => i !== person?.id); }; -export const pipelineIds = (person) => nurHaus(ohneVerborgene(pipelineIdsRoh(person), person), person); +export const pipelineIds = (person) => ohneAussen(nurHaus(ohneVerborgene(pipelineIdsRoh(person), person), person)); diff --git a/tools/crew-buehne.mjs b/tools/crew-buehne.mjs index 30f21128..ea3f7a51 100644 --- a/tools/crew-buehne.mjs +++ b/tools/crew-buehne.mjs @@ -61,35 +61,6 @@ const SZENE = join(QUELLE, "gate-spicy-dogfather.jpeg"); const DOGI = join(QUELLE, "dogfather-husky.png"); const ZIEL = process.env.BUEHNE_ZIEL || join(WURZEL, "workspace", "assets", "img", "buehne"); -/* ZWEI TUEREN, EIN WERKZEUG (11.09.2026). - - Der Treff bekommt seine eigene Zugangswand, und sie braucht ihr - eigenes Motiv -- nicht weil sie anders aussehen soll, sondern weil - in diesem Bild "TEAM DOGI" EINGEBRANNT ist. Die Community traegt - diesen Namen nicht; auf ihrer Tuer stuende sonst der Name eines - Bereichs, zu dem sie nicht gehoert. - - WARUM KEINE ZWEITE DATEI: Eine Kopie dieser 520 Zeilen haette die - drei Gegenproben mitkopiert -- und beim naechsten Nachbessern waere - genau eine der beiden verbessert worden. Hier aendern sich zwei - Dinge, also sind es zwei Werte und kein zweites Werkzeug. - - DIE FARBE BLEIBT DIESELBE, und das ist eine Entscheidung, keine - Bequemlichkeit: Gemessen liegt der naechstbeste eigene Farbton - (Tannengruen) 60 Lab-Einheiten vom Marineblau entfernt -- er waere - also klar unterscheidbar gewesen. Nur haette der Treff damit wie ein - DRITTES Haus ausgesehen, neben Spicy Media und Team Dogi. Er ist - aber kein drittes Haus, sondern eine zweite Tuer in demselben: - "lila und babyblau ueberall" (Filipe, 10.09.2026). Unterschieden - werden die Tueren durch das WORT darauf, nicht durch die Farbe -- - so, wie man es in einem Gebaeude auch macht. - - node tools/crew-buehne.mjs - WAND_TEXT="DER TREFF" WAND_PRAEFIX=treff-gate node tools/crew-buehne.mjs -*/ -const WAND_TEXT = process.env.WAND_TEXT || "TEAM DOGI"; -const WAND_PRAEFIX = process.env.WAND_PRAEFIX || "crew-gate"; - for (const q of [SZENE, DOGI]) { if (!existsSync(q)) { console.error(`Fehlt: ${q}`); process.exit(1); } } @@ -324,14 +295,14 @@ async function bauen({ breite, ausschnitt = null, guete = 0.92, dunkel = 0.08, g.restore(); } - /* ---- 5. Der Name auf der Tuer --------------------------------- */ + /* ---- 5. "TEAM DOGI" ------------------------------------------- */ const schriftHoehe = Math.round(zh * 0.072); g.save(); g.textAlign = "center"; g.textBaseline = "alphabetic"; g.font = `800 ${schriftHoehe}px "Segoe UI", system-ui, sans-serif`; /* 0,955 lag halb unter dem Bildrand -- im Bildschirmfoto war von - dem Namen die untere Haelfte abgeschnitten. Jetzt so weit + "TEAM DOGI" die untere Haelfte abgeschnitten. Jetzt so weit herauf, dass die Unterlaengen noch auf dem Wasser stehen. */ const ty = zh * 0.915; /* Erst ein kalter Schein darunter, dann die Schrift in Metall -- @@ -339,7 +310,7 @@ async function bauen({ breite, ausschnitt = null, guete = 0.92, dunkel = 0.08, g.shadowColor = "rgba(70,150,220,0.85)"; g.shadowBlur = schriftHoehe * 0.9; g.fillStyle = "rgba(10,25,45,0.9)"; - g.fillText(a.text, mx, ty); + g.fillText("TEAM DOGI", mx, ty); g.shadowBlur = 0; const metall = g.createLinearGradient(0, ty - schriftHoehe, 0, ty); metall.addColorStop(0, "#ffffff"); @@ -347,7 +318,7 @@ async function bauen({ breite, ausschnitt = null, guete = 0.92, dunkel = 0.08, metall.addColorStop(0.55, "#7ea6c6"); metall.addColorStop(1, "#e8f2fa"); g.fillStyle = metall; - g.fillText(a.text, mx, ty); + g.fillText("TEAM DOGI", mx, ty); g.restore(); /* ---- 6. Vignette und ein Hauch Dunkel ------------------------- */ @@ -393,7 +364,7 @@ async function bauen({ breite, ausschnitt = null, guete = 0.92, dunkel = 0.08, dogiKasten, }; }, { szene: szeneDaten, dogi: dogiDaten, breite, ausschnitt, guete, dunkel, vignette, - ohneDogi, tafel: TAFEL, text: WAND_TEXT }); + ohneDogi, tafel: TAFEL }); return { ...roh, puffer: Buffer.from(roh.bild.split(",")[1], "base64") }; } @@ -468,17 +439,17 @@ let gebaut = 0; console.log("\n--- Quer ---"); for (const b of [960, 1280, 1600, 1920, 2560]) { const r = await bauen({ breite: b, guete: 0.93, dunkel: 0.08, vignette: 0.26 }); - writeFileSync(join(ZIEL, `${WAND_PRAEFIX}-${b}.webp`), r.puffer); + writeFileSync(join(ZIEL, `crew-gate-${b}.webp`), r.puffer); gebaut++; if (r.rotAnteil > 0.01) maengel.push( - `${WAND_PRAEFIX}-${b}: noch ${(r.rotAnteil * 100).toFixed(1)} % rote Bildpunkte -- Umfaerbung hat nicht gegriffen`); + `crew-gate-${b}: noch ${(r.rotAnteil * 100).toFixed(1)} % rote Bildpunkte -- Umfaerbung hat nicht gegriffen`); if (r.tafelMittel > 46) maengel.push( - `${WAND_PRAEFIX}-${b}: die Tafel ist zu hell (${r.tafelMittel.toFixed(1)}) -- die Karte braucht einen ruhigen Grund`); + `crew-gate-${b}: die Tafel ist zu hell (${r.tafelMittel.toFixed(1)}) -- die Karte braucht einen ruhigen Grund`); if (r.tafelStreuung > 34) maengel.push( - `${WAND_PRAEFIX}-${b}: in der Tafel ist zu viel Muster (Streuung ${r.tafelStreuung.toFixed(1)})`); + `crew-gate-${b}: in der Tafel ist zu viel Muster (Streuung ${r.tafelStreuung.toFixed(1)})`); - console.log(` ${WAND_PRAEFIX}-${b}.webp`.padEnd(30) + console.log(` crew-gate-${b}.webp`.padEnd(30) + `${(r.puffer.length / 1024).toFixed(0)} KB · ${r.breite}x${r.hoehe}` + ` · Rot ${(r.rotAnteil * 100).toFixed(2)} %` + ` · Tafel ${r.tafelMittel.toFixed(1)}/${r.tafelStreuung.toFixed(1)}`); @@ -500,11 +471,11 @@ for (const b of [440, 580, 900]) { ausschnitt: [0.13, 0.02, 0.32, 0.98], guete: 0.88, dunkel: 0.22, vignette: 0.30, }); - writeFileSync(join(ZIEL, `${WAND_PRAEFIX}-hoch-${b}.webp`), r.puffer); + writeFileSync(join(ZIEL, `crew-gate-hoch-${b}.webp`), r.puffer); gebaut++; if (r.rotAnteil > 0.01) maengel.push( - `${WAND_PRAEFIX}-hoch-${b}: noch ${(r.rotAnteil * 100).toFixed(1)} % rote Bildpunkte`); - console.log(` ${WAND_PRAEFIX}-hoch-${b}.webp`.padEnd(30) + `crew-gate-hoch-${b}: noch ${(r.rotAnteil * 100).toFixed(1)} % rote Bildpunkte`); + console.log(` crew-gate-hoch-${b}.webp`.padEnd(30) + `${(r.puffer.length / 1024).toFixed(0)} KB · ${r.breite}x${r.hoehe}` + ` · Rot ${(r.rotAnteil * 100).toFixed(2)} %`); } diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 22ce2b3a..f376972c 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609111804", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609111836", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609111804", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609111836", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111804", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609111836", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111804", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609111836", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/img/buehne/treff-gate-1280.webp b/workspace/assets/img/buehne/treff-gate-1280.webp deleted file mode 100644 index 8c09a74e..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-1280.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-1600.webp b/workspace/assets/img/buehne/treff-gate-1600.webp deleted file mode 100644 index 9b3882eb..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-1600.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-1920.webp b/workspace/assets/img/buehne/treff-gate-1920.webp deleted file mode 100644 index 2b8990b7..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-1920.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-2560.webp b/workspace/assets/img/buehne/treff-gate-2560.webp deleted file mode 100644 index 49e357cf..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-2560.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-960.webp b/workspace/assets/img/buehne/treff-gate-960.webp deleted file mode 100644 index f636fbc4..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-960.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-hoch-440.webp b/workspace/assets/img/buehne/treff-gate-hoch-440.webp deleted file mode 100644 index b0ea734a..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-hoch-440.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-hoch-580.webp b/workspace/assets/img/buehne/treff-gate-hoch-580.webp deleted file mode 100644 index 5b0f8f10..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-hoch-580.webp and /dev/null differ diff --git a/workspace/assets/img/buehne/treff-gate-hoch-900.webp b/workspace/assets/img/buehne/treff-gate-hoch-900.webp deleted file mode 100644 index 7b8a7fc8..00000000 Binary files a/workspace/assets/img/buehne/treff-gate-hoch-900.webp and /dev/null differ diff --git a/workspace/assets/js/gate.js b/workspace/assets/js/gate.js index 17884fd9..a877e0bd 100644 --- a/workspace/assets/js/gate.js +++ b/workspace/assets/js/gate.js @@ -76,31 +76,6 @@ und zwar ohne Fehlermeldung im Protokoll. */ const ohneKacheln = rollen.length === 0; - /* UND SEIT DEM 11.09.2026 GIBT ES SIE WIEDER -- die Wand des Treffs. - - Dort ist nur EINE Sorte Zugang vorgesehen, und eine Kachelreihe - mit genau einem Eintrag waere keine Auswahl, sondern eine Huerde: - ein Knopf, den man druecken muss, damit der naechste Knopf - funktioniert. - - DER SERVER BRAUCHT DIE ROLLE TROTZDEM. Er vergleicht sie gegen den - Kachelsatz seiner Adresse; kaeme "" an, waere die Antwort dieselbe - wie bei einem falschen Code -- 401, ohne dass irgendwo etwas - kaputt ist. Genau so eine Stelle sucht man lange, denn sie sieht - aus wie ein Tippfehler des Nutzers. - - WARUM DAS AM
STEHT UND NICHT IM SKRIPT: gate.js wird an - ALLE drei Waende ausgeliefert. Eine Fallunterscheidung hier - ("wenn Treff, dann gast") waere eine vierte Stelle, an der die - Adressen aufgezaehlt werden -- und die erste, die beim naechsten - Umbau vergessen wird. Die Wand sagt selbst, wofuer sie da ist. - - EIN LECK IST DAS NICHT: Auf dieser Wand gibt es genau diese eine - Rolle. Der Name verraet nichts, was der Seitenkopf nicht ohnehin - sagt -- die beiden anderen Waende nennen ihre Rollen seit jeher - offen im Quelltext. */ - const VORGABE = document.body.dataset.rolle || ""; - /* ---------- Rolle wählen ------------------------------------------ */ function waehle(rolle, fokus = false) { @@ -186,7 +161,7 @@ braucht ihn dort nicht: Er prueft den verborgenen Zugang VOR dem gewohnten Weg, und ein leerer Name faellt danach in dieselbe Antwort wie ein erfundener. */ - body: JSON.stringify({ rolle: gewaehlt || VORGABE, code: wert }), + body: JSON.stringify({ rolle: gewaehlt || '', code: wert }), credentials: 'same-origin', }); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 9844f795..3de2a79d 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - + @@ -252,10 +252,10 @@ - - - - - + + + + +