43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { spawn } from "node:child_process";
|
|
import { findVideosForCode } from "@/lib/video";
|
|
import { assertLocalRequest } from "@/lib/api/localOnly";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
|
|
/**
|
|
* Open the OS file manager pre-selected on the cover's video file.
|
|
* Local-only — explicitly gated by assertLocalRequest.
|
|
*/
|
|
export async function POST(req: NextRequest, ctx: { params: Promise<{ code: string }> }) {
|
|
const blocked = assertLocalRequest(req);
|
|
if (blocked) return blocked;
|
|
|
|
const { code } = await ctx.params;
|
|
const decoded = decodeURIComponent(code);
|
|
const url = new URL(req.url);
|
|
const partRaw = url.searchParams.get("part");
|
|
const part = partRaw == null ? 0 : Math.max(0, parseInt(partRaw, 10) || 0);
|
|
|
|
const files = findVideosForCode(decoded);
|
|
if (files.length === 0) return NextResponse.json({ error: "not found" }, { status: 404 });
|
|
const file = files[Math.min(part, files.length - 1)];
|
|
|
|
try {
|
|
if (process.platform === "win32") {
|
|
// explorer doesn't return zero-exit even on success; detach and don't await.
|
|
spawn("explorer", ["/select,", file.abs], { detached: true, stdio: "ignore" }).unref();
|
|
} else if (process.platform === "darwin") {
|
|
spawn("open", ["-R", file.abs], { detached: true, stdio: "ignore" }).unref();
|
|
} else {
|
|
// Linux: open the parent dir; most file managers don't have a select API.
|
|
const parent = file.abs.replace(/[/\\][^/\\]*$/, "");
|
|
spawn("xdg-open", [parent], { detached: true, stdio: "ignore" }).unref();
|
|
}
|
|
return NextResponse.json({ ok: true, path: file.abs });
|
|
} catch (e) {
|
|
return NextResponse.json({ error: (e as Error).message }, { status: 500 });
|
|
}
|
|
}
|