Der Upload lief gegen einen einzigen globalen Wert von 5 MB, der fuer alle
Dateitypen galt. Eine Getraenkekarte liegt aber schnell bei 15-20 MB, das
Hochladen scheiterte deshalb zuverlaessig.
- MAX_FILE_SIZE (Bilder) von 5 auf 20 MB. Bilder werden ohnehin auf 1600px
heruntergerechnet, die Grenze muss nur ein unbearbeitetes Handyfoto
durchlassen - 5 MB reichten dafuer schon nicht.
- MAX_PDF_SIZE neu, 40 MB. PDFs werden unveraendert abgelegt.
- Beides ueber Umgebungsvariablen uebersteuerbar.
Die Registrierung von multipart nimmt den groesseren der beiden Werte als
Obergrenze, die Routen setzen ihn per request.file({ limits }) auf ihren
eigenen herunter. Die Fehlermeldung im PDF-Zweig nannte bisher die
Bildgrenze.
Geprueft am laufenden Container, auch mit --memory=512m wie auf Fly: 18-MB-
PDF und 18-MB-Foto gehen durch, 45 MB bzw. 25 MB werden mit 413 und
passender Meldung abgelehnt, kein OOM.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Bilder, die vor der Umstellung hochgeladen wurden, liegen unverkleinert im
Originalformat im Repo - damals war sharp im Container kaputt, es gab also
weder Verkleinerung noch Formatwandlung. Das Skript schickt sie durch
dieselbe Verarbeitung wie einen frischen Upload.
node dist/scripts/convert-images-to-avif.js # nur anzeigen
node dist/scripts/convert-images-to-avif.js --apply # wirklich tun
Ohne --apply wird nichts geschrieben.
Ablauf: Workspace auf den Repo-Stand bringen, jedes referenzierte Bild
umwandeln und nach seinem Inhalt benennen (Event-Titel, Alt-Text, Zweck),
die Verweise in Events, Gallery und Textbereichen nachziehen, die
Vorgaenger wegraeumen und committen.
Weggeraeumt wird nur, was das CMS selbst angelegt hat. Ein handgepflegtes
event_karaoke.jpg bekommt zwar eine AVIF-Fassung und der Verweis zeigt
darauf, das Original bleibt aber liegen und wird im Bericht genannt.
saveImageBuffer aus upload.service.ts herausgeloest, damit Upload und
Skript nachweislich dieselbe Verarbeitung benutzen statt zweier Kopien.
replaceImageUrls ersetzt Pfade in den Textbereichen, ohne die restliche
JSON-Struktur anzufassen.
Durchgespielt an vier Bildern zu je ~656 KB: 2.56 MB -> 0.16 MB. Die
Vorschau schreibt nichts, ein zweiter Lauf findet nichts mehr, die
Highlights-Liste bleibt unversehrt und alle Bilder werden danach als
image/avif ausgeliefert.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Uploads werden nach AVIF gewandelt statt nach WebP und heissen jetzt nach
dem Inhalt statt nach Zeitstempel und Zufall:
- Events uebernehmen den Event-Titel, die Gallery den Alt-Text, Bilder in
den Textbereichen ihren Zweck, PDFs den urspruenglichen Dateinamen.
- Ohne solche Angabe wird der Dateiname vor dem Upload verwendet.
- Umlaute werden ausgeschrieben (Getraenkekarte, nicht Getrnkekarte),
Sonderzeichen und Pfadanteile fallen weg.
- Gleiche Namen werden durchnummeriert, damit zwei Events namens
"Karaoke" sich nicht gegenseitig ueberschreiben.
Besitz statt Namensmuster:
Die Loeschsicherheit haing bisher am Namensmuster <zeitstempel>-<zufall>.
Mit sprechenden Namen traegt der Name diese Information nicht mehr - ein
hochgeladenes karaoke-abend.avif ist von einem handgepflegten
event_karaoke.jpg nicht zu unterscheiden. Deshalb fuehrt das CMS jetzt in
managed_assets Buch darueber, welche Dateien es selbst angelegt hat, und
loescht ausschliesslich diese. Uploads von vor der Umstellung werden
weiterhin am alten Muster erkannt, damit sie aufraeumbar bleiben.
initDatabase() legte Tabellen nur an, wenn users noch fehlte. Auf einer
bestehenden Datenbank waere managed_assets damit nie entstanden. Der
Block laeuft jetzt bei jedem Start; alle Anweisungen sind IF NOT EXISTS.
Nebenbei repariert: die Formulare schickten den Alt-Text NACH der Datei.
Zu dem Zeitpunkt hat der Server ihn noch nicht geparst, in der Gallery
landete deshalb immer der Dateiname als Alt-Text. Textfelder gehen jetzt
vor der Datei raus.
Gemessen an einem 4032x3024-Bild: 0.90 MB rein, 36 KB AVIF bei 1600x1200
raus, 579 ms. Das Testbild ist synthetisch und komprimiert besser als ein
echtes Foto - die Verkleinerung auf 1600px greift aber immer.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Bilder wurden nie geloescht - weder beim Loeschen eines Datensatzes noch
beim Austauschen. Dazu waren die Textbereiche zwar im Backend vorhanden,
aber ohne Oberflaeche, und die Getraenkekarte hing hartkodiert im Markup.
Aufraeumen:
- asset.service.ts loescht ausschliesslich Dateien, die das CMS selbst
angelegt hat (Muster <zeitstempel>-<zufall>.<ext>) und nur innerhalb der
Upload-Ordner. Handgepflegte Assets wie event_karaoke.jpg oder Welcome.png
bleiben unangetastet, auch wenn sie nirgends referenziert sind.
- image-refs.service.ts sammelt alle benutzten Bild-URLs, inklusive der
Pfade aus den Textbereichen. Geloescht wird nur, was wirklich niemand
mehr benutzt - ein von zwei Events geteiltes Bild bleibt liegen.
- Events und Gallery raeumen beim Loeschen und beim Bildwechsel mit auf.
- Der Publish entfernt zusaetzlich Verwaiste. Die Referenzliste deckt
bewusst alle Zeilen ab, auch unveroeffentlichte, sonst verlieren die ihr
Bild. Die Loeschungen werden mitcommittet, das Repo schrumpft also.
Texte:
- Neuer Adminbereich fuer Hero, Willkommen und Drinks ueber die schon
vorhandenen /api/content-Endpunkte, samt Highlights-Liste und Bildern.
- Der Generator maskiert Texte jetzt. Ohne das haette ein "<" oder "&" in
einem Feld gereicht, um den Astro-Build und damit den Deploy zu killen.
PDF:
- POST /api/pdf/drinks nimmt die Getraenkekarte entgegen, hinterlegt die
URL in der drinks-Section und raeumt den Vorgaenger weg. Der Generator
verlinkt sie, statt den Pfad fest im Markup zu haben.
Ausserdem:
- Vorschaubilder im Admin auf eine feste Box gezwungen. Uploads sind bis
1600px breit und haben das Layout je nach Seitenverhaeltnis zerrissen.
- git.service.ts sichert public/pdf beim Neu-Clone mit weg, nicht nur
public/images.
- Der Publish scheitert nicht mehr, wenn nur das Audit-Log nicht
geschrieben werden kann - der Push ist da laengst durch.
- Upload-Logik lag dreifach kopiert vor, jetzt in upload.service.ts. Der
Helfer prueft auch auf abgeschnittene Dateien; bisher landete bei zu
grossen Uploads ein kaputtes Bild auf der Platte.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Die Bild-URLs in der DB sind /images/events/... bzw. /images/gallery/... -
das ist der Pfad auf der publizierten Astro-Seite. Das Backend lieferte die
Dateien aber nur unter /static/ mit Root auf dem Workspace aus, die Datei lag
also unter /static/public/images/... Ein Aufruf von /images/... traf auf gar
keine Route, kam als JSON-404 zurueck und wurde vom Browser als
OpaqueResponseBlocking verworfen. Dadurch waren im Adminbereich saemtliche
Event- und Gallery-Bilder kaputt.
Zusaetzlicher statischer Mount /images/ -> <workspace>/public/images. Die DB
bleibt unveraendert und die URLs stimmen weiterhin fuer die publizierte Seite.
/static/ bleibt zusaetzlich bestehen.
Beide Verzeichnisse werden vorab angelegt - @fastify/static verweigert sonst
die Registrierung, wenn der Workspace noch nicht geklont ist.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Publish schlug bei jedem Versuch mit "spawn git ENOENT" fehl, obwohl git im
Container installiert ist. Node meldet ENOENT auch dann, wenn das cwd des
Kindprozesses nicht existiert: initialize() setzte simple-git auf den
Workspace, loeschte diesen per rm -rf und startete den Clone anschliessend
aus dem geloeschten Verzeichnis heraus.
Nebeneffekt davon: da Uploads unter GIT_WORKSPACE_DIR/public/images liegen,
hat jeder fehlgeschlagene Publish die hochgeladenen Bilder mitgeloescht.
git.service.ts:
- Clone laeuft aus dem Elternverzeichnis statt aus dem geloeschten Ziel
- Re-Clone nur noch wenn kein brauchbares Repo vorhanden ist
- Uploads werden ueber den Re-Clone hinweg gesichert (Repo-Stand gewinnt)
- commitAndPush scheitert nicht mehr, wenn es nichts zu committen gibt
- reset() nur bei echtem Repo, mit Ausnahme fuer public/images
- Token wird in Fehlermeldungen maskiert
Dockerfile:
- COPY von src/db/migrations entfernt. Das Verzeichnis war nie im Git und
liess den Image-Build scheitern. Zur Laufzeit wird es nicht gebraucht,
das Schema legt initDatabase() selbst an.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
- Streamlined CORS logic by consolidating `allowedOrigins` checks and improving readability.
- Updated callback invocations for consistency and clarity.
- Updated `fly.toml` to allow multiple CORS origins.
- Refactored CORS logic in `index.ts` to validate and support multiple origins, including handling requests with no origin.
- Adjusted server logic in `/banners/active` to resolve timezone issues and ensure consistent date handling.
- Sorted active banners by creation date in descending order for better relevance.
- Integrated `Banner.astro` component into the homepage layout for displaying active banners.
- Introduced `banners.ts` with CRUD operations for managing banners.
- Added `/banners/active` endpoint to fetch active banners.
- Secured admin-only routes for banner creation, update, and deletion.
- Created `Banner.css` for banner styling.
- Introduced a new "Banners" feature, enabling banner creation, management, and display across the admin panel and frontend.
- Enhanced image handling for events and gallery by converting images to optimized webp format.
- Added `banners` table in the database schema for storing announcements.
- Integrated new `/api/banners` route in backend for banner operations.
- Updated `index.astro` to include banner display component.
- Added supporting UI and APIs in the admin panel for banner management.
- Introduced `/events/upload` endpoint for securely uploading and processing event images.
- Added image validation, resizing, and conversion to WebP with fallback support for original formats.
- Updated `uploadImage` to `uploadEventImage` and introduced `uploadGalleryImage` in `admin.astro`.
- Add migration script to convert and copy images
- Include 7 events (Karaoke, Pub Quiz, etc.) in WebP format
- Include 9 gallery images in WebP format
- Update .gitignore to allow images in data/images/
- Add migration documentation in MIGRATION_README.md
Images are stored in backend/data/images/ which maps to
the persistent Fly.io volume at /app/data/
- Implemented public `/gallery/public` and `/events/public` endpoints for fetching published data without authentication.
- Updated persistent volume configuration for Fly.io across backend and static file serving.
- Adjusted frontend to dynamically fetch events and gallery images from backend API.
- Refined Woodpecker pipeline for clearer separation of backend and frontend deployments.
- Updated frontend to use `https://cms.gallus-pub.ch` as the API base URL.
- Configured cookies with `SameSite=None` and `Secure` for production in `auth.ts`.
- Enhanced `fly.toml` to include `FRONTEND_URL`, `CORS_ORIGIN`, and `GITEA_REDIRECT_URI`.
- Adjusted `.gitignore` to ignore `/ai/` directory.
- Introduced Caddy server for serving frontend and API backend.
- Implemented admin dashboard for creating, editing, and managing events.
- Replaced session-based authentication with token-based OAuth using Gitea.
- Added support for drag-and-drop event reordering in the admin interface.
- Standardized Fastify route validation with JSON schemas.
- Enhanced authentication flow with cookie-based state and secure token storage.
- Reworked backend routes to handle publishing, event management, and content updates.
- Updated `Dockerfile.caddy` and `fly.toml` for deployment configuration.