fix(admin): repair media upload redirect
This commit is contained in:
parent
c35bd5877e
commit
203c94b128
12 changed files with 248 additions and 51 deletions
106
lib/admin/media.ts
Normal file
106
lib/admin/media.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { randomUUID } from "crypto";
|
||||
import { mkdir, readdir, rm, stat, writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import { uploadDirectory } from "@/lib/runtime/config";
|
||||
|
||||
const maxUploadSize = 5 * 1024 * 1024;
|
||||
|
||||
const allowedMimeTypes = new Map([
|
||||
["image/jpeg", [".jpg", ".jpeg"]],
|
||||
["image/png", [".png"]],
|
||||
["image/webp", [".webp"]],
|
||||
["application/pdf", [".pdf"]],
|
||||
]);
|
||||
|
||||
export type MediaFile = {
|
||||
name: string;
|
||||
url: string;
|
||||
type: string;
|
||||
size: number;
|
||||
uploadedAt: string;
|
||||
isImage: boolean;
|
||||
};
|
||||
|
||||
export async function ensureUploadDirectory() {
|
||||
await mkdir(uploadDirectory, { recursive: true });
|
||||
const probe = path.join(uploadDirectory, `.write-check-${Date.now()}`);
|
||||
await writeFile(probe, "ok", "utf8");
|
||||
await rm(probe, { force: true });
|
||||
}
|
||||
|
||||
export function validateUpload(file: File) {
|
||||
const extensions = allowedMimeTypes.get(file.type);
|
||||
const extension = path.extname(file.name).toLowerCase();
|
||||
|
||||
if (!extensions || !extensions.includes(extension)) {
|
||||
return "Bitte eine JPG-, PNG-, WebP- oder PDF-Datei hochladen.";
|
||||
}
|
||||
|
||||
if (file.size <= 0) {
|
||||
return "Die Datei ist leer.";
|
||||
}
|
||||
|
||||
if (file.size > maxUploadSize) {
|
||||
return "Die Datei darf maximal 5 MB groß sein.";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
export function createSafeUploadName(originalName: string) {
|
||||
const extension = path.extname(originalName).toLowerCase();
|
||||
const rawBaseName = path.basename(originalName, extension);
|
||||
const safeBaseName = rawBaseName
|
||||
.toLowerCase()
|
||||
.normalize("NFKD")
|
||||
.replace(/[\u0300-\u036f]/g, "")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 80) || "upload";
|
||||
|
||||
return `${Date.now()}-${randomUUID().slice(0, 8)}-${safeBaseName}${extension}`;
|
||||
}
|
||||
|
||||
export function resolveUploadTarget(fileName: string) {
|
||||
const target = path.resolve(uploadDirectory, fileName);
|
||||
const uploadRoot = path.resolve(uploadDirectory);
|
||||
|
||||
if (!target.startsWith(`${uploadRoot}${path.sep}`)) {
|
||||
throw new Error("Ungültiger Upload-Pfad.");
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export async function listMediaFiles(): Promise<MediaFile[]> {
|
||||
await mkdir(uploadDirectory, { recursive: true });
|
||||
const entries = await readdir(uploadDirectory);
|
||||
const visibleEntries = entries.filter((entry) => !entry.startsWith("."));
|
||||
const files = await Promise.all(visibleEntries.map(async (entry) => {
|
||||
const fileStat = await stat(path.join(uploadDirectory, entry));
|
||||
const extension = path.extname(entry).toLowerCase();
|
||||
|
||||
return {
|
||||
name: entry,
|
||||
url: `/uploads/images/${encodeURIComponent(entry)}`,
|
||||
type: extension.replace(".", "").toUpperCase() || "Datei",
|
||||
size: fileStat.size,
|
||||
uploadedAt: fileStat.birthtime.toISOString(),
|
||||
isImage: [".jpg", ".jpeg", ".png", ".webp"].includes(extension),
|
||||
};
|
||||
}));
|
||||
|
||||
return files.sort((left, right) => right.uploadedAt.localeCompare(left.uploadedAt));
|
||||
}
|
||||
|
||||
export async function saveMediaFile(file: File) {
|
||||
const error = validateUpload(file);
|
||||
if (error) return { error };
|
||||
|
||||
await ensureUploadDirectory();
|
||||
const safeName = createSafeUploadName(file.name);
|
||||
const target = resolveUploadTarget(safeName);
|
||||
await writeFile(target, Buffer.from(await file.arrayBuffer()));
|
||||
|
||||
return { fileName: safeName };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue