70 lines
2.0 KiB
TypeScript
70 lines
2.0 KiB
TypeScript
import { existsSync, readFileSync, mkdirSync } from "node:fs";
|
|
import { execSync } from "node:child_process";
|
|
import { join, basename } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { resolve } from "node:path";
|
|
import { safeResolvePath } from "@/lib/workspace";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
export const runtime = "nodejs";
|
|
|
|
const THUMB_DIR = join(tmpdir(), "ironclaw-thumbs");
|
|
mkdirSync(THUMB_DIR, { recursive: true });
|
|
|
|
/**
|
|
* Resolve a file path — supports absolute paths and workspace-relative paths.
|
|
*/
|
|
function resolveFile(path: string): string | null {
|
|
if (path.startsWith("/")) {
|
|
const abs = resolve(path);
|
|
if (existsSync(abs)) {return abs;}
|
|
}
|
|
return safeResolvePath(path) ?? null;
|
|
}
|
|
|
|
/**
|
|
* GET /api/workspace/thumbnail?path=...&size=200
|
|
* Uses macOS Quick Look (qlmanage) to generate a thumbnail image.
|
|
* Returns the thumbnail as image/png.
|
|
*/
|
|
export async function GET(req: Request) {
|
|
const url = new URL(req.url);
|
|
const path = url.searchParams.get("path");
|
|
const size = url.searchParams.get("size") ?? "200";
|
|
|
|
if (!path) {
|
|
return new Response("Missing path", { status: 400 });
|
|
}
|
|
|
|
const absolute = resolveFile(path);
|
|
if (!absolute) {
|
|
return new Response("Not found", { status: 404 });
|
|
}
|
|
|
|
// The thumbnail output filename is <original-basename>.png
|
|
const thumbName = `${basename(absolute)}.png`;
|
|
const thumbPath = join(THUMB_DIR, thumbName);
|
|
|
|
try {
|
|
// Generate thumbnail using macOS Quick Look
|
|
execSync(
|
|
`qlmanage -t -s ${parseInt(size, 10)} -o "${THUMB_DIR}" "${absolute}" 2>/dev/null`,
|
|
{ timeout: 5000 },
|
|
);
|
|
|
|
if (!existsSync(thumbPath)) {
|
|
return new Response("Thumbnail generation failed", { status: 500 });
|
|
}
|
|
|
|
const buffer = readFileSync(thumbPath);
|
|
return new Response(buffer, {
|
|
headers: {
|
|
"Content-Type": "image/png",
|
|
"Cache-Control": "public, max-age=3600",
|
|
},
|
|
});
|
|
} catch {
|
|
return new Response("Thumbnail generation failed", { status: 500 });
|
|
}
|
|
}
|