diff --git a/public/assets/images/media/1782918343388-1l2ffb.png b/public/assets/images/media/1782918343388-1l2ffb.png new file mode 100644 index 00000000..585c0831 Binary files /dev/null and b/public/assets/images/media/1782918343388-1l2ffb.png differ diff --git a/public/assets/images/media/1782918356934-ka3e9z.png b/public/assets/images/media/1782918356934-ka3e9z.png new file mode 100644 index 00000000..a9ca5294 Binary files /dev/null and b/public/assets/images/media/1782918356934-ka3e9z.png differ diff --git a/public/assets/images/media/1782918498476-6wuch2.gif b/public/assets/images/media/1782918498476-6wuch2.gif new file mode 100644 index 00000000..4222eb4b Binary files /dev/null and b/public/assets/images/media/1782918498476-6wuch2.gif differ diff --git a/src/actions/admin-media.ts b/src/actions/admin-media.ts index c88936f3..933a9b33 100644 --- a/src/actions/admin-media.ts +++ b/src/actions/admin-media.ts @@ -62,5 +62,5 @@ export async function uploadMediaAndReturn(formData: FormData): Promise revalidatePath("/api/media"); revalidatePath("/admin/media"); - return `/assets/images/media/${name}`; + return `/api/media/${name}`; } diff --git a/src/app/api/media/[name]/route.ts b/src/app/api/media/[name]/route.ts new file mode 100644 index 00000000..29a592c9 --- /dev/null +++ b/src/app/api/media/[name]/route.ts @@ -0,0 +1,42 @@ +import { NextResponse } from "next/server"; +import { readFile } from "fs/promises"; +import { existsSync } from "fs"; +import path from "path"; + +export const dynamic = "force-dynamic"; + +const MEDIA_DIR = "public/assets/images/media"; +const ALLOWED_EXT = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"]; + +export async function GET( + _request: Request, + { params }: { params: Promise<{ name: string }> }, +) { + const { name } = await params; + // Prevent path traversal + if (name.includes("..") || name.includes("/") || name.includes("\\")) { + return new NextResponse("Forbidden", { status: 403 }); + } + const ext = path.extname(name).toLowerCase(); + if (!ALLOWED_EXT.includes(ext)) { + return new NextResponse("Forbidden", { status: 403 }); + } + + const filePath = path.join(process.cwd(), MEDIA_DIR, name); + if (!existsSync(filePath)) { + return new NextResponse("Not found", { status: 404 }); + } + + const bytes = await readFile(filePath); + const mime: Record = { + ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", + ".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml", ".bmp": "image/bmp", + }; + + return new NextResponse(bytes, { + headers: { + "Content-Type": mime[ext] ?? "application/octet-stream", + "Cache-Control": "public, max-age=86400", + }, + }); +} diff --git a/src/app/api/media/route.ts b/src/app/api/media/route.ts index 76aacf3d..da463061 100644 --- a/src/app/api/media/route.ts +++ b/src/app/api/media/route.ts @@ -15,7 +15,7 @@ export async function GET() { .filter((f) => /\.(png|jpg|jpeg|gif|webp|svg|bmp)$/i.test(f)) .map((f) => ({ name: f, - url: `/${MEDIA_DIR}/${f}`, + url: `/api/media/${f}`, })) .sort((a, b) => a.name.localeCompare(b.name));