import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/lib/auth"; import { db } from "@/lib/db"; import { mcPlayers, playerBans, playerChatHistory, playerSpawnPoints, auditLogs } from "@/lib/db/schema"; import { checkRateLimit, getClientIp } from "@/lib/security/rateLimit"; import { eq, desc } from "drizzle-orm"; import { rconClient } from "@/lib/minecraft/rcon"; import { sanitizeRconCommand } from "@/lib/security/sanitize"; import { z } from "zod"; import { nanoid } from "nanoid"; export async function GET( req: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { const session = await auth.api.getSession({ headers: req.headers }); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); const { id } = await params; const player = await db.select().from(mcPlayers).where(eq(mcPlayers.id, id)).get(); if (!player) return NextResponse.json({ error: "Player not found" }, { status: 404 }); const [bans, chatHistory, spawnPoints] = await Promise.all([ db.select().from(playerBans).where(eq(playerBans.playerId, id)).orderBy(desc(playerBans.bannedAt)), db.select().from(playerChatHistory).where(eq(playerChatHistory.playerId, id)).orderBy(desc(playerChatHistory.timestamp)).limit(200), db.select().from(playerSpawnPoints).where(eq(playerSpawnPoints.playerId, id)), ]); return NextResponse.json({ player, bans, chatHistory, spawnPoints }); } const BanSchema = z.object({ reason: z.string().min(1).max(500), expiresAt: z.number().optional(), }); export async function POST( req: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { const session = await auth.api.getSession({ headers: req.headers }); if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); if (!["superadmin", "admin", "moderator"].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 { id } = await params; const { searchParams } = new URL(req.url); const action = searchParams.get("action"); const player = await db.select().from(mcPlayers).where(eq(mcPlayers.id, id)).get(); if (!player) return NextResponse.json({ error: "Player not found" }, { status: 404 }); if (action === "ban") { let body: z.infer; try { body = BanSchema.parse(await req.json()); } catch { return NextResponse.json({ error: "Invalid request" }, { status: 400 }); } // Insert ban record await db.insert(playerBans).values({ id: nanoid(), playerId: id, reason: body.reason, bannedBy: session.user.id, bannedAt: Date.now(), expiresAt: body.expiresAt ?? null, isActive: true, }); await db.update(mcPlayers).set({ isBanned: true }).where(eq(mcPlayers.id, id)); // Execute ban via RCON try { const cmd = sanitizeRconCommand(`ban ${player.username} ${body.reason}`); await rconClient.sendCommand(cmd); } catch { /* RCON might be unavailable */ } // Audit await db.insert(auditLogs).values({ id: nanoid(), userId: session.user.id, action: "player.ban", target: "player", targetId: id, details: JSON.stringify({ reason: body.reason }), ipAddress: ip, createdAt: Date.now(), }); return NextResponse.json({ success: true }); } if (action === "unban") { await db.update(playerBans).set({ isActive: false, unbannedBy: session.user.id, unbannedAt: Date.now() }).where(eq(playerBans.playerId, id)); await db.update(mcPlayers).set({ isBanned: false }).where(eq(mcPlayers.id, id)); try { const cmd = sanitizeRconCommand(`pardon ${player.username}`); await rconClient.sendCommand(cmd); } catch { /* RCON might be unavailable */ } await db.insert(auditLogs).values({ id: nanoid(), userId: session.user.id, action: "player.unban", target: "player", targetId: id, details: null, ipAddress: ip, createdAt: Date.now(), }); return NextResponse.json({ success: true }); } if (action === "kick") { const { reason } = await req.json(); try { const cmd = sanitizeRconCommand(`kick ${player.username} ${reason ?? "Kicked by admin"}`); await rconClient.sendCommand(cmd); } catch (err) { return NextResponse.json({ error: "RCON unavailable" }, { status: 503 }); } await db.insert(auditLogs).values({ id: nanoid(), userId: session.user.id, action: "player.kick", target: "player", targetId: id, details: JSON.stringify({ reason }), ipAddress: ip, createdAt: Date.now(), }); return NextResponse.json({ success: true }); } return NextResponse.json({ error: "Unknown action" }, { status: 400 }); }