69 lines
2.2 KiB
TypeScript
69 lines
2.2 KiB
TypeScript
// /app/api/files/list/route.ts
|
|
import { NextResponse } from "next/server";
|
|
import { promises as fs } from "fs";
|
|
import path from "path";
|
|
import { getUserBearerFromRequest } from "@/lib/directus";
|
|
|
|
export const runtime = "nodejs";
|
|
export const dynamic = "force-dynamic";
|
|
export const revalidate = 0;
|
|
|
|
const ROOT = process.env.FILES_ROOT || "/app/files";
|
|
|
|
function safeJoin(root: string, p: string) {
|
|
const rootResolved = path.resolve(root);
|
|
const raw = path.normalize("/" + (p || "/")); // normalize input
|
|
const abs = path.resolve(rootResolved, "." + raw); // resolve under root
|
|
if (abs === rootResolved) return abs; // allow root itself
|
|
if (abs.startsWith(rootResolved + path.sep)) return abs;
|
|
throw new Error("Invalid path");
|
|
}
|
|
|
|
export async function GET(req: Request) {
|
|
try {
|
|
// Require auth (no anonymous browsing)
|
|
const bearer = getUserBearerFromRequest(req);
|
|
if (!bearer) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
|
|
const { searchParams } = new URL(req.url);
|
|
const input = searchParams.get("path") || "/";
|
|
|
|
const abs = safeJoin(ROOT, input);
|
|
const s = await fs.stat(abs);
|
|
if (!s.isDirectory()) {
|
|
return NextResponse.json({ error: "Not a directory" }, { status: 400 });
|
|
}
|
|
|
|
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
|
|
const items = await Promise.all(
|
|
entries.map(async (d) => {
|
|
const p = path.join(abs, d.name);
|
|
const st = await fs.stat(p);
|
|
return {
|
|
name: d.name,
|
|
isDir: d.isDirectory(),
|
|
size: st.size,
|
|
mtime: st.mtimeMs,
|
|
};
|
|
})
|
|
);
|
|
|
|
// Optional: directories first, then alphabetical
|
|
items.sort((a, b) =>
|
|
a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1
|
|
);
|
|
|
|
const safePath = input.startsWith("/") ? input : `/${input}`;
|
|
return NextResponse.json(
|
|
{ path: safePath, items },
|
|
{ headers: { "Cache-Control": "no-store" } }
|
|
);
|
|
} catch (err: any) {
|
|
const msg = err?.message === "Invalid path" ? "Invalid path" : "Not found";
|
|
const code = msg === "Invalid path" ? 400 : 404;
|
|
return NextResponse.json({ error: msg }, { status: code });
|
|
}
|
|
}
|