feat: Bild-Upload fuer Inhalte, Produkte und Ernten
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m34s
Docker Image bauen und veroeffentlichen / build-and-push (push) Successful in 2m34s
Echter Datei-Upload statt CSS-Platzhaltergrafiken, gespeichert unter UPLOAD_DIR (Laufzeit-Verzeichnis, weiss nichts von seiner tatsaechlichen Speicher-Anbindung — in Produktion ein von der Betreiberin selbst per Kubernetes eingebundenes Volume, z.B. SMB; lokal ein simples ./uploads). - lib/uploads.ts: Kernlogik. Bildformat wird ausschliesslich aus den tatsaechlichen Bilddaten erkannt (Magic-Bytes via sharp), nie aus Client-Dateiname/MIME-Type. Zufaellige, nicht erratbare Dateinamen (Path-Traversal-/Overwrite-Schutz). Re-Encoding validiert Pixel-Daten vollstaendig und entfernt eingebettete Metadaten (EXIF/ICC/XMP) als Nebeneffekt. Content-Type beim Ausliefern kommt ausschliesslich aus einer festen Endung->MIME-Zuordnung, nie aus Dateiinhalt/-namen. - app/api/admin/upload/route.ts: Admin-only Upload-Endpunkt (CSRF- + Auth- + Rate-Limit-Pruefung, max. 8 MB, JPEG/PNG/WebP/AVIF). - app/uploads/[...path]/route.ts: liefert hochgeladene Bilder aus (public/ wird zur Build-Zeit fest ins Image gebacken, kann also keine zur Laufzeit hinzugefuegten Dateien bedienen) — mit Path-Traversal-Schutz und nosniff-Header. - content-registry.ts: neuer "image"-Feldtyp fuer Hero-/Startseite- Grafiken, admin/inhalte hat dafuer ein Upload-Widget. - Produkt-/Ernten-Admin-Formulare: Mehrfach-Bild-Upload fuer Product.images/Harvest.images (beide Felder existierten im Schema, hatten aber bislang keine Admin-Oberflaeche). Oeffentliche Seiten (Shop, Ernten, Chargen-QR-Seite) zeigen jetzt echte Fotos statt Platzhalter, sobald vorhanden. - Kubernetes: nur ein einfacher volumeMount in 05-app-deployment.yaml (Platzhalter-emptyDir, klar als CHANGE-ME markiert) — die eigentliche Volume-Quelle (SMB o.ae.) bindet die Betreiberin selbst ein, wie gewuenscht keine SMB/CSI-spezifischen Manifeste hinzugefuegt. Verifiziert: tsc --noEmit clean, 139/139 Tests gruen (21 neu), pnpm build clean. Upload-/Serving-Pipeline live gegen einen laufenden Server getestet (Auth-/CSRF-Gates, Path-Traversal-Ablehnung, Format-Ablehnung) — ein vollstaendiger authentifizierter Upload end-to-end (braucht echten Admin-Login inkl. 2FA) wurde nicht live durchgespielt, nur per Code-Review verifiziert. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
1 parent
3de9f2906e
commit
54919d82ed
35 files changed
+1014
-44
No files matched your search
@@ -0,0 +1,41 @@
|
||||
import Image from "next/image";
|
||||
import { PlaceholderImage } from "@/components/placeholder-image";
|
||||
|
||||
interface ContentImageProps {
|
||||
/** Hochgeladener Bildpfad (z.B. "/uploads/<id>.webp") oder null/leer -> Platzhalter. */
|
||||
src: string | null | undefined;
|
||||
alt: string;
|
||||
/** Groessen-/Seitenverhaeltnis-Klassen (z.B. "aspect-[4/5] w-full") — gilt fuer Platzhalter UND Bild-Wrapper gleichermassen. */
|
||||
className: string;
|
||||
tone?: "forest" | "cream" | "honey";
|
||||
label?: string;
|
||||
sizes?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt ein hochgeladenes Bild an, sobald eines gepflegt ist, sonst denselben
|
||||
* PlaceholderImage-Platzhalter wie zuvor an dieser Stelle — nie ein kaputtes
|
||||
* <img>. Uploads liegen same-origin unter /uploads/... (siehe
|
||||
* app/uploads/[...path]/route.ts) und brauchen deshalb KEINEN
|
||||
* next.config.ts-remotePatterns-Eintrag: remotePatterns/domains werden nur
|
||||
* fuer externe (cross-origin) Bildquellen benoetigt, next/image behandelt
|
||||
* einen mit "/" beginnenden src immer als lokal und ruft dafuer intern die
|
||||
* eigene Optimierungsroute auf.
|
||||
*/
|
||||
export function ContentImage({
|
||||
src,
|
||||
alt,
|
||||
className,
|
||||
tone = "forest",
|
||||
label,
|
||||
sizes = "100vw",
|
||||
}: ContentImageProps) {
|
||||
if (!src) {
|
||||
return <PlaceholderImage tone={tone} className={className} label={label} />;
|
||||
}
|
||||
return (
|
||||
<div className={`relative overflow-hidden ${className}`}>
|
||||
<Image src={src} alt={alt} fill sizes={sizes} className="object-cover" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user