diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 92bb541..1ee0aa3 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -4,6 +4,7 @@ import { formatPrice } from "@/lib/format"; import { getLocale } from "@/i18n/locale"; import { getDictionary } from "@/i18n/get-dictionary"; import { checkUploadFiles } from "@/lib/upload-health"; +import { checkUploadVolume } from "@/lib/uploads"; const DASHBOARD_ICON_PROPS = { width: 20, @@ -228,7 +229,10 @@ export default async function AdminDashboardPage() { // Bewusst NACH den Kennzahlen abgefragt und nicht in deren Promise.all: // schlaegt der Dateiabgleich fehl (etwa weil das Upload-Verzeichnis gar // nicht lesbar ist), soll das Dashboard trotzdem seine Zahlen zeigen. - const uploadHealth = await checkUploadFiles().catch(() => null); + const [uploadHealth, uploadVolume] = await Promise.all([ + checkUploadFiles().catch(() => null), + checkUploadVolume().catch(() => null), + ]); return (
+ {t.adminDashboardPage.uploadsNotPersistentTitle} +
++ {t.adminDashboardPage.uploadsNotPersistentHint(uploadVolume.uploadDir)} +
+
diff --git a/src/i18n/dictionaries/de.ts b/src/i18n/dictionaries/de.ts
index 52de3f8..46172f7 100644
--- a/src/i18n/dictionaries/de.ts
+++ b/src/i18n/dictionaries/de.ts
@@ -1098,6 +1098,10 @@ export const de: Dictionary = {
adminDashboardPage: {
missingUploadsTitle: (missing: number, checked: number) =>
`${missing} von ${checked} hinterlegten Bild-/Videodateien fehlen auf dem Server.`,
+ uploadsNotPersistentTitle:
+ "Das Upload-Verzeichnis liegt im Container und ist nicht dauerhaft.",
+ uploadsNotPersistentHint: (uploadDir: string) =>
+ `Alle Bilder und Videos unter ${uploadDir} gehen beim nächsten Redeploy verloren. In Kubernetes muss das Volume "uploads" ein PersistentVolumeClaim sein (kubectl apply -f k8s/04-uploads-pvc.yaml und k8s/05-app-deployment.yaml), im Docker-Betrieb ein benanntes Volume. Details in k8s/README.md, Abschnitt "Bilder/Videos verschwinden nach jedem Redeploy".`,
missingUploadsHint:
"Die Datenbank kennt die Pfade noch, die Dateien selbst sind weg — im Shop erscheinen dort leere Flächen. Fast immer liegt das an einem nicht dauerhaften Upload-Verzeichnis: In Kubernetes muss das Volume \"uploads\" ein PersistentVolumeClaim sein (k8s/04-uploads-pvc.yaml), kein emptyDir, sonst geht es bei jedem Redeploy verloren. Nach dem Umstellen müssen die betroffenen Dateien einmalig neu hochgeladen werden.",
title: "Dashboard",
diff --git a/src/i18n/dictionaries/en.ts b/src/i18n/dictionaries/en.ts
index 908ce32..9accfd3 100644
--- a/src/i18n/dictionaries/en.ts
+++ b/src/i18n/dictionaries/en.ts
@@ -1098,6 +1098,10 @@ export const en: Dictionary = {
adminDashboardPage: {
missingUploadsTitle: (missing: number, checked: number) =>
`${missing} of ${checked} referenced image/video files are missing on the server.`,
+ uploadsNotPersistentTitle:
+ "The upload directory lives inside the container and is not persistent.",
+ uploadsNotPersistentHint: (uploadDir: string) =>
+ `All images and videos under ${uploadDir} will be lost on the next redeploy. In Kubernetes the "uploads" volume must be a PersistentVolumeClaim (kubectl apply -f k8s/04-uploads-pvc.yaml and k8s/05-app-deployment.yaml); with Docker it must be a named volume. See k8s/README.md.`,
missingUploadsHint:
"The database still holds the paths, but the files themselves are gone — the shop shows empty areas there. This is almost always a non-persistent upload directory: in Kubernetes the \"uploads\" volume must be a PersistentVolumeClaim (k8s/04-uploads-pvc.yaml), not an emptyDir, otherwise it is lost on every redeploy. After fixing it, the affected files have to be uploaded once more.",
title: "Dashboard",
diff --git a/src/i18n/dictionary-type.ts b/src/i18n/dictionary-type.ts
index 5d3d4d0..076361d 100644
--- a/src/i18n/dictionary-type.ts
+++ b/src/i18n/dictionary-type.ts
@@ -798,6 +798,8 @@ export interface Dictionary {
};
adminDashboardPage: {
missingUploadsTitle: (missing: number, checked: number) => string;
+ uploadsNotPersistentTitle: string;
+ uploadsNotPersistentHint: (uploadDir: string) => string;
missingUploadsHint: string;
title: string;
statRevenueToday: string;
diff --git a/src/lib/uploads-volume.test.ts b/src/lib/uploads-volume.test.ts
new file mode 100644
index 0000000..9911b5c
--- /dev/null
+++ b/src/lib/uploads-volume.test.ts
@@ -0,0 +1,71 @@
+import { describe, test, expect, vi, afterEach } from "vitest";
+
+/**
+ * checkUploadVolume() erkennt, ob UPLOAD_DIR ein eigenes, von aussen
+ * gemountetes Dateisystem ist. Liegt es stattdessen im Container, sind alle
+ * Bilder und Videos beim naechsten Redeploy weg.
+ *
+ * UPLOAD_DIR wird beim Import des Moduls aus der Umgebung gelesen, deshalb
+ * wird das Modul je Fall frisch geladen (resetModules + dynamischer Import)
+ * statt einmal oben.
+ */
+async function loadWithEnv(env: Record