Commit Graph
99 Commits
Author SHA1 Message Date
ClaudeandClaude Opus 5.5 5ce8aa6fbb Zuweisen: Warnungen statt Abbruch, Zuweisung unter der Projekt-Sperre
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 a960701d48 Zuweisen mit Umzug und Nachtragen der Pushes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 405b2a0485 Umzug: Webhook-Adresse aus config.url, Aufräumen auch nach Selbstheilung
Fix round 1 (Review C1/I1/I2/I3, Controller-Ruling R11):
- C1 (kritisch): Hooks haben in echtem Gitea kein Top-Level-url, nur config.url -
  isInternalRepo/deletePortalHooks lasen bisher ein Feld, das es in Produktion nicht gibt.
- I1: Schritt-2-Namensvergleich jetzt ohne Ruecksicht auf Gross-/Kleinschreibung.
- I2: fs.rm in Schritt 9 gegen EACCES/EBUSY/ENOTEMPTY & Co. abgesichert.
- I3: Aufraeumen (Cache, alter Einzel-Webhook, Preview-Verzeichnis) laeuft jetzt ueber den
  neuen idempotenten finishMove()-Baustein in jedem Fall, in dem das Repo tatsaechlich in
  WEBklar liegt - auch nach einem Appwrite-Fehler, bei Selbstheilung und bei einem schon
  dort liegenden Repo (WARN.hookNotRemoved bei fehlgeschlagenem Loeschen).
- Fake Gitea: Hook-Form ohne Top-Level-url, 301 mit relativer Location (auch fuer
  Unterpfade) wie im echten Gitea 1.25.2, failNext() fuer gezielte Fehler in Tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 6e6c9a4c24 Umzug zugewiesener Repos nach WEBklar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 51c4852488 Push-Historie: Obergrenze beim Blättern, Test für HeadCommit
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 e409c11d5c Push-Historie aus den Gitea-Aktivitäten lesen
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 8d5de87ead Push-Arbeitsblätter: deutsche Zeit, keine System-Pushes, keine Doppelten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 531ee2a02b Gitea-Client herausgelöst, Konfiguration für Organisation und System-Konto, Test-Nachbauten
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 ea71b3f1c3 Sperren: Timeout eines Wartenden hebt die Sperre nicht auf
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 679f996597 Bausteine: slugify mit Umlauten, deutsche Zeit, Sperren
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 10:21:34 +00:00
ClaudeandClaude Opus 5.5 8c9d28c30f Profilbild: zufälliger DiceBear-Avatar (Big Smile) pro Kunde
Der Server erzeugt das SVG lokal unter /api/avatar.svg (Seed = Kunden-ID),
es geht also keine Anfrage an dicebear.com. Die Initialen bleiben als
Fallback, bis das Bild geladen ist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:36:49 +00:00
ClaudeandClaude Opus 5.5 e4efdac226 Hosting-Abo: Preis 15 € -> 20 € pro Monat (Vertragsbedingungen v4, Katalog, Leitfaden)
- server/config.js: amountCents 2000, lookup_key webklar_hosting_monatlich_2000
- ensurePrice haengt einen neuen Preis an das vorhandene Stripe-Produkt (metadata.webklar),
  statt ein zweites "WEBklar Hosting-Abo" anzulegen
- abo-bedingungen.html (Uebersicht, Preise, Leistungsbeschreibung) + PDF, Leitfaden + PDF
- Tests auf 20 € umgestellt (399/399)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:29:01 +00:00
ClaudeandClaude Opus 5.5 78b4d7b7fe Livegang: Vertragsbedingungen Version 4 und Datenschutzerklärung Stand 30.09.2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 16:03:39 +00:00
ClaudeandClaude Sonnet 5.5 36b54f6dab Löschfristen: bei abgeschnittenen Listen abbrechen, Teilfehler melden, Randfälle
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 14:54:43 +00:00
ClaudeandClaude Sonnet 5.5 9342ba10c8 Löschfristen: Probelauf, Bericht ans Team, Löschen nach drei Jahren
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 14:51:50 +00:00
ClaudeandClaude Sonnet 5.5 cc42ab16c1 Admin E-Mail-Speicher: belegter Speicher wie in konsoleH, klare Vorgabe je Kunde
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 14:48:49 +00:00
ClaudeandClaude Fable 5.1 12a24514a4 Mails: Vertragsbestätigung je Fassung, Zustimmung zum AVV, kleinere Textkorrekturen
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 14:46:41 +00:00
ClaudeandClaude Fable 5.1 63d3bb11bf Admin: Reiter E-Mail-Speicher, Abo mit Produkt; Schnittstelle fürs Ticketsystem
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 14:37:42 +00:00
ClaudeandClaude Fable 5.1 19f9c747a2 Mails: Produktname statt fest Wartungs-Abo, neue Mails für Wechsel und Zusatzspeicher, UptimeRobot-Hinweis
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 10:38:43 +00:00
ClaudeandClaude Fable 5.1 0d2254ca20 Kündigung und Widerruf: Paketzahl in jeder Bestätigung, klare Hinweise fürs Team, Tests
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 10:15:51 +00:00
ClaudeandClaude Sonnet 5 e5cc2f3c82 Abo-Angebote: für Hosting- oder Wartungs-Abo
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-29 10:08:59 +00:00
ClaudeandClaude Fable 5.1 7ffe95f24a Kündigung und Widerruf: Vertrag bzw. Bestellung auswählbar (§ 312k, § 356a BGB)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 09:52:37 +00:00
ClaudeandClaude Fable 5.1 f66a713531 Zusatzspeicher: Plan nach Stripe-Abgleich erneut prüfen, Aufgabenliste unter Sperre, K13 ohne Kosten
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 09:31:28 +00:00
ClaudeandClaude Opus 5.5 662f8d8144 Wechsel: Lastschrift-Fehler nur für den echten Wechsel, Abgleich ohne Webhook, Gutschrift netto, Skript prüft Stripe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 09:24:02 +00:00
ClaudeandClaude Fable 5.1 bc20ce0673 Zusatzspeicher: Vorschau im flexiblen Abrechnungsmodus, Aufgaben nach Zeitpunkt, keine stillen Abbrüche
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-29 09:16:07 +00:00
ClaudeandClaude Opus 5.5 188db4b3c1 Wechsel Hosting → Wartung: neuer Abbuchungstag, Gutschrift, 12 neue Monate
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:30:21 +00:00
ClaudeandClaude Fable 5.1 379cc26dcd Zusatzspeicher: Plus sofort anteilig, Minus zum Monatsende, Aufgaben für konsoleH
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 10:48:29 +00:00
ClaudeandClaude Fable 5.1 887e1e54a0 Abo-API: Nachweis-ID nach Appwrite-Regeln, Webhook bei dauerhaften Fehlern, Tests
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 10:19:05 +00:00
ClaudeandClaude Opus 5.5 7df0b72019 Stripe: Testrabatt-Namen, Produktbild, Hosting-Leistungen, Tests für Bestandskunden
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 10:02:03 +00:00
ClaudeandClaude Fable 5.1 c4607e9c43 Abo-API: Bestellung je Produkt, Katalog, Speicherstand, Wechsel-Möglichkeit
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 09:43:09 +00:00
ClaudeandClaude Opus 5.5 05363d06ad Vertragsereignisse: Speicher-Aufgaben mit gelesenen Details
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:25:16 +00:00
ClaudeandClaude Opus 5.5 e5cc57cefd Stripe: Preise je Produkt, Testrabatt Hosting, Kundenportal „Abo verwalten“, Mindestlaufzeit ab termStartAt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 09:21:25 +00:00
ClaudeandClaude Opus 5.5 7799d7c4de Vertragsereignisse: Nachweise, Widerrufsfristen, Speicher-Aufgaben
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 08:59:30 +00:00
ClaudeandClaude Fable 5.1 e456c3de69 Übernahmegutschrift: Verrechnung nach Wechsel, Mail K09, Leitfaden, Admin zeigt das Abo, Tests
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-28 07:49:51 +00:00
ClaudeandClaude Fable 5.1 a2c1f0d81c Übernahmegutschrift und Leitfaden: für beide Abos (Entwurf)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-27 20:23:44 +00:00
ClaudeandClaude Opus 5.5 f19187cc16 Vertragsbedingungen Version 4 (Entwurf): Hosting- und Wartungs-Abo, Postfächer, Zusatzspeicher, Wechsel; Fassung 3 archiviert
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 16:17:31 +00:00
ClaudeandClaude Opus 5.5 47c1af7015 Stripe: Testadresse STRIPE_API_BASE nur für 127.0.0.1/localhost
Ein verirrter Wert in Produktion kann den Live-Schlüssel so nie an einen
fremden Server schicken; getStripe() bricht mit klarer Meldung ab.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:46:26 +00:00
ClaudeandClaude Opus 5.5 2694ef18d6 Abo-Spiegel: fehlt ein neues Feld in Appwrite, wird ohne dieses Feld gespiegelt
Schützt Webhooks und den öffentlichen Kündigungs-/Widerrufsknopf, falls das
Setup-Skript erst nach dem Deploy läuft; der Fehler steht laut im Log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:45:54 +00:00
ClaudeandClaude Opus 5.5 dc61649228 Stundenkonto: Abos ohne bekannten Preis bekommen als Altbestand weiter ihre Stunde
Wie im Abo-Spiegel gelten Rechnungen ohne jeden bekannten Abo-Preis als
Wartung. Ein unbekannter Preis neben einem bekannten (z. B. Hosting) bringt
weiterhin keine Stunde.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:45:09 +00:00
ClaudeandClaude Opus 5.5 51fc2434e5 Stundenkonto: Arbeitszeit nur für bezahlte Wartungs-Monate, nicht für andere Abo-Rechnungen
Die Rechnung wird frisch geladen; eine Stunde bringt nur eine reguläre
(nicht anteilige) Wartungs-Zeile über 0 €. Preis-IDs werden einmal bei
Stripe nachgeschlagen und zwischengespeichert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:33:33 +00:00
ClaudeandClaude Opus 5.5 d097704d49 Abo-Spiegel: Produkt und tatsächlicher Preis aus den Stripe-Positionen
Neues Appwrite-Feld subscriptions.productKey (Setup-Skript). Ohne bekannten
Abo-Preis gilt ein Abo weiter als Wartungs-Abo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:31:56 +00:00
ClaudeandClaude Opus 5.5 54defb75c4 Tests: Stripe- und Appwrite-Nachbau im Testprozess
getStripe() nutzt in Tests über STRIPE_API_BASE den Nachbau auf 127.0.0.1;
in Produktion ist der Wert leer und nichts ändert sich.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:30:56 +00:00
ClaudeandClaude Opus 5.5 877fde7a6f Abo-Katalog: Wartung, Hosting und Zusatzspeicher als Produkte (noch ohne Verwendung)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 15:29:16 +00:00
ClaudeandClaude Opus 5.5 18c3cdf3e5 Automatische Mails im neuen Design (HTML mit Textfassung)
Alle 22 automatischen Mails gehen jetzt als HTML nach den Vorlagen aus
server/mail-templates raus, dazu eine aus dem HTML erzeugte Textfassung
(multipart/alternative). Werte werden HTML-sicher eingesetzt, optionale
Blöcke (WENN/ENDE) je nach Fall ein- oder ausgeblendet.

