Files
CubeAdmin/app/api/files/list/route.ts
2026-03-08 15:49:34 +01:00

61 lines
1.9 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { checkRateLimit, getClientIp } from "@/lib/security/rateLimit";
import { sanitizeFilePath } from "@/lib/security/sanitize";
import * as fs from "node:fs";
import * as path from "node:path";
export async function GET(req: NextRequest) {
const session = await auth.api.getSession({ headers: req.headers });
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const ip = getClientIp(req);
const { allowed } = checkRateLimit(ip);
if (!allowed) return NextResponse.json({ error: "Too many requests" }, { status: 429 });
const mcBase = path.resolve(process.env.MC_SERVER_PATH ?? "/opt/minecraft/server");
const relativePath = new URL(req.url).searchParams.get("path") ?? "/";
let resolvedPath: string;
try {
resolvedPath = sanitizeFilePath(relativePath, mcBase);
} catch {
return NextResponse.json({ error: "Invalid path" }, { status: 400 });
}
try {
const entries = fs.readdirSync(resolvedPath, { withFileTypes: true });
const files = entries.map((entry) => {
const fullPath = path.join(resolvedPath, entry.name);
let size = 0;
let modifiedAt = 0;
try {
const stat = fs.statSync(fullPath);
size = stat.size;
modifiedAt = stat.mtimeMs;
} catch {}
return {
name: entry.name,
path: path.relative(mcBase, fullPath),
isDirectory: entry.isDirectory(),
size,
modifiedAt,
};
});
// Sort: directories first, then files, alphabetically
files.sort((a, b) => {
if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
return a.name.localeCompare(b.name);
});
return NextResponse.json({
path: path.relative(mcBase, resolvedPath) || "/",
entries: files,
});
} catch (err) {
return NextResponse.json({ error: "Cannot read directory" }, { status: 500 });
}
}