Initial push
This commit is contained in:
88
app/api/plugins/route.ts
Normal file
88
app/api/plugins/route.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } 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 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 });
|
||||
|
||||
// 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 auth.api.getSession({ headers: 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 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user