mirror of
https://github.com/joelhooks/joelclaw.git
synced 2026-09-19 01:24:04 +08:00
63 lines
1.7 KiB
TypeScript
63 lines
1.7 KiB
TypeScript
/**
|
|
* Serve vault images for authenticated users.
|
|
* GET /api/vault/image/{path} → reads from ~/Vault/{path}
|
|
*/
|
|
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { extname, join } from "node:path";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { isAuthenticated } from "@/lib/auth-server";
|
|
|
|
const VAULT_PATH = process.env.VAULT_PATH || join(process.env.HOME || "/Users/joel", "Vault");
|
|
|
|
const MIME_TYPES: Record<string, string> = {
|
|
".png": "image/png",
|
|
".jpg": "image/jpeg",
|
|
".jpeg": "image/jpeg",
|
|
".gif": "image/gif",
|
|
".webp": "image/webp",
|
|
".svg": "image/svg+xml",
|
|
".avif": "image/avif",
|
|
};
|
|
|
|
export async function GET(
|
|
_request: NextRequest,
|
|
{ params }: { params: Promise<{ path: string[] }> }
|
|
) {
|
|
const authed = await isAuthenticated().catch(() => false);
|
|
if (!authed) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const { path } = await params;
|
|
const imagePath = path.join("/");
|
|
const fullPath = join(VAULT_PATH, imagePath);
|
|
|
|
// Security: ensure path stays within vault
|
|
if (!fullPath.startsWith(VAULT_PATH)) {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
|
|
const ext = extname(fullPath).toLowerCase();
|
|
const mime = MIME_TYPES[ext];
|
|
if (!mime) {
|
|
return NextResponse.json({ error: "Not an image" }, { status: 400 });
|
|
}
|
|
|
|
if (!existsSync(fullPath)) {
|
|
return NextResponse.json({ error: "Not found" }, { status: 404 });
|
|
}
|
|
|
|
try {
|
|
const data = readFileSync(fullPath);
|
|
return new NextResponse(new Uint8Array(data), {
|
|
headers: {
|
|
"Content-Type": mime,
|
|
"Cache-Control": "public, max-age=86400, immutable",
|
|
},
|
|
});
|
|
} catch {
|
|
return NextResponse.json({ error: "Read failed" }, { status: 500 });
|
|
}
|
|
}
|