From 01151574ae4203edcb299a5c773c7f7190b3638b Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sun, 20 Sep 2026 18:56:03 +0200 Subject: [PATCH] Die rechte Hand verteilt Aufgaben und wechselt Rollen Filipe: "jeder soll sich selber aufgabe vergeben koennen aber nur die rechte hand und dogfather aufgaben an andere verteilen." Und: "ich will dass ich da auch die rollen der leute wechseln kann ohne dass ich denen einen neuen account machen muss ... perfektionier das fuer die rolle dogfather und rechte hand." ---- AUFGABEN VERTEILEN --------------------------------------------- Die Regel gab es schon -- sie fragte `istLeitung`, und darin steht die rechte Hand NICHT. Sie konnte also keine Aufgabe weitergeben. `hand` DORT EINZUTRAGEN WAERE FAHRLAESSIG GEWESEN: `istLeitung` wird an 57 Stellen in 19 Dateien gefragt -- unter anderem im vertraulichen Meldeweg, in der Personenverwaltung und in den Auswertungen ueber Menschen. Das haette in einem Zug ueber Einsicht in fremde Meldungen entschieden, und das hat niemand gewollt. Stattdessen `darfAufgabenVerteilen` -- eine eigene Regel mit eigenem Namen. Man sieht an der Aufrufstelle, worum es geht, und wer sie spaeter aendert, aendert genau diese eine Sache. Wer nicht verteilen darf, bekommt KEINE Absage: Die Aufgabe landet bei ihm selbst. Das ist genau, was Filipe wollte ("jeder soll sich selber aufgabe vergeben koennen") und freundlicher als ein Fehler. ---- ROLLEN WECHSELN ------------------------------------------------ Auch das gab es schon, samt dem wichtigen Satz in der Rueckfrage: "Der Zugangscode bleibt derselbe." Es konnte nur DogFather (und Spicy Media). DREI GRENZEN, JEDE MIT GRUND: * Niemand wird zu "admin" -- unveraendert seit dem 11.09.2026. * Die rechte Hand ernennt keine zweite rechte Hand. Wer jemanden auf die eigene Ebene hebt, vergibt Vertrauen, das ihm nicht gehoert. Und wer eine Rolle UEBER sich aendern koennte, koennte sich selbst befoerdern, indem er zuerst den anderen herabstuft. * Niemand aendert die eigene Rolle -- sonst waere jede Grenze nur ein Umweg. SPICY MEDIA BEHAELT, WAS SIE HATTE. Beim ersten Anlauf waere sie durch die neue Regel ausgesperrt gewesen -- ein Rueckschritt, den ich selbst verursacht haette. Sie steht jetzt ausdruecklich in der Tabelle. ---- UND DIE OBERFLAECHE RECHNET NICHT MEHR SELBST ------------------ Die Rollenwahl nahm die Knoepfe des ANLEGE-Formulars. Fuer die rechte Hand ist das leer -- sie legt niemanden an. Die Wahl waere leer geblieben, das Recht unbenutzbar. Der Server schickt jetzt zwei Listen mit: wozu ich machen darf (`rollen_zum_aendern`) und wessen Rolle ich anfassen darf (`rollen_anfassbar`). Beide aus denselben Funktionen wie die Schranke. Damit kann die Seite weder zu streng sein (ein Recht, das niemand findet) noch zu grosszuegig (ein Knopf, der eine Absage bringt) -- der Fehler vom 10.09.2026, als zwei Listen drei Zeilen auseinander einander widersprachen. Dabei fast hineingelaufen: `ROLLEN_REIHE` ist die SORTIERUNG, und darin fehlt "gast" mit Absicht. Wer sie fuer eine Aufzaehlung nimmt, verliert die Community still -- genau diese Verwechslung hat am 17.09.2026 schon einmal verhindert, dass sich jemand zu "Community" machen liess. ---- AUSSERDEM ------------------------------------------------------ Screen 2: "Unsere Seiten" und "Regeln & Hilfe" haben die Plaetze getauscht -- nur diese zwei. GEPRUEFT: pruef-verteilen 11/0 (neu, inkl. der Abgrenzung "darf verteilen, ist aber NICHT Leitung"), pruef-rollen-anlegen 11/0, pruef-personen-formular 36/0, pruef-personen-liste 33/0, pruef-personen-loeschen 40/0, pruef-rechtetafel 19/0, pruef-treff 72/0. Co-Authored-By: Claude Opus 5 --- server/pruef-verteilen.mjs | 174 ++++++++++++++++++++++++++++++ server/workspace-aufgaben.js | 9 +- server/workspace-personen.js | 19 +++- server/workspace.js | 181 +++++++++++++++++++++++++++----- workspace/anruf-probe.html | 8 +- workspace/app.webmanifest | 8 +- workspace/assets/js/aufgaben.js | 11 +- workspace/assets/js/personen.js | 35 +++++- workspace/aufgaben.html | 30 +++--- workspace/automation.html | 30 +++--- workspace/befinden.html | 28 ++--- workspace/bereich.html | 38 +++---- workspace/bewerben.html | 34 +++--- workspace/bewerbungen.html | 32 +++--- workspace/calls.html | 32 +++--- workspace/chat.html | 34 +++--- workspace/content.html | 32 +++--- workspace/crew-index.html | 10 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 32 +++--- workspace/entwicklung.html | 28 ++--- workspace/hilfe.html | 26 ++--- workspace/index.html | 8 +- workspace/kalender.html | 32 +++--- workspace/leistung.html | 30 +++--- workspace/personen.html | 30 +++--- workspace/profil.html | 34 +++--- workspace/rechte.html | 28 ++--- workspace/report.html | 32 +++--- workspace/scouting.html | 30 +++--- workspace/start.html | 28 ++--- workspace/startcheck.html | 32 +++--- workspace/steckbrief.html | 34 +++--- workspace/talente.html | 30 +++--- workspace/team.html | 28 ++--- workspace/teamlage.html | 28 ++--- workspace/teilen.html | 26 ++--- workspace/treff-moderation.html | 28 ++--- workspace/treff-regeln.html | 28 ++--- workspace/uebersicht.html | 30 +++--- workspace/unsere-seiten.html | 24 ++--- workspace/werdegang.html | 28 ++--- workspace/wissen.html | 32 +++--- 43 files changed, 902 insertions(+), 537 deletions(-) create mode 100644 server/pruef-verteilen.mjs diff --git a/server/pruef-verteilen.mjs b/server/pruef-verteilen.mjs new file mode 100644 index 00000000..d422ed1e --- /dev/null +++ b/server/pruef-verteilen.mjs @@ -0,0 +1,174 @@ +/* ===================================================================== + pruef-verteilen.mjs — Wer darf Aufgaben an andere geben? + + Filipe, 20.09.2026: "jeder soll sich selber aufgabe vergeben koennen + aber nur die rechte hand und dogfather aufgaben an andere verteilen." + + --------------------------------------------------------------------- + WARUM DAS EINE EIGENE PRUEFUNG BRAUCHT + + Die Regel hat ZWEI Seiten, und beide muessen dasselbe sagen: + + * Der Server lehnt ab, wer nicht darf. + * Die Seite zeigt die Felder nur dem, der darf. + + Stimmt nur die erste, sieht die rechte Hand Felder, die nichts + bewirken. Stimmt nur die zweite, kommt jeder durch, der die + Schnittstelle direkt anspricht. Beides ist schon vorgekommen. + + UND DIE GEFAEHRLICHE STELLE: `hand` steht bewusst NICHT in + `istLeitung` -- diese Menge wird an 57 Stellen gefragt, unter + anderem im vertraulichen Meldeweg. Die Pruefung haelt fest, dass + das Verteilen eine EIGENE Regel ist und nicht versehentlich zur + Leitung wird. + + AUFRUF: node server/pruef-verteilen.mjs + ===================================================================== */ + +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { request as httpAnfrage } from "node:http"; +import { portMussFreiSein } from "./helfer-port.mjs"; +import { notbremse } from "./helfer-notbremse.mjs"; + +const PORT = await portMussFreiSein(4433, "pruef-verteilen"); +const ordner = mkdtempSync(join(tmpdir(), "ws-verteilen-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); +process.env.PORT = String(PORT); +process.env.SITE_ACCESS_SECRET = "lokaler-test"; +process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00"; +const express = (await import("express")).default; +const ec = express.response.cookie; +express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); }; +await import("./index.js"); +notbremse(180_000, "pruef-verteilen"); +await new Promise((r) => setTimeout(r, 900)); + +process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); }); +process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); }); + +let fehler = 0, geprueft = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; +const melde = (t) => console.log(t); + +console.log("\n=== Wer darf Aufgaben an andere geben? ==="); + +/* ---- 1. Die Regel selbst, ohne Server ----------------------------- */ +melde("\n=== 1. Die Regel ==="); +{ + const { darfAufgabenVerteilen, istLeitung } = await import("./workspace.js"); + const darf = ["admin", "hand", "spicy", "manager"]; + const nicht = ["modi", "scout", "creator", "gast"]; + ok(darf.every((r) => darfAufgabenVerteilen({ rolle: r })), + `verteilen duerfen: ${darf.join(", ")}`); + ok(nicht.every((r) => !darfAufgabenVerteilen({ rolle: r })), + `und nicht: ${nicht.join(", ")}`); + /* DIE WICHTIGE ABGRENZUNG: Verteilen ist NICHT Leitung. Waere die + rechte Hand in `istLeitung` gerutscht, saehe sie damit auch + vertrauliche Meldungen ueber Menschen -- eine Entscheidung, die + niemand getroffen hat. */ + ok(!istLeitung({ rolle: "hand" }), + "die rechte Hand darf verteilen, ist aber NICHT „Leitung“ — das bleibt getrennt"); + ok(darfAufgabenVerteilen({ rolle: undefined }) === false + && darfAufgabenVerteilen(null) === false, + "Gegenprobe: ohne Rolle darf niemand"); +} + +/* ---- 2. Durch die Schnittstelle ----------------------------------- */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes, createHmac } = await import("node:crypto"); +const jetzt = new Date().toISOString(); +let idDogi, idHand, idModi, idModi2; +{ + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const k = randomBytes(32).toString("hex"); + d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von) VALUES (?,?,?,NULL)" + + " ON CONFLICT(schluessel) DO NOTHING").run("code_kennung_schluessel", k, jetzt); + const anlegen = (name, rolle, code) => { + const salt = randomBytes(16).toString("hex"); + const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex"); + d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, code_kennung, aktiv, erstellt)" + + " VALUES (?,?,?,?,?,?,1,?)") + .run(name, rolle, hash, salt, 32768, createHmac("sha256", k).update(code).digest("hex"), jetzt); + return d.prepare("SELECT last_insert_rowid() AS id").get().id; + }; + idDogi = anlegen("Filipe", "admin", "CODE-A"); + idHand = anlegen("VanVan", "hand", "CODE-H"); + idModi = anlegen("Diene", "modi", "CODE-M"); + idModi2 = anlegen("Ghost", "modi", "CODE-M2"); + d.close(); +} + +const CREW = "crew.dogfather-universe.com"; +function ruf(pfad, keks, methode = "GET", koerper = null) { + return new Promise((fertig, schief) => { + const rumpf = koerper ? JSON.stringify(koerper) : null; + const a = httpAnfrage({ + host: "127.0.0.1", port: PORT, path: pfad, method: methode, + headers: { + Host: CREW, ...(keks ? { Cookie: keks } : {}), + ...(rumpf ? { "Content-Type": "application/json", Origin: `https://${CREW}`, + "Content-Length": Buffer.byteLength(rumpf) } : {}), + }, + }, (antwort) => { + let text = ""; + antwort.on("data", (x) => { text += x; }); + antwort.on("end", () => fertig({ + code: antwort.statusCode, text, + keks: [].concat(antwort.headers["set-cookie"] || []).map((c) => c.split(";")[0]).join("; "), + })); + }); + a.on("error", schief); + if (rumpf) a.write(rumpf); + a.end(); + }); +} +const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } }; +const anmelden = async (rolle, code) => + (await ruf("/workspace/api/anmelden", null, "POST", { rolle, code })).keks; + +melde("\n=== 2. Durch die Schnittstelle ==="); +const kDogi = await anmelden("admin", "CODE-A"); +const kHand = await anmelden("hand", "CODE-H"); +const kModi = await anmelden("modi", "CODE-M"); +ok(!!kDogi && !!kHand && !!kModi, "DogFather, rechte Hand und ein Modi sind angemeldet"); + +/* Sagt der Server es auch der Seite? */ +for (const [name, keks, soll] of [["DogFather", kDogi, true], ["rechte Hand", kHand, true], ["Modi", kModi, false]]) { + const ich = json(await ruf("/workspace/api/ich", keks)); + ok(ich.darf_verteilen === soll, + `„${name}“ bekommt darf_verteilen = ${ich.darf_verteilen} (erwartet ${soll})`); +} + +/* Und die Tat: eine Aufgabe FUER JEMAND ANDEREN. */ +async function anlegenFuer(keks, zielId, titel) { + const a = await ruf("/workspace/api/aufgaben", keks, "POST", + { titel, verantwortlich_id: zielId, prioritaet: "mittel" }); + if (a.code !== 201) return { code: a.code, verant: null }; + const d = new DatabaseSync(process.env.WORKSPACE_DB); + const z = d.prepare("SELECT verantwortlich_id FROM aufgaben WHERE id = ?").get(json(a).id); + d.close(); + return { code: a.code, verant: z?.verantwortlich_id ?? null }; +} + +const vonHand = await anlegenFuer(kHand, idModi, "Von der rechten Hand an Diene"); +ok(vonHand.code === 201 && vonHand.verant === idModi, + `die rechte Hand gibt eine Aufgabe an Diene (${vonHand.code}, liegt bei ${vonHand.verant})`); + +const vonDogi = await anlegenFuer(kDogi, idModi2, "Von DogFather an Ghost"); +ok(vonDogi.code === 201 && vonDogi.verant === idModi2, + `DogFather ebenso (${vonDogi.code}, liegt bei ${vonDogi.verant})`); + +/* DIE ENTSCHEIDENDE GEGENPROBE: Ein Modi versucht dasselbe. Die + Aufgabe wird NICHT abgelehnt -- sie landet bei ihm selbst. Das ist + freundlicher als ein Fehler und genau das, was Filipe wollte: + "jeder soll sich selber aufgabe vergeben koennen". */ +const vonModi = await anlegenFuer(kModi, idModi2, "Versuch eines Modi"); +ok(vonModi.code === 201 && vonModi.verant === idModi, + `Gegenprobe: ein Modi kann es versuchen — die Aufgabe landet bei ihm selbst ` + + `(${vonModi.verant}, nicht ${idModi2})`); + +console.log(`\n${fehler ? fehler + " FEHLER" : "Alles in Ordnung"} — ${geprueft} Prüfungen\n`); +try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ } +process.exit(fehler ? 1 : 0); diff --git a/server/workspace-aufgaben.js b/server/workspace-aufgaben.js index f2aea738..f17c82e8 100644 --- a/server/workspace-aufgaben.js +++ b/server/workspace-aufgaben.js @@ -10,7 +10,7 @@ import express from "express"; import { - db, protokolliere, echteIp, sitzungLesen, betreutWo, istLeitung, ohneDogFather, heuteLokal, ROLLEN_SORTIERUNG, ROLLEN_GRUPPE, betreuteIds, + db, protokolliere, echteIp, sitzungLesen, betreutWo, istLeitung, darfAufgabenVerteilen, ohneDogFather, heuteLokal, ROLLEN_SORTIERUNG, ROLLEN_GRUPPE, betreuteIds, externPruefen, externSql, sichtbarePersonenIds, sichtbareCreatorIds, MODI_KATEGORIEN, kategorienFuer, kategoriePersonen, KANAELE, kanaeleFuer, @@ -742,7 +742,12 @@ aufgabenRouter.post("/workspace/api/aufgaben", gleicheHerkunft, (req, res) => { /* Wer nicht Management ist, darf ausschließlich für sich selbst anlegen -- egal, was im Aufruf steht. */ - if (!istLeitung(req.person)) { + /* VERTEILEN DARF NUR, WER ES DARF -- alle anderen legen die + Aufgabe auf sich selbst, egal was im Aufruf steht. Seit dem + 20.09.2026 gehoert die rechte Hand dazu (darfAufgabenVerteilen), + ein Modi nicht: Er kann sich Aufgaben notieren, aber keine an + andere geben. */ + if (!darfAufgabenVerteilen(req.person)) { aus.creator_id = req.person.rolle === "creator" ? req.person.id : null; aus.verantwortlich_id = req.person.id; } diff --git a/server/workspace-personen.js b/server/workspace-personen.js index 5864d969..313848c8 100644 --- a/server/workspace-personen.js +++ b/server/workspace-personen.js @@ -19,7 +19,7 @@ import express from "express"; naechsten Umzug stehen bleibt. */ import { anmeldeAdresseFuer } from "./crew-adresse.js"; import { - db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtModis, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, darfRollenWechseln, hausBedingung, + db, protokolliere, echteIp, sitzungLesen, personAnlegen, codeNeu, sitzungToken, personSperren, betreuungSetzen, scoutZuteilungSetzen, istLeitung, istDogFather, siehtModis, siehtAlles, ROLLEN_SORTIERUNG, ROLLEN_REIHE, istSpicy, verborgeneIds, TEAM_DOGI_ROLLEN, darfAnlegen, darfRollenWechseln, darfRolleAendern, rollenZumAendern, hausBedingung, } from "./workspace.js"; import { sicherungJetzt } from "./workspace-sicherung.js"; @@ -886,6 +886,23 @@ personenRouter.put("/workspace/api/verwaltung/personen/:id/rolle", gleicheHerkun const person = db().prepare("SELECT id, name, rolle, aktiv FROM personen WHERE id = ?").get(id); if (!person) return res.status(404).json({ fehler: "nicht_gefunden" }); + /* ==== DARF ICH DIESEN MENSCHEN ANFASSEN? (20.09.2026) ========= + + Seit die rechte Hand Rollen aendern darf, genuegt "ist + angemeldet und kam durch die Schranke" nicht mehr. Die Regel + steht in darfRolleAendern() -- an EINER Stelle, damit die + Oberflaeche und der Server nicht auseinanderlaufen. + + Sie beantwortet drei Fragen auf einmal: nicht die eigene Rolle, + nicht DogFather, und fuer die rechte Hand auch keine zweite + rechte Hand. */ + if (!darfRolleAendern(req.person, person)) { + return res.status(403).json({ fehler: "Diese Rolle darfst du nicht ändern." }); + } + if (!rollenZumAendern(req.person).includes(rolle)) { + return res.status(403).json({ fehler: "Diese Rolle vergibst du nicht." }); + } + /* Dieselbe Auskunft wie beim Anlegen: Wer eine Rolle nicht vergeben darf, darf sie auch nicht zuweisen. Sonst waere das Zuweisen der bequemere Weg an der Regel vorbei. */ diff --git a/server/workspace.js b/server/workspace.js index 89b67e61..f813f210 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -212,6 +212,93 @@ const LEITUNG = new Set(["spicy", "admin", "manager"]); export const istLeitung = (person) => !!person && LEITUNG.has(person.rolle); export const istDogFather = (person) => !!person && person.rolle === "admin"; +/* ===== WER DARF AUFGABEN AN ANDERE VERTEILEN? (20.09.2026) ========== + + Filipe: "jeder soll sich selber aufgabe vergeben koennen aber nur die + rechte hand und dogfather aufgaben an andere verteilen." + + WARUM EINE EIGENE REGEL UND NICHT `hand` IN DIE LEITUNG: + `istLeitung` wird an 57 Stellen in 19 Dateien gefragt -- unter + anderem im vertraulichen Meldeweg, in der Personenverwaltung und in + den Auswertungen ueber Menschen. Die rechte Hand dort ueberall + mitzunehmen waere eine Entscheidung ueber Einsicht in fremde + Meldungen, und die hat niemand getroffen. + + Verteilen ist eine Frage fuer sich. Sie bekommt deshalb ihren + eigenen Namen: Man sieht an der Aufrufstelle, worum es geht, und + wer sie spaeter aendert, aendert genau diese eine Sache. + + spicy und manager bleiben drin: Auf der Agenturseite verteilen sie + seit jeher Aufgaben, und daran soll sich nichts aendern. */ +export const darfAufgabenVerteilen = (person) => + istLeitung(person) || person?.rolle === "hand"; + +/* ===== WER DARF EINE ROLLE AENDERN -- UND ZU WELCHER? (20.09.2026) == + + Filipe: "ich will dass ich da auch die rollen der leute wechseln kann + ohne dass ich denen einen neuen account machen muss mit einem neuen + code. einfach rolle wechseln und gut ist. perfektionier das fuer die + rolle dogfather und rechte hand. nur die sollen das machen koennen." + + Das Wechseln selbst gab es schon -- der Zugangscode bleibt dabei + derselbe, das stand auch in der Rueckfrage. Es konnte nur DogFather. + + DREI GRENZEN, UND JEDE HAT EINEN GRUND: + + (1) NIEMAND WIRD ZU "admin". Das gilt seit dem 11.09.2026 fuer alle, + auch fuer DogFather selbst -- siehe ANLEGBAR. Hier aendert sich + daran nichts. + + (2) DIE RECHTE HAND FASST KEINE ROLLE AN, DIE UEBER ODER NEBEN IHR + STEHT. Sie kann also einen Modi zur Community machen und + umgekehrt -- aber keinen DogFather antasten und keine zweite + rechte Hand ernennen. Wer jemanden auf die eigene Ebene hebt, + vergibt Vertrauen, das ihm nicht gehoert; das bleibt bei + DogFather. Und wer eine Rolle UEBER sich aendern koennte, + koennte sich selbst befoerdern, indem er zuerst den anderen + herabstuft. + + (3) NIEMAND AENDERT DIE EIGENE ROLLE. Sonst waere jede Grenze oben + nur ein Umweg: erst sich selbst hochstufen, dann alles duerfen. + + Was sie danach vergeben darf, steht in `rollenZumAendern` -- an + EINER Stelle, damit die Oberflaeche keine zweite Liste fuehrt. Genau + daran ist es am 10.09.2026 schon einmal gescheitert. */ +const ROLLEN_ZUM_AENDERN = { + /* DogFather: alles, was er auch anlegen darf. */ + admin: null, // null = dieselbe Liste wie ANLEGBAR + /* SPICY MEDIA STAND HIER SCHON, BEVOR ES DIESE TABELLE GAB -- sie + durfte Rollen wechseln, seit es den Weg gibt. Sie muss deshalb + drinstehen, sonst nimmt die neue Regel ihr etwas weg, das niemand + ihr wegnehmen wollte. `null` heisst: dasselbe wie beim Anlegen, + also Agenturrollen. */ + spicy: null, + hand: ["modi", "gast"], +}; + +/** Welche Rollen darf diese Person an ANDERE vergeben? */ +export function rollenZumAendern(person) { + if (!person) return []; + const eigen = ROLLEN_ZUM_AENDERN[person.rolle]; + if (eigen === undefined) return []; + return eigen === null ? darfAnlegen(person) : [...eigen]; +} + +/** Darf diese Person die Rolle DIESES Menschen anfassen? */ +export function darfRolleAendern(person, ziel) { + if (!person || !ziel) return false; + if (person.id === ziel.id) return false; // (3) + if (!rollenZumAendern(person).length) return false; + if (person.rolle === "admin") return ziel.rolle !== "admin"; + /* Spicy Media wie bisher: das ganze Haus ausser DogFather. */ + if (person.rolle === "spicy") return ziel.rolle !== "admin"; + if (person.rolle === "hand") { + /* (2): nur unter sich -- nicht admin, nicht eine andere hand. */ + return ziel.rolle !== "admin" && ziel.rolle !== "hand"; + } + return false; +} + /* ======================================================================= WER DARF WEN ANLEGEN — die einzige Liste dazu (10.09.2026) @@ -335,8 +422,20 @@ export function darfAnlegen(person) { * wechseln" und "darf DIESE Rolle vergeben" sind zwei Fragen, und * ihre Antworten laufen auseinander, sobald eine Rolle dazukommt. */ +/* WER DARF DEN WEG "Rolle aendern" UEBERHAUPT BETRETEN? + * + * DIE RECHTE HAND KAM AM 20.09.2026 DAZU. Filipe: "perfektionier das + * fuer die rolle dogfather und rechte hand. nur die sollen das machen + * koennen." + * + * DIESE ZEILE OEFFNET NUR DIE TUER. Wen sie anfassen darf und wozu, + * entscheidet darfRolleAendern() / rollenZumAendern() weiter oben -- + * dort steht auch, warum die rechte Hand keine zweite rechte Hand + * ernennen kann. Zwei getrennte Fragen, zwei getrennte Regeln: Wer + * hier etwas aendert, oeffnet keine Grenze versehentlich mit. */ export const darfRollenWechseln = (person) => - !!person && (person.rolle === "admin" || person.rolle === "spicy"); + !!person && (person.rolle === "admin" || person.rolle === "spicy" + || person.rolle === "hand"); /* WER FUEHRT TEAM DOGI? (10.09.2026) * @@ -1061,27 +1160,8 @@ const TREFF_BEREICHE = [ zeichen: "content", ton: 30, ziel: "bereich.html?b=wunsch", szene: "portal" }, { ...TREFF_GRUPPE, name: "Highlights", unter: "Eure Clips und Bilder", zeichen: "live", ton: 29, ziel: "bereich.html?b=highlight", szene: "arena" }, - { ...TREFF_GRUPPE, name: "Regeln & Hilfe", unter: "Wie es hier l\u00e4uft", - /* ZEIGT JETZT AUF DIE SEITE, DIE DIE ANTWORT HAT (19.09.2026). - - Sie fuehrte auf `bereich.html?b=regeln` -- ein Brett, auf das die - Leitung Regeln eintragen KANN und das am ersten Tag leer ist - ("Hier stehen die Regeln des Treffs -- sobald sie eingetragen - sind."). - - Die Regeln, die es WIRKLICH gibt, stehen auf `treff-regeln.html`: - Begruessung, die fuenf Regeln, die drei Stufen, das Mindestalter, - was bei Aerger zu tun ist. Diese Seite war von genau EINER Stelle - aus erreichbar -- einem kleinen Textlink unter der Unterzeile - einer Brettseite. Keine der elf Kacheln fuehrte hin. - - Damit war die einzige "So laeuft das hier"-Seite des Hauses fuer - einen neuen Zuschauer praktisch unsichtbar, waehrend die Kachel, - die ihren Namen trug, auf eine leere Liste zeigte. - - Das Brett gibt es weiter; treff-regeln.html verlinkt darauf. Ein - Ort fuer die Frage, nicht zwei. */ - zeichen: "schutz", ton: 28, ziel: "treff-regeln.html", szene: "studio" }, + { ...TREFF_GRUPPE, name: "Unsere Seiten", unter: "Website, Shop & dein Rabatt", + zeichen: "hinaus", ton: 38, ziel: "unsere-seiten.html", szene: "portal" }, { ...TREFF_GRUPPE, name: "Mitmachen", unter: "Wie du dazugeh\u00f6ren kannst", zeichen: "personen", ton: 32, ziel: "bereich.html?b=mitmachen", szene: "garage" }, /* UNSERE SEITEN (19.09.2026) -- die einzige Kachel, die HINAUSFUEHRT. @@ -1104,8 +1184,27 @@ const TREFF_BEREICHE = [ Kachel sind es fuer die Community 7 + 1 Kacheln = 9 Plaetze, also genau drei volle Reihen. Vorher waren es acht Plaetze und damit eine Luecke am Ende. Sie passt also nicht nur inhaltlich. */ - { ...TREFF_GRUPPE, name: "Unsere Seiten", unter: "Website, Shop & dein Rabatt", - zeichen: "hinaus", ton: 38, ziel: "unsere-seiten.html", szene: "portal" }, + { ...TREFF_GRUPPE, name: "Regeln & Hilfe", unter: "Wie es hier l\u00e4uft", + /* ZEIGT JETZT AUF DIE SEITE, DIE DIE ANTWORT HAT (19.09.2026). + + Sie fuehrte auf `bereich.html?b=regeln` -- ein Brett, auf das die + Leitung Regeln eintragen KANN und das am ersten Tag leer ist + ("Hier stehen die Regeln des Treffs -- sobald sie eingetragen + sind."). + + Die Regeln, die es WIRKLICH gibt, stehen auf `treff-regeln.html`: + Begruessung, die fuenf Regeln, die drei Stufen, das Mindestalter, + was bei Aerger zu tun ist. Diese Seite war von genau EINER Stelle + aus erreichbar -- einem kleinen Textlink unter der Unterzeile + einer Brettseite. Keine der elf Kacheln fuehrte hin. + + Damit war die einzige "So laeuft das hier"-Seite des Hauses fuer + einen neuen Zuschauer praktisch unsichtbar, waehrend die Kachel, + die ihren Namen trug, auf eine leere Liste zeigte. + + Das Brett gibt es weiter; treff-regeln.html verlinkt darauf. Ein + Ort fuer die Frage, nicht zwei. */ + zeichen: "schutz", ton: 28, ziel: "treff-regeln.html", szene: "studio" }, ]; /* MELDUNGEN & MASSNAHMEN -- FUER DAS TEAM, NICHT FUER DIE COMMUNITY @@ -5249,6 +5348,29 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { `ich.rolle === 'admin'`, und dieselbe Zeile schaltete auch das Loeschen frei; die beiden gehoeren nicht zusammen. */ darf_rollen_wechseln: darfRollenWechseln(person), + /* ZWEI LISTEN STATT EINER RECHNUNG IM BROWSER (20.09.2026). + + Die Rollenwahl nahm bisher die Knoepfe des Anlege-Formulars -- + also `darf_anlegen`. Fuer die rechte Hand ist das leer (sie legt + niemanden an), und damit waere die Wahl leer geblieben, obwohl + sie Rollen aendern darf. Ein Recht, das man hat und nicht + ausueben kann, ist keines. + + `rollen_zum_aendern` = wozu darf ich machen + `rollen_anfassbar` = wessen Rolle darf ich ueberhaupt anfassen + + Beide aus denselben Funktionen, die auch die Route fragt. Die + Oberflaeche fuehrt damit keine eigene Liste mehr -- genau daran + ist es am 10.09.2026 schon einmal gescheitert, als zwei Listen + drei Zeilen auseinander einander widersprachen. */ + rollen_zum_aendern: rollenZumAendern(person), + /* [...ROLLEN] UND NICHT ROLLEN_REIHE: Die Reihe ist die + SORTIERUNG, und in ihr fehlt "gast" mit Absicht. Wer sie hier + nimmt, verliert die Community stillschweigend -- genau diese + Verwechslung hat am 17.09.2026 schon einmal dafuer gesorgt, dass + sich niemand zu "Community" machen liess. */ + rollen_anfassbar: [...ROLLEN].filter((r) => + darfRolleAendern(person, { id: -1, rolle: r })), /* OB DER CHAT-KNOPF IN DER KOPFLEISTE ERSCHEINT (18.09.2026). Er wurde auf JEDER Seite gebaut. Fuer ein Mitglied der Community @@ -5280,6 +5402,17 @@ workspaceRouter.get("/workspace/api/ich", (req, res) => { Rechnungen waeren die Stelle, an der die Seite ein Telefon anbietet, das der Server ablehnt. */ darf_anrufen: darfTelefonieren(person), + /* DARF ICH AUFGABEN AN ANDERE GEBEN? (20.09.2026) + + AUSDRUECKLICH GESAGT, NICHT ERRATEN. Das Aufgabenbrett fragte + bisher `LEITUNG.has(ich.rolle)` -- eine zweite Fassung derselben + Regel im Browser. Seit die rechte Hand verteilen darf, waere sie + die falsche gewesen: Der Server haette es erlaubt, die Seite + haette die Felder nicht gezeigt. Eine Berechtigung, die man + hat und nicht sieht, ist keine. + + Aus DERSELBEN Funktion, die auch die Route benutzt. */ + darf_verteilen: darfAufgabenVerteilen(person), /* DIE KACHELN, WENN SIE NICHT IM BROWSER STEHEN DUERFEN. Fuer die fuenf bekannten Rollen steht hier `null`, und die diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 16513e64..1d9df34a 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`. --> - - + + - +