Übersicht
Das Uploads-Modul verarbeitet Bild-Uploads für Kurse (Hauptbild + Galerie), Kategorie-Bilder und das Tenant-Logo.
Alle Bilder werden serverseitig validiert, zu WebP konvertiert und auf eine maximale Breite skaliert.
Uploads sind eine Kernfunktion — kein eigener Registry-Eintrag in modules.js, in allen Plänen enthalten,
und kosten 0 € (Solo, Growth, Scale). Begrenzt wird ausschließlich über das Storage-Quota des Plans
(max_storage_mb).
Erlaubte Formate & Limits
- Erlaubte Formate: JPEG, PNG, WebP, GIF, HEIC/HEIF
- Typprüfung: Magic-Bytes (nicht nur MIME-Type des Browsers) — gefälschte Dateiendungen werden abgelehnt
- Maximale Dateigröße: 12 MB pro Datei (
LIMIT_FILE_SIZE) - Galerie: max. 12 Bilder pro Kurs (
upload.array('images', 12))
sharp zu WebP konvertiert:
Hauptbild Quality 82, Resize max. 1200 px Breite; Thumbnail Quality 75, max. 400 px Breite —
jeweils mit withoutEnlargement (kleines Bild wird nicht hochskaliert).
Speicherort & Persistenz
Dateien liegen im Dateisystem unter /uploads/{tenantId}/{type}/{id}.webp sowie
…_thumb.webp für das Thumbnail. URLs werden gespeichert in:
events.meta.image_url,events.meta.thumbnail_url— Hauptbildevents.meta.gallery_urls— Galerie (Array, max. 12 Einträge viaslice(0,12))event_categories.meta.image_url— Kategorie-Bildtenants.config.logo_url,tenants.config.logo_thumbnail_url— Tenant-Logo
Statische Auslieferung über /uploads (Express static-Middleware).
API-Endpunkte
Alle Endpunkte erfordern requireAuth + Rolle admin oder owner
(Schutz über requireRole('admin','owner')). Kein requireFeature-Gate.
| Methode | Pfad | Zweck |
|---|---|---|
| POST | /api/v1/uploads/events/:eventId/image | Event-Hauptbild hochladen |
| DELETE | /api/v1/uploads/events/:eventId/image | Hauptbild löschen |
| POST | /api/v1/uploads/events/:eventId/gallery | Galerie-Bilder (max. 12) |
| DELETE | /api/v1/uploads/events/:eventId/gallery | Einzelnes Galerie-Bild entfernen (url im Body) |
| POST | /api/v1/uploads/categories/:categoryId/image | Kategorie-Bild hochladen |
| POST | /api/v1/uploads/logo | Tenant-Logo hochladen |
Fehlercodes
| Code | HTTP | Bedeutung |
|---|---|---|
NO_FILE / NO_FILES | 400 | Keine Datei im Request |
INVALID_FILE_TYPE | 400 | Dateiformat nicht erlaubt (per Magic-Bytes erkannt) |
LIMIT_FILE_SIZE | 400 | Datei überschreitet 12 MB |
STORAGE_QUOTA_EXCEEDED | 413 | Storage-Quota des Plans überschritten (max_storage_mb) |
UPLOAD_FAILED | 500 | Fehler beim Speichern/Verarbeiten |
DELETE_FAILED | 500 | Fehler beim Löschen |
Storage-Quota
Jeder Tenant hat ein Storage-Kontingent (max_storage_mb) aus den Plan-Limits.
Bei Überschreitung antwortet der Server mit HTTP 413 und Fehlercode
STORAGE_QUOTA_EXCEEDED. Ein Wert von null bedeutet unbegrenzt.
Aktuelle Limits pro Plan: Solo — 2 GB, Growth — 10 GB, Scale — unbegrenzt.
Sicherheit
-
Path-Traversal-Schutz:
entityIdundtenantIdmit Pfadzeichen (../) werden neutralisiert —path.basename+ Whitelist-Regex verhindert Schreibzugriff außerhalb vonuploads/. -
Zugriffsschutz: Alle Upload-Endpunkte erfordern
requireAuthund die Rolleadminoderowner. Kunden-Logins können keine Uploads durchführen. -
Magic-Bytes-Prüfung: Der Dateityp wird über
file-typeermittelt — nicht über den vom Browser gemeldetenContent-Type. Gefälschte MIME-Types werden zuverlässig erkannt.
Tier-Gating & Preis
Uploads sind eine Kernfunktion von kursflow und kosten 0 € — in allen Plänen
(Solo, Growth, Scale) enthalten. Es gibt keinen eigenen Registry-Key in backend/src/config/modules.js
und kein requireFeature-Gate. Begrenzt wird ausschließlich über das Storage-Quota des jeweiligen Plans.
Abnahmekriterien (Referenz)
- Upload einer Datei > 12 MB liefert HTTP 400 mit Code
LIMIT_FILE_SIZE. - Eine Nicht-Bild-Datei (per Magic-Bytes erkannt) wird mit HTTP 400
INVALID_FILE_TYPEabgelehnt, auch wenn der gemeldete MIME-Type ein Bild vorgibt. - Gespeicherte Dateien liegen unter
uploads/{tenantId}/{type}/{id}.webpmit zugehörigem…_thumb.webp. - Hauptbild ist auf max. 1200 px, Thumbnail auf max. 400 px Breite skaliert; beide im WebP-Format.
- Bei gesetztem
max_storage_mbund Überschreitung antwortet der Server mit HTTP 413STORAGE_QUOTA_EXCEEDED; beinullgreift kein Limit. - Die Galerie speichert höchstens 12 URLs in
events.meta.gallery_urls. entityId/tenantIdmit Pfadzeichen (../) werden neutralisiert.- Alle Upload-Endpunkte erfordern
requireAuth+ Rolleadmin/owner.