fix(admin): serve media from private storage

This commit is contained in:
Schubert Ferenc 2026-07-03 23:45:33 +02:00
parent d9da86dd46
commit faddaa91d0
14 changed files with 114 additions and 20 deletions

View file

@ -1,7 +1,7 @@
import { randomUUID } from "crypto";
import { mkdir, readdir, rm, stat, writeFile } from "fs/promises";
import { mkdir, readFile, readdir, rm, stat, writeFile } from "fs/promises";
import path from "path";
import { uploadDirectory } from "@/lib/runtime/config";
import { migrateLegacyUploads, uploadDirectory } from "@/lib/runtime/config";
const maxUploadSize = 5 * 1024 * 1024;
@ -23,6 +23,15 @@ export type MediaFile = {
isPdf: boolean;
};
export function getMediaMimeType(fileName: string) {
const extension = path.extname(fileName).toLowerCase();
if (extension === ".jpg" || extension === ".jpeg") return "image/jpeg";
if (extension === ".png") return "image/png";
if (extension === ".webp") return "image/webp";
if (extension === ".pdf") return "application/pdf";
return "";
}
export async function ensureUploadDirectory() {
await mkdir(uploadDirectory, { recursive: true });
const probe = path.join(uploadDirectory, `.write-check-${Date.now()}`);
@ -81,6 +90,7 @@ function isAllowedStoredFile(fileName: string) {
}
export async function listMediaFiles(): Promise<MediaFile[]> {
await migrateLegacyUploads();
await mkdir(uploadDirectory, { recursive: true });
const entries = await readdir(uploadDirectory);
const visibleEntries = entries.filter(isAllowedStoredFile);
@ -92,7 +102,7 @@ export async function listMediaFiles(): Promise<MediaFile[]> {
return {
name: entry,
url: `/uploads/images/${encodeURIComponent(entry)}`,
url: `/api/media/${encodeURIComponent(entry)}`,
type: isPdf ? "pdf" as const : "image" as const,
extension: extension.replace(".", "").toUpperCase() || "Datei",
size: fileStat.size,
@ -117,6 +127,24 @@ export async function saveMediaFile(file: File) {
return { fileName: safeName };
}
export async function readMediaFile(fileName: string) {
if (!isAllowedStoredFile(fileName) || path.basename(fileName) !== fileName) {
return null;
}
const mimeType = getMediaMimeType(fileName);
if (!mimeType) return null;
const target = resolveUploadTarget(fileName);
try {
const [data, fileStat] = await Promise.all([readFile(target), stat(target)]);
return { data, mimeType, size: fileStat.size, uploadedAt: fileStat.birthtime.toISOString() };
} catch {
return null;
}
}
export async function deleteMediaFile(fileName: string) {
if (!isAllowedStoredFile(fileName) || path.basename(fileName) !== fileName) {
return { error: "Ungültiger Dateiname." };