- Neu: services/mailTemplates.js (Vorlagen lesen, Platzhalter, Blöcke,
  Textfassung); mailer.js nimmt { template, values, blocks }.
- K11 "Passwort geändert" war nicht im Design-Export: aus den Bausteinen
  von K02 zusammengesetzt.
- T09 (Widerruf, intern): Fälle "kein Abo gefunden" und "Stripe-Kündigung
  fehlgeschlagen" ergänzt; bisher stand dort immer "auf Kündigung gesetzt".
- Einheitlich: Datum TT.MM.JJJJ, Beträge deutsch (T04 "50,00 €"),
  "WEBklar" statt "Webklar", Anrede mit Kundennamen, Team-Absender
  "WEBklar Kundenportal", Betreff T05 "Kündigung (Stripe)".
- Logo für die Mails: public/webklar-logo.png (192 px, aus dem Design-SVG).

Tests: 52 neue (jede Vorlage: Kopf, Platzhalter, Blöcke, Rendern,
Escaping, Textfassung), npm test 97/97 auf Node 20.19.5; alle 40 Auslöser
einmal durch den Server geschickt (Appwrite-Mock, Stripe-Double,
SMTP-Falle mit STARTTLS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:37:50 +00:00
ClaudeandClaude Opus 5.5 e3daffffa3 Mail-Vorlagen aus dem Design übernommen (unverändert)
Die 21 HTML-Vorlagen aus dem Claude-Design-Projekt "Webklar Bestätigungsmail
gestalten" (Ordner WEBklar-Mail-Vorlagen), Byte für Byte wie im Export.
K01–K10 gehen an Kunden, T01–T11 intern ans Team.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 14:23:54 +00:00
ClaudeandClaude Opus 5.5 60baeb3271 Mailversand: Antwortadresse, Absendername WEBklar, nur mit STARTTLS
- SMTP_REPLY_TO (z. B. support@webklar.com) setzt Reply-To auf allen
  Kundenmails, damit Antworten nicht im Versandpostfach (noreply@) landen
- Absendername einheitlich "WEBklar" (Kunden) bzw. "WEBklar Kundenbereich"
  (interne Hinweise) statt "Webklar"/"Webklar Kundenportal"
- requireTLS auf Port 587: der neue Mailserver (Hetzner) nimmt die
  Anmeldung auch unverschluesselt an; ohne STARTTLS wird jetzt nicht gesendet

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:12:37 +00:00
ClaudeandClaude Opus 5.5 32e441c8bc Abo-Angebote: Mail-Status in der Liste und E-Mail erneut senden
Bisher sah man nirgends, ob die Angebots-Mail rausging, Fehler landeten
nicht im Log, und senden ließ sie sich nur beim Anlegen.
- Angebotsliste: Kunden-Adresse unter dem Namen, im Status "E-Mail
  gesendet <Zeitpunkt>" bzw. "E-Mail nicht verschickt" / "Keine
  E-Mail-Adresse hinterlegt"
- Knopf "E-Mail senden" / "Erneut senden" mit Rückfrage (Adresse, letzter
  Versand), solange das Angebot offen und gültig ist; bei laufendem Abo,
  solange die erste rabattierte Abbuchung noch bevorsteht (die Mail
  nennt sie). POST /api/portal-admin/offers/:id/notify setzt notifiedAt.
- Server-Log: "[offers] Angebots-Mail <Ref> vom Mailserver angenommen:
  <Message-ID> 250 … queued as …" bzw. "… fehlgeschlagen: <Grund>"
- Lehnt der Mailserver nur den Kunden ab (die BCC-Kopie ans Team geht
  trotzdem raus), gilt die Mail als nicht verschickt
- sendCustomerMail gibt die nodemailer-Info zurück

Tests: npm test (40), Server-Durchlauf gegen Appwrite-Mock, SMTP-Sink
und Stripe-Double (28 Prüfungen), Oberfläche (18 Prüfungen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 11:12:39 +00:00
ClaudeandClaude Opus 5.5 81119c19bc Kundenbereich: Passwort selbst ändern
Im Konto-Menü (Profilbild) gibt es unter "Konto" jetzt "Passwort
ändern". Die Seite fragt aktuelles Passwort, neues Passwort und
Wiederholung ab (mindestens 8 Zeichen, "Passwörter anzeigen"). Am
Handy ist sie eine Unterseite des Tabs, aus dem man kam; Direktlink
/dashboard.html#passwort. Im Ansichtsmodus ist sie gesperrt.

Server (POST /api/auth/password):
- Das aktuelle Passwort wird über eine kurze Appwrite-Session geprüft,
  die gleich wieder gelöscht wird. Tippfehler lösen nicht die globale
  Login-Sperre aus. Es gibt höchstens 5 Prüfungen je Kunde und Stunde,
  das bleibt unter Appwrites Grenze (10 Anmeldungen je E-Mail und
  Stunde).
- Das neue Passwort wird über die Users-API gesetzt. Fehler von
  Appwrite (zuletzt benutzt, zu häufig, persönliche Daten) kommen als
  verständliche Meldung.
- Die Klartext-Kopie in customers.portalPassword und ein Alt-Eintrag
  in den Notizen werden geleert. Ein selbst gewähltes Passwort wird
  nie im Klartext gespeichert; das Ticketsystem zeigt dann "–".
- Der Kunde bekommt eine Bestätigungsmail (Kopie an die Admins wie
  bei allen Kundenmails).

Andere Geräte werden abgemeldet: Kunden-Sessions merken sich beim
Login den Passwortstand (Appwrite passwordUpdate, pwv).
requireSession und /api/auth/me prüfen ihn, 60 s zwischengespeichert.
Das gilt auch, wenn wir im Ticketsystem ein neues Passwort setzen. Ist
Appwrite nicht erreichbar, bleibt die Session gültig. Sessions von
vor diesem Update haben keinen Stand und laufen normal aus (höchstens
7 Tage).

Login-Seite: Hinweis auf "Passwort ändern" statt "über das
Ticketsystem", dazu "Passwort vergessen?" mit support@webklar.com.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:22:37 +00:00
ClaudeandClaude Opus 5.5 054b04d5b6 Abo-Angebote: Admin sieht, bis wann der Rabatt läuft
"Gültig bis" ist nur die Frist zum Annehmen. Jetzt steht auch das
Rabattende da:
- Formular "Neues Angebot", Kunde ohne Abo: Schätzung "Rabatt läuft
  N Monate ab Abschluss und endet voraussichtlich zwischen <heute + N>
  und <gültig bis + N>", mit Hinweis auf den späteren Start nach der
  Widerrufsfrist (gut 2 Wochen)
- Formular, laufendes Abo: genaues Ende aus den nächsten Abbuchungen
  (Anker = Leistungsbeginn, Monatsende wie bei Stripe), auch in der
  Erfolgsmeldung
- Angebotsliste: offen -> Schätzung, aktiv -> "Rabatt endet am ..."
  aus dem Abo-Dokument (bzw. "endete", "endet mit dem Abo am ...",
  "unbekannt (Abo nicht gefunden)")

Rechnung in server/services/offerDiscount.js: Der Coupon deckt die
N Abbuchungen ab dem Anwenden. Beim Checkout ist das der Abo-Start, bei
laufendem Abo und Testkonto (Coupon per Webhook) der Zeitpunkt des
Anhängens; Testkonten laufen deshalb einen Monat länger.

Tests: npm test (37 Fälle, davon 8 neu).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:01:45 +00:00
ClaudeandClaude Opus 5.5 d5eb908cb6 Preview-Router: öffentliche Seiten überstehen Server-Neustart
Nach einem Neustart startet der Kundenbereich oft vor Appwrite. Bisher
lieferte getPublicSubdomains dann ein leeres Set, syncPreviewTls setzte
alle Preview-Hosts hinter die Login-Weiche (preview-chain) und wiederholte
das nie. Öffentliche Seiten wie anelia-wolf und emmas-schatzkiste waren
bis zum nächsten Deploy nur noch mit Login erreichbar bzw. 403.

- Antwortet Appwrite nicht, übernimmt syncPreviewTls die öffentlichen
  Seiten aus der zuletzt geschriebenen preview-hosts.yml. Neue Subdomains
  bleiben login-geschützt, ohne alte Datei ist alles geschützt.
- Beim Start wird der Abgleich alle 15 s wiederholt (max. 10 min), bis er
  mit Appwrite-Daten geklappt hat.
- Tests: Datei lesen, Abgleich mit/ohne Appwrite, Startup-Wiederholung.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 22:19:33 +00:00