import { NextRequest, NextResponse } from "next/server"; import { auth, getAuthSession } from "@/lib/auth"; import { db } from "@/lib/db"; import { plugins, auditLogs } from "@/lib/db/schema"; import { checkRateLimit, getClientIp } from "@/lib/security/rateLimit"; import { rconClient } from "@/lib/minecraft/rcon"; import { sanitizeRconCommand } from "@/lib/security/sanitize"; import { eq } from "drizzle-orm"; import { nanoid } from "nanoid"; import * as fs from "node:fs"; import * as path from "node:path"; export async function GET(req: NextRequest) { const session = await getAuthSession(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 }); // Read plugins from DB + sync with filesystem const mcPath = process.env.MC_SERVER_PATH ?? "/opt/minecraft/server"; const pluginsDir = path.join(mcPath, "plugins"); const dbPlugins = await db.select().from(plugins); // Try to read actual plugin jars from filesystem let jarFiles: string[] = []; try { jarFiles = fs .readdirSync(pluginsDir) .filter((f) => f.endsWith(".jar")); } catch { /* plugins dir might not exist */ } return NextResponse.json({ plugins: dbPlugins, jarFiles }); } export async function POST(req: NextRequest) { const session = await getAuthSession(req.headers); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); if (!["superadmin", "admin"].includes(session.user.role ?? "")) { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const ip = getClientIp(req); const { allowed } = checkRateLimit(ip); if (!allowed) return NextResponse.json({ error: "Too many requests" }, { status: 429 }); const { searchParams } = new URL(req.url); const action = searchParams.get("action"); const pluginName = searchParams.get("name"); if (!pluginName || !/^[a-zA-Z0-9_\-]+$/.test(pluginName)) { return NextResponse.json({ error: "Invalid plugin name" }, { status: 400 }); } try { if (action === "enable" || action === "disable") { const cmd = sanitizeRconCommand( `plugman ${action} ${pluginName}`, ); const result = await rconClient.sendCommand(cmd); await db .update(plugins) .set({ isEnabled: action === "enable" }) .where(eq(plugins.name, pluginName)); await db.insert(auditLogs).values({ id: nanoid(), userId: session.user.id, action: `plugin.${action}`, target: "plugin", targetId: pluginName, details: null, ipAddress: ip, createdAt: Date.now(), }); return NextResponse.json({ success: true, result }); } if (action === "reload") { const cmd = sanitizeRconCommand(`plugman reload ${pluginName}`); const result = await rconClient.sendCommand(cmd); return NextResponse.json({ success: true, result }); } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } catch (err) { const message = err instanceof Error ? err.message : "Unknown error"; return NextResponse.json({ error: message }, { status: 503 }); } }