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 }); } }