Persist media uploads outside public/ to survive rebuilds and redeploys
Local Build and Deploy / deploy (push) Successful in 1m9s

Move media storage from public/assets/images/media to storage/media
(outside Git and public/), so uploaded images, favicons and logos are
preserved across rebuilds and git clean. Add a shared media-storage helper,
update the upload/serve actions and API routes, and gitignore storage/.
This commit is contained in:
openhands committed 2026-07-18 17:04:48 +02:00
1 parent 0d82f1325e
commit 1bbbf6e5a4
693 files changed
+38 -19

No files matched your search

+7 -7
View File
@@ -5,8 +5,8 @@ import path from "node:path";
import { revalidatePath } from "next/cache";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { MEDIA_ROOT, resolveMediaPath } from "@/lib/media-storage";
const MEDIA_DIR = "public/assets/images/media";
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED = ["image/png", "image/jpeg", "image/gif", "image/webp"];
@@ -17,14 +17,14 @@ export async function uploadMedia(formData: FormData): Promise<void> {
if (file.size > MAX_SIZE) return;
if (!ALLOWED.includes(file.type)) return;
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
const baseDir = MEDIA_ROOT;
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(baseDir, { recursive: true });
const ext = file.name.split(".").pop() ?? "png";
const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
const bytes = await file.arrayBuffer();
const filePath = path.resolve(baseDir, name);
const filePath = resolveMediaPath(name);
if (!filePath.startsWith(baseDir + path.sep)) throw new Error("Invalid path");
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(filePath, Buffer.from(bytes));
@@ -36,8 +36,8 @@ export async function uploadMedia(formData: FormData): Promise<void> {
export async function deleteMedia(name: string): Promise<void> {
await requirePermission(PERMS.PAGES_EDIT);
const { unlink } = await import("node:fs/promises");
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
const filePath = path.resolve(baseDir, name);
const baseDir = MEDIA_ROOT;
const filePath = resolveMediaPath(name);
if (!filePath.startsWith(baseDir + path.sep)) return;
try {
await unlink(filePath);
@@ -57,14 +57,14 @@ export async function uploadMediaAndReturn(
if (file.size > MAX_SIZE) return "";
if (!ALLOWED.includes(file.type)) return "";
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
const baseDir = MEDIA_ROOT;
// eslint-disable-next-line security/detect-non-literal-fs-filename
await mkdir(baseDir, { recursive: true });
const ext = file.name.split(".").pop() ?? "png";
const name = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.${ext}`;
const bytes = await file.arrayBuffer();
const filePath = path.resolve(baseDir, name);
const filePath = resolveMediaPath(name);
if (!filePath.startsWith(baseDir + path.sep)) return "";
// eslint-disable-next-line security/detect-non-literal-fs-filename
await writeFile(filePath, Buffer.from(bytes));