Bild-Uploads

Kursbilder, Galerie-Fotos und Ihr Tenant-Logo hochladen — automatische Optimierung, sichere Speicherung, Storage-Quota-Kontrolle.

✓ Kernfunktion — 0 € Solo · Growth · Scale max. 12 MB pro Datei

Ü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))
Automatische Bildoptimierung Alle hochgeladenen Bilder werden mit 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 — Hauptbild
  • events.meta.gallery_urls — Galerie (Array, max. 12 Einträge via slice(0,12))
  • event_categories.meta.image_url — Kategorie-Bild
  • tenants.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.

MethodePfadZweck
POST/api/v1/uploads/events/:eventId/imageEvent-Hauptbild hochladen
DELETE/api/v1/uploads/events/:eventId/imageHauptbild löschen
POST/api/v1/uploads/events/:eventId/galleryGalerie-Bilder (max. 12)
DELETE/api/v1/uploads/events/:eventId/galleryEinzelnes Galerie-Bild entfernen (url im Body)
POST/api/v1/uploads/categories/:categoryId/imageKategorie-Bild hochladen
POST/api/v1/uploads/logoTenant-Logo hochladen

Fehlercodes

CodeHTTPBedeutung
NO_FILE / NO_FILES400Keine Datei im Request
INVALID_FILE_TYPE400Dateiformat nicht erlaubt (per Magic-Bytes erkannt)
LIMIT_FILE_SIZE400Datei überschreitet 12 MB
STORAGE_QUOTA_EXCEEDED413Storage-Quota des Plans überschritten (max_storage_mb)
UPLOAD_FAILED500Fehler beim Speichern/Verarbeiten
DELETE_FAILED500Fehler 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: entityId und tenantId mit Pfadzeichen (../) werden neutralisiert — path.basename + Whitelist-Regex verhindert Schreibzugriff außerhalb von uploads/.
  • Zugriffsschutz: Alle Upload-Endpunkte erfordern requireAuth und die Rolle admin oder owner. Kunden-Logins können keine Uploads durchführen.
  • Magic-Bytes-Prüfung: Der Dateityp wird über file-type ermittelt — nicht über den vom Browser gemeldeten Content-Type. Gefälschte MIME-Types werden zuverlässig erkannt.
Hinweis: Keine HEIF-Vorschau in älteren Browsern HEIC/HEIF-Dateien werden serverseitig akzeptiert und zu WebP konvertiert. Die Vorschau im Browser funktioniert erst nach der Konvertierung.

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)

  1. Upload einer Datei > 12 MB liefert HTTP 400 mit Code LIMIT_FILE_SIZE.
  2. Eine Nicht-Bild-Datei (per Magic-Bytes erkannt) wird mit HTTP 400 INVALID_FILE_TYPE abgelehnt, auch wenn der gemeldete MIME-Type ein Bild vorgibt.
  3. Gespeicherte Dateien liegen unter uploads/{tenantId}/{type}/{id}.webp mit zugehörigem …_thumb.webp.
  4. Hauptbild ist auf max. 1200 px, Thumbnail auf max. 400 px Breite skaliert; beide im WebP-Format.
  5. Bei gesetztem max_storage_mb und Überschreitung antwortet der Server mit HTTP 413 STORAGE_QUOTA_EXCEEDED; bei null greift kein Limit.
  6. Die Galerie speichert höchstens 12 URLs in events.meta.gallery_urls.
  7. entityId/tenantId mit Pfadzeichen (../) werden neutralisiert.
  8. Alle Upload-Endpunkte erfordern requireAuth + Rolle admin/owner.