c64732b16d4f5bb8d6aef717e74ca44a907291da
53
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8a2d2b802a |
Wächter-Protokoll hält sich selbst klein (keine unbegrenzte Log-Datei)
waechter.log wuchs unbegrenzt: alle 5 Minuten eine Zeile, ~288/Tag. Ohne Grenze irgendwann zu groß zum Durchsehen -- dann verliert das Protokoll seinen Zweck. Der Wächter kürzt jetzt selbst, statt logrotate: Das bräuchte eine Datei in /etc (kein Schreibrecht) und einen Extra-Dienst. Vor dem Anhängen wird nur die Größe abgefragt (billig); erst über 1 MB (~45 Tage) wird die Datei einmal gelesen und auf die jüngsten 2000 Zeilen (~1 Woche) gestutzt. Beim Bauen einen eigenen Fehler gefangen: statSync war in waechter.mjs nicht importiert (beim Auslagern der Sicherungsprüfung mit entfernt worden). node --check meldet das nicht -- es hätte erst zur Laufzeit im nächsten Cron-Lauf gekracht. Import ergänzt. Test pruef-protokoll-kuerzen.mjs: klein bleibt unangetastet, groß wird auf die JÜNGSTEN Zeilen gestutzt (älteste fallen weg), Grenzfall und fehlende Datei sauber. 8/8. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
a612a0f0fd |
Anfragebremse für /submit und /testimonials/submit (die letzten zwei ungebremsten Routen)
Bei der Bestandsaufnahme als "keine Route hat eine Bremse" gemeldet -- das war falsch (grep suchte nach rateLimit/bremse, im Code heißen sie Kontingent/Sperre/Fehlversuche). Fast alle öffentlichen Routen SIND gebremst: Anmeldung, Supporter-Login, Upload, Kundenanfragen. Übrig blieben genau zwei schreibende Routen: Bewerbungen und Stimmen. Ohne Bremse könnte ein Skript die Datenbank mit Müll fluten. Kein Sicherheitsleck (beide landen in einer Warteschlange, nichts wird ungesehen veröffentlicht), aber eine sinnvolle Härtung. Statt das vorhandene Muster ein drittes Mal zu kopieren: ein Baustein lib/kontingent.js, den nun alle drei Routen nutzen. Zwei Verbesserungen gegenüber dem Original in testimonials.js: - getrennte Töpfe je Zweck (ein Bild-Upload verbraucht kein Bewerbungs-Kontingent) - Selbstreinigung: die alte Zähler-Map ließ jede IP für immer im Speicher stehen (langsames Leck), die neue räumt abgelaufene Einträge auf Grenzen: Uploads 10/Stunde/IP (belegen Plattenplatz), Text-Einreichungen 20/Stunde/IP (großzügig für geteilte Anschlüsse, stoppt Fluten). Tests: pruef-kontingent.mjs (Baustein, 7/7), test-kontingent-routen.mjs (echte Routen liefern 429 ab Grenze, getrennte Töpfe, IPs unabhängig, 4/4). Bestehende Tests unverändert grün (37/37). NOCH NICHT LIVE: server-internal läuft aus /home/dogiintern (kein Zugriff), wird mit den übrigen Server-Änderungen in einem Deploy live geschaltet. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
46563b02fc |
Waechter merkt jetzt, wenn die naechtliche Sicherung ausfaellt
Beim Nachweis des ersten automatischen Laufs gefunden: Der Cron-Eintrag endet auf >/dev/null 2>&1 -- jede Fehlermeldung wird verworfen. Das Sicherungsskript fuehrt zwar ein eigenes Protokoll, aber alles, was VOR der ersten Protokollzeile schiefgeht (Skript geloescht, sqlite3 weg, Platte voll, Cron gestoppt), passiert spurlos. Niemand haette es gemerkt -- ausser in dem Moment, in dem man die Sicherung braucht. Der Waechter prueft ab sofort die Datei selbst, nicht das Protokoll: Ein Protokoll kann "erfolgreich" melden, waehrend die Datei fehlt. In lib/ ausgelagert, weil waechter.mjs beim Import sofort seinen ganzen Durchlauf startet -- testbar war die Funktion dort nicht. Beim Testentwurf einen eigenen Fehler gefunden: readdirSync wirft sowohl bei fehlendem Ordner (ENOENT) als auch bei fehlenden Rechten (EACCES). Die erste Fassung behandelte beides als "kein Urteil" und haette damit einen geloeschten Sicherungsordner verschwiegen. Jetzt getrennt: ENOENT meldet, EACCES schweigt. Die 26-Stunden-Grenze ist bewusst nicht enger: Kurz vor dem naechsten Lauf ist die juengste Sicherung regulaer 24 Stunden alt. Genau dort entstehen die Fehlalarme, nach denen man die Meldungen abschaltet -- und dann geht der eine echte mit unter. Als eigener Testfall abgesichert. 14 von 14 Pruefungen bestanden. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
b037867a99 |
Tests beenden sich jetzt sauber (process.exitCode statt process.exit)
Der Absturz auf dem Server bestand nach dem ersten Fix fort: node::RemoveEnvironmentCleanupHook … Assertion failed: (env) != nullptr Statement::~Statement() … better_sqlite3.node Abgebrochen DER ERSTE VERSUCH GING AN DER URSACHE VORBEI Ich hatte db.close() entfernt -- naheliegend, weil der Aufrufverlauf auf einen Statement-Destruktor zeigte. Es half nicht. Die Ursache liegt eine Ebene tiefer: process.exit() beendet Node SOFORT, waehrend better-sqlite3 noch offene Statements haelt. Deren Aufraeumhaken laeuft dann ins Leere. process.exitCode setzt nur den Rueckgabewert; Node beendet sich danach von selbst, sobald nichts mehr aussteht -- und raeumt dabei in der richtigen Reihenfolge auf. WARUM DAS MEHR ALS EIN SCHOENHEITSFEHLER WAR Der Absturz kam NACH allen Pruefungen und VOR der Zusammenfassung. Der Test meldete einen Fehler, obwohl inhaltlich alles bestanden war. In einer mit && verketteten Befehlsfolge blieb deshalb der anschliessende Dienst-Neustart aus, und die neuen Endpunkte antworteten weiter mit 404. Gesucht habe ich bei den Endpunkten, beim Deploy, an der Zugangswand -- die Ursache lag beim Beenden eines Testprozesses. BEMERKENSWERT Fuenf Tests im Projekt benutzten process.exitCode bereits. Das Muster war also etabliert; meine neuen Dateien wichen davon ab, ohne dass es jemandem auffiel. Sechs Tests sind jetzt angeglichen, alle geprueft: Rueckgabewert 0, Zusammenfassung vollstaendig. test-push-kette 30, test-altabbruch 16, test-webdesign-anfragen 37, test-webdesign-portal 49, test-webdesign-paypal 28, test-personendaten 15 Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
76b88c6b71 |
Tests: kein db.close() vor process.exit()
Auf dem Server brach test-personendaten.mjs ab: node::RemoveEnvironmentCleanupHook … Assertion failed: (env) != nullptr Statement::~Statement() … better_sqlite3.node Abgebrochen better-sqlite3 raeumt seine Statements ueber einen Aufraeumhaken ab. Wird die Verbindung unmittelbar vor dem Prozessende geschlossen, laeuft dieser Haken ins Leere. DIE FOLGE WAR SCHLIMMER ALS DER ABSTURZ Der Absturz kam NACH allen Pruefungen, aber VOR der Zusammenfassung. Der Test lieferte also einen Fehlercode, obwohl inhaltlich alles bestanden war. In der mit && verketteten Befehlsfolge blieb deshalb der anschliessende Dienst-Neustart aus -- und die neuen Endpunkte antworteten weiter mit 404. Man sucht dann den Fehler bei den Endpunkten, beim Deploy, an der Zugangswand. Die Ursache lag beim Aufraeumen einer Testdatenbank. Node schliesst die Verbindung beim Beenden ohnehin. Zusaetzlich faengt das Loeschen der Testdateien jetzt Fehler ab: Ohne db.close() haelt der Prozess sie noch offen, unter Windows scheitert das Loeschen dann mit EBUSY -- und "force" hilft dagegen nicht, es unterdrueckt nur "Datei nicht gefunden". Ein Test, der an seinem eigenen Aufraeumen scheitert, meldet einen Fehler, den es fachlich nicht gibt. Beide betroffenen Tests geprueft: Rueckgabewert 0, Zusammenfassung vollstaendig. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
7b09d752f8 |
Auskunft und Loeschung nach DSGVO (Art. 15/17)
Schreibt jemand "Welche Daten haben Sie ueber mich?" oder "Bitte loeschen Sie meine Daten", laeuft eine Frist von einem Monat (Art. 12 Abs. 3 DSGVO). Bisher haette man dafuer von Hand durch ein Dutzend Tabellen suchen muessen -- und uebersieht man eine, ist die Auskunft unvollstaendig, ohne dass man es ihr ansieht. ⚠️ LOESCHEN IST NICHT EINFACH LOESCHEN Der naheliegende Weg waere ein "Alles loeschen". Das waere bequem und doppelt falsch: Rechnungen und Buchungsbelege unterliegen einer gesetzlichen Aufbewahrungspflicht -- § 147 Abs. 3 AO nennt acht Jahre fuer Buchungsbelege, zehn fuer Handelsbuecher, gerechnet ab dem Ende des Kalenderjahres (Abs. 4). Wer sie auf Zuruf loescht, verstoesst gegen Steuerrecht, um Datenschutzrecht zu erfuellen. Die DSGVO nimmt diesen Fall selbst aus (Art. 17 Abs. 3 lit. b). Fuer das, was bleiben muss, sieht sie die Einschraenkung der Verarbeitung vor (Art. 18) -- genau so wird es ausgewiesen, samt Datum, ab dem geloescht werden darf. Dasselbe bei Widerrufserklaerungen: Sie sind der Nachweis, DASS und WANN widerrufen wurde. Wer sie loescht, vernichtet seinen eigenen Beleg in genau der Sache, in der es spaeter Streit geben koennte. EIN FEHLER, DEN DER TEST VOR DEM ERSTEN LAUF GEFUNDEN HAT Die Tabelle wd_widerrufe fuehrt die Adresse nicht als "email", sondern als "kontakt". Die erste Fassung suchte nach "email" -- sie haette dort NIE einen Treffer geliefert, und die Auskunft haette trotzdem sauber ausgesehen. Aufgefallen nur, weil die Testdaten gegen das echte Schema angelegt wurden statt gegen die Annahme. Deshalb steht in der Quellenliste jetzt die Spalte, nicht eine Vermutung. WEITERE ENTSCHEIDUNGEN - Die Suche geht ueber die Adresse UND ueber die Kundenkennung. Vieles haengt nicht an der Adresse; ohne diesen Schritt bliebe die halbe Auskunft leer. - Gross- und Kleinschreibung spielt keine Rolle -- sonst bekaeme jemand eine leere Auskunft, weil er seine Adresse anders schreibt. - Die Loeschung verlangt die Adresse ein zweites Mal. Der Unterschied zwischen .org und .com ist ein Buchstabe, die Folge unwiederbringlich. - Sie laeuft in einer Transaktion: Bricht sie ab, bliebe sonst ein halbgeloeschter Bestand, und niemand wuesste, welcher Teil weg ist. - Der Vorgang wird protokolliert, aber OHNE die geloeschten Inhalte -- sie dabei erneut zu speichern waere das Gegenteil des Zwecks. 15 Pruefungen gruen, mit Gegenprobe (fremde Adresse liefert nichts). Die Oberflaeche in der Verwaltung folgt. Deploy braucht Filipe: server-internal liegt unter /home/dogiintern mit Rechten 700. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
ac1d5c9916 |
Waechter: Rechte wurden gesetzt, aber wirkten nicht
Nachgemessen: /var/lib/dogfather-waechter stand auf 755 -- weltweit lesbar, mit einer vollstaendigen Liste aller Dienste, Adressen und ihres Zustands darin. Also genau das, was der Commit von heute Nachmittag verhindern sollte. Die Absicht stand im Code, die Wirkung fehlte. Zwei Gruende, beide still: 1. "mode" bei mkdirSync gilt nur, wenn das Verzeichnis dabei NEU entsteht. Beim zweiten Lauf existiert es immer -- dann laesst mkdirSync die Rechte unberuehrt. Hier war es sogar schon vor dem Einbau der Zeile angelegt worden. 2. Selbst beim Neuanlegen zieht die umask des Prozesses Bits ab. Der Unterschied zur Sicherung ist lehrreich: /var/backups/dogfather steht korrekt auf 700, weil dort ein ausdrueckliches chmod im Skript steht. Dieselbe Ueberlegung, einmal umgesetzt und einmal nur gemeint. Jetzt chmod bei jedem Lauf, fuer Verzeichnis UND Dateien. Aufgefallen ist es nur, weil die Rechte nachgesehen wurden statt angenommen -- der Code sah richtig aus. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
6077c1700f |
server-internal: package-lock.json aufgenommen
Nach der Installation auf dem Server vom dortigen Stand uebernommen -- nicht hier erzeugt. Das ist der entscheidende Unterschied: Eine lokal erzeugte Datei haette festgeschrieben, was HIER aufgeloest wird, nicht was dort tatsaechlich laeuft. Genau diese Luecke sollte sie ja schliessen. Festgeschrieben sind jetzt: multer 2.2.0 (war 1.4.5-lts.1, veraltet) node-cron 4.6.0 (war 3.0.3) express 4.22.2 (package.json sagt ^4.21.2) better-sqlite3 11.10.0 dotenv 16.6.1 cors 2.8.6 express 4.22.2 zeigt, warum das noetig war: Die package.json erlaubt alles unter 5.0, installiert war eine andere Fassung als die genannte. uuid taucht in der Liste nicht mehr auf. Es kam ausschliesslich ueber node-cron 3.x herein und war die einzige Luecke, die "npm audit" gefunden hatte. Gegenprobe nach der Umstellung: "found 0 vulnerabilities". Auch die Veraltet-Markierung von multer ist weg. Der Dienst laeuft stabil (PID unveraendert ueber mehrere Messungen, keine zusaetzlichen Neustarts) und antwortet mit 401 -- er lebt also und prueft Rechte. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
a2487212c8 |
multer 2.x und node-cron 4.x: geprueft, package.json angehoben
multer 1.4.5-lts.1 ist von den Betreuern ausdruecklich als veraltet markiert. Die Meldung der Registry, woertlich: "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version." ⚠️ BEMERKENSWERT: "npm audit" meldet dazu NICHTS. Die Pruefung nannte nur eine Luecke in uuid, eingeschleppt ueber node-cron 3.x. Wer sich allein auf audit verlaesst, haette multer fuer unbedenklich gehalten. Die Deprecation-Meldung ist hier die eigentliche Warnung -- sie steht aber an einer Stelle, an die man nur kommt, wenn man gezielt nachfragt. 2.0.0 behebt CVE-2025-47935 und CVE-2025-47944. Einzige dokumentierte Breaking Change: Node ab 10.16. Auf dem Server laeuft 24. node-cron 4.x behebt die uuid-Luecke. Die Fassung ist eine Umstellung auf TypeScript, ohne dokumentierte API-Aenderung -- genau dabei aendert sich aber gern die Art des Standard-Exports, und "import cron from 'node-cron'" wuerde danach beim START scheitern, nicht bei der Installation. GEPRUEFT STATT ANGENOMMEN In einem eigenen Verzeichnis gegen multer 2.2.0 und node-cron 4.6.0 getestet, mit genau den Aufrufen aus index.js: diskStorage mit destination/filename, limits, fileFilter, single/array/fields, MulterError samt code, cron.schedule("* * * * *") und task.stop(). 15 von 15 bestanden. Die Pruefung liegt jetzt als server-internal/test-pakete.mjs im Projekt -- die Frage "laeuft unser Code damit noch?" stellt sich bei jedem Hauptversionswechsel neu. Express bleibt bewusst bei 4.x. Der Sprung auf 5 ist ein eigener Vorgang mit deutlich mehr Flaeche; ihn hier mitzunehmen wuerde zwei unabhaengige Risiken in einem Schritt buendeln. Die Installation selbst braucht Filipe: server-internal liegt unter /home/dogiintern mit Rechten 700. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
b2f4567cce |
Waechter: leere Geraeteliste und fehlende Tabelle unterscheiden
Der erste echte Probelauf meldete "kein Geraet angemeldet". Richtig -- aber dieselbe Meldung waere auch erschienen, wenn die Tabelle gar nicht existierte, weil dbLesen in beiden Faellen "" zurueckgibt. Das sind zwei voellig verschiedene Lagen: Einmal genuegt ein Klick in der Verwaltung, einmal ist die Migration nicht gelaufen. Wer die falsche Meldung liest, drueckt auf Einschalten, sieht keine Wirkung und sucht dann beim Browser statt bei der Datenbank. Genau die Sorte Verwechslung, gegen die dieser ganze Strang gebaut wurde. Jetzt wird zuerst gefragt, ob die Tabelle da ist, und die Meldung sagt im harmlosen Fall auch gleich, was zu tun ist. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
bcf6e01fa9 |
Waechter: engere Dateirechte und ein Probeschalter
Der erste automatische Lauf war erfolgreich (14:20, 18 Punkte, 0 auffaellig). Zwei Nachtraege: RECHTE Zustandsdatei und Protokoll lagen mit 644 in einem 755-Verzeichnis. Personenbezogene Daten stehen dort keine -- wohl aber eine vollstaendige Liste aller Dienste, Adressen und ihres Zustands. Fuer jemanden, der einen Angriff vorbereitet, ist das eine bequeme Landkarte. Jetzt 700/600. Kostet nichts, also gibt es auch keinen Grund, es herzugeben. PROBESCHALTER node waechter.mjs --probe Verschickt eine Meldung, ohne dass etwas kaputt sein muss. Das ist die einzige Moeglichkeit, den MELDEWEG zu pruefen, ohne auf eine echte Stoerung zu warten. Der Grund ist derselbe wie beim stillen "if (!url) return;", das diese Reihe ausgeloest hat: Eine Ueberwachung, deren Zustellung stillschweigend nicht funktioniert, ist schlimmer als gar keine. Man haelt die Stille dann fuer "alles in Ordnung" -- dabei ist sie nur Stille. Steht bewusst VOR den Messungen: Wer den Meldeweg pruefen will, soll nicht erst 18 Punkte abfragen muessen. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
3a5805bfad |
Waechter: meldet Ausfaelle, statt sie unbemerkt zu lassen
Der Befund war besser als der Aufgabentitel: Alle Dienste haben
Restart=always und stehen nach einem Absturz von selbst wieder auf. Die
Luecke liegt woanders:
- Dauerschleife: Startet ein Dienst und stuerzt sofort wieder ab, gibt
systemd nach wenigen Versuchen auf. Dann bleibt er unten.
- "active" heisst nicht "antwortet". Ein haengender Dienst gilt
systemd als gesund.
- Zertifikatsablauf und volle Platte machen keinen Dienst inaktiv,
legen aber alles lahm.
Geprueft werden 18 Punkte: sechs Dienste, neun Adressen, Plattenplatz,
zwei Zertifikatslaufzeiten.
WARUM ALS ROOT PER CRON
Ein Waechter, der ueber den internen Dienst meldet, hat einen
Konstruktionsfehler mit Ansage: Ausgerechnet wenn DIESER Dienst das
Problem ist, kaeme keine Meldung durch. Also liest er .env und Datenbank
selbst und verschickt selbst -- unabhaengig davon, ob noch irgendetwas
laeuft. Ohne Fremdpakete, nur Node-Bordmittel, sqlite3 und systemctl.
NUR BEI ZUSTANDSWECHSEL
Gemeldet wird, wenn etwas kippt -- in beide Richtungen. Nicht alle fuenf
Minuten dasselbe. Wer staendig Meldungen bekommt, sieht irgendwann keine
mehr an und uebersieht die eine, auf die es ankam.
ZWEI EIGENE FEHLER, DIE DER TEST GEFUNDEN HAT
1. Zuerst stand je Adresse eine handgepflegte Liste erlaubter
Antwortcodes. Der erste Lauf meldete VanVans Shop als ausgefallen --
er war es nicht, er steht ebenfalls hinter einer Zugangswand und
antwortet mit 302. Ich war damit genau in die Falle gelaufen, vor der
der Kommentar an derselben Stelle warnte.
2. Danach galt "unter 400" als heil. Jetzt meldete das Postfach einen
Ausfall, weil die geprueften Adresse 404 lieferte -- der Dienst lief
einwandfrei, ich hatte die Adresse falsch gewaehlt.
Beide Male dieselbe Lehre: Eine Ueberwachung, die bei einer falsch
getippten Adresse "Ausfall" ruft, erzieht einen dazu, ihre Meldungen zu
ignorieren. Die Regel lautet jetzt "unter 500", denn der Waechter fragt
"lebt der Dienst?", nicht "ist der Inhalt richtig?". 401, 403 und 404
BEWEISEN, dass jemand da ist und zuhoert. Nur 5xx und Schweigen heissen,
dass dahinter nichts mehr laeuft. Ausnahme sind die beiden Pflichtseiten
Impressum und Widerruf -- dort ist alles ausser 200 bereits ein Mangel.
GEPRUEFT AM SERVER
Ausfall eingebaut: erkannt und gemeldet. Ausfall dauert an: still, keine
Wiederholung. Wieder erreichbar: Entwarnung. 18 Punkte, 0 Fehlalarme
ueber mehrere Laeufe.
⚠️ GRENZE, DIE BLEIBT
Ist der Server als Ganzes weg -- Netz, Strom, Hardware --, meldet auch
dieser Waechter nichts. Dagegen hilft nur eine Ueberwachung ausserhalb
der Maschine. Steht so im Kopf der Datei, damit sich niemand in falscher
Sicherheit wiegt.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
f435d89a16 |
Benachrichtigung bei neuer Anfrage: Push aufs Handy
BEFUND ZUERST, DENN ER WAR ANDERS ALS ERWARTET
Es gab sehr wohl eine Benachrichtigung -- ueber einen Discord-Webhook,
sauber gebaut, bewusst ohne Kundendaten im Text. Sie begann aber mit:
const url = process.env.DISCORD_WEBHOOK_WEBDESIGN;
if (!url) return;
Und diese Variable ist in .env.example nirgends aufgefuehrt. Eine
Variable, die niemand kennt, wird nicht gesetzt; dann kehrte die
Funktion wortlos zurueck. Kein Fehler, keine Protokollzeile. Es gab
eine Benachrichtigung, die nur im Quelltext existierte -- und niemand
konnte das bemerken, weil "funktioniert" und "kaputt" identisch
aussehen, solange nichts passiert.
WAS JETZT DA IST
Push aufs Handy, ohne Fremdpaket. Der interne Dienst liegt unter
/home/dogiintern mit Rechten 700 -- dort ist kein npm erreichbar, jede
Abhaengigkeit haette dauerhafte Handarbeit bedeutet. Node bringt P-256,
HKDF und AES-128-GCM selbst mit.
Die Schluessel erzeugt der Server beim ersten Start selbst und legt den
privaten Teil verschluesselt in der Datenbank ab (derselbe Weg wie die
PayPal-Zugangsdaten). Damit gibt es keinen Einrichtungsschritt, der
vergessen werden kann.
GEPRUEFT
Gegen RFC 8291 statt gegen ein Bauchgefuehl: alle fuenf Zwischenwerte
aus Anhang A stimmen (ECDH-Geheimnis, PRK_key, IKM, CEK, NONCE), und
die fertige Nachricht ist Byte fuer Byte die aus Abschnitt 5 der Norm.
Bei Kryptographie erzeugt ein Ableitungsfehler keinen Absturz, sondern
Bytes, die genauso zufaellig aussehen wie richtige.
Dazu ein Kettentest mit einem echten Empfaenger, der entschluesselt:
Kopfzeilen, Inhalt, keine Kundendaten in der Meldung, 410 loescht das
Geraet, 500 loescht es NICHT (sonst kostet eine einzelne Stoerung die
Anmeldung). 50 Pruefungen, alle gruen.
DREI ENTSCHEIDUNGEN
1. In der Meldung stehen nur Nummer und Paket. Sie erscheint auf einem
Sperrbildschirm, den auch jemand sieht, der zufaellig danebensteht.
2. Ist der Schluessel unlesbar, wird KEIN neuer erzeugt. Das waere der
bequeme Weg und der schlimmste: Ein neuer oeffentlicher Schluessel
macht schlagartig jede Anmeldung wertlos, ohne dass jemand erfaehrt,
warum nichts mehr ankommt.
3. Die Verwaltung zeigt den Zustand an und hat einen Testknopf. Genau
das fehlte dem Discord-Weg. Bei verweigerter Erlaubnis erscheint
kein Knopf, der nichts bewirkt, sondern der Weg ueber die
Browsereinstellungen.
Das stille "if (!url) return;" ist ersetzt: Jeder Weg wird einzeln
protokolliert -- auch und gerade, wenn er uebersprungen wird.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
75ab3f713f |
Sicherung: Kundendaten nicht mehr fuer jedes Konto lesbar
Der erste echte Lauf hat einen Mangel sichtbar gemacht, den das Skript selbst verursacht hat: root legt Dateien standardmaessig mit 644 an, also welt-lesbar. Nachgemessen als gewoehnliches Konto, ohne sudo: Die Sicherung liess sich nach /tmp kopieren und daraus Kundennamen, E-Mail-Adressen und Paketwahl auslesen; das Upload-Archiv ebenso. Auf dieser Maschine bestehen fuenf Konten. Eine Sicherung buendelt an einer Stelle, was sonst verstreut liegt -- sie muss enger geschuetzt sein als das Original, nicht lockerer. umask 077 fuer alles Neue; fuer die bereits angelegten Verzeichnisse zusaetzlich ausdruecklich 700 bzw. 600, denn umask wirkt nur auf neu Erzeugtes. Der gleiche Mangel besteht beim Original selbst (644 dogiintern) -- das kann ich nicht aendern, es gehoert nicht mir. Wird gemeldet. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
d62ff53062 |
Datensicherung: taeglicher Stand und geprueft Wiederherstellung
Bisher gab es keine Sicherung. Ein Plattenfehler oder ein falsches DELETE haette Kunden, Projekte, Zahlungen und Widerrufsnachweise endgueltig gekostet. sicherung.sh legt taeglich einen Stand an -- mit SQLites eigenem ".backup", nicht mit "cp". Der Grund ist messbar: Die Datenbank ist 778 KB gross, ihr WAL 4,1 MB. Eine Kopie der .db allein waere also nicht bloss veraltet, sondern weitgehend leer. Ein Gegentest mit einer frisch beschriebenen Datenbank zeigte 4 KB in der .db gegen 2,1 MB im WAL. Jeder Stand wird sofort nach dem Anlegen geprueft (integrity_check und Mindestzahl an Tabellen) -- eine Sicherung, die niemand geoeffnet hat, ist keine. 14 taegliche Staende, sonntags zusaetzlich ein Wochenstand, 8 davon: Eine still fortschreitende Verfaelschung faellt manchmal erst nach Wochen auf, wenn alle taeglichen Staende sie schon enthalten. wiederherstellen.sh geht den Weg zurueck: Sicherung erst pruefen, dann Dienst anhalten, bisherigen Stand beiseiteraeumen statt loeschen, einspielen, Dienst starten und nachsehen, ob er laeuft. Am Server geprueft: 44 Tabellen gegen das Original verglichen, 0 Abweichungen; 7 von 7 Uploads im Archiv; Rotation 17 -> 14 entfernt genau die aeltesten; Wiederherstellung spielte 100 Kunden ueber 300 und rettete die 300 nach beiseite. Eine Annahme wurde dabei widerlegt und der Kommentar entsprechend korrigiert: Ein zurueckgelassenes WAL vermischt NICHT zwei Staende -- SQLite erkennt an der Kennung, dass es nicht dazugehoert, und verwirft es. Der echte Schutz ist das Anhalten des Dienstes. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
0ef64ba222 |
Systemabnahme: fuenf Altlasten geschlossen
Vollstaendiger Durchlauf ueber alle Pruefungen. Dabei kamen fuenf Dinge ans Licht, die teils seit Wochen offen standen. 1. WCAG-KONTRAST -- der einzige Punkt, der echte Besucher betraf Die lila Schilder erreichten nur 4,08:1, verlangt sind 4,5:1 fuer normalen Text. 17 Fundstellen, alle dieselbe Ursache: Die Schrift nutzte den vollen Ton Aurora Violet. Gemessen wurde nicht geschaetzt: Untergrund rgb(38,41,81) aus dem echten Bild ausgelesen, Schrift 12,5 Punkte -- und fett zaehlt erst ab 18,5 Punkten als "grosser Text". Neu ist --wd-lila-hell (#C197FF, 6,02:1). Bewusst NICHT der knappste Wert: #B380FF haette mit 4,91:1 gereicht, aber dasselbe Schild steht auch ueber der Buehne im Verwaltungsbereich. Ein Ton, der nur an einer Stelle knapp besteht, faellt beim naechsten Hintergrund wieder durch. Dasselbe Muster wie --wd-blau-hell, das genau deshalb existiert. 2. GEHEIMNIS-TEST -- der Test war veraltet, nicht der Code Er verlangte ".env hat Vorrang", die Umsetzung macht das Gegenteil. Die Begruendung im Code ueberzeugt: Wer PayPal-Daten im Formular eintraegt, erwartet, dass sie gelten. Andernfalls koennte ein alter Wert in der .env sie stumm ueberstimmen -- und man sucht stundenlang. Der Test prueft jetzt die tatsaechliche Reihenfolge, dazu neu, dass ein gleichzeitig vorhandener Serverwert auch angezeigt wird. Ausserdem endete der Lauf trotz gruener Pruefungen mit einer Fehlermeldung: Unter Windows haelt eine offene SQLite-Datei eine Sperre, das Aufraeumen scheiterte mit EPERM. Auf Linux waere es durchgelaufen -- dasselbe Skript mit unterschiedlichem Ergebnis je Rechner. Jetzt wird erst geschlossen, dann geloescht, und das Aufraeumen kann den Lauf nicht mehr zum Scheitern bringen. 3. PORTAL-TEST -- ebenfalls veraltet Er suchte "1500,00" und schlug fehl, seit der Formatierer den Tausenderpunkt setzt. "1.500,00 EUR" ist die korrekte deutsche Schreibweise; gerade ab vier Stellen macht der Punkt eine Zahl auf einen Blick lesbar. 4. ZWEI SEITEN OHNE WOERTERBUCH -- eine Entscheidung, keine Luecke "diagnose" ist ein Betriebswerkzeug. "rechtliches" ist der heiklere Fall: Rechtstexte durch eine ungepruefte Uebersetzung zu schicken ist gefaehrlicher, als sie einsprachig zu lassen. Ein Fehler in einer Widerrufsbelehrung wirkt gegen den Verfasser. Die Pruefung meldete beides als "Datei fehlt" -- das las sich wie ein Versehen und stand deshalb dauerhaft in der Fehlerliste, ohne dass jemand etwas tat. Jetzt sind beide benannt und begruendet. 5. TAG-UNGLEICHGEWICHT -- eine Fehlmessung Die Pruefung zaehlte auch HTML-Schnipsel, die als Zeichenketten im JavaScript stehen. Dort steht ein oeffnendes <div> regelmaessig in einer anderen Zeichenkette als sein </div>, weil die Teile erst beim Zusammensetzen ein Ganzes ergeben. Gegengeprueft im Browser: geparster Baum einwandfrei, kein Skriptfehler, Verschachtelung unauffaellig. Es war nie ein Strukturfehler. Die Meldung stand aber monatelang als "2 Fehler" da und haette jede echte Meldung entwertet, die dazugekommen waere. STAND Browser 717 Pruefungen 0 offen Server 566 Pruefungen 0 offen i18n keine Fehler WCAG 0 Fundstellen (vorher 17) Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
01b61905aa |
Altfaelle aufraeumen: abgebrochene Projekte verschwinden aus der Liste
P-2608-0001 wurde abgebrochen, bevor es das Archiv-Kennzeichen gab. Es hat deshalb zwar den Status "abgebrochen", aber archiviert = 0 -- und stand bis heute zwischen den offenen Auftraegen. Also genau das, was der Wunsch "wenn ich abbreche dan sollen die auch da weg" abstellen sollte. Ueber die Oberflaeche war das nicht zu reparieren: Die Verwaltung zeigt bei Status "abgebrochen" den Info-Block statt des Abbrechen-Knopfes, ein zweiter Klick war also gar nicht moeglich. Migration statt Handarbeit in der Datenbank: Ein einzelner UPDATE per Hand repariert einen Fall und hinterlaesst keine Spur. Die Migration erwischt jeden Altfall, laeuft ueberall gleich (Server, Testdatenbank, ein spaeterer Neuaufbau) und steht nachvollziehbar in der Geschichte. Bewusst NICHT gefuellt: abbruch_am, abbruch_grund, abbruch_wer, abbruch_erstattung_cent. Diese Felder gab es beim damaligen Abbruch noch nicht. Sie nachtraeglich mit plausiblen Werten zu fuellen waere eine Behauptung ueber einen Vorgang, bei dem niemand mehr weiss, wie er wirklich ablief -- und ausgerechnet im Streitfall waere das die gefaehrlichste Stelle fuer eine Erfindung. Ein leeres Feld sagt ehrlich "unbekannt", und die Verwaltung zeigt diese Angaben ohnehin nur an, wenn sie gefuellt sind. Der Test stellt den Altzustand echt nach: alle Migrationen bis 0017, dann der Altfall, dann erst 0018. Geprueft wird auch, was NICHT passieren darf -- ein laufendes Projekt bleibt unberuehrt, ein sauber abgebrochenes behaelt Datum, Grund und Erstattung, und ein zweiter Durchlauf aendert nichts mehr (beim Wiederherstellen einer Sicherung passiert genau das). Geprueft: 167 Pruefungen gruen (Altabbruch 16, Automatik 55, Angebote 56, Kette 40). Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
521f0d5a80 |
Kompletten Ablauf einmal durchgespielt — echten Fehler dabei gefunden
Wunsch: "ich will dass du es abcheckst" — nicht Stück für Stück (das war
schon geprüft), sondern EINMAL DURCHGÄNGIG als ein zusammenhängender
Ablauf, so wie ein echter Auftrag tatsächlich läuft: Anfrage → Angebot
→ Zusage im Portal → Anzahlung → laufende Uhr → Benachrichtigung →
Arbeit → Restzahlung → Gegenprobe mit Abbruch.
test-kette.mjs bildet genau das ab, gegen die echten Funktionen aus
server-internal/, mit einer Wegwerf-Datenbank. 40 Prüfungen, alle grün.
DABEI GEFUNDEN: Der Liefertermin-Countdown im Portal prüfte nicht, ob
die Uhr überhaupt schon läuft. Ein frisch zugesagtes Projekt hat schon
ein termin_am (aus der Zusage vorgerechnet), aber uhr_start_am steht
noch auf null, solange die Anzahlung nicht da ist. Der Kunde hätte also
direkt nach der Zusage einen tickenden Countdown gesehen — und wenn die
Anzahlung eintrifft, wird der Termin ab dem Zahlungstag NEU berechnet
und springt dann sichtbar nach hinten. Das sieht aus wie ein Fehler,
auch wenn die Zahl rechnerisch stimmt: Kaum zu erklären, warum "noch 8
Tage" plötzlich wieder "noch 10 Tage" werden.
Jetzt zwei klar getrennte Zustände: Vor der Anzahlung steht "Startet,
sobald deine Anzahlung da ist — voraussichtlicher Liefertermin danach:
ca. {datum}" (kein Countdown, aber der Termin bleibt sichtbar, kein
Verstecken). Danach der echte Countdown. Fünf Sprachen ergänzt.
Nebenbei einen irreführenden Kommentar korrigiert: Ein neuer Kunde wird
beim Angebot-Senden SOFORT freigeschaltet (nicht erst bei Zusage, wie
der alte Kommentar behauptete) — sonst könnte er sich gar nicht
einloggen, um sein eigenes Angebot anzusehen. Der Code war richtig,
nur die Erklärung falsch, und mein erster Testentwurf ist genau darauf
hereingefallen.
Bei der Gelegenheit auch die bekannte better-sqlite3-Aussetzer-Eigenart
(zufälliger Absturz beim Prozessende, dokumentiert seit früheren
Commits) noch einmal eingegrenzt: Derselbe Import in test-angebote.mjs
brach mal nach "GEHEIM", mal nach "VORLAGEN" ab — der Absturzpunkt
verschiebt sich zwischen identischen Läufen. Das ist der endgültige
Beweis, dass es reine GC-Zeitfensterflakiness in der nativen
Bibliothek ist, kein Fehler im eigenen Code.
Geprüft: 40 (Kette) + 55 + 56 + 70 + 28 + 49 serverseitig (alle
mehrfach unabhängig grün), 261 im Browser (Portal, Angebot, Übersicht,
Verwaltung, Abbruch, Design). Ein Screenshot bestätigt den neuen
Wartehinweis visuell.
|
||
|
|
e43375728c |
Abgebrochene Projekte verschwinden aus der Liste
Rueckmeldung: 'wenn ich abbreche dan sollen die auch da weg'. Ein abgebrochenes Projekt in der laufenden Liste stehen zu lassen ist doppelt schaedlich: Es verstellt den Blick auf das, was wirklich laeuft, und beim schnellen Durchsehen haelt man es fuer eine offene Baustelle. Beim Abbrechen wandert das Projekt jetzt automatisch ins Archiv. Kein neues Konzept: Den Archiv-Umschalter in der Projektliste gibt es laengst, und die Suche sowie das Cockpit klammern Archiviertes ohnehin aus. Es verschwindet damit auch aus dem PORTAL des Kunden -- und das ist richtig so, ein abgebrochener Auftrag hat dort nichts mehr zu suchen. Archivieren statt loeschen: Zahlungen, Erstattungsbetrag und Verlauf haengen daran, und bei einem Streit braucht man genau das. Der Test prueft beides -- weg aus der Liste UND noch vorhanden. Geprueft: 55 gegen eine echte Datenbank. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
cea265eb8c |
Angebote: der Kunde sagt mit einem Klick zu
DER LEITGEDANKE: SO WENIG TIPPEN WIE MOEGLICH Niemand schreibt hier den Leistungsumfang. Er entsteht aus der Aufgabenvorlage des Pakets -- denselben 92 Punkten, nach denen spaeter gearbeitet wird. Der Nebeneffekt ist wichtiger als die gesparte Tipparbeit: Was im Angebot steht, IST die Arbeitsliste. Ein von Hand geschriebenes Angebot und eine getrennt gepflegte Aufgabenliste laufen unweigerlich auseinander -- und dann steht im Angebot etwas, das niemand abarbeitet. Zu tun bleibt: Paket waehlen, Preis bestaetigen. Beides ist vorbelegt, Laufzeit und Ablaufdatum werden gerechnet. DER WORTLAUT WIRD BEIM ABSENDEN EINGEFROREN Ein angenommenes Angebot ist ein Vertrag. Bei einem Streit zaehlt, WAS dem Kunden gezeigt wurde, als er zusagte. Wuerde der Text bei jeder Ansicht neu aus den Vorlagen erzeugt, staende nach der naechsten Vorlagenaenderung etwas anderes da als damals. Die Zusage wird mit Zeitpunkt und (gehashter) Herkunft belegt -- die Beweislast liegt beim Unternehmer. WAS AUTOMATISCH PASSIERT Angebot raus -> Anfrage steht auf 'angebot' (der Wunsch: 'wenn ich ein angebot rausschicke soll der automatisch das erkennen'). Zusage -> Projekt angelegt, Aufgabenliste eingesetzt, Anzahlung als offene Rechnung erzeugt, Anfrage auf 'angenommen', Benachrichtigung an mich. Die UHR startet dabei NICHT. Sie startet erst mit dem Zahlungseingang -- das ist die Regel aus dem vorigen Schritt, und sie gilt auch dann, wenn der Kunde selbst zugesagt hat. Genau dieser Punkt wird ausdruecklich geprueft: Es fuehlt sich richtig an, mit der Zusage loszulegen. ZWEI FEHLER IN DER GELDANZEIGE GEFUNDEN Beide fielen erst auf, als das erste Angebot ueber tausend Euro entstand -- alle frueheren Testbetraege lagen darunter: - Kein Tausenderpunkt: 1490 Euro erschienen als '1490,00 €'. - Das Minuszeichen ging verloren: Math.trunc(-0.5) ergibt -0, und -0 schreibt sich als '0'. Eine ERSTATTUNG von 50 Cent erschien damit als '0,50 €' -- also wie eine Forderung. Ausgerechnet beim Abbruch mit Rueckzahlung waere das der falsche Ort fuer einen Anzeigefehler. Bewusst von Hand statt ueber Intl.NumberFormat: Die Ausgabe muss auf dem Server und im Browser zeichengleich sein -- ein Betrag, der in der Verwaltung anders aussieht als im Portal, saet Zweifel an der Zahl. Geprueft: 56 zu den Angeboten, 53 zur Automatik (inkl. der neun Geldpruefungen), 70 zur Annahme, 28 zur Suche -- alle gegen eine echte Datenbank. Darunter: zweiter Klick legt nichts doppelt an, abgelaufene Angebote lassen sich nicht mehr annehmen (ein Angebot, das man sieht aber nicht annehmen kann, waere eine Falle), ein fremder Kunde erfaehrt nicht einmal, dass ein Angebot existiert. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
65b98ace89 |
Die Uhr laeuft erst, wenn die Anzahlung da ist
Bisher begann der Liefertermin mit der Annahme. Das ist unfair in beide Richtungen: Wer zehn Tage bis zur Zahlung braucht, verbraucht zehn Tage der zugesagten Zeit, ohne dass ein Handschlag Arbeit passiert waere -- und ich stehe am Ende als der da, der seinen Termin reisst. Ein Projekt hat jetzt drei Abschnitte statt zwei: 1. angenommen, wartet auf Anzahlung -> Uhr steht 2. Anzahlung da -> Uhr laeuft, Termin ab HEUTE neu 3. uebergeben oder abgebrochen -> Uhr steht wieder Der Zahlungseingang loest alles Weitere von selbst aus: Uhr starten, Termin neu rechnen, Status von briefing auf design, 'wer ist am Zug' auf mich, Benachrichtigung in der Verwaltung. Der bei der Annahme genannte Termin bleibt als termin_geplant_am erhalten, und die Meldung nennt BEIDE -- so sieht man, dass sich etwas verschoben hat, ohne nachrechnen zu muessen. Eingehaengt an der Stelle, an der beide Wege zusammenlaufen (PayPals Meldung UND das Vermerken von Hand). Nur am Webhook haenge sich die Seite verschieden verhalten, je nachdem WIE das Geld ankam -- eine von Hand verbuchte Zahlung startete die Uhr nie. Zwei Grundsaetze fuer die Automatik: Sie setzt Dinge in Gang, nimmt aber nie eine Entscheidung zurueck, die ein Mensch getroffen hat (ein von Hand pausiertes Projekt wird nicht kommentarlos wieder gestartet). Und jeder Schritt hinterlaesst eine Spur im Verlauf UND als Meldung -- eine Automatik, die stillschweigend arbeitet, ist kein Helfer, sondern ein Raetsel. ABBRECHEN Ein angenommener Auftrag bleibt abbrechbar: Der Kunde zahlt nicht, meldet sich nicht, springt ab. Vorschau und Ausfuehrung sind getrennt -- die Seite rechnet aus dem Aufgabenfortschritt vor, wie viel Leistung erbracht wurde, und schlaegt daraus einen Erstattungsbetrag vor. Der Betrag ist ein VORSCHLAG: Ob im Einzelfall mehr oder weniger angemessen ist, haengt an Dingen, die keine Tabelle kennt. Offene Rechnungen werden storniert (eine Zahlungsaufforderung ohne Gegenleistung), bezahlte bleiben unangetastet, und die Rueckzahlung loest die Seite bewusst NICHT selbst aus -- PayPal-Rueckzahlungen sind nicht umkehrbar. BENACHRICHTIGUNGEN Eigene Tabelle statt im Verlauf: Der Verlauf haelt fest, WAS geschehen ist -- vollstaendig, zum Nachschlagen. Eine Benachrichtigung ist ein Anstupsen, das gelesen und weggelegt wird. Beides in einer Tabelle hiesse: entweder ein Verlauf voller Rauschen oder Meldungen, die man nicht wegklicken kann. Wegklicken markiert nur als gelesen, loescht nichts. Dazu die Liste der Projekte, die seit ueber einer Woche auf ihre Anzahlung warten. Sie stehen in keiner anderen Zahl, weil ihre Uhr nie zu laufen begann -- ohne diesen Hinweis vergisst man sie. Geprueft: 44 gegen eine echte Datenbank. Darunter der Kern -- die Annahme wird zehn Tage zurueckdatiert, und der Termin muss danach trotzdem volle 20 Werktage entfernt liegen. Beim Bauen des Tests selbst ein Fehler gefunden: Die erste Fassung datierte nur die Annahme zurueck, nicht den damals errechneten Termin, und bildete damit genau den Fall nicht ab, um den es geht. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
2ae77fc54f |
Verwaltung: eine Suche ueber alles, mit Tastatur bedienbar
Bisher gab es genau ein Suchfeld, und es durchsuchte nur die Anfragenliste. Wer den Namen eines Kunden im Kopf hatte, musste raten, in welchem Reiter er nachsehen muss: War das eine Anfrage, ein laufendes Projekt, eine offene Rechnung? Bei drei Vorgaengen merkt man sich das, bei dreissig nicht mehr. Jetzt: Strg+K von ueberall, auf dem Handy der Lupenknopf oben. Ein Aufruf durchsucht Anfragen, Kunden, Projekte und Zahlungen; jeder Treffer traegt seinen Zusammenhang (Nummer, Kunde, Betrag, Liefertermin) und fuehrt per Enter in den passenden Reiter, bei einer Anfrage direkt in die Detailansicht. Gebaut nach dem ARIA-Muster 'Combobox mit Listbox-Popup' aus den W3C Authoring Practices -- nachgeschlagen, nicht aus dem Gedaechtnis: role=combobox am Eingabefeld, aria-expanded, aria-controls, aria-activedescendant, role=listbox, role=option mit aria-selected. Der Fokus bleibt dabei im Eingabefeld, damit man weitertippen kann; die Auswahl wandert ueber aria-activedescendant. Ohne diese Auszeichnung waere ein Feld, das Vorschlaege einblendet, fuer einen Screenreader stumm -- das sieht man beim Testen mit den Augen nie. Vier Fallen ausdruecklich behandelt: - Nicht bei jedem Tastendruck suchen (180 ms Wartezeit): 'Musterbau' haette sonst neun Abfragen ausgeloest, acht davon veraltet. - Das Wettrennen der Antworten: Jede Abfrage bekommt eine laufende Nummer, nur die neueste darf zeichnen. Sonst ueberschreibt eine spaet eintreffende alte Antwort die neue. - Leer, laedt und Fehler sind eigene Zustaende. Ein Kasten, der bei einem Serverfehler leer bleibt, sieht aus wie 'nichts gefunden'. - LIKE-Sonderzeichen: '%' waere ein Platzhalter, '_' ein beliebiges Zeichen -- und Unterstriche stehen regelmaessig in E-Mail-Adressen. Der Fehler ist tueckisch, weil die Suche trotzdem Treffer liefert, nur die falschen. Ausserdem zum dritten Mal dieselbe Spezifitaetsfalle gefunden: '.wd p' schlug die eigene Regel, der Tastaturhinweis erschien in 17,9px statt 11,5px -- so gross wie der Inhalt, den er erklaert. Jetzt festgenagelt durch eine Pruefung, die Groessenverhaeltnisse vergleicht. Geprueft: 28 gegen eine echte Datenbank (darunter alle LIKE-Sonderzeichen), 64 im Browser auf Computer und Handy inkl. der ARIA-Vorgaben, des Wettrennens und des Fehlerzustands. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
131bc8a755 |
Anfragen annehmen und ablehnen — mit laufender Zeit bis zur Übergabe
Annehmen war bisher nur ein Etikett: Man stellte den Status um, und es passierte nichts. Kunde, Projekt, Aufgabenliste, Termin und Zugang musste man danach von Hand in fünf Schritten nachbauen. Der alte Code gab das im Kommentar selbst zu -- schlug der zweite von zwei Aufrufen fehl, stand der Kunde schon in der Datenbank und man durfte es nicht noch einmal versuchen. Jetzt macht das EIN Aufruf, ganz oder gar nicht: Kunde finden oder anlegen, Projekt anlegen, die komplette Aufgabenliste des Pakets einsetzen, Liefertermin berechnen, Einladungslink erzeugen. Der Kunde verfolgt ab diesem Moment alles in seinem Portal. Die Zeit läuft wirklich: - Eigenes Datumsfeld (termin_am) neben dem freien Text. Aus 'Mitte Oktober' kann man keine verbleibenden Tage rechnen. - Werktage statt Kalendertage, inklusive luxemburgischer Feiertage. Die beweglichen werden über die Osterformel berechnet statt gepflegt -- eine Liste ist im übernächsten Jahr lautlos falsch. - Vorschlag je Paket (Onepager 10, Website 20, Shop 30 Werktage), überschreibbar vor dem Bestätigen. - Verbleibende Zeit wird SERVERSEITIG gerechnet. Der Browser kennt die Feiertage nicht; zwei verschiedene Zahlen für denselben Termin wären schlimmer als gar keine. Ablehnen mit Grund und vorbereiteter Absage in fünf Sprachen, Text serverseitig erzeugt und vor dem Abschicken lesbar. Ein laufendes Projekt lässt sich nicht nachträglich als Anfrage ablehnen. Sechs Fehler dabei gefunden: - Ein unbekanntes Paket hätte GAR KEINEN Termin bekommen statt des Ersatzwerts. Zwei Funktionen lasen dieselbe Tabelle, nur eine hatte einen Rückfallwert. Eine fehlende Zahl fällt nirgends auf. - wd_anfragen hat keine Spalte 'firma' -- better-sqlite3 weist undefined ab, die ganze Annahme wäre gescheitert. - Migrationen stehen in einer ausdrücklichen Liste; 0015 fehlte darin. - datumKurz() wurde aufgerufen, gab es aber nicht. Der Fehler wäre erst NACH dem Anlegen aufgetreten. - Der Installations-Hinweis lag fest über dem Annehmen-Knopf und machte ihn auf dem Handy untreffbar. Die Seite reserviert jetzt Platz dafür. - 'richttermin' ist freier Text, lief aber durch einen Datumsformatierer: Der Kunde las 'Invalid Date' an der wichtigsten Stelle seines Projekts. Geprüft: 49 Prüfungen der Terminrechnung gegen nachschlagbare Osterdaten und Wochentage, 70 gegen eine echte Datenbank (darunter: zweiter Klick legt nichts doppelt an, Abbruch mittendrin lässt NICHTS zurück, gesperrter Kunde wird nicht still entsperrt), 42 im Browser auf Computer und Handy, 40 im Portal. Alle bestehenden Prüfungen weiter grün. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
c1cb51884f |
Verwaltung: Übersicht als Startansicht, Reiter unter den Titel
Die Reiter standen neben dem Titel. Das las sich wie eine einzige lange
Zeile, in der der Titel nur der erste von sechs Knöpfen zu sein schien --
man sah nicht auf einen Blick, in welchem Bereich man war. Jetzt zwei
Zeilen: oben WO man ist, darunter WOHIN man kann.
Die Verwaltung öffnete bisher mit der Anfragenliste. Eine Liste ist eine
Ablage: Sie zeigt, WAS es gibt, nicht was zu TUN ist. Neu ist ein
Cockpit, das in drei Stufen antwortet -- was auf mich wartet, was beim
Kunden liegt, wie es ums Geld steht -- und dann laufende Projekte mit
Fortschritt sowie den Verlauf.
Zwei Grundsätze machen die Zahlen brauchbar: Getrennt nach 'wartet auf
mich' und 'wartet auf den Kunden' (zwölf offene Punkte sind entspannt,
wenn elf beim Kunden liegen). Und das ALTER färbt, nicht die Menge --
vier neue Anfragen sind kein Problem, eine seit sechs Tagen liegende
schon. Ein offener Widerruf ist immer rot, weil eine gesetzliche Frist
läuft.
Jede Kachel ist ein echter <button> und führt in den passenden Reiter.
Farbe ist nie der einzige Träger: Neben jedem farbigen Zustand steht der
Text ('älteste seit 8 Tagen').
Drei Fehler dabei gefunden und behoben:
- Der Titel wurde nur beim Klicken gesetzt. Frisch geladen zeigte die
Seite das Cockpit, während darüber noch 'Projektanfragen' stand. Die
Zuordnung Reiter->Titel liegt jetzt ausserhalb des Klick-Zuhörers.
- '.wd h2' überstimmte '.vw-ub-h': 44px Überschrift über 33px Zahl, die
Seite las sich wie ein Plakat. Dieselbe Spezifitätsfalle wie früher
bei '.wd a'.
- Eine Antwort mit ok:true aber ohne Inhalt riss die ganze Verwaltung
mit. Wird jetzt abgefangen -- ein halber Server ist ein realistischer
Fall.
Geprüft: 22 Serverprüfungen gegen eine echte Datenbank (inkl. vier
Fällen, die belegen, dass die Rechteprüfung wirklich greift), 56 im
Browser auf Computer und Handy, 69 im bestehenden Verwaltungstest,
0 Fundstellen im Design-/WCAG-Test, 5 im Farbsystemtest.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
14488d6df2 | Test: unumgepufferte Ausgabe (Absturz beim Beenden verschluckte sie) | ||
|
|
779fb94c6b | Test fuer den Quellen-Vorrang | ||
|
|
ebe10b9c49 | WIP Vorrang umgedreht (Test folgt auf dem Server) | ||
|
|
5043642951 | WIP: PayPal-Einstellungen ueber die Verwaltung (Test folgt auf dem Server) | ||
|
|
692d702ebf |
Einrichtungsskript fuer PayPal -- Filipe tippt nur die Werte
Auf Wunsch, die Werte selbst einzutragen, zuerst geprueft statt vermutet: sudo erlaubt claudian: apt, apt-get, systemctl, docker, docker-compose /home/dogiintern/ -> Keine Berechtigung .../server-internal/.env -> Keine Berechtigung Der Zugriff fehlt also technisch, und das ist die Trennung, die Filipe am 08.08.2026 bewusst so eingerichtet hat. Selbst mit Zugriff waere es falsch: Damit ich die Werte eintrage, muesste das Secret durch den Chatverlauf wandern und stuende dort dauerhaft. Also alles abnehmen, was NICHT das Eintippen ist: paypal-einrichten.sh fragt die vier Werte nacheinander ab, zeigt zu jedem an, ob schon etwas hinterlegt ist (ENTER = behalten), legt vorher eine Sicherung an, setzt die Rechte auf 600, startet den Dienst neu und meldet den Stand. DREI DINGE, DIE DAS SKRIPT RICHTIG MACHT Das Secret wird mit "read -s" eingelesen -- es erscheint weder auf dem Bildschirm noch in der Bash-History. Auch die Abschlussmeldung zeigt nur die LAENGE, nie den Wert. Geschrieben wird mit awk und dem Wert in einer Variablen, NICHT mit "sed -i". Ein PayPal-Secret kann &, \, $, / und Anfuehrungszeichen enthalten; sed liest davon mehrere als Befehl und haette den Wert still zerstoert. Der Fehler waere erst bei der ersten echten Zahlung aufgefallen, mit der Meldung "invalid client" -- und dann sucht man an der falschen Stelle. Gegengeprueft mit dem Secret A&B/C\D$E"F~G : zeichengenau in der Datei angekommen, umliegende Zeilen unveraendert, vorhandener Schluessel ersetzt statt ein zweites Mal angehaengt. Laeuft der Dienst nach dem Neustart nicht, nennt das Skript den Pfad der Sicherung und den fertigen Befehl zum Zurueckholen -- in dem Moment will niemand erst suchen. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
eede01ca70 | WIP: Zahlungsrouten (Test folgt auf dem Server) | ||
|
|
14ad108c87 |
Rechtliches: elektronische Widerrufsfunktion (§ 356a BGB), Impressum, AGB,
Widerrufsbelehrung und Datenschutz
RECHERCHIERT, NICHT AUS DEM GEDAECHTNIS
Der Webdesign-Bereich hatte KEINE eigene Rechtsseite -- obwohl dort
Vertraege geschlossen und Zahlungen ausgeloest werden. Die Fusszeile
verwies auf die Kontaktseite des Universe, die ein Content-Projekt
beschreibt, kein Dienstleistungsgeschaeft.
Die Web-Recherche hat eine Pflicht zutage gefoerdert, die seit zwei
Monaten gilt und die die Seite nicht erfuellt hat:
§ 356a BGB -- elektronische Widerrufsfunktion, in Kraft seit
19.06.2026. Sie gilt fuer ALLE Fernabsatzvertraege, die ueber eine
Online-Benutzeroberflaeche geschlossen werden, nicht nur fuer
Finanzdienstleistungen.
WARUM DAS HIER GILT: Im Kundenportal nimmt der Kunde Zusatzangebote per
Klick VERBINDLICH an ("Damit nimmst du das Zusatzangebot verbindlich
an"). Das ist ein Vertragsschluss ueber eine Online-Benutzeroberflaeche.
Fuer die geplanten PayPal-Zahlungen gilt dasselbe.
Der Anbieter sitzt in Luxemburg. Fuer Verbraucher mit gewoehnlichem
Aufenthalt in Deutschland gilt nach Art. 6 Rom-I-VO trotzdem das
deutsche zwingende Verbraucherrecht, weil die Seite sich erkennbar an den
deutschsprachigen Markt richtet. Deshalb wurde der STRENGERE Standard
umgesetzt, nicht der bequemere.
WAS GEBAUT WURDE
webdesign/widerruf.html -- die Widerrufsfunktion, genau nach dem
Wortlaut der Vorschrift:
* Beschriftung "Vertrag widerrufen" (gesetzlich vorgegeben)
* ZWEITE Schaltflaeche "Widerruf bestaetigen" (ebenfalls vorgegeben --
ein einstufiges Formular wuerde die Vorschrift nicht erfuellen)
* unverzuegliche Eingangsbestaetigung mit Nummer, ZEITPUNKT (nicht nur
Datum), Empfaenger und dem Wortlaut der Erklaerung
* Link in der Fusszeile JEDER Seite -- "staendig verfuegbar,
hervorgehoben platziert, leicht zugaenglich" heisst nicht "in den AGB
versteckt"
Drei Entscheidungen, die unmittelbar aus dem Sinn der Vorschrift folgen:
KEINE ANMELDUNG. Die Seite ist von der Zugangswand ausgenommen. Wer
seinen Code verlegt hat, wuerde sonst seine Frist verlieren --
Fristverlust durch eine selbstgebaute Huerde ist der schlimmste
denkbare Fall.
KEINE MENGENBEGRENZUNG. Ueberall sonst richtig, hier ein
Rechtsverlust: Wer in der letzten Stunde seiner Frist wegen eines
hakenden Netzes dreimal klickt, darf nicht abgewiesen werden.
KEIN ZWISCHENSPEICHER. Beim Anfrageformular ein Segen, hier eine
Falle: Auf einem geteilten Rechner laege der halb ausgefuellte Widerruf
des einen im Browser des naechsten.
webdesign/rechtliches.html -- Impressum, AGB, Widerrufsbelehrung samt
Muster-Widerrufsformular, Datenschutzerklaerung. Alle Betreiberangaben
sind aus den bestehenden Seiten UEBERNOMMEN, nichts erfunden: Anschrift
in Mondercange, Kleinunternehmerregelung nach Art. 57 TVA-Gesetz LU und
Richtlinie (EU) 2020/285, netcup/Cloudflare, PayPal Luxemburg.
Migration 0014: wd_widerrufe (der dauerhafte Datentraeger und der
Nachweis, wird nie geleert) und wd_zustimmungen. Letztere speichert nicht
nur den Haken, sondern den WORTLAUT, den der Kunde gesehen hat -- die
Beweislast fuer die Zustimmung zum vorzeitigen Leistungsbeginn liegt beim
Unternehmer (§ 356 Abs. 4 BGB), und im Streit zaehlt, WAS bestaetigt
wurde. Bewusst OHNE Fremdschluessel: CASCADE wuerde den Nachweis mit dem
Kunden mitloeschen, RESTRICT wuerde eine DSGVO-Loeschung blockieren.
Druckansicht: Die Eingangsbestaetigung ist ein Rechtsnachweis. Auf Papier
schwarz auf weiss, ohne Navigation und Knoepfe -- ein Nachweis, den
niemand ausdruckt, weil er eine halbe Patrone kostet, erfuellt seinen
Zweck nicht.
ZWEI ECHTE FEHLER GEFUNDEN
1. Die Pflicht-Sternchen verschwanden nach der Uebersetzung. Ursache:
data-i18n stand auf dem <label> selbst und ersetzte dessen ganzen
Inhalt -- samt <span class="wd-pflicht">. Das Anfrageformular macht es
laengst richtig (data-i18n auf einem INNEREN span); die neue Seite
hatte das Muster nicht uebernommen. Aufgefallen ist es nur, weil der
Test die Pflichtfelder GEZAEHLT hat statt sie vorauszusetzen.
2. window.WD.sprache() gibt es nicht, die Funktion heisst getSprache().
Dadurch brach der Uebergang zur zweiten Stufe stumm ab.
GEPRUEFT: 58 Pruefungen, alle bestanden. Darunter die gesetzlich
vorgegebenen Beschriftungen in allen FUENF Sprachen, "kein Grund noetig",
genau drei Pflichtfelder, Datum UND Uhrzeit in der Bestaetigung, und die
Druckansicht.
WAS FILIPE NOCH PRUEFEN LASSEN MUSS: Diese Texte sind sorgfaeltig
recherchiert, aber ich bin keine Rechtsanwaeltin. Vor dem oeffentlichen
Start gehoert das Ganze einmal zu einer im luxemburgischen Recht
qualifizierten Fachperson -- besonders die grenzueberschreitende Lage
(Sitz LU, Kunden in DE/CH/FR/PT) und die Frage, ob die Seite in
Franzoesisch und Portugiesisch aktiv verkaufen soll. Dann muessen die
Rechtstexte auch dorthin uebersetzt werden, und zwar fachlich.
Versionsstempel und Cache-Name auf v7.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
98fc04c666 |
Verwaltung: Projektstand aendern, Wuensche beziffern, im Projekt antworten
Drei Server-Funktionen existierten seit Tagen und hatten KEINE Oberflaeche.
Erst im Vergleich "was kann der Server" gegen "was ruft die Seite auf"
ist es aufgefallen:
POST /admin/projekte/:id projektAendern
POST /admin/aenderungen/:id/beziffern
POST /admin/projekt-nachricht
Jede davon schliesst einen Kreis, der bisher offen war.
1. DIE SCHMERZHAFTESTE LUECKE: DER STAND
Der Kunde sieht in seinem Portal GANZ OBEN die Phasenleiste und den
"naechsten Schritt". Beides liess sich nirgends aendern. Ein Projekt blieb
also fuer immer im Briefing stehen, egal wie weit es wirklich war -- und
der prominenteste Text im ganzen Kundenportal war dauerhaft leer.
Jetzt: sieben Phasen als Knoepfe (plus Pausiert/Abgebrochen daneben, denn
das sind keine Phasen, sondern Zustaende), "wer ist am Zug", naechster
Schritt, Richttermin, Zahlungsstand, Portfolio-Freigabe.
Phase und "wer ist am Zug" speichern SOFORT ohne Speichern-Knopf: Es ist
ein Klick auf genau einen Wert, ein zweiter Klick waere Zeremonie. Die
Textfelder haben einen Knopf, weil man beim Tippen zwischendurch nicht
speichern will.
2. AENDERUNGSWUENSCHE
Der Kunde konnte Ideen einreichen, seit es das Portal gibt. Beziffern ging
serverseitig auch -- nur gab es keine Oberflaeche. Der Kreis war offen: Er
schickt etwas los und hoert nie wieder davon.
Jetzt stehen alle Wuensche im Projekt, offene mit Feldern fuer Preis,
Dauer und einer kurzen Erklaerung. Ohne Preis geht nichts raus (geprueft)
-- ohne Preis kann der Kunde nicht entscheiden, und ein "Angebot" ohne
Zahl ist keins.
3. NACHRICHTEN ZUM PROJEKT
Getrennt vom allgemeinen Postfach, weil sie zum Projekt gehoeren und im
Portal auch dort erscheinen. Sie hier nicht zu haben hiess: Der Kunde
schreibt im Projekt, und ich kann ihm nur woanders antworten.
AUFBAU
Neuer Endpunkt /admin/projekte/:id/alles liefert Projekt, Aufgaben,
Fortschritt, Aenderungswuensche und Nachrichten in EINER Antwort. Fuenf
Anfragen hintereinander wuerden die Ansicht sichtbar ruckelnd aufbauen.
Dieselbe Aufteilung wie in der Anfrageansicht -- links die Arbeit
(Aufgaben), rechts das Steuern. Wer zwischen beiden Ansichten wechselt,
muss nicht umdenken.
GEPRUEFT: 69 Pruefungen, Computer und Handy, alle bestanden.
Die neuen Pruefungen schauen nicht darauf, ob sich ein Knopf einfaerbt --
das beweist nichts. Sie schneiden mit, was die Seite TATSAECHLICH an den
Server schickt: {"status":"entwicklung"}, {"restBezahlt":true},
{"preisEuro":250,"dauer":"3 Tage"}. Und sie pruefen den Fall, in dem
nichts rausgehen darf: Ohne Preis bleibt die Mitschrift leer.
Ein Fehler im Test selbst behoben: Die Nachrichtenzaehlung war
document-weit und zaehlte die Nachrichten der geschlossenen Projektansicht
mit -- geschlossen heisst nicht aus dem Dokument entfernt. Jetzt auf den
Postfach-Verlauf eingegrenzt.
Versionsstempel und Cache-Name auf v6.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
b7c333fca3 |
Verwaltung: Aufgabenlisten abhaken und Postfach
Schritt 3 von 4 zu den zwei Wuenschen vom 22.08.2026. Die Datenbank und
die Server-Routen standen schon; das hier ist die Seite, auf der ICH
arbeite. Was hier passiert, sieht der Kunde unmittelbar in seinem Portal
(Schritt 4).
ZWEI NEUE REITER
"Projekte" -- bisher gab es Projekte nur als Zahl neben dem Kunden
("3 Proj."), man konnte kein einzelnes oeffnen. Man denkt aber in
Projekten, nicht in Kunden. Neuer Endpunkt projekteListe liefert die
flache Liste samt Aufgabenzahlen; die einzeln nachzufragen haette bei
zwanzig Projekten einundzwanzig Anfragen bedeutet.
"Postfach" -- mit Abzeichen fuer Ungelesenes. Steht nichts an, ist das
Abzeichen GANZ weg statt eine 0 zu zeigen: Eine Null, die man taeglich
sieht, wird zu Rauschen, und irgendwann uebersieht man auch die 3.
Die Reiterumschaltung war vorher ein Ja/Nein zwischen zwei Ansichten. Mit
vier Reitern haette jede neue Ansicht eine weitere "hidden = ..."-Zeile
gebraucht -- und beim naechsten Reiter vergisst man eine, dann liegen zwei
Ansichten uebereinander. Jetzt eine Zuordnung Reiter -> Kasten, die sich
selbst aufraeumt.
AUFGABENLISTE
Vier Abschnitte in den vier Prozessfarben -- dieselben wie im Portal und
auf der Ablaufseite. Das ist der Punkt: Eine Farbe bedeutet ueberall
dasselbe, sonst waere sie Deko.
Ein Klick aufs Kaestchen schaltet weiter: offen -> in Arbeit -> erledigt.
Drei Zustaende ueber einen Knopf statt eines Auswahlfeldes -- beim
Abarbeiten einer Liste zaehlt jeder gesparte Klick.
Punkte, die auf den KUNDEN warten, sind golden hinterlegt und tragen eine
Marke. Ohne diese Unterscheidung liest man die Liste als reinen
Fortschrittsbericht und uebersieht, dass man selbst gar nicht am Zug ist.
Der goldene Grund verschwindet, sobald der Punkt erledigt ist -- er
braucht dann keine Aufmerksamkeit mehr.
Abhaken faerbt die Zeile sofort um, ohne auf den Server zu warten. Bei
zehn Punkten hintereinander waere ein Neuaufbau nach jedem Klick zaeh, und
man verliert die Stelle. Geht es schief, wird die Liste neu geholt und der
Zustand ist wieder ehrlich.
POSTFACH
Links Verlaeufe, rechts das Gespraech. Nicht aus Nachahmung, sondern weil
man ein Gespraech abarbeitet und nicht eine Zeile: Man will sehen, was
vorher besprochen wurde, waehrend man antwortet. Kunde links, eigene
Nachrichten rechts -- man erkennt den Absender an der Seite, bevor man den
Namen liest.
Der Schalter "Nur interne Notiz" faerbt das ganze Schreibfeld um. Ein
Haekchen allein uebersieht man, und eine interne Bemerkung, die beim
Kunden landet, ist der peinlichste Fehler, den dieses System machen kann.
Interne Notizen im Verlauf sind gestrichelt umrandet und golden -- sie
duerfen nicht wie etwas Gesendetes aussehen.
Strg+Enter sendet, Enter allein nicht: In einem mehrzeiligen Feld will man
Absaetze machen koennen, ohne dass die halbe Nachricht rausgeht.
EIN FEHLER, DEN DIE TESTS NICHT GEFUNDEN HABEN
Der erste Lauf meldete 37 von 37 gruen -- und jede Aufgabenzeile war
sichtbar falsch herum gebaut: Kaestchen, dann die kleinen Knoepfe, Titel
ganz rechts. Aufgefallen ist es erst am Screenshot.
Ursache: Das Raster platziert zuerst alle Elemente mit FESTGELEGTER Zeile
und erst danach die freien. Kaestchen und Knopfgruppe hatten beide
"grid-row: 1 / span 2" und wurden deshalb zusammen nach vorne gesetzt; der
Titel landete als letzter in der dritten Spalte. Jetzt bekommt jedes Kind
Zeile UND Spalte ausdruecklich.
Die eigentliche Lehre steckt im Test: Alle 37 Pruefungen betrafen
Bestandteile (Farben, Anzahl, Durchgestrichenes), keine einzige die
ANORDNUNG. Wer eine Anordnung baut, muss die Anordnung pruefen. Drei neue
Pruefungen vergleichen jetzt die tatsaechlichen Bildschirmpositionen --
und sie wurden gegengeprueft: Mit dem alten Zustand schlagen sie fehl,
mit dem neuen nicht. Ein Test, der nie fehlschlaegt, ist wertlos.
GEPRUEFT: 43 Pruefungen, Computer und Handy, alle bestanden. Darunter:
alle Touch-Ziele mindestens 24 px, interne Notiz sieht anders aus als eine
gesendete, Verlauf springt ans Ende, keine Fehler im Protokoll.
Versionsstempel und Cache-Name auf v4.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
60c1e7be94 | Test: Absturz beim Beenden vermeiden (better-sqlite3) | ||
|
|
4ce51c127e | WIP: Aufgaben- und Postfach-Routen (Test folgt auf dem Server) | ||
|
|
548ec6bc00 |
Aufgabenlisten und allgemeines Postfach: Datenbank und Vorlagen
Erster von vier Schritten fuer zwei Wuensche vom 22.08.2026:
"wenn sie drauf druecken sollen sie auch sehen was ich schon gemacht habe
von dem was im plan war" und "die kunden sollen mir auch nachrichten
hinterlassen koennen".
DATENBANK (3 neue Tabellen, jetzt 18 insgesamt, Fremdschluessel geprueft)
wd_aufgaben -- die Punkte je Projekt. Drei Entscheidungen darin:
* "wer_dran" ist eine eigene Spalte, kein Text. Manche Punkte warten
auf den KUNDEN ("Texte liefern"), und genau die muessen ihm ins Auge
springen. Ohne diese Unterscheidung liest er die Liste als reinen
Fortschrittsbericht und uebersieht seinen eigenen Teil -- der
haeufigste Grund fuer Verzoegerungen ueberhaupt.
* "nicht_enthalten" bildet ab, was NICHT zum Umfang gehoert. Das ist
die haeufigste Streitfrage in jedem Projekt ("ich dachte, das ist
dabei"). Vorher sichtbar aufgeschrieben kostet es nichts, hinterher
kostet es Geld oder den Kunden.
* "kategorie" nutzt dieselben vier Abschnitte wie die Farben im Portal.
Damit bedeutet eine Farbe ueberall dasselbe statt nur huebsch zu sein.
wd_postfach -- Nachrichten OHNE Projektbezug. Bewusst eine EIGENE Tabelle:
wd_nachrichten hat projekt_id als NOT NULL, und SQLite kann das nicht
lockern, ohne die ganze Tabelle zu kopieren -- ein unnoetiges Risiko bei
echten Kundennachrichten. Die neue Tabelle hat ohnehin andere
Anforderungen (Anhaenge, Lesestatus in BEIDE Richtungen, optionaler Bezug
auf eine Aufgabe). Zwei klar getrennte Tabellen sind ehrlicher als eine,
die beides halb kann.
VORLAGEN (56 Punkte ueber 5 Pakete, davon 20 beim Kunden)
Bei jedem Projekt fuenfzehn Punkte von Hand einzutippen fuehrt
zuverlaessig dazu, dass es irgendwann niemand mehr macht -- und dann
steht der Kunde wieder vor der leeren Liste, die der Ausloeser war.
Die Punkte sind in der Sprache formuliert, in der ein KUNDE denkt:
"Aufbau der Seiten festlegen" statt "Informationsarchitektur", "Auf dem
Handy durchtesten" statt "Responsive QA". Er liest diese Liste -- sie
muss ihm etwas sagen, nicht mir.
Die Vorlagen wandern beim ersten Start in die Datenbank und sind ab dann
dort aenderbar. Bereits vorhandene werden NIE ueberschrieben: sonst waeren
von Hand angepasste Vorlagen nach dem naechsten Neustart weg, und niemand
kaeme auf die Idee, dass der Neustart schuld war.
Naechste Schritte: Server-Routen, Verwaltung, Portal.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
74091590b8 |
Verwaltung: nur noch EINMAL den Code eingeben
Rueckmeldung 22.08.2026: "ich hab auf der verwaltungs seite 2 mal dass ich
den zugangscode eingeben muss und vanvan auch, ich will es nur einmal
eingeben muessen." Berechtigt.
Die Zugangswand hat serverseitig laengst geprueft, WER da ist -- Dogfather
oder VanVan, laut Masterplan S.4 beide gleichberechtigte
Volladministratoren. Ein zweites Passwort danach bringt keinen
zusaetzlichen Schutz, es kostet nur jedes Mal Zeit.
Warum es nicht einfach "Cookie mitschicken" ist: Die Verwaltungsdaten
liegen hinter postfach.dogfather-universe.com, einem ANDEREN Rechnernamen.
Das Sitzungs-Cookie gilt dort nicht -- so sind Cookies gebaut, und das ist
gut so.
Loesung: Die Zugangswand stellt unter /webdesign/api-ausweis einen
kurzlebigen, signierten Ausweis aus, den die interne API anerkennt.
- 15 Minuten gueltig, die Seite holt bei Bedarf still einen neuen
- traegt bereich="wd-admin", damit ein Sitzungs-Token der Zugangswand
hier NICHT durchgeht und umgekehrt
- nur mit gueltiger Zugangssitzung zu bekommen
- gilt AUSSCHLIESSLICH fuer die /webdesign-Endpunkte. Postfach,
Bewerbungen, Supporter und Teamverwaltung bleiben unberuehrt
Fehlt WEBDESIGN_API_SECRET auf einer der beiden Seiten, gilt kein Ausweis
und die Verwaltung fragt wie bisher nach dem Team-Code. Ein fehlender
Konfigurationswert darf niemals eine Tuer oeffnen -- nur eine schliessen.
Genau das prueft der letzte Testfall.
Bei einem 401 wird EINMAL still ein neuer Ausweis geholt und die Anfrage
wiederholt, statt jemanden mitten im Arbeiten rauszuwerfen.
10/10 Tests, darunter: gefaelschte Signatur, fremdes Geheimnis,
abgelaufen, falscher Bereich, erfundene Rolle.
Co-Authored-By: Claude Opus 5 <[email protected]>
|
||
|
|
2542a2598e |
Kunden und Projekte in der Verwaltung anlegen, inklusive Testzugang
Wunsch 22.08.2026: "ich will einen provisorischen zugang fuer mich". Der richtige Weg dahin war ohnehin ueberfaellig -- Kundenzugaenge entstehen ausschliesslich hier, es gibt bewusst keine Selbstregistrierung. Neu: * Kundenzugang anlegen (Name, E-Mail, Firma, Sprache, sofort freischalten ja/nein) mit Einladungslink * "Testzugang fuer mich" -- ein Klick, legt einen als Test erkennbaren Zugang mit Datumsstempel an. Bewusst NICHT die echte Geschaeftsadresse: die E-Mail ist gleichzeitig der Anmeldename und laesst sich aus gutem Grund nicht mehr aendern, ein Test wuerde also spaeter mit einem echten Kundenkonto kollidieren. * Zugaenge auflisten, freischalten, sperren, neuen Einladungslink erzeugen * Projekte anlegen und aendern, Aenderungswuensche beziffern, Nachrichten Entscheidungen: * Der Einladungslink geht EINMAL im Klartext raus, direkt beim Anlegen. In der Datenbank liegt nur sein Hash. Wer ihn verliert, bekommt einen neuen -- das ist sicherer, als ihn dauerhaft abrufbar zu halten. Die Oberflaeche sagt das auch klar dazu, sonst klickt man ihn weg und wundert sich. * Ein neuer Link entwertet alle offenen alten. Sonst sammeln sich mehrere gueltige Generalschluessel fuer dasselbe Konto an. * "Sofort freischalten" ist eine bewusste Handlung. Ohne Haekchen wird der Zugang angelegt, kommt aber noch nicht hinein -- Masterplan S.12 verlangt, dass Projektkauf oder Betreuung VOR der Freischaltung geprueft werden. * Sperren beendet laufende Sitzungen sofort, nicht erst nach Ablauf. * Die 30 % Anzahlung werden aus dem Preis BERECHNET, nicht eingetippt. Ein Tippfehler in der Anzahlung faellt sonst erst beim Geldeingang auf. * Preise kommen als Euro herein und werden sofort in Cent umgerechnet (Math.round, damit 49.99 nicht zu 4998 wird). Ab da nie wieder Komma. * Vier klar unterscheidbare Zustaende in der Liste. Wichtig vor allem "Einladung offen": freigeschaltet, aber noch kein Passwort gesetzt -- der Kunde war also noch nie drin. * Kopieren faellt auf Markieren zurueck, wenn die Zwischenablage blockiert ist. Eine Fehlermeldung waere dort nutzlos. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
04a1af1253 |
Kundenportal: Server-Seite mit strikter Mandantentrennung
Masterplan S.12. Anmeldung, Uebersicht, Projektdetail, Aenderungswuensche, Zusatzangebote annehmen/ablehnen, Nachrichten, Profil. DIE ZENTRALE ENTSCHEIDUNG: Die Trennung zwischen Kunden sitzt NICHT in der Oberflaeche, sondern in jeder einzelnen Abfrage. Ueberall steht "WHERE id = ? AND kunde_id = ?" statt "WHERE id = ?", wobei die kunde_id IMMER aus der Sitzung kommt, nie aus der Anfrage. Wer eine fremde Projektkennung errät, bekommt dadurch "nicht gefunden" statt Daten. Die Oberflaeche liegt im Browser des Kunden und ist beliebig manipulierbar -- sie kann diese Aufgabe grundsaetzlich nicht uebernehmen. Weitere bewusste Entscheidungen: * KEINE Selbstregistrierung. Masterplan S.12 verlangt, dass Projektkauf oder Betreuung VOR der Freischaltung geprueft werden. Ein Portal, in das sich jeder selbst eintraegt, waere das Gegenteil. Kunden werden in der Verwaltung angelegt und bekommen einen Einladungslink. * Passwoerter mit scrypt (in Node eingebaut, kein Zusatzpaket). Ein SHA-256 ueber ein Passwort ist milliardenfach pro Sekunde durchprobierbar; scrypt ist absichtlich langsam UND speicherhungrig. * Mindestanforderung ist LAENGE, nicht Zeichenklassen. "Hund1234!" erfuellt jede Klassenregel und ist trotzdem schlecht; "mein blauer stuhl steht krumm" erfuellt keine und ist ausgezeichnet. * Gleiche Fehlermeldung bei unbekannter Adresse und falschem Passwort -- sonst lassen sich Kundenadressen durchprobieren. * Die Anmeldesperre haengt an der E-Mail, nicht an der IP. Eine IP-Sperre wuerde mehrere Kunden hinter demselben Firmenanschluss gemeinsam aussperren -- genau der Fehler, der im Universe am 19.08.2026 auftrat. * Sperre und Passwortwechsel beenden laufende Sitzungen SOFORT, nicht erst nach zwoelf Stunden. * Ein Zusatzangebot laesst sich nur annehmen, wenn es beziffert ist -- sonst entstuende eine Zahlungspflicht ohne Preis. Dazu der wichtigste Test des Bereichs (test-webdesign-portal.mjs): zwei echte Kunden, und Kunde B versucht systematisch mit Kunde As echten Kennungen an dessen Projekt, Nachrichten, Dateien und Zusatzangebote zu kommen. Prueft ausserdem, dass interne Notizen und interne Arbeitsdateien auch dem BERECHTIGTEN Kunden verborgen bleiben. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
4b3ec450d5 |
Webdesign-Bereich: Fundament, oeffentliche Seiten, Zugangsschutz, PayPal
Umsetzung des "Website Masterplan" (16 Seiten) unter /webdesign. Zugangsschutz mit EIGENER Schranke (server/webdesign-gate.js) statt gate.js: gate.js laesst seit dem oeffentlichen Start am 21.08.2026 jeden durch, weil die Pruefung auf SITE_PUBLIC_LAUNCH_AT als allererste Zeile steht. Haette man /webdesign dahintergehaengt, waere der ausdruecklich nicht-oeffentliche Bereich inklusive Preisen und spaeteren Kundendaten ab der ersten Sekunde fuer jeden lesbar gewesen. Eigenes Sitzungs-Cookie, bereich="webdesign" im Token, damit ein gueltiges Universe-Cookie hier NICHT gilt. 37/37 Tests. Sieben oeffentliche Seiten in fuenf Sprachen (de, de-CH mit echtem Dialekt, en, fr, pt). Preise, Zeitrahmen, Paketnamen und die 30-%-Regel stehen an genau EINER Stelle in wd-core.js -- der Masterplan verlangt "ueberall widerspruchsfrei", und vier Kopien laufen bei der ersten Preisaenderung auseinander. Als App installierbar auf Handy und PC. Der Service Worker speichert bewusst KEINE HTML-Seite zwischen: nach dem Abmelden wuerden sonst geschuetzte Seiten weiter ausgeliefert, ohne dass der Server je gefragt wird. 18/18 Tests. Handy-Abnahme ueber alle Seiten in fuenf Breiten (320-1440) und fuenf Sprachen: 40/40. Der Test fand 35 echte Fehler (Touch-Ziele unter 44px), behoben im Designsystem statt einzeln pro Seite. PayPal (Wunsch 22.08.2026 "sofort auf meinem paypal"): Orders API mit intent=CAPTURE, also sofortiger Einzug statt blosser Reservierung. Gebuehr und Nettobetrag getrennt gespeichert. Betraege durchgehend als Ganzzahl in Cent. Gefaelschte Webhooks werden abgewiesen. Fail closed solange Zugangsdaten fehlen. 28/28 Tests gegen einen nachgebauten PayPal-Server. Datenbank: 15 Tabellen mit Praefix wd_, fachlich vollstaendig vom Universe getrennt. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
6910112d97 |
Stimmen: nur noch die zwei Originalfiguren + eigenes Bild hochladen
Nutzer-Wunsch 21.08.2026: "benutz da bitte nur die originalen husky und hasen. mach nur die zwei. und die auswahl wo die leute selbst ein bild rein setzen können." - Auswahl von acht auf zwei reduziert (DogFather-Husky, HasiDog). Die übrigen Bilddateien bleiben liegen, falls sie je zurücksollen -- es genügt, die Zeile in data-stimmen-avatare.js und die Id in ERLAUBTE_AVATARE wieder zu ergänzen. - Neue Kachel "eigenes Bild" (gestrichelter Rand + Plus), die den Dateidialog öffnet, das Bild sofort hochlädt und als Vorschau in der Kachel zeigt. Bereits freigegebene Stimmen mit einer der entfernten Figuren zeigen wieder den Anfangsbuchstaben statt eines kaputten Bildes -- die Auflösung unbekannter Ids liefert null, das war schon so vorgesehen. Sicherheit des öffentlichen Uploads (bisher war Hochladen bewusst nur der Verwaltung erlaubt): - Gleiche multer-Härtung wie der Verwaltungs-Upload: nur JPG/PNG/WebP, max. 5 MB, zufälliger UUID-Dateiname (kein Originalname). - Der Server nimmt im Avatar-Feld weiterhin NUR bekannte Ids an oder eine Adresse, die exakt auf den eigenen Upload-Ordner zeigt und danach nur aus UUID + Bildendung besteht. Gegengetestet: fremde Domains, "../"-Ausbruch, .svg/.html, javascript:, angehängte Skripte und http statt https werden alle abgelehnt. - Missbrauchsbremse gegen Vollschreiben der Festplatte: max. 10 Uploads pro Stunde und IP. - Sichtbar wird ein Bild ohnehin erst, wenn die Stimme freigegeben wird. Mit Playwright end-to-end geprüft (10 Tests) plus 9 Sicherheitsfälle. Cache-Busting-Version auf 20260821q erhöht. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
5a5912636d |
Registrierung: E-Mail-Bestätigung per Einmalcode vor dem Zugangscode
Nutzer-Wunsch 21.08.2026: "bei der ersten registrierung sollen die eine email bekommen mit einem einmaligen code damit wir auch wissen dass email stimmt und danach wenn sie den code eingegeben haben sollen die erst ihren zugangscode auswählen/eintippen können." Damit wird eine echte Lücke geschlossen: registerSupporter() hat bisher `email_verified = 1` gesetzt, OHNE dass irgendetwas geprüft wurde -- man konnte sich mit einer fremden oder erfundenen Adresse registrieren und kam sofort rein. Neuer Ablauf in drei Schritten: 1. Name/TikTok/E-Mail -> Konto wird als UNBESTÄTIGT angelegt (email_verified = 0, noch kein Zugangscode). Es kommt bewusst KEIN Session-Token zurück -- eingeloggt ist man hier noch nicht. 2. Einmalcode aus der E-Mail eingeben -> verify-email bestätigt und loggt ein. Die Willkommens-Mail wandert hierher, sie ging vorher an eine noch ungeprüfte Adresse. 3. Erst jetzt den eigenen Zugangscode festlegen. Serverseitig abgesichert: setSupporterAccessCode() lehnt ab, solange die E-Mail nicht bestätigt ist -- der Schritt ist damit nicht nur im Formular versteckt, sondern auch per Direktaufruf nicht überspringbar. Wiederverwendet wird die bereits vorhandene Mechanik (issueAuthCode/ verifyAuthCode mit Zweck "verify_email", sendVerifyEmailCode, die Panels #panel-code und #panel-neuer-code) -- der "Code vergessen?"-Weg nutzt dieselbe Code-Eingabe und bleibt unverändert; eine neue Variable codeZweck unterscheidet, welcher Endpunkt aufgerufen wird. Mit Playwright end-to-end geprüft (14 Tests): Reihenfolge der Aufrufe, kein Token vor der Bestätigung, falscher Code kommt nicht weiter, Zugangscode-Feld erst im letzten Schritt, "Code vergessen?" unberührt. Cache-Busting-Version auf 20260821o erhöht. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
5d0ea11570 |
Arbeit der parallelen Session in git gesichert (war nur auf dem Server)
Diese Änderungen liefen bereits live auf dem Server, lagen dort aber ausschließlich als nicht eingecheckte Arbeitskopie — bei jedem Deploy (stash/pull/pop) und bei jedem Serverproblem wären sie verloren gewesen. Deshalb hier unverändert in git übernommen, bevor darauf aufgebaut wird. Enthalten (nicht von mir gebaut, nur gesichert): - Supporter: eigener fester Zugangscode statt Einmalcode-Login (Migration 0009, lib/crypto.js scrypt-Hash, routes/supporter.js, abonnieren.html, supporter.html, i18n-abonnieren/-supporter) - Stimmen: Profilbilder (Migration 0010, routes/testimonials.js) - Event-Bild-Upload: voller Pfad statt relativem (routes/events.js) - Neue/überarbeitete Hintergrundbilder für viele Seiten - Teilen-Funktion (streamplan.js, i18n-index/-streamplan, main.css) Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
971c5ea3f6 |
Bugfix: Team-Foto-Upload lieferte relativen statt vollen Pfad zurück
Gleicher Fehler wie in routes/events.js/uploadEventImage (dort gerade direkt auf dem Server gefunden und gefixt, siehe git stash auf dogiintern): die Verwaltung und die echte Website laufen auf dogfather-universe.com (dogiweb), hochgeladene Fotos liegen aber unter postfach.dogfather-universe.com (dogiintern). Ein relativer Pfad wurde vom Browser also gegen die falsche Domain aufgelöst -> kaputtes Bild-Symbol für jedes über die Team-Verwaltung hochgeladene Foto. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||
|
|
81f55d67db |
Team-Verwaltung Runde 2: bestehende Mitglieder importieren + Kategorien/Seitentitel editierbar
Nutzer-Wunsch 21.08.2026: "wenn ich eine änderung mache ist es auch für
jeden für die die schon da sind und die neuen" + "ich will ich titel
und namen von kategorien und seiten ändern können in der verwaltung".
Backend (braucht Filipes manuellen Deploy):
- Migration 0008: team_members bekommt intro/bioHtml/extraCta-Spalten
-- volle Feld-Parität mit den von Hand gepflegten Profilen (Diene/
Patrick/Bananenstift/Marina nutzen diese Felder).
- routes/team.js: neue importLegacyMember()-Funktion -- übernimmt ein
bestehendes Profil 1:1 in die Datenbank, OHNE erneut zu übersetzen
(die vorhandenen, von Hand geschriebenen Übersetzungen bleiben
erhalten). Idempotent: mehrfacher Import erzeugt keine Duplikate.
- routes/site-texts.js (neu): admin-editierbare Kategorie-Namen und
Seitentitel, generischer key->{de,...}-Override über app_settings
(wie "Event des Jahres"), fester Schlüssel-Katalog aus
Sicherheitsgründen. Leeres Feld setzt auf den Standardtext zurück.
- 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3
lokal kompilierbar).
Frontend:
- window.dogiTeamZusammenfuehren() (main.js): admin-Mitglieder
ÜBERSCHREIBEN jetzt gleichnamige statische Einträge (per slug) statt
sie zu duplizieren -- eine Bearbeitung wirkt dadurch für alle.
- window.dogiSiteTexteLaden() + applyTranslations() erweitert um
data-site-text-key -- Kategorie-Seitentitel (team-modis.html/
team-scouts.html/creator.html/manager.html) sind jetzt live editierbar.
- Verwaltung: "📥 Bestehende Mitglieder importieren"-Knopf (holt VanVan/
Diene/Funny/Miss/Marina/Ghost/Patrick/Bananenstift aus den data-*.js-
Dateien, VanVan bewusst ausgenommen -- eigene Sonderkarte + Seite),
erweitertes Formular (Intro/ausführliche Vorstellung/zweiter Button,
eingeklappt unter "Erweitert"), neue Sektion "Kategorien &
Seitentitel" (4 Karten, sofort wirksam auf Website UND Verwaltung).
Beim Testen einen echten UX-Bug gefunden und gefixt: die "Gespeichert"-
Meldung nach dem Speichern eines Mitglieds wurde von der direkt
anschließenden Formular-Zurücksetzung sofort wieder überschrieben und
war nie sichtbar.
Cache-Busting-Version auf 20260821k erhöht.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
|
||
|
|
7060a4a6ae |
Verwaltungsseite perfektioniert: Team-Verwaltung für Modis/Scouts/Creator/Manager
Nutzer-Wunsch 21.08.2026: "überall wo Modis sind oder Scouts oder Manager, oder Creator, will ich dass ich die easy über meine Verwaltungsseite hinzufüge und die automatisch in der Website hinzugefügt werden ... auch mit Fotos und TikTok Link." Backend (server-internal, braucht Filipes manuellen Deploy): - Neue Tabelle team_members (Migration 0007) für alle vier Kategorien gemeinsam -- ergänzt, überschreibt NIE die von Hand gepflegten Einträge in data-modis.js/data-scouts.js/data-creator.js. - routes/team.js: öffentliches Lesen (nur aktive Mitglieder, optional nach Kategorie gefiltert), TEAM_MANAGE-geschütztes Anlegen/Bearbeiten/ Löschen, Foto-Upload (gleiches Muster wie "Event des Jahres"), automatische Übersetzung von Rolle/Bio/Zitat wie bei anderen admin-gepflegten Texten. Slug global eindeutig (mit automatischer Kollisionsauflösung), TikTok-Kurzname wird zu voller URL ergänzt. 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3 lokal kompilierbar). Frontend: - window.dogiTeamLaden() in main.js: holt admin-gepflegte Mitglieder einer (oder aller) Kategorien und bringt sie in exakt dieselbe Form wie die bestehenden data-*.js-Einträge. - team-scouts.html/team-modis.html/creator.html hängen das Ergebnis einfach an ihre bestehenden Arrays an und rendern erneut -- exakt derselbe Look wie die schon bestehenden Profile (Foto/Avatar-Initiale, Rolle, Zitat, TikTok-Button). creator.html hatte bisher ein "return" bei leerem CREATORS-Array, wodurch admin-Creator NIE geladen worden wären -- gefixt. team-modis.html berücksichtigt Rang/Rang-Bezeichnung für die Pyramide. - Neue Sektion "Weitere Manager" auf manager.html, komplett unsichtbar bis der erste Manager angelegt wird (data-manager.js neu, wie data-creator.js aktuell leer). - profil.html sucht jetzt kategorieübergreifend auch in admin-gepflegten Profilen, inkl. korrektem Theme/Zurück-Link auch für Manager. Verwaltung: neue Sektion "Team verwalten" (TEAM_MANAGE-Berechtigung) -- ein Formular für alle vier Kategorien mit Foto-Sofort-Upload, TikTok-Feld, Bio/Zitat, bedingten Rang-Feldern (nur Modi), Kategorie- Filterleiste und Bearbeiten/Löschen pro Eintrag. Beim Testen mit Playwright einen echten Syntaxfehler gefunden und gefixt (ASCII-Anführungszeichen statt schließendem „" in einem Statustext), der das GESAMTE Verwaltungs-Skript und damit die komplette Seite lahmgelegt hätte. Cache-Busting-Version auf 20260821j erhöht. Co-Authored-By: Claude Sonnet 5 <[email protected]> |
||
|
|
399260b805 |
"Stimmen" verwandelt: Fans reichen selbst Testimonials ein
Nutzer-Wunsch 20.08.2026: "eine richtig geile kachel ... wo die leute mit namen und tiktok namen mir eine nachricht schreiben können die ich in die verwaltung kriege, und dann kann ich die ausgewählten über die verwaltungsseite auf die website hinzufügen ... das wird mega persoenlich zu den fans, bitte wirklich krass geil speziell." - Neue, augenschonend gestaltete Einreich-Kachel auf stimmen.html: Name, TikTok-Name (optional), Nachricht -- landet NIE automatisch oeffentlich, sondern immer erst als Entwurf in der Verwaltung. Warmer Babyblau/Rosé-Farbverlauf, wandernder Lichtschein, pulsierendes Herz -- ersetzt die 3 ewigen "Hier steht bald..."-Platzhalterkarten. - Neue Verwaltungs-Sektion "💬 Stimmen verwalten": Warteschlange (offene zuerst), Freigeben/Ablehnen/Löschen pro Eintrag. - Freigegebene Stimmen erscheinen automatisch in einer neuen, spezielleren Zitat-Kartenoptik (großes Anführungszeichen, Name + TikTok-Chip) -- Abschnitt bleibt komplett unsichtbar, solange keine einzige freigegeben wurde. - Backend: neue Tabelle `testimonials` (Migration 0006), routes/ testimonials.js (oeffentliches Einreichen + Lesen freigegebener, admin-Warteschlange + Status/Loeschen mit neuer TESTIMONIALS_MANAGE- Berechtigung). 18 automatisierte Tests gegen eine Fake-DB bestanden. - Alles per Playwright visuell durchgespielt: leerer Zustand, Einreichen -> Erfolgsmeldung, freigegebene Stimmen-Anzeige, Verwaltungs-Warteschlange mit allen Aktionen. Backend-Teil (server-internal/, cloudflare-worker/migrations/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
519e209bce |
Zweite Live-Uhr fuer "spezielles Live-Event" + mehr Farbe im Radar
Nutzer-Wunsch 20.08.2026: "wuensch mir nur noch bissl farbe und sehr wichtig ist dass ich auch spezielle live events da auch eintragen kann in der verwaltungsseite so dass da eine zweite uhr erscheint wo die zeit dann fuer dieses spezielle event laeuft." - Neue Verwaltungs-Sektion "Spezielles Live-Event": Titel (Deutsch, wird automatisch uebersetzt), echtes Datum+Uhrzeit, optionaler Link, Aktiv- Schalter -- nur mit Titel + Zukunftsdatum aktivierbar. - Zweite Uhr auf streamplan.html (Magenta/Violett statt Cyan/Gold, direkt neben der bestehenden), nur sichtbar wenn ein Event aktiviert ist und das Zieldatum noch nicht vorbei ist. Echter Countdown inkl. Tage (z.B. "2T 03:14:59"), "Jetzt"-Zeiger zeigt wie bei Uhr 1 die tatsaechliche Uhrzeit, der magentafarbene Fixpunkt markiert die Tageszeit des Events. Zieldatum wird als UTC gespeichert -- jede besuchende Person sieht den exakt richtigen Countdown in ihrer eigenen Zeitzone. - "Bissl Farbe": bunter Farbverlauf (Cyan/Violett/Gold) auf dem aeusseren Ring statt reinem Grauton, kraeftigerer Sweep-Farbschein. - Backend: neue routes/special-event.js (getSpecialEventPublic nur bei aktiv+zukuenftig, getSpecialEventAdmin fuer Entwuerfe, saveSpecialEvent mit Validierung), 13 automatisierte Tests gegen eine Fake-DB bestanden. - Bugfix unterwegs gefunden (Playwright-Screenshot, wiederholtes Muster auf dieser Seite): .stream-radar-wrap blieb trotz [hidden]-Attribut sichtbar (display:block ueberschreibt die eingebaute [hidden]-Regel bei gleicher Spezifitaet) -- explizite Regel ergaenzt, per Playwright erneut verifiziert (display:none bestaetigt). Backend-Teil (server-internal/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen, sonst bleibt die zweite Uhr unsichtbar. Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
b3e191d3ca |
"Event des Jahres": Aktivieren-Schalter, leer bis befuellt, Detail-Fenster
Nutzer-Wunsch 20.08.2026: "diese zwei kisten sollen leer sein solang wie ich nichts rein setze, am besten sollen die sogar immer nur erscheinen wenn ich was rein setze und sie aktiviere ... wen ich drauf druecke dass dann ein kleines fenster aufgeht wo das bild bissl groesser ist mit einem groesseren text und beschreibung, und mit einem link ... die kacheln sollen auch bissl spezieller sein." - Kein hartkodierter Standardinhalt mehr auf der Startseite -- die Kachel UND der ganze Abschnitt bleiben komplett unsichtbar, bis mindestens ein Event in der Verwaltung ausgefuellt UND ueber einen neuen "Aktiv"-Schalter freigeschaltet ist. Genau 1 aktives Event -> zentrierte Einzelkachel statt halbleerem Zwei-Spalten-Raster. - Klick auf eine Kachel oeffnet jetzt ein Detail-Fenster (groesseres Bild, groesserer Titel/Text, optionaler direkter Link) statt sofort wegzunavigieren. - Kacheln bekommen einen goldenen Trophaeen-Akzent + dezenten wandernden Lichtschimmer statt der neutralen Standardkarten-Optik. - Bugfix unterwegs gefunden (Playwright-Screenshot): .jahres-event-card blieb trotz [hidden]-Attribut sichtbar (dieselbe Ursache wie der frühere .jahres-event-bild-Bug: display:block ueberschreibt die eingebaute [hidden]-Regel bei gleicher Spezifitaet) -- explizite [hidden]-Regel ergaenzt. - Backend: server-internal/routes/events.js liefert oeffentlich NUR noch aktivierte Slots aus (getEventsPublic), neuer authentifizierter Endpunkt getEventsAdmin liefert der Verwaltung auch Entwuerfe zum Vorausfuellen. 10 automatisierte Tests gegen eine Fake-DB bestanden. Backend-Teil (server-internal/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen, sonst bleibt die Startseite beim alten Verhalten (immer beide Slots zeigen, kein Aktiv-Schalter in der Verwaltung). Co-Authored-By: Claude Opus 5 <[email protected]> |
||
|
|
bda3198c76 |
Neue Funktion: Supporter-Abstimmungen (Verwaltung + DogiCrew-Bereich)
Naechste "Ausbaustufe" aus Dogfather_VanVan_Supporter_Abo.odt Abschnitt 20 (siehe Supporter-Abo-System.md), auf Nutzerwunsch "perfektioniere meine Verwaltungsseite": Dogi/VanVan koennen in verwaltung.html eine Frage mit 2-6 Antwortoptionen auf Deutsch erstellen, automatische Uebersetzung beim Speichern (gleiches Muster wie "Event des Jahres"). Jede aktive DogiCrew- Person sieht die Abstimmung in ihrem Supporter-Bereich, stimmt genau einmal ab (UNIQUE-Constraint in der DB, nicht nur Anwendungslogik), sieht danach die Live-Ergebnisse. Admin-Seite zeigt Ergebnisbalken live, kann schliessen/ wiedereroeffnen/loeschen. - Neue Migration 0005_supporter_polls.sql (supporter_polls, supporter_poll_votes), neue Berechtigung POLLS_MANAGE. - server-internal/routes/polls.js: 30 End-to-End-Tests gegen eine Fake-DB bestanden (better-sqlite3 laesst sich lokal nicht kompilieren). - verwaltung.html: neue "Abstimmungen"-Kiste im bestehenden vw-overview-box-Stil (violett/pink Ergebnisbalken). - supporter.html: neue "Aktuelle Abstimmung"-Karte im bestehenden Gold-Look, Optionen -> Stimme -> Ergebnisbalken, alle 5 Sprachen. Backend-Teil (server-internal/, cloudflare-worker/migrations/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen. Co-Authored-By: Claude Opus 5 <[email protected]> |