Initial push

This commit is contained in:
2026-03-08 15:49:34 +01:00
parent 8da12bb7d1
commit 47127f276d
101 changed files with 13844 additions and 8 deletions

View File

@@ -0,0 +1,133 @@
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<typeof BanSchema>;
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 });
}