Bereiche nur lesend fuer Creator + Content-Planung als echte Strecke
ZWEI SACHEN, beide auf Wunsch vom 31.08.2026.
1) "die creator sollen da nur die sehen die wir ihnen eintragen, also
dogfather manager und scout."
Vorher durfte ein Creator in LIVE, Content, Technik, Community und Schutz
selbst anlegen, aendern und eigene Eintraege loeschen. Das hebelt den
Zweck dieser Bereiche aus: Sie sind die Betreuungsakte. Wer eine
LIVE-Auswertung umschreiben oder eine unbequeme Notiz verschwinden lassen
kann, macht die Akte wertlos.
Der Creator SIEHT weiterhin alles, was zu ihm gehoert -- vollstaendig.
Er kann es nur nicht veraendern. Umgesetzt als EINE Schranke vor allen
schreibenden Wegen, nicht als Pruefung an drei Stellen: Genau so ist hier
schon einmal ein Loch entstanden (zwei von drei Stellen abgesichert, die
dritte vergessen). 403 mit klarem Text statt 404 -- der Bereich existiert
fuer ihn ja, er steht davor.
Vorsicht Express 4: Der Platzhalter heisst dort `*`, nicht `*name` wie in
Fassung 5. Mit der falschen Schreibweise haette die Sperre stumm nichts
getan -- kein Fehler, keine Warnung.
37 Pruefungen, Schwerpunkt auf den verneinenden Faellen: alle fuenf
Bereiche, alle drei Wege, dazu der eigene Alteintrag (der frueher
ausdruecklich erlaubt war) und der Nachweis, dass hinterher wirklich
nichts veraendert wurde.
2) Content-Planung: aus der Liste wird eine Strecke.
Recherchiert statt geraten. Die Quellen sind an drei Punkten eindeutig:
* "A content calendar for creators is a pipeline, not a datebook" --
eine Wand aus Terminen verbirgt genau die Stellen, an denen es hakt.
* Die ersten ein bis drei Sekunden entscheiden ueber die Verbreitung.
Der Hook ist deshalb ein eigenes Feld auf jeder Karte, kein Fliesstext.
* Regelmaessigkeit schlaegt Menge. Wer regelmaessig senden will, braucht
VORRAT -- und Vorrat ist die eine Zahl, die eine Ideenliste
verschweigt.
Daraus:
* Fuenf Stufen statt drei (Idee, Hook & Skript, Gedreht, Fertig &
geplant, Veroeffentlicht). Alte 'produktion'-Eintraege werden beim
naechsten Start auf 'gedreht' umgestellt -- ohne das fielen sie aus
jeder Spalte heraus.
* Neue Felder hook, format, saeule_id, geplant (ALTER TABLE, gefahrlos).
* Content-Saeulen je Creator, drei bis fuenf, nach der 70/20/10-Regel.
Mit Balance-Balken ueber 90 Tage, nur Veroeffentlichtes -- was auf dem
Kanal steht, ist die Wahrheit ueber seine Ausrichtung.
* Die Kachel VORRAT rechnet in TAGE um ("reicht noch etwa 7 Tage").
"4 Videos uebrig" sagt nichts; die Tage sagen, ob man am Wochenende
drehen muss. Gezaehlt wird nur Gedrehtes und Fertiges -- eine Idee ist
kein Video, und wer Ideen mitzaehlt, steht am Donnerstag ohne Material.
* "Ideen lassen sich direkt in Aufgabe/Termin/LIVE-Thema ueberfuehren"
-- woertlich aus dem Konzept, das letzte unumgesetzte Stueck. Die Idee
bleibt bestehen und bekommt einen Vermerk, damit niemand dieselbe
Aufgabe zweimal anlegt.
Farben: Die fuenf Saeulenfarben sind Daten, keine Dekoration. Geprueft
gegen genau diesen Hintergrund (#0a0d13) auf Helligkeitsband,
Farbstaerke, Farbfehlsichtigkeit (schlechtestes Paar dE 8.4),
Normalsicht-Abstand (19.3) und Kontrast -- alle Pruefungen bestanden.
Immer mit Legende UND Beschriftung; Farbe allein traegt nie die
Information.
Beim Ansehen des ersten Bildes fiel der klassische Brett-Fehler auf: Die
Spalte "Veroeffentlicht" waechst ewig und schiebt alles andere aus dem
Bild. Sie zeigt jetzt die juengsten sechs, der Rest ist einen Klick
entfernt. Die vorderen Spalten bleiben ungekuerzt -- was dort liegt, ist
Arbeit.
Nur EIN Schreiber fuer die Tabelle eintraege: workspace-content.js
enthaelt ausschliesslich, was es woanders nicht gibt (Saeulen,
Kennzahlen, Ueberfuehren). Zwei Module auf denselben Zeilen waeren der
sichere Weg zu zwei Wahrheiten.
45 + 40 Pruefungen, Computer und Handy, dazu die Gegenprobe: Ein Creator
sieht die ganze Strecke, die Kennzahlen und die Verteilung -- und keinen
einzigen Knopf, auch keinen versteckten. Ausgeblendet ist nicht dasselbe
wie nicht vorhanden.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -30,11 +30,28 @@ export const BEREICHE = {
|
||||
bewertung: true, // Review-Score aus dem Konzept, Seite 7
|
||||
dringlichkeit: false,
|
||||
},
|
||||
/* Content ist der einzige Bereich mit einer STRECKE statt einer Liste:
|
||||
Die Arten sind hier Stufen, die ein Video der Reihe nach durchlaeuft.
|
||||
Grundlage ist der Satz aus dem Konzept ("Hook bis Upload") und die
|
||||
Recherche vom 31.08.2026: Eine Redaktionsplanung ist eine Strecke,
|
||||
kein Kalender -- ein Kalender voller Termine verbirgt genau die drei
|
||||
Stellen, an denen es klemmt.
|
||||
|
||||
Fuenf Stufen, nicht drei: Zwischen "Idee" und "Veroeffentlicht" lag
|
||||
vorher alles, was den Unterschied macht. Und nicht mehr als fuenf --
|
||||
jede weitere Spalte ist eine, die auf dem Handy nicht mehr passt. */
|
||||
content: {
|
||||
name: "Content-Planung",
|
||||
arten: { idee: "Idee", produktion: "Produktion", veroeffentlicht: "Veröffentlicht" },
|
||||
arten: {
|
||||
idee: "Idee",
|
||||
skript: "Hook & Skript",
|
||||
gedreht: "Gedreht",
|
||||
fertig: "Fertig & geplant",
|
||||
veroeffentlicht: "Veröffentlicht",
|
||||
},
|
||||
bewertung: false,
|
||||
dringlichkeit: false,
|
||||
strecke: true, // hat eine Reihenfolge, nicht nur Kategorien
|
||||
},
|
||||
technik: {
|
||||
name: "Technik",
|
||||
@@ -81,6 +98,47 @@ function gleicheHerkunft(req, res, next) {
|
||||
|
||||
bereicheRouter.use("/workspace/api/bereich", angemeldet);
|
||||
|
||||
/* ---------- Die Bereiche sind fuer Creator zum LESEN da ----------------
|
||||
|
||||
Wunsch Filipe, 31.08.2026: "die creator sollen da nur die sehen die wir
|
||||
ihnen eintragen, also dogfather manager und scout."
|
||||
|
||||
Vorher durfte ein Creator hier selbst Eintraege anlegen, aendern und
|
||||
die eigenen wieder loeschen. Das klingt harmlos, hebelt aber den Zweck
|
||||
dieser Bereiche aus: Sie sind die Betreuungsakte -- LIVE-Auswertungen,
|
||||
Technikbefunde, Konfliktverlaeufe, Schutzvorfaelle. Was dort steht,
|
||||
muss der Stand der Betreuung sein und nicht das, was jemand ueber sich
|
||||
selbst notiert hat. Ein Creator, der eine Auswertung umschreiben oder
|
||||
eine unbequeme Notiz loeschen kann, macht die Akte wertlos.
|
||||
|
||||
Der Creator SIEHT weiterhin alles, was zu ihm gehoert -- vollstaendig,
|
||||
nichts wird ihm vorenthalten. Er kann es nur nicht veraendern.
|
||||
|
||||
Bewusst 403 mit klarem Text und nicht 404: Der Bereich existiert fuer
|
||||
ihn ja, er steht sogar davor. Ein 404 wuerde hier luegen. (Bei etwas,
|
||||
das er gar nicht sehen darf, bleibt es weiterhin 404.)
|
||||
|
||||
Steht als EINE Schranke vor allen schreibenden Wegen. Die Alternative
|
||||
-- in jeder der drei Stellen einzeln pruefen -- ist genau die Bauweise,
|
||||
durch die am 31.08.2026 schon einmal ein Loch entstanden ist: Zwei von
|
||||
drei Stellen waren abgesichert, die dritte vergessen. */
|
||||
function nichtSchreibendFuerCreator(req, res, next) {
|
||||
if (req.person?.rolle === "creator") {
|
||||
return res.status(403).json({
|
||||
fehler: "Hier tragen deine Betreuer ein. Du siehst alles, was zu dir gehört.",
|
||||
});
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
/* Express 4 -- dort ist der Platzhalter `*`, nicht `*name` wie in
|
||||
Fassung 5. Mit der Fassung-5-Schreibweise haette der Pfad wortwoertlich
|
||||
gepasst werden muessen und die Schranke waere NIE gegriffen: kein
|
||||
Fehler, keine Warnung, nur eine Sperre, die stumm nichts tut. */
|
||||
for (const weg of ["post", "patch", "delete", "put"]) {
|
||||
bereicheRouter[weg]("/workspace/api/bereich/*", nichtSchreibendFuerCreator);
|
||||
}
|
||||
|
||||
/* Scouts haben mit der Creator-Betreuung nichts zu tun -- sie sehen hier
|
||||
nichts. Creator sehen ihren eigenen Bereich, das Management alles. */
|
||||
export function sichtbar(person) {
|
||||
@@ -94,7 +152,20 @@ export function sichtbar(person) {
|
||||
const SPALTEN = `
|
||||
e.id, e.bereich, e.art, e.titel, e.text, e.datum, e.bewertung,
|
||||
e.dringlichkeit, e.status, e.creator_id, e.erstellt, e.erstellt_von, e.geaendert,
|
||||
pc.name AS creator_name, pe.name AS erstellt_name`;
|
||||
e.hook, e.format, e.saeule_id, e.geplant,
|
||||
pc.name AS creator_name, pe.name AS erstellt_name,
|
||||
(SELECT name FROM content_saeulen s WHERE s.id = e.saeule_id) AS saeule_name,
|
||||
(SELECT slot FROM content_saeulen s WHERE s.id = e.saeule_id) AS saeule_slot`;
|
||||
|
||||
/* Passt die gewaehlte Saeule zu dem Creator, um den es geht? Gibt einen
|
||||
Fehlertext zurueck oder null, wenn alles stimmt. */
|
||||
function saeulePasst(saeuleId, creatorId) {
|
||||
if (!saeuleId) return null;
|
||||
const s = db().prepare("SELECT creator_id FROM content_saeulen WHERE id = ?").get(saeuleId);
|
||||
if (!s) return "Diese Säule gibt es nicht.";
|
||||
if (creatorId && s.creator_id !== creatorId) return "Diese Säule gehört zu einem anderen Creator.";
|
||||
return null;
|
||||
}
|
||||
|
||||
const VERBUND = `
|
||||
FROM eintraege e
|
||||
@@ -175,6 +246,40 @@ function pruefe(bereich, körper, { neu }) {
|
||||
if (!DRINGLICHKEITEN.includes(körper.dringlichkeit)) fehler.push("Unbekannte Dringlichkeit.");
|
||||
else aus.dringlichkeit = körper.dringlichkeit;
|
||||
}
|
||||
|
||||
/* Die vier Content-Felder gibt es nur in der Content-Planung. In jedem
|
||||
anderen Bereich werden sie stillschweigend verworfen -- wie Bewertung
|
||||
und Dringlichkeit auch. Sonst stuende irgendwann ein Aufhaenger an
|
||||
einem Schutzvorfall. */
|
||||
if (bereich === "content") {
|
||||
if (körper.hook !== undefined) {
|
||||
const h = String(körper.hook ?? "").trim();
|
||||
if (h.length > 300) fehler.push("Der Hook ist zu lang.");
|
||||
else aus.hook = h || null;
|
||||
}
|
||||
if (körper.format !== undefined) {
|
||||
const f = String(körper.format ?? "").trim();
|
||||
if (f.length > 60) fehler.push("Das Format ist zu lang.");
|
||||
else aus.format = f || null;
|
||||
}
|
||||
if (körper.saeule_id !== undefined) {
|
||||
if (körper.saeule_id === null || körper.saeule_id === "") aus.saeule_id = null;
|
||||
else {
|
||||
const s = Number(körper.saeule_id);
|
||||
if (!Number.isInteger(s) || s < 1) fehler.push("Unbekannte Säule.");
|
||||
else aus.saeule_id = s;
|
||||
}
|
||||
}
|
||||
if (körper.geplant !== undefined) {
|
||||
const g = String(körper.geplant ?? "").trim();
|
||||
if (!g) aus.geplant = null;
|
||||
/* Datum ODER Datum mit Uhrzeit -- wer nur den Tag weiss, soll nicht
|
||||
zu einer erfundenen Uhrzeit gezwungen werden. */
|
||||
else if (!/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2})?$/.test(g) || Number.isNaN(Date.parse(g))) {
|
||||
fehler.push("Der geplante Zeitpunkt ist ungültig.");
|
||||
} else aus.geplant = g;
|
||||
}
|
||||
}
|
||||
if (körper.creator_id !== undefined) {
|
||||
const w = körper.creator_id;
|
||||
if (w === null || w === "") aus.creator_id = null;
|
||||
@@ -218,14 +323,23 @@ bereicheRouter.post("/workspace/api/bereich/:bereich", gleicheHerkunft, (req, re
|
||||
return res.status(400).json({ fehler: "Zugeordneter Creator existiert nicht." });
|
||||
}
|
||||
|
||||
/* Eine Saeule gehoert immer GENAU EINEM Creator. Ohne diese Pruefung
|
||||
koennte man ein Video an die Saeule eines fremden Creators haengen
|
||||
-- und darueber in der Balance-Auswertung Namen sehen, die einen
|
||||
nichts angehen. Die Sichtbarkeitsregel oben schuetzt die Eintraege,
|
||||
nicht die Verweise zwischen ihnen. */
|
||||
const saeuleFehler = saeulePasst(aus.saeule_id, aus.creator_id);
|
||||
if (saeuleFehler) return res.status(400).json({ fehler: saeuleFehler });
|
||||
|
||||
const { lastInsertRowid } = db().prepare(`
|
||||
INSERT INTO eintraege
|
||||
(bereich, art, titel, text, datum, bewertung, dringlichkeit, status,
|
||||
creator_id, erstellt, erstellt_von)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)`).run(
|
||||
creator_id, erstellt, erstellt_von, hook, format, saeule_id, geplant)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(
|
||||
bereich, aus.art, aus.titel, aus.text ?? null, aus.datum,
|
||||
aus.bewertung ?? null, aus.dringlichkeit ?? "mittel", aus.status ?? "offen",
|
||||
aus.creator_id ?? null, jetzt(), req.person.id);
|
||||
aus.creator_id ?? null, jetzt(), req.person.id,
|
||||
aus.hook ?? null, aus.format ?? null, aus.saeule_id ?? null, aus.geplant ?? null);
|
||||
|
||||
protokolliere("eintrag_angelegt", {
|
||||
personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req),
|
||||
@@ -260,6 +374,14 @@ bereicheRouter.patch("/workspace/api/bereich/:bereich/:id", gleicheHerkunft, (re
|
||||
if (fehler.length) return res.status(400).json({ fehler: fehler.join(" ") });
|
||||
if (!istLeitung(req.person)) delete aus.creator_id;
|
||||
|
||||
/* Beim Aendern gilt dieselbe Regel -- sonst waere die Pruefung beim
|
||||
Anlegen wertlos: Man legt ohne Saeule an und haengt sie danach an. */
|
||||
if (aus.saeule_id !== undefined) {
|
||||
const zuWem = aus.creator_id !== undefined ? aus.creator_id : eintrag.creator_id;
|
||||
const saeuleFehler = saeulePasst(aus.saeule_id, zuWem);
|
||||
if (saeuleFehler) return res.status(400).json({ fehler: saeuleFehler });
|
||||
}
|
||||
|
||||
const felder = Object.keys(aus);
|
||||
if (!felder.length) return res.status(400).json({ fehler: "nichts_zu_aendern" });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user