Spicy sieht DogFathers Sachen nicht mehr -- und sechs Fehler dazu
VIER DINGE AUS DEN BILDSCHIRMFOTOS, jedes ein echter Fehler:
1. SPICY MEDIA SAH DOGFATHERS DATEN. Beim ersten Anlauf bekam die Rolle
dieselbe Regel wie DogFather (`1=1`) -- damit stimmte der Ueberblick
ueber Manager, Scouts und Creator, und nebenbei standen seine eigenen
Termine, Aufgaben, Dateien und Eintraege mit drin. Jetzt gibt es
ohneDogFather() als EINE Stelle dafuer: Zwei Spalten werden geprueft,
`creator_id` (um wen geht es) und `erstellt_von` (wer hat es
geschrieben) -- ein Termin, den er sich selbst anlegt, haengt nur an
der zweiten. `IS NULL OR NOT IN` und nicht bloss `NOT IN`: In SQL ist
`NULL NOT IN (...)` weder wahr noch falsch, die Zeile fiele
stillschweigend heraus.
2. "PERSOENLICHER ZUGANGSCODE · undefined" auf der Anmeldeseite. Eine
zweite Namensliste im Browser, die beim Hinzufuegen der Rolle
niemand gepflegt hat. Ein unbekannter Schluessel faellt jetzt auf
sich selbst zurueck statt auf `undefined` -- haesslich, aber
sichtbar.
3. SPICY KAM NICHT IN DIE PERSONENVERWALTUNG. Der Server liess sie
herein, das Skript warf sie wieder hinaus: zwei Listen fuer dieselbe
Frage, gepflegt wurde nur die erste.
4. DIE ANMELDEKARTE WAR ZU KLEIN FUER FUENF ROLLEN. Der Kommentar an
genau dieser Stelle warnt woertlich davor -- und ich habe getan,
wovor er warnt: eine Rolle eingefuegt und die Zahl daneben nicht
angefasst. Gemessen: Inhalt 573 px in einer 526 px hohen Tafel, die
Fusszeile stand unter dem Rahmen. Schrift kleiner half nicht (sie lag
schon auf dem Anschlag), also sind die Abstaende an neun Stellen
enger. Nachgemessen auf fuenf Groessen: passt ueberall.
DIE ZEIT WAR WIRKLICH FALSCH. Nicht nur in meinem Satz: `datum()` in
personen.js schnitt die ISO-Zeichenkette ab -- und die ist UTC. Im
Protokoll stand 03:00, wo 05:00 war. Das Tueckische daran ist, dass es
nie kaputt aussieht: Eine Uhrzeit ist immer plausibel.
PROFILBILDER WERDEN JETZT GEZEICHNET. Der Server lieferte sie seit
gestern mit, gezeichnet wurden sie nirgends -- deshalb aenderte sich
nichts. Jetzt in der Personenauswahl (Aufgaben, Termine, Dateien,
Bereiche), in der Gespraechsliste und an jeder Nachricht. Der
Anfangsbuchstabe bleibt als Unterlage LIEGEN: Faellt das Bild aus, steht
dort weiter etwas Sinnvolles.
DER CHAT: Gesichter mit Rollenfarbe, Blasen mit Richtung (die erste
einer Folge eckig, die naechsten rund -- so sieht man, wo ein Gedanke
anfaengt), das offene Gespraech mit Schiene, Ungelesenes hervorgehoben,
und das Eingabefeld in einer eigenen Leiste, die sich beim Schreiben
hebt.
ZWEI EIGENE PATZER, beide von Pruefungen gefunden:
- `o is not defined`: Mein Suchmuster hat die zwei Zeilen fuer das
Bild ans DATEIENDE gesetzt statt in die Schleife -- und in
kalender.js an einen <span> statt ans <option>. Gemeldet von
pruef-sicht, das die Browserkonsole mitliest.
- Ein Kommentar mit `bild` in schraegen Anfuehrungszeichen stand INNEN
in einer Vorlagenzeichenkette und hat sie geschlossen. Die halbe SQL
wurde zu Programmtext.
Dazu: `.chat-neu` gibt es nicht (heisst `.chat__eingabe`), und der
Rollenknopf hatte fuer den Manager keinen sichtbaren Fokus -- ein
box-shadow wird von `overflow: hidden` abgeschnitten, ein outline nicht.
Gruen: spicy (43), creator-anlegen (29), personen-liste (33), sicht (48),
chat (48), chat-optik (24), buehne (38), struktur (32), css-klassen (15),
handy (59), breiten (23), formulare (19), start-ansicht (136),
lesbarkeit (14), barrierefrei (18), tempo (8), kopf-messen (3),
glocke (26), haerte (20), manager-sicht (43), alle-wege (19),
aufgabenbrett (44), kalender (84).
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -326,6 +326,70 @@ const ruf = async (art, weg, keks, koerper) => {
|
||||
ok(rein.status === 403 || rein.status === 404,
|
||||
`und kommt auch mit der Raumnummer nicht hinein (HTTP ${rein.status})`);
|
||||
}
|
||||
{
|
||||
/* ===================================================================
|
||||
WAS DOGFATHER GEHOERT, SIEHT SPICY MEDIA NICHT (07.09.2026)
|
||||
|
||||
Filipe: "wieso sieht die spicy rolle meine daten die ich habe und
|
||||
speichere, meins sollen die nicht sehen von der rolle dogfather."
|
||||
|
||||
Beim ersten Anlauf bekam Spicy Media dieselbe Regel wie DogFather
|
||||
(`1=1`) -- damit stimmte der Ueberblick und nebenbei standen seine
|
||||
eigenen Termine, Aufgaben, Dateien und Eintraege mit drin.
|
||||
|
||||
Geprueft wird mit ZWEI Datensaetzen je Art: einem von DogFather und
|
||||
einem von Max. Mit nur einem waere jedes Ergebnis mehrdeutig -- eine
|
||||
leere Liste koennte auch heissen, dass die Regel ALLES ausblendet.
|
||||
Genau das ist der Unterschied zwischen "sieht das Richtige nicht"
|
||||
und "sieht gar nichts". */
|
||||
/* ORTSZEIT, nicht UTC. `toISOString().slice(0,10)` liegt zwischen
|
||||
Mitternacht und zwei Uhr einen Tag daneben -- die Pruefung waere
|
||||
dann nachts rot, ohne dass sich etwas geaendert haette. Gefunden
|
||||
von pruef-struktur, das genau danach sucht. */
|
||||
const jetztHier = new Date();
|
||||
const zz = (n) => String(n).padStart(2, "0");
|
||||
const heute = `${jetztHier.getFullYear()}-${zz(jetztHier.getMonth() + 1)}-${zz(jetztHier.getDate())}`;
|
||||
await ruf("POST", "/workspace/api/bereich/technik", keksDogi,
|
||||
{ art: "problem", titel: "DogFathers Notiz", datum: heute });
|
||||
await ruf("POST", "/workspace/api/bereich/technik", keksMax,
|
||||
{ art: "problem", titel: "Max seine Notiz", datum: heute, creator_id: idLuna });
|
||||
|
||||
const { daten } = await ruf("GET", "/workspace/api/bereich/technik", keksSpicy);
|
||||
const titel = (daten?.eintraege || []).map((e) => e.titel);
|
||||
ok(!titel.includes("DogFathers Notiz"),
|
||||
`DogFathers Eintrag ist fuer Spicy Media unsichtbar (sie sieht: ${titel.join(", ") || "nichts"})`);
|
||||
ok(titel.includes("Max seine Notiz"),
|
||||
"Max seiner steht aber da -- die Regel blendet nicht einfach alles aus");
|
||||
|
||||
/* Dasselbe fuer Aufgaben und Termine -- drei Module, drei Wege, und
|
||||
jeder haette einzeln vergessen werden koennen. */
|
||||
await ruf("POST", "/workspace/api/aufgaben", keksDogi, { titel: "DogFathers Aufgabe" });
|
||||
await ruf("POST", "/workspace/api/aufgaben", keksMax,
|
||||
{ titel: "Max seine Aufgabe", creator_id: idLuna });
|
||||
const auf = await ruf("GET", "/workspace/api/aufgaben", keksSpicy);
|
||||
const aTitel = (auf.daten?.aufgaben || []).map((a) => a.titel);
|
||||
ok(!aTitel.includes("DogFathers Aufgabe"),
|
||||
`auch seine Aufgabe nicht (sie sieht: ${aTitel.join(", ") || "nichts"})`);
|
||||
ok(aTitel.includes("Max seine Aufgabe"), "Max seine schon");
|
||||
|
||||
await ruf("POST", "/workspace/api/termine", keksDogi,
|
||||
{ titel: "DogFathers Termin", art: "termin", beginn: heute + "T20:00", dauer_min: 30 });
|
||||
await ruf("POST", "/workspace/api/termine", keksMax,
|
||||
{ titel: "Max sein Termin", art: "termin", beginn: heute + "T21:00", dauer_min: 30,
|
||||
creator_id: idLuna });
|
||||
const term = await ruf("GET", `/workspace/api/termine?von=${heute}&bis=${heute}`, keksSpicy);
|
||||
const tTitel = (term.daten?.termine || []).map((t) => t.titel);
|
||||
ok(!tTitel.includes("DogFathers Termin"),
|
||||
`und seinen Termin nicht (sie sieht: ${tTitel.join(", ") || "nichts"})`);
|
||||
ok(tTitel.includes("Max sein Termin"), "Max seinen schon");
|
||||
|
||||
/* GEGENPROBE: DogFather selbst sieht weiterhin BEIDE. Ohne sie
|
||||
bewiesen die Zeilen oben nur, dass irgendetwas gefiltert wird. */
|
||||
const beiDogi = await ruf("GET", "/workspace/api/bereich/technik", keksDogi);
|
||||
const dTitel = (beiDogi.daten?.eintraege || []).map((e) => e.titel);
|
||||
ok(dTitel.includes("DogFathers Notiz") && dTitel.includes("Max seine Notiz"),
|
||||
`DogFather sieht weiterhin beide (${dTitel.length})`);
|
||||
}
|
||||
{
|
||||
/* Und DogFather bleibt der Einzige mit dem letzten Wort. */
|
||||
const { status } = await ruf("GET", "/workspace/api/verwaltung/personen", keksDogi);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import express from "express";
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, istLeitung, heuteLokal, ROLLEN_SORTIERUNG, betreuteIds,
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, istLeitung, ohneDogFather, heuteLokal, ROLLEN_SORTIERUNG, betreuteIds,
|
||||
externPruefen, externSql, sichtbarePersonenIds, sichtbareCreatorIds,
|
||||
} from "./workspace.js";
|
||||
|
||||
@@ -58,6 +58,13 @@ export function sichtbar(person) {
|
||||
(01.09.2026) -- und ausdruecklich NUR er. */
|
||||
case "admin":
|
||||
return { wo: "1=1", werte: [] };
|
||||
/* SPICY MEDIA sieht alles ausser dem, was DogFather gehoert
|
||||
(07.09.2026). Die Bedingung kommt aus workspace.js -- eine
|
||||
abgeschriebene Fassung waere die Stelle, an der es beim naechsten
|
||||
Umbau wieder durchsickert. */
|
||||
case "spicy":
|
||||
return { wo: ohneDogFather("a", ["creator_id", "verantwortlich_id", "erstellt_von"]),
|
||||
werte: [] };
|
||||
case "creator":
|
||||
return {
|
||||
wo: "(a.creator_id = ? OR a.verantwortlich_id = ? OR a.erstellt_von = ?)",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import express from "express";
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles,
|
||||
db, protokolliere, echteIp, sitzungLesen, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, istSpicy, ohneDogFather,
|
||||
externPruefen, externSql,
|
||||
} from "./workspace.js";
|
||||
|
||||
@@ -262,6 +262,7 @@ export function sichtbar(person) {
|
||||
darf (freigeben, aendern, Personen verwalten), haengt weiterhin an
|
||||
istLeitung und bleibt unveraendert -- sonst haette dieser eine
|
||||
Wunsch stillschweigend seine halben Rechte mitgenommen. */
|
||||
if (istSpicy(person)) return { wo: ohneDogFather("e"), werte: [] };
|
||||
if (siehtAlles(person)) return { wo: "1=1", werte: [] };
|
||||
|
||||
/* "ODER ich habe ihn selbst angelegt" kam am 02.09.2026 dazu -- aus
|
||||
|
||||
@@ -88,13 +88,27 @@ function istDrin(raumId, personId) {
|
||||
}
|
||||
|
||||
/** Alle (noch aktiven) Teilnehmer eines Raums. */
|
||||
/* Das Profilbild gehoert dazu (07.09.2026). Ohne es zeigt die
|
||||
Gespraechsliste Initialen, obwohl ein Bild hochgeladen ist -- der
|
||||
Fehler liegt dann nicht in der Anzeige, sondern in der Abfrage. Die
|
||||
fertige ADRESSE wird danach gebaut, nicht der Dateiname
|
||||
weitergereicht: Sonst setzt jede Stelle im Browser denselben Pfad
|
||||
zusammen.
|
||||
|
||||
UND DER KOMMENTAR STEHT AUSSERHALB DER ABFRAGE. Beim ersten Anlauf
|
||||
stand er darin -- mit dem Wort `bild` in schraegen Anfuehrungszeichen.
|
||||
Die beenden in JavaScript eine Vorlagenzeichenkette; die halbe SQL
|
||||
wurde damit zu Programmtext, und die Datei liess sich nicht mehr
|
||||
lesen. Ein Kommentar, der die Zeichenkette schliesst, in der er
|
||||
steht. */
|
||||
function teilnehmerVon(raumId) {
|
||||
return db().prepare(`
|
||||
SELECT t.person_id AS id, p.name, p.rolle, t.leitung, t.gelesen_bis
|
||||
SELECT t.person_id AS id, p.name, p.rolle, p.bild, t.leitung, t.gelesen_bis
|
||||
FROM chat_teilnehmer t
|
||||
JOIN personen p ON p.id = t.person_id
|
||||
WHERE t.raum_id = ? AND t.raus_am IS NULL
|
||||
ORDER BY ` + ROLLEN_SORTIERUNG + ", p.name").all(raumId);
|
||||
ORDER BY ` + ROLLEN_SORTIERUNG + ", p.name").all(raumId)
|
||||
.map((z) => ({ ...z, bild: z.bild ? `/workspace/api/steckbrief/bild/${z.bild}` : null }));
|
||||
}
|
||||
|
||||
/** Findet das Zweier-Gespräch der beiden -- oder legt es an.
|
||||
@@ -287,7 +301,8 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
über `vor`. */
|
||||
const vor = Number(req.query.vor) || 0;
|
||||
const reihen = db().prepare(`
|
||||
SELECT n.id, n.person_id, n.text, n.erstellt, n.weg_am, p.name AS von, p.rolle
|
||||
SELECT n.id, n.person_id, n.text, n.erstellt, n.weg_am, p.name AS von, p.rolle,
|
||||
p.bild AS von_bild
|
||||
FROM chat_nachrichten n
|
||||
LEFT JOIN personen p ON p.id = n.person_id
|
||||
WHERE n.raum_id = ? ${vor ? "AND n.id < ?" : ""}
|
||||
@@ -300,11 +315,13 @@ chatRouter.get("/workspace/api/chat/raeume/:id/nachrichten", (req, res) => {
|
||||
raum: {
|
||||
id: raum.id, art: raum.art,
|
||||
name: raumName(raum, leute, req.person.id),
|
||||
teilnehmer: leute.map((t) => ({ id: t.id, name: t.name, rolle: t.rolle, leitung: !!t.leitung })),
|
||||
teilnehmer: leute.map((t) => ({ id: t.id, name: t.name, rolle: t.rolle,
|
||||
bild: t.bild, leitung: !!t.leitung })),
|
||||
},
|
||||
/* Aufsteigend zurück -- gelesen wird von oben nach unten. */
|
||||
nachrichten: reihen.reverse().map((n) => ({
|
||||
id: n.id, von_id: n.person_id, von: n.von || "Gelöscht", rolle: n.rolle,
|
||||
von_bild: n.von_bild ? `/workspace/api/steckbrief/bild/${n.von_bild}` : null,
|
||||
text: n.weg_am ? null : n.text,
|
||||
zurueckgenommen: !!n.weg_am,
|
||||
erstellt: n.erstellt,
|
||||
|
||||
@@ -29,7 +29,7 @@ import { randomBytes } from "node:crypto";
|
||||
import { join, extname, basename } from "node:path";
|
||||
import { mkdirSync, createReadStream, writeFileSync, unlinkSync, statSync } from "node:fs";
|
||||
import {
|
||||
db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, ROLLEN_SORTIERUNG,
|
||||
db, protokolliere, echteIp, sitzungLesen, DATEN_ORDNER, betreutWo, darfCreator, betreuteIds, istLeitung, istDogFather, siehtAlles, istSpicy, ohneDogFather, ROLLEN_SORTIERUNG,
|
||||
externSql, EXTERN_MAX, sichtbarePersonenIds,
|
||||
} from "./workspace.js";
|
||||
|
||||
@@ -79,6 +79,7 @@ export function sichtbar(person) {
|
||||
darf (freigeben, aendern, Personen verwalten), haengt weiterhin an
|
||||
istLeitung und bleibt unveraendert -- sonst haette dieser eine
|
||||
Wunsch stillschweigend seine halben Rechte mitgenommen. */
|
||||
if (istSpicy(person)) return { wo: ohneDogFather("d", ["creator_id", "hochgeladen_von"]), werte: [] };
|
||||
if (siehtAlles(person)) return { wo: "1=1", werte: [] };
|
||||
|
||||
/* Ausdrueckliche Freigabe -- gilt fuer jede Rolle gleich. Wer in der
|
||||
|
||||
@@ -112,6 +112,44 @@ export const istDogFather = (person) => !!person && person.rolle === "admin";
|
||||
gar keinen Weg gibt -- nicht, weil eine Abfrage es verbietet.
|
||||
======================================================================= */
|
||||
export const istSpicy = (person) => !!person && person.rolle === "spicy";
|
||||
|
||||
/* =======================================================================
|
||||
SPICY MEDIA SIEHT ALLES -- AUSSER DEM, WAS DOGFATHER GEHOERT
|
||||
(07.09.2026, Nachtrag am selben Tag)
|
||||
|
||||
Filipe: "wieso sieht die spicy rolle meine daten die ich habe und
|
||||
speichere, meins sollen die nicht sehen von der rolle dogfather."
|
||||
|
||||
Beim ersten Anlauf bekam Spicy Media dieselbe Regel wie DogFather:
|
||||
`1=1`. Damit stimmte der Ueberblick ueber Manager, Scouts und Creator
|
||||
-- und nebenbei standen DogFathers eigene Termine, Aufgaben, Dateien
|
||||
und Eintraege mit drin. Der Chat war ausgenommen (er haengt an der
|
||||
Teilnehmerliste), alles andere nicht.
|
||||
|
||||
DIESE FUNKTION IST DIE EINE STELLE DAFUER. Sie baut die Bedingung
|
||||
"gehoert keinem DogFather" fuer eine beliebige Tabelle. Jede
|
||||
Sichtbarkeitsregel im Haus benutzt sie -- eine zweite, abgeschriebene
|
||||
Fassung waere die Stelle, an der es beim naechsten Modul wieder
|
||||
durchsickert.
|
||||
|
||||
ZWEI SPALTEN, NICHT EINE. `creator_id` sagt, UM WEN es geht;
|
||||
`erstellt_von` sagt, WER es geschrieben hat. Beide muessen gepruegt
|
||||
werden: Ein Termin, den DogFather fuer sich selbst anlegt, hat
|
||||
moeglicherweise gar keine creator_id -- er haenge dann nur an
|
||||
erstellt_von. Und eine Akte UEBER einen DogFather traegt seine
|
||||
creator_id, auch wenn ein Manager sie geschrieben hat.
|
||||
|
||||
`IS NULL OR NOT IN` und nicht bloss `NOT IN`: In SQL ist
|
||||
`NULL NOT IN (...)` weder wahr noch falsch, sondern NULL -- die Zeile
|
||||
fiele stillschweigend heraus. Genau so verschwinden Daten, ohne dass
|
||||
jemand einen Fehler sieht.
|
||||
======================================================================= */
|
||||
export function ohneDogFather(praefix, spalten = ["creator_id", "erstellt_von"]) {
|
||||
return spalten
|
||||
.map((sp) => `(${praefix}.${sp} IS NULL OR ${praefix}.${sp} NOT IN `
|
||||
+ `(SELECT id FROM personen WHERE rolle = 'admin'))`)
|
||||
.join(" AND ");
|
||||
}
|
||||
export const siehtAlles = (person) => !!person
|
||||
&& (person.rolle === "admin" || person.rolle === "spicy");
|
||||
|
||||
@@ -2329,6 +2367,7 @@ export const externSql = (personSpalte, externSpalte) =>
|
||||
spaeter auseinandergelaufen -- und dann haette eine Wiederholung
|
||||
jemandem etwas gezeigt, was der einzelne Termin ihm verbirgt. */
|
||||
export function termineSichtbar(person, praefix = "t") {
|
||||
if (istSpicy(person)) return { wo: ohneDogFather(praefix), werte: [] };
|
||||
if (siehtAlles(person)) return { wo: "1=1", werte: [] };
|
||||
const p = praefix;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user