fix(admin): serve media from private storage
This commit is contained in:
parent
d9da86dd46
commit
faddaa91d0
14 changed files with 114 additions and 20 deletions
|
|
@ -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." };
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import { existsSync } from "fs";
|
||||
import { access, mkdir, writeFile, rm } from "fs/promises";
|
||||
import { access, copyFile, mkdir, readdir, rm, writeFile } from "fs/promises";
|
||||
import path from "path";
|
||||
import { appVersion } from "./version";
|
||||
|
||||
export const dataDirectory = path.join(process.cwd(), "data");
|
||||
export const uploadDirectory = path.join(process.cwd(), "public", "uploads", "images");
|
||||
export const storageDirectory = path.join(process.cwd(), "storage");
|
||||
export const uploadDirectory = path.join(storageDirectory, "uploads", "images");
|
||||
export const legacyPublicUploadDirectory = path.join(process.cwd(), "public", "uploads", "images");
|
||||
|
||||
export function adminConfigurationStatus() {
|
||||
return process.env.ADMIN_EMAIL && process.env.ADMIN_PASSWORD && process.env.ADMIN_SESSION_SECRET ? "configured" : "missing";
|
||||
|
|
@ -17,6 +19,7 @@ export function isDockerEnvironment() {
|
|||
export async function ensureRuntimeDirectories() {
|
||||
await mkdir(dataDirectory, { recursive: true });
|
||||
await mkdir(uploadDirectory, { recursive: true });
|
||||
await migrateLegacyUploads();
|
||||
}
|
||||
|
||||
export async function checkStorage() {
|
||||
|
|
@ -36,4 +39,20 @@ export function smtpStatus() {
|
|||
return "prepared";
|
||||
}
|
||||
|
||||
export async function migrateLegacyUploads() {
|
||||
if (!existsSync(legacyPublicUploadDirectory)) return;
|
||||
|
||||
await mkdir(uploadDirectory, { recursive: true });
|
||||
const entries = await readdir(legacyPublicUploadDirectory, { withFileTypes: true });
|
||||
|
||||
await Promise.all(entries.map(async (entry) => {
|
||||
if (!entry.isFile() || entry.name.startsWith(".")) return;
|
||||
|
||||
const source = path.join(legacyPublicUploadDirectory, entry.name);
|
||||
const target = path.join(uploadDirectory, entry.name);
|
||||
|
||||
if (!existsSync(target)) await copyFile(source, target);
|
||||
}));
|
||||
}
|
||||
|
||||
export { appVersion };
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue