"Die neuen Sachen in der LIVE-Analyse soll der Creator FEST sehen, auch
schoen kategorisiert, und der Ansprechpartner soll anklicken koennen,
wenn er findet, dass der Creator was verbessern sollte."
DER UMBAU. Vorher waren die Punkte Vorschlaege zum Uebernehmen: Man
holte sie sich und bekam einen eigenen Eintrag. Das war falsch gedacht.
Eine Checkliste, die man erst anfordern muss, ist keine Checkliste --
und schlimmer: Zwei Creator haetten unterschiedliche Listen gehabt, je
nachdem wer sich was geholt hat. Genau das macht einen Vergleich
unmoeglich, und um Vergleich geht es bei einer Betreuung.
Jetzt stehen dieselben Punkte fuer JEDEN fest da, gruppiert:
LIVE 21 Punkte -- vor, waehrend, nach der Sendung
Community 14 Punkte -- Moderation vorbereiten, aufbauen, wenn es kippt
Technik 12 Punkte -- Einrichtung, Ausfall, was geholfen hat
Content 13 Ideen -- nach Saeule (70/20/10) statt nach Ablauf
Jede Gruppe hat einen Satz, der erklaert, wofuer sie da ist. Eine
Ueberschrift allein sagt das nicht.
Was sich je Creator unterscheidet, ist nur der STAND -- und den setzt
die Betreuung: "Passt so" oder "Verbessern". Ein Creator kann sich nicht
selbst bewerten; koennte er es, stuende alles auf gruen. "Verbessern"
verlangt einen Satz, WAS zu verbessern ist -- eine Bewertung, mit der er
nichts anfangen kann, ist nicht streng, sondern nur entmutigend.
Der Creator sieht alles: die Stufe, den Grund, den Namen und den
Zeitpunkt. Und er kann an JEDEM Punkt antworten -- das ist der Kanal,
ueber den er ueberhaupt etwas sagen kann.
Oben steht eine Bilanz in einer Zeile: wie viele passen, wie viele sind
zu verbessern, wie viele hat noch niemand angesehen. Das ist die Frage,
die beide Seiten zuerst haben.
STABILE SCHLUESSEL statt Positionen. Ein Stand haengt am Schluessel des
Punktes ("ton-geprueft"), nicht an seiner Nummer. Haenge er an der
Position, waere beim Einfuegen eines Punktes in der Mitte jede Bewertung
dahinter am falschen Punkt -- und niemand wuerde es merken, weil beides
plausibel aussieht. 64 Punkte haben jetzt einen.
"Offen" loescht den Stand, statt ihn auf "offen" zu setzen: Ein
Datensatz, der nichts aussagt, ist Ballast.
ZWEI EIGENE FEHLER, VON DEN PRUEFUNGEN GEFUNDEN:
1. DAS CSS LAG IN DER FALSCHEN DATEI. Ich hatte es in content.css
geschrieben -- bereich.html laedt die gar nicht. Die Punkte standen
auf drei von vier Seiten nackt und ohne Flaeche da. Es ist derselbe
Fehler wie im August bei .knopf-still, .schalter und .kopf-zeile, und
es gibt seitdem eine Warnung dazu in start.css. Sie hat nichts
genutzt, solange keine Pruefung sie nachhielt.
Jetzt gibt es eine: Sie misst, ob eine Karte wirklich eine Kante und
Polsterung hat -- nicht nur, ob das Element existiert. Gegenprobe
gemacht: Klasse umbenannt, Pruefung meldet "STIL FEHLT".
2. Ein Betreuer sah beim Oeffnen keine Bewertungsknoepfe, weil noch kein
Creator gewaehlt war -- und musste erst raten, dass er oben jemanden
auswaehlen soll. Jetzt nimmt der Server den ersten betreuten Creator,
wenn keiner angegeben ist.
Die alte Oberflaechenpruefung fuer den Vorschlaege-Block wurde entfernt
statt angepasst: Sie verlangte etwas, das es nicht mehr gibt. Eine
dauerhaft rote Pruefung ist schlimmer als keine -- man gewoehnt sich
daran, und beim naechsten echten Fehler sieht niemand hin.
Co-Authored-By: Claude Opus 5 <[email protected]>
430 lines
26 KiB
JavaScript
430 lines
26 KiB
JavaScript
/* =====================================================================
|
||
workspace-vorlagen.js — fertige Inhalte, die man übernehmen kann.
|
||
|
||
Der Wunsch vom 01.09.2026, an fünf Stellen gleichlautend: "ich will
|
||
dass da schon fertige Sachen stehen". Eine leere Seite mit einem
|
||
Knopf "Neuer Eintrag" verlangt vom Creator genau das, was er noch
|
||
nicht kann — zu wissen, was überhaupt hineingehört.
|
||
|
||
WARUM VORLAGEN UND KEINE VORAUSGEFÜLLTEN DATEN.
|
||
Man könnte beim Anlegen eines Creators dreißig Einträge in seine
|
||
Datenbank schreiben. Das wäre falsch:
|
||
* Sie wären ab dem ersten Tag "seine" Einträge -- und damit
|
||
Altlast, sobald sich etwas ändert.
|
||
* Ändert Filipe später eine Formulierung, gilt sie nur für neue
|
||
Creator. Die alten behalten die alte für immer.
|
||
* Und die Liste sähe voll aus, obwohl noch nichts geschehen ist --
|
||
genau das Gegenteil einer ehrlichen Übersicht.
|
||
|
||
Vorlagen bleiben deshalb VORSCHLÄGE, bis jemand sie übernimmt. Sie
|
||
stehen im Code, gelten für alle sofort, und werden erst dann zu Daten,
|
||
wenn sie tatsächlich gebraucht werden. Wer sie übernimmt, bekommt
|
||
einen ganz normalen Eintrag, den er ändern und löschen kann.
|
||
|
||
FACHLICHE GRUNDLAGE der Inhalte:
|
||
* Die ersten ein bis drei Sekunden eines Kurzvideos entscheiden über
|
||
die Verbreitung -- deshalb hat jede Content-Vorlage einen Hook,
|
||
und zwar als eigenes Feld.
|
||
* Ein LIVE steht und fällt mit dem, was VOR der Sendung passiert.
|
||
Deshalb sind die Vorbereitungspunkte die längste Liste.
|
||
* Was während der Sendung auffällt, ist am nächsten Tag vergessen.
|
||
Deshalb gibt es Punkte für "während LIVE", die man in Sekunden
|
||
anklicken kann, statt sie zu tippen.
|
||
* 70/20/10: etwa 70 % Inhalte mit Wert, 20 % Community, 10 %
|
||
Eigenwerbung. Die Content-Vorlagen sind danach verteilt.
|
||
===================================================================== */
|
||
|
||
import express from "express";
|
||
import {
|
||
db, sitzungLesen, istLeitung, darfCreator, protokolliere, echteIp,
|
||
} from "./workspace.js";
|
||
|
||
export const vorlagenRouter = express.Router();
|
||
|
||
const jetzt = () => new Date().toISOString();
|
||
|
||
/* Welche Bereiche haben Punktelisten? Eine Zuordnung statt drei
|
||
Sonderfaellen im Code. */
|
||
const BEREICH_PUNKTE = {};
|
||
|
||
/* =====================================================================
|
||
CONTENT-IDEEN
|
||
|
||
Jede Vorlage bringt mit, was ein Video braucht, bevor man es dreht:
|
||
einen Aufhänger, ein Format und die Säule, zu der es gehört. Der
|
||
Aufhänger ist ausformuliert, nicht beschrieben -- "mach was
|
||
Persönliches" hilft niemandem, "Das hätte ich am Anfang gern gewusst"
|
||
kann man sagen.
|
||
===================================================================== */
|
||
|
||
export const CONTENT_IDEEN = [
|
||
/* ---- Wert (70 %) ---------------------------------------------------- */
|
||
{ schluessel: "das-haette-ich-am-anfang-gern-gewusst", titel: "Das hätte ich am Anfang gern gewusst",
|
||
hook: "„Ich hab drei Monate gebraucht, um das zu kapieren – du brauchst 30 Sekunden.\"",
|
||
format: "Talking Head", saeule: "Wert",
|
||
text: "Ein Fehler, den du selbst gemacht hast, und was du heute anders machst. "
|
||
+ "Konkret bleiben: ein Fehler, eine Lösung. Wer drei Dinge sagt, sagt nichts." },
|
||
{ schluessel: "mein-setup-fuer-unter-100", titel: "Mein Setup für unter 100 €",
|
||
hook: "„Alle sagen, du brauchst teure Technik. Hier ist meins für 87 Euro.\"",
|
||
format: "Zeigen & Erklären", saeule: "Wert",
|
||
text: "Was du wirklich benutzt, mit Preisen. Ehrlich sein, wo es hakt – "
|
||
+ "das ist der Teil, den niemand sonst zeigt." },
|
||
{ schluessel: "drei-einstellungen-die-sofort-was-bringe", titel: "Drei Einstellungen, die sofort was bringen",
|
||
hook: "„Wenn dein Stream unscharf aussieht, liegt es an einer davon.\"",
|
||
format: "Bildschirmaufnahme", saeule: "Wert",
|
||
text: "Nicht mehr als drei. Jede in unter 15 Sekunden erklärt, mit Vorher/Nachher." },
|
||
{ schluessel: "was-ich-in-einer-woche-gelernt-habe", titel: "Was ich in einer Woche gelernt habe",
|
||
hook: "„Sieben Tage, eine Erkenntnis – die hier hat alles verändert.\"",
|
||
format: "Talking Head", saeule: "Wert",
|
||
text: "Rückblick auf eine echte Woche. Funktioniert nur mit einer konkreten Zahl "
|
||
+ "oder einem konkreten Moment." },
|
||
{ schluessel: "antwort-auf-die-frage-die-immer-kommt", titel: "Antwort auf die Frage, die immer kommt",
|
||
hook: "„Das werde ich jeden Tag gefragt – hier einmal in Ruhe.\"",
|
||
format: "Talking Head", saeule: "Wert",
|
||
text: "Aus dem Chat sammeln, welche Frage sich wiederholt. Die Antwort einmal "
|
||
+ "gut aufnehmen und immer wieder verwenden." },
|
||
{ schluessel: "so-bereite-ich-einen-stream-vor", titel: "So bereite ich einen Stream vor",
|
||
hook: "„Zwei Stunden vor LIVE mache ich immer dasselbe.\"",
|
||
format: "Vlog", saeule: "Wert",
|
||
text: "Der Ablauf hinter der Sendung. Zeigt Verlässlichkeit und gibt Anfängern "
|
||
+ "eine Vorlage." },
|
||
{ schluessel: "fehler-die-ich-bei-anderen-sehe", titel: "Fehler, die ich bei anderen sehe",
|
||
hook: "„Bitte mach das nicht – ich sehe es jeden Tag.\"",
|
||
format: "Talking Head", saeule: "Wert",
|
||
text: "Niemanden bloßstellen. Das Verhalten beschreiben, nicht die Person." },
|
||
|
||
/* ---- Community (20 %) ------------------------------------------------ */
|
||
{ schluessel: "der-beste-moment-aus-dem-letzten-live", titel: "Der beste Moment aus dem letzten LIVE",
|
||
hook: "„Das ist gestern passiert, und ich rede immer noch davon.\"",
|
||
format: "Zusammenschnitt", saeule: "Community",
|
||
text: "Ein Ausschnitt, keine Zusammenfassung. Wer alles zeigt, zeigt nichts." },
|
||
{ schluessel: "ich-beantworte-eure-kommentare", titel: "Ich beantworte eure Kommentare",
|
||
hook: "„Ihr habt gefragt – jetzt rede ich.\"",
|
||
format: "Talking Head", saeule: "Community",
|
||
text: "Drei bis fünf echte Kommentare, sichtbar eingeblendet. Auch die kritischen." },
|
||
{ schluessel: "danke-an-die-die-immer-da-sind", titel: "Danke an die, die immer da sind",
|
||
hook: "„Ihr seid seit dem ersten Stream dabei. Das hier ist für euch.\"",
|
||
format: "Talking Head", saeule: "Community",
|
||
text: "Namen nennen, wenn die Leute einverstanden sind. Sonst allgemein bleiben – "
|
||
+ "aber ehrlich." },
|
||
{ schluessel: "eine-entscheidung-bei-der-ihr-mitredet", titel: "Eine Entscheidung, bei der ihr mitredet",
|
||
hook: "„Ich kann mich nicht entscheiden. Ihr habt 24 Stunden.\"",
|
||
format: "Umfrage", saeule: "Community",
|
||
text: "Echte Wahl, keine Scheinfrage. Das Ergebnis danach auch wirklich umsetzen." },
|
||
|
||
/* ---- Eigenwerbung (10 %) --------------------------------------------- */
|
||
{ schluessel: "wann-und-wo-ich-streame", titel: "Wann und wo ich streame",
|
||
hook: "„Immer dienstags und donnerstags – und ich fange pünktlich an.\"",
|
||
format: "Kurzclip", saeule: "Eigenwerbung",
|
||
text: "Fester Sendeplan als eigener Clip. Wiederholbar, sobald sich etwas ändert." },
|
||
{ schluessel: "was-diese-woche-ansteht", titel: "Was diese Woche ansteht",
|
||
hook: "„Drei Sachen, und eine davon habe ich noch nie gemacht.\"",
|
||
format: "Kurzclip", saeule: "Eigenwerbung",
|
||
text: "Vorschau auf die Woche. Kurz halten, sonst wird es eine Ankündigung "
|
||
+ "statt einer Einladung." },
|
||
];
|
||
|
||
/* =====================================================================
|
||
LIVE-PUNKTE
|
||
|
||
Drei Listen entlang der Sendung. Die mittlere ist die wichtigste und
|
||
zugleich die, die es sonst nirgends gibt: Was während des Streams
|
||
auffällt, ist am nächsten Tag weg. Deshalb sind diese Punkte so
|
||
formuliert, dass man sie in einem Moment anklicken kann, in dem man
|
||
eigentlich keine Zeit hat.
|
||
|
||
`hilfe: true` heißt: Der Creator kann das anklicken und sagen, dass
|
||
er dabei Unterstützung braucht -- daraus wird eine Aufgabe für seinen
|
||
Betreuer.
|
||
===================================================================== */
|
||
|
||
export const LIVE_PUNKTE = {
|
||
vorbereitung: [
|
||
{ schluessel: "ton-geprueft", titel: "Ton geprüft", text: "Eine Minute aufnehmen und anhören. Ton ist der Grund Nummer eins, warum Leute wieder gehen.", hilfe: true },
|
||
{ schluessel: "licht-sitzt", titel: "Licht sitzt", text: "Gesicht heller als der Hintergrund. Kein Fenster im Rücken.", hilfe: true },
|
||
{ schluessel: "verbindung-getestet", titel: "Verbindung getestet", text: "Upload-Geschwindigkeit geprüft, Kabel statt WLAN wenn möglich.", hilfe: true },
|
||
{ schluessel: "titel-und-thema-stehen", titel: "Titel und Thema stehen", text: "Wer den Titel liest, weiß, was ihn erwartet – und ob es für ihn ist.", hilfe: false },
|
||
{ schluessel: "ablauf-grob-im-kopf", titel: "Ablauf grob im Kopf", text: "Drei Blöcke reichen. Ein Stream ohne roten Faden fühlt sich für Zuschauer wie Warten an.", hilfe: false },
|
||
{ schluessel: "angekuendigt", titel: "Angekündigt", text: "Mindestens einmal vorher gesagt, dass es losgeht – Story, Post oder Nachricht.", hilfe: false },
|
||
{ schluessel: "moderation-geklaert", titel: "Moderation geklärt", text: "Weiß jemand Bescheid, der eingreifen kann, wenn es im Chat kippt?", hilfe: true },
|
||
{ schluessel: "technik-ersatz-bereit", titel: "Technik-Ersatz bereit", text: "Was passiert, wenn der Stream abbricht? Handy als Notlösung bereitlegen.", hilfe: true },
|
||
],
|
||
mitschrift: [
|
||
{ schluessel: "guter-moment-aufheben", titel: "Guter Moment – aufheben", text: "Etwas ist gut gelaufen. Kurz notieren, woran es lag – daraus wird später ein Clip.", hilfe: false },
|
||
{ schluessel: "hier-sind-leute-gegangen", titel: "Hier sind Leute gegangen", text: "Zeitpunkt merken. Später nachsehen, was in dem Moment passiert ist.", hilfe: false },
|
||
{ schluessel: "frage-aus-dem-chat-die-wiederkommt", titel: "Frage aus dem Chat, die wiederkommt", text: "Wird oft gefragt? Dann gehört sie in ein eigenes Video.", hilfe: false },
|
||
{ schluessel: "technik-hat-gehakt", titel: "Technik hat gehakt", text: "Ton weg, Bild eingefroren, Verzögerung – aufschreiben, solange man es noch weiß.", hilfe: true },
|
||
{ schluessel: "unangenehme-situation-im-chat", titel: "Unangenehme Situation im Chat", text: "Was war es, wie ist es ausgegangen? Auch wenn es gut lief.", hilfe: true },
|
||
{ schluessel: "neue-zuschauer-aufgetaucht", titel: "Neue Zuschauer aufgetaucht", text: "Woher kamen sie? Wurde jemand begrüßt oder ging es unter?", hilfe: false },
|
||
{ schluessel: "ich-wusste-nicht-weiter", titel: "Ich wusste nicht weiter", text: "Stelle merken, an der es gestockt hat. Dafür gibt es Hilfe.", hilfe: true },
|
||
],
|
||
auswertung: [
|
||
{ schluessel: "was-hat-diesmal-getragen", titel: "Was hat diesmal getragen?", text: "Ein Ding. Nicht drei. Das eine, das man wiederholen will.", hilfe: false },
|
||
{ schluessel: "was-war-zaeh", titel: "Was war zäh?", text: "Ehrlich sein. Die zähen Stellen sind die, an denen man wächst.", hilfe: false },
|
||
{ schluessel: "zahlen-im-vergleich", titel: "Zahlen im Vergleich", text: "Zuschauer, Verweildauer, Kommentare – gegen das letzte Mal, nicht gegen andere.", hilfe: true },
|
||
{ schluessel: "clips-gesichert", titel: "Clips gesichert", text: "Die guten Momente rausgeschnitten und abgelegt, solange man sie noch findet.", hilfe: false },
|
||
{ schluessel: "eine-sache-fuer-naechstes-mal", titel: "Eine Sache für nächstes Mal", text: "Genau eine. Wer sich fünf Dinge vornimmt, macht keins davon.", hilfe: false },
|
||
{ schluessel: "braucht-besprechung", titel: "Braucht Besprechung", text: "Etwas, das man nicht allein lösen kann – geht an die Betreuung.", hilfe: true },
|
||
],
|
||
};
|
||
|
||
/* =====================================================================
|
||
CALL-LEITFÄDEN
|
||
|
||
Jeder Call endet mit To-dos -- das steht schon auf der Seite. Was
|
||
fehlte, ist der Weg dorthin. Diese Leitfäden sind bewusst kurz: Ein
|
||
Gespräch nach Fragebogen ist ein Verhör, kein Gespräch.
|
||
===================================================================== */
|
||
|
||
/* =====================================================================
|
||
COMMUNITY-PUNKTE
|
||
|
||
Moderation ist die Arbeit, die niemand sieht, solange sie
|
||
funktioniert. Die Punkte hier sind so gewaehlt, dass sie VOR dem
|
||
Ernstfall abgehakt werden koennen -- danach ist es zu spaet.
|
||
===================================================================== */
|
||
|
||
export const COMMUNITY_PUNKTE = {
|
||
moderation: [
|
||
{ schluessel: "regeln-stehen-sichtbar", titel: "Regeln stehen sichtbar", text: "Kurz, verstaendlich, an einer Stelle, die man findet. Drei Saetze reichen.", hilfe: false },
|
||
{ schluessel: "jemand-kann-eingreifen", titel: "Jemand kann eingreifen", text: "Waehrend des Streams ist eine zweite Person mit Rechten da -- oder es ist klar, dass keine da ist.", hilfe: true },
|
||
{ schluessel: "wortfilter-eingerichtet", titel: "Wortfilter eingerichtet", text: "Die offensichtlichen Sachen automatisch weg, damit man sich um den Rest kuemmern kann.", hilfe: true },
|
||
{ schluessel: "was-passiert-bei-einem-verstoss", titel: "Was passiert bei einem Verstoss?", text: "Verwarnen, stummschalten, sperren -- vorher entscheiden, nicht im Moment.", hilfe: false },
|
||
{ schluessel: "umgang-mit-minderjaehrigen", titel: "Umgang mit Minderjaehrigen", text: "Wie erkennt man sie, und was gilt dann? Das ist keine Ermessensfrage.", hilfe: true },
|
||
],
|
||
aktion: [
|
||
{ schluessel: "neue-begruessen", titel: "Neue begruessen", text: "Wer zum ersten Mal da ist, wird angesprochen. Kostet zwei Sekunden und entscheidet, ob jemand bleibt.", hilfe: false },
|
||
{ schluessel: "stammleute-erkennen", titel: "Stammleute erkennen", text: "Namen merken oder notieren. Wiedererkannt zu werden ist der halbe Grund wiederzukommen.", hilfe: false },
|
||
{ schluessel: "etwas-geben-das-nur-hier-gibt", titel: "Etwas geben, das nur hier gibt", text: "Ein Insider, ein Ritual, ein Spitzname. Community entsteht aus Gemeinsamem.", hilfe: false },
|
||
{ schluessel: "ausserhalb-des-streams-antworten", titel: "Ausserhalb des Streams antworten", text: "Kommentare und Nachrichten -- die Community lebt auch zwischen den Sendungen.", hilfe: false },
|
||
],
|
||
konflikt: [
|
||
{ schluessel: "ruhig-bleiben-und-nicht-oeffentlich-stre", titel: "Ruhig bleiben und nicht oeffentlich streiten", text: "Im Chat gewinnt niemand. Kurz ansagen, dann privat klaeren.", hilfe: true },
|
||
{ schluessel: "was-ist-genau-passiert", titel: "Was ist genau passiert?", text: "Zeitpunkt, Beteiligte, Wortlaut. Ohne das ist jede Entscheidung angreifbar.", hilfe: false },
|
||
{ schluessel: "beweise-sichern", titel: "Beweise sichern", text: "Bildschirmfoto oder Ausschnitt, solange es noch da ist. Chats verschwinden.", hilfe: true },
|
||
{ schluessel: "betreuung-einbeziehen", titel: "Betreuung einbeziehen", text: "Bei allem, was groesser ist als ein blöder Kommentar. Dafuer ist sie da.", hilfe: true },
|
||
{ schluessel: "danach-was-hat-gefehlt", titel: "Danach: was hat gefehlt?", text: "Regel unklar, niemand zum Eingreifen da, zu spaet gemerkt -- daraus wird der naechste Punkt oben.", hilfe: false },
|
||
],
|
||
};
|
||
|
||
/* =====================================================================
|
||
TECHNIK-PUNKTE
|
||
|
||
Technik faellt immer im ungeeignetsten Moment aus. Die Punkte sind
|
||
deshalb danach sortiert, was am haeufigsten schiefgeht -- und Ton
|
||
steht vorn, weil schlechter Ton der Grund Nummer eins ist, warum
|
||
Leute einen Stream verlassen.
|
||
===================================================================== */
|
||
|
||
export const TECHNIK_PUNKTE = {
|
||
setup: [
|
||
{ schluessel: "mikrofon-richtig-eingestellt", titel: "Mikrofon richtig eingestellt", text: "Nicht uebersteuert, nicht zu leise, kein Rauschen. Einmal aufnehmen und anhoeren.", hilfe: true },
|
||
{ schluessel: "kamera-scharf-und-gut-belichtet", titel: "Kamera scharf und gut belichtet", text: "Gesicht heller als der Hintergrund. Automatik oft abschalten -- sie pumpt.", hilfe: true },
|
||
{ schluessel: "szenen-im-streaming-programm-angelegt", titel: "Szenen im Streaming-Programm angelegt", text: "Start, Vollbild, Pause, Ende. Wer waehrend der Sendung baut, sendet Chaos.", hilfe: true },
|
||
{ schluessel: "bitrate-zur-leitung-passend", titel: "Bitrate zur Leitung passend", text: "Lieber stabil und etwas weicher als scharf mit Aussetzern.", hilfe: true },
|
||
{ schluessel: "alles-einmal-komplett-getestet", titel: "Alles einmal komplett getestet", text: "Eine Probesendung von fuenf Minuten, aufgezeichnet und angesehen.", hilfe: true },
|
||
],
|
||
problem: [
|
||
{ schluessel: "ton-weg-oder-verzerrt", titel: "Ton weg oder verzerrt", text: "Wann genau, welches Geraet, was lief gleichzeitig? Ohne das findet es niemand.", hilfe: true },
|
||
{ schluessel: "bild-ruckelt-oder-friert-ein", titel: "Bild ruckelt oder friert ein", text: "Auslastung, Leitung, Aufloesung -- eins nach dem anderen ausschliessen.", hilfe: true },
|
||
{ schluessel: "stream-bricht-ab", titel: "Stream bricht ab", text: "Zeitpunkt notieren und im Protokoll des Programms nachsehen.", hilfe: true },
|
||
{ schluessel: "ton-und-bild-laufen-auseinander", titel: "Ton und Bild laufen auseinander", text: "Meist ein Puffer. Wann faellt es auf, sofort oder erst spaeter?", hilfe: true },
|
||
],
|
||
loesung: [
|
||
{ schluessel: "was-hat-es-behoben", titel: "Was hat es behoben?", text: "Genau die eine Aenderung aufschreiben, nicht alles was probiert wurde.", hilfe: false },
|
||
{ schluessel: "kommt-es-wieder", titel: "Kommt es wieder?", text: "Beim naechsten Mal nachsehen. Einmal weg ist nicht geloest.", hilfe: false },
|
||
{ schluessel: "einstellung-gesichert", titel: "Einstellung gesichert", text: "Damit sie nach einem Update nicht wieder weg ist.", hilfe: false },
|
||
],
|
||
};
|
||
export const CALL_LEITFAEDEN = [
|
||
{ schluessel: "erstgespraech", titel: "Erstgespräch",
|
||
text: "Wer bist du, was machst du, was willst du erreichen? · Was läuft schon gut? · "
|
||
+ "Was nervt dich gerade am meisten? · Wie viel Zeit hast du wirklich? · "
|
||
+ "Was erwartest du von uns – und was erwarten wir von dir?" },
|
||
{ schluessel: "wochen-call", titel: "Wochen-Call",
|
||
text: "Was ist seit dem letzten Mal passiert? · Was ist liegen geblieben und warum? · "
|
||
+ "Zahlen kurz ansehen – eine Auffälligkeit reicht · Was steht diese Woche an? · "
|
||
+ "Brauchst du irgendwo Hilfe?" },
|
||
{ schluessel: "wenn-es-gerade-nicht-laeuft", titel: "Wenn es gerade nicht läuft",
|
||
text: "Zuerst zuhören, nicht lösen · Seit wann geht es so? · Was hat sich geändert? · "
|
||
+ "Was war das letzte Mal, als es gut war? · Eine kleine Sache vereinbaren, "
|
||
+ "nicht fünf große" },
|
||
{ schluessel: "monats-review", titel: "Monats-Review",
|
||
text: "Die drei Ziele vom letzten Mal durchgehen · Was ist erreicht, was nicht? · "
|
||
+ "Woran lag es – Zeit, Können oder Motivation? · Drei neue Ziele, verbindlich · "
|
||
+ "Termin für das nächste Review festhalten" },
|
||
];
|
||
|
||
/* Erst hier gefuellt -- die Listen stehen weiter oben und muessen
|
||
definiert sein, bevor sie eingetragen werden. */
|
||
BEREICH_PUNKTE.live = LIVE_PUNKTE;
|
||
BEREICH_PUNKTE.community = COMMUNITY_PUNKTE;
|
||
BEREICH_PUNKTE.technik = TECHNIK_PUNKTE;
|
||
|
||
/* ---------- Schnittstelle ------------------------------------------------ */
|
||
|
||
function angemeldet(req, res, next) {
|
||
const person = sitzungLesen(req);
|
||
if (!person) return res.status(401).json({ fehler: "nicht_angemeldet" });
|
||
req.person = person;
|
||
next();
|
||
}
|
||
vorlagenRouter.use("/workspace/api/vorlagen", angemeldet);
|
||
|
||
vorlagenRouter.get("/workspace/api/vorlagen", (req, res) => {
|
||
res.json({
|
||
content: CONTENT_IDEEN,
|
||
live: LIVE_PUNKTE,
|
||
community: COMMUNITY_PUNKTE,
|
||
technik: TECHNIK_PUNKTE,
|
||
calls: CALL_LEITFAEDEN,
|
||
});
|
||
});
|
||
|
||
/* ---------- Übernehmen ---------------------------------------------------
|
||
|
||
Aus einer Vorlage wird ein ganz normaler Eintrag: änderbar, löschbar,
|
||
und ab dann unabhängig von der Vorlage. Wer die Vorlage später
|
||
ändert, ändert damit NICHT, was schon übernommen wurde -- das wäre
|
||
eine böse Überraschung.
|
||
|
||
Creator dürfen ausdrücklich selbst übernehmen. Es ist ihre Arbeit;
|
||
sie sollen nicht darauf warten müssen, dass ein Betreuer für sie eine
|
||
Idee anlegt. */
|
||
|
||
function gleicheHerkunft(req, res, next) {
|
||
const herkunft = req.get("origin");
|
||
if (!herkunft) return next();
|
||
let erlaubt;
|
||
try { erlaubt = new URL(herkunft).host === req.get("host"); } catch { erlaubt = false; }
|
||
if (!erlaubt) return res.status(403).json({ fehler: "fremde_herkunft" });
|
||
next();
|
||
}
|
||
|
||
vorlagenRouter.post("/workspace/api/vorlagen/uebernehmen", gleicheHerkunft,
|
||
express.json({ limit: "16kb" }), (req, res) => {
|
||
try {
|
||
const art = String(req.body.art || "");
|
||
const nr = Number(req.body.nr);
|
||
/* Für wen? Ein Creator immer für sich; Betreuer für den Creator,
|
||
den sie gerade ansehen. */
|
||
let creatorId = req.person.rolle === "creator"
|
||
? req.person.id
|
||
: Number(req.body.creator_id) || null;
|
||
|
||
if (req.person.rolle !== "creator" && creatorId && !darfCreator(req.person, creatorId)) {
|
||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||
}
|
||
|
||
const angelegt = [];
|
||
const einfuegen = (bereich, unterart, titel, text, zusatz = {}) => {
|
||
db().prepare(`INSERT INTO eintraege
|
||
(bereich, art, titel, text, datum, status, creator_id, hook, format,
|
||
erstellt, erstellt_von)
|
||
VALUES (?,?,?,?,?,?,?,?,?,?,?)`)
|
||
.run(bereich, unterart, titel, text, jetzt().slice(0, 10), "offen",
|
||
creatorId, zusatz.hook || null, zusatz.format || null,
|
||
jetzt(), req.person.id);
|
||
angelegt.push(titel);
|
||
};
|
||
|
||
if (art === "content") {
|
||
const v = CONTENT_IDEEN[nr];
|
||
if (!v) return res.status(400).json({ fehler: "Unbekannte Vorlage." });
|
||
einfuegen("content", "idee", v.titel, v.text, { hook: v.hook, format: v.format });
|
||
|
||
} else if (art === "content_alle") {
|
||
for (const v of CONTENT_IDEEN) {
|
||
einfuegen("content", "idee", v.titel, v.text, { hook: v.hook, format: v.format });
|
||
}
|
||
|
||
} else if (BEREICH_PUNKTE[art]) {
|
||
/* live, community, technik -- alle drei funktionieren gleich.
|
||
Drei fast gleiche Bloecke waeren der sichere Weg dazu, dass
|
||
der dritte irgendwann anders reagiert als die ersten beiden. */
|
||
const unterart = String(req.body.unterart || "");
|
||
const liste = BEREICH_PUNKTE[art][unterart];
|
||
if (!liste) return res.status(400).json({ fehler: "Unbekannter Abschnitt." });
|
||
const v = liste[nr];
|
||
if (!v) return res.status(400).json({ fehler: "Unbekannte Vorlage." });
|
||
einfuegen(art, unterart, v.titel, v.text);
|
||
|
||
} else if (BEREICH_PUNKTE[art.replace(/_alle$/, "")] && art.endsWith("_alle")) {
|
||
const bereich = art.replace(/_alle$/, "");
|
||
const unterart = String(req.body.unterart || "");
|
||
const liste = BEREICH_PUNKTE[bereich][unterart];
|
||
if (!liste) return res.status(400).json({ fehler: "Unbekannter Abschnitt." });
|
||
for (const v of liste) einfuegen(bereich, unterart, v.titel, v.text);
|
||
|
||
} else {
|
||
return res.status(400).json({ fehler: "Unbekannte Art." });
|
||
}
|
||
|
||
protokolliere("vorlage_uebernommen", req.person, echteIp(req),
|
||
`${art}: ${angelegt.length} Eintrag/Einträge`);
|
||
res.json({ angelegt: angelegt.length, titel: angelegt });
|
||
} catch (fehler) {
|
||
console.error("[workspace] Vorlage übernehmen:", fehler?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|
||
|
||
/* ---------- "Ich brauche Hilfe" ------------------------------------------
|
||
|
||
Ein Creator klickt an einem LIVE-Punkt auf "Hilfe". Daraus wird eine
|
||
Aufgabe für seinen Betreuer -- nicht eine Nachricht, die untergeht,
|
||
sondern ein Eintrag im Aufgabenbrett mit Frist.
|
||
|
||
Warum eine Aufgabe und keine Benachrichtigung: Eine Benachrichtigung
|
||
ist gelesen und dann weg. Eine Aufgabe bleibt stehen, bis sie jemand
|
||
erledigt -- und taucht auf der Startseite des Betreuers auf, wenn sie
|
||
überfällig wird. */
|
||
|
||
vorlagenRouter.post("/workspace/api/vorlagen/hilfe", gleicheHerkunft,
|
||
express.json({ limit: "8kb" }), (req, res) => {
|
||
try {
|
||
const thema = String(req.body.thema || "").trim().slice(0, 160);
|
||
if (thema.length < 3) return res.status(400).json({ fehler: "Worum geht es?" });
|
||
const notiz = String(req.body.notiz || "").trim().slice(0, 2000);
|
||
|
||
const creatorId = req.person.rolle === "creator" ? req.person.id
|
||
: Number(req.body.creator_id) || null;
|
||
if (!creatorId) return res.status(400).json({ fehler: "Kein Creator angegeben." });
|
||
if (req.person.rolle !== "creator" && !darfCreator(req.person, creatorId)) {
|
||
return res.status(404).json({ fehler: "nicht_gefunden" });
|
||
}
|
||
|
||
/* An wen? An den Betreuer. Gibt es keinen, an die Leitung -- eine
|
||
Bitte um Hilfe darf nicht ins Leere laufen, nur weil die
|
||
Zuordnung fehlt. */
|
||
const betreuer = db().prepare(
|
||
"SELECT betreuer_id FROM betreuung WHERE creator_id = ? LIMIT 1").get(creatorId);
|
||
const ziel = betreuer?.betreuer_id
|
||
|| db().prepare(
|
||
"SELECT id FROM personen WHERE rolle = 'admin' AND aktiv = 1 ORDER BY id LIMIT 1")
|
||
.get()?.id
|
||
|| null;
|
||
|
||
const name = db().prepare("SELECT name FROM personen WHERE id = ?").get(creatorId)?.name
|
||
|| "Creator";
|
||
|
||
/* Frist: in drei Tagen. Eine Bitte um Hilfe ohne Frist ist eine
|
||
Bitte, die liegen bleibt. */
|
||
const frist = new Date(Date.now() + 3 * 86400_000).toISOString().slice(0, 10);
|
||
|
||
db().prepare(`INSERT INTO aufgaben
|
||
(titel, beschreibung, status, prioritaet, creator_id, verantwortlich_id,
|
||
frist, erstellt, erstellt_von)
|
||
VALUES (?,?,'offen','hoch',?,?,?,?,?)`)
|
||
.run(`Hilfe gewünscht: ${thema}`,
|
||
`${name} hat um Unterstützung gebeten.\n\n${notiz || "(keine weitere Angabe)"}`,
|
||
creatorId, ziel, frist, jetzt(), req.person.id);
|
||
|
||
protokolliere("hilfe_angefragt", req.person, echteIp(req), thema);
|
||
res.json({ ok: true, an: ziel });
|
||
} catch (fehler) {
|
||
console.error("[workspace] Hilfe anfragen:", fehler?.message);
|
||
res.status(503).json({ fehler: "nicht_verfuegbar" });
|
||
}
|
||
});
|