Reapply "Add missing admin action files and navigation links"
This reverts commit 4d515bc400.
This commit is contained in:
1 parent
96ed768f14
commit
5b4228261a
338 files changed
+28143
-5948
No files matched your search
@@ -39,19 +39,13 @@ export async function POST(request: Request) {
|
||||
const action = String(formData.get("action") || "");
|
||||
|
||||
if (!userId || !username) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid user data" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid user data" }, { status: 400 });
|
||||
}
|
||||
|
||||
if (action === "set_rank") {
|
||||
const rank = Number(formData.get("rank") || "0");
|
||||
if (!rank || rank < 0 || rank > 10) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid rank value" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid rank value" }, { status: 400 });
|
||||
}
|
||||
|
||||
await prisma.user.update({ where: { id: userId }, data: { rank } });
|
||||
@@ -66,7 +60,7 @@ export async function POST(request: Request) {
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Set rank of user #${userId} to ${rank}` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -74,94 +68,72 @@ export async function POST(request: Request) {
|
||||
await rcon.disconnectUser(userId, username);
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Disconnected user #${userId} (${username})` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "alert") {
|
||||
const message = String(formData.get("message") || "").trim().slice(0, 512);
|
||||
const message = String(formData.get("message") || "")
|
||||
.trim()
|
||||
.slice(0, 512);
|
||||
if (!message) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Alert message is required" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Alert message is required" }, { status: 400 });
|
||||
}
|
||||
await rcon.alertUser(userId, message);
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Sent alert to user #${userId}` },
|
||||
{ status: 200 }
|
||||
);
|
||||
return NextResponse.json({ success: true, message: `Sent alert to user #${userId}` }, { status: 200 });
|
||||
}
|
||||
|
||||
if (action === "give_credits") {
|
||||
const credits = Number(formData.get("credits") || "0");
|
||||
if (!credits || credits <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid credit amount" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid credit amount" }, { status: 400 });
|
||||
}
|
||||
await giveCurrency({ rconClient: rcon, db: prisma, userId, type: "credits", amount: credits });
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Gave ${credits} credits to user #${userId}` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "give_duckets") {
|
||||
const amount = Number(formData.get("amount") || "0");
|
||||
if (!amount || amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid duckets amount" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid duckets amount" }, { status: 400 });
|
||||
}
|
||||
await giveCurrency({ rconClient: rcon, db: prisma, userId, type: "duckets", amount });
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Gave ${amount} duckets to user #${userId}` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "give_diamonds") {
|
||||
const amount = Number(formData.get("amount") || "0");
|
||||
if (!amount || amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid diamonds amount" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid diamonds amount" }, { status: 400 });
|
||||
}
|
||||
await giveCurrency({ rconClient: rcon, db: prisma, userId, type: "diamonds", amount });
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Gave ${amount} diamonds to user #${userId}` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
if (action === "give_points") {
|
||||
const amount = Number(formData.get("amount") || "0");
|
||||
if (!amount || amount <= 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Invalid points amount" },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Invalid points amount" }, { status: 400 });
|
||||
}
|
||||
await giveCurrency({ rconClient: rcon, db: prisma, userId, type: "points", amount });
|
||||
return NextResponse.json(
|
||||
{ success: true, message: `Gave ${amount} points to user #${userId}` },
|
||||
{ status: 200 }
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, message: `Unknown action: ${action}` },
|
||||
{ status: 400 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: `Unknown action: ${action}` }, { status: 400 });
|
||||
} catch (error) {
|
||||
logger.error("Admin users actions error", { module: "admin/users/actions", error: String(error) });
|
||||
return NextResponse.json(
|
||||
{ success: false, message: "Internal server error" },
|
||||
{ status: 500 }
|
||||
);
|
||||
return NextResponse.json({ success: false, message: "Internal server error" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,7 @@ import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function POST(
|
||||
req: Request,
|
||||
{ params }: { params: Promise<{ slug: string }> },
|
||||
) {
|
||||
export async function POST(req: Request, { params }: { params: Promise<{ slug: string }> }) {
|
||||
const uid = await bearerUserId(req);
|
||||
if (!uid) return apiError("Unauthorized", 401);
|
||||
|
||||
|
||||
@@ -7,10 +7,7 @@ export const dynamic = "force-dynamic";
|
||||
* GET /api/articles/:slug — single website_article by slug, including the
|
||||
* fullStory body. Returns { error } (404) when the slug is unknown.
|
||||
*/
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ slug: string }> },
|
||||
) {
|
||||
export async function GET(_req: Request, { params }: { params: Promise<{ slug: string }> }) {
|
||||
const { slug } = await params;
|
||||
|
||||
try {
|
||||
|
||||
@@ -41,9 +41,6 @@ export async function GET(req: Request) {
|
||||
});
|
||||
} catch {
|
||||
// DB unavailable — return an empty payload instead of a 500.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
return apiJson({ data: [], meta: { page, perPage, total: 0, lastPage: 1 } }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -48,9 +48,7 @@ function assignRarity(ownerCount: number): BadgeRarityKey {
|
||||
}
|
||||
|
||||
function rankEntries(entries: BadgeLeaderboardEntry[]): BadgeLeaderboardEntry[] {
|
||||
return entries
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map((e, i) => ({ ...e, rank: i + 1 }));
|
||||
return entries.sort((a, b) => b.score - a.score).map((e, i) => ({ ...e, rank: i + 1 }));
|
||||
}
|
||||
|
||||
async function loadTotalBadgesBoard(userId: number | null): Promise<BadgeLeaderboardBoard> {
|
||||
@@ -127,7 +125,9 @@ async function loadTotalBadgesBoard(userId: number | null): Promise<BadgeLeaderb
|
||||
}
|
||||
|
||||
async function loadAchievementBoard(userId: number | null): Promise<BadgeLeaderboardBoard> {
|
||||
const rows = await prisma.$queryRaw<Array<{ userId: bigint; username: string; look: string; score: number }>>(
|
||||
const rows = await prisma.$queryRaw<
|
||||
Array<{ userId: bigint; username: string; look: string; score: number }>
|
||||
>(
|
||||
Prisma.sql`
|
||||
SELECT us.user_id AS userId, u.username, u.look, us.achievement_score AS score
|
||||
FROM users_settings us
|
||||
|
||||
@@ -7,10 +7,7 @@ import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
|
||||
if (!/^\d+$/.test(id)) {
|
||||
|
||||
@@ -39,9 +39,6 @@ export async function GET(req: Request) {
|
||||
});
|
||||
} catch {
|
||||
// DB unreachable — never 500; return an empty, well-formed payload.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
return apiJson({ data: [], meta: { page, perPage, total: 0, lastPage: 1 } }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,7 @@ export const dynamic = "force-dynamic";
|
||||
* about reachability.
|
||||
*/
|
||||
export async function GET() {
|
||||
const database = await prisma
|
||||
.$queryRaw`SELECT 1`.then(() => true)
|
||||
.catch(() => false);
|
||||
const database = await prisma.$queryRaw`SELECT 1`.then(() => true).catch(() => false);
|
||||
|
||||
const emulator = await rcon.send("ping", null).catch(() => false);
|
||||
|
||||
@@ -25,11 +23,12 @@ export async function GET() {
|
||||
host: env.SMTP_HOST,
|
||||
port: env.SMTP_PORT,
|
||||
secure: env.SMTP_SECURE,
|
||||
auth: env.SMTP_USER
|
||||
? { user: env.SMTP_USER, pass: env.SMTP_PASSWORD ?? "" }
|
||||
: undefined,
|
||||
auth: env.SMTP_USER ? { user: env.SMTP_USER, pass: env.SMTP_PASSWORD ?? "" } : undefined,
|
||||
});
|
||||
smtp = await test.verify().then(() => true).catch(() => false);
|
||||
smtp = await test
|
||||
.verify()
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
return apiJson({
|
||||
|
||||
@@ -58,14 +58,13 @@ export async function GET(req: Request) {
|
||||
try {
|
||||
const sp = new URL(req.url).searchParams;
|
||||
const requested = sp.get("type");
|
||||
const type: LeaderboardType =
|
||||
requested === "diamonds" || requested === "duckets" ? requested : "credits";
|
||||
const type: LeaderboardType = requested === "diamonds" || requested === "duckets" ? requested : "credits";
|
||||
|
||||
const rows =
|
||||
type === "credits"
|
||||
? await loadCreditsRows()
|
||||
// eslint-disable-next-line security/detect-object-injection -- type validated to "diamonds"|"duckets"
|
||||
: await loadCurrencyRows(CURRENCY_TYPE[type]);
|
||||
: // eslint-disable-next-line security/detect-object-injection -- type validated to "diamonds"|"duckets"
|
||||
await loadCurrencyRows(CURRENCY_TYPE[type]);
|
||||
|
||||
return apiJson({ type, data: rows }, { status: 200 });
|
||||
} catch {
|
||||
|
||||
@@ -8,10 +8,7 @@ export const dynamic = "force-dynamic";
|
||||
const MEDIA_DIR = "public/assets/images/media";
|
||||
const ALLOWED_EXT = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp"];
|
||||
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
{ params }: { params: Promise<{ path: string[] }> },
|
||||
) {
|
||||
export async function GET(_request: Request, { params }: { params: Promise<{ path: string[] }> }) {
|
||||
const { path: segments } = await params;
|
||||
const name = segments.join("/");
|
||||
// Prevent path traversal
|
||||
@@ -36,8 +33,13 @@ export async function GET(
|
||||
// eslint-disable-next-line security/detect-non-literal-fs-filename
|
||||
const bytes = await readFile(filePath);
|
||||
const mime: Record<string, string> = {
|
||||
".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg",
|
||||
".gif": "image/gif", ".webp": "image/webp", ".svg": "image/svg+xml", ".bmp": "image/bmp",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".webp": "image/webp",
|
||||
".svg": "image/svg+xml",
|
||||
".bmp": "image/bmp",
|
||||
};
|
||||
|
||||
return new NextResponse(bytes, {
|
||||
|
||||
@@ -11,9 +11,7 @@ export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(_req: Request) {
|
||||
try {
|
||||
const count = await cached("online_count", 10_000, () =>
|
||||
prisma.user.count({ where: { online: "1" } }),
|
||||
);
|
||||
const count = await cached("online_count", 10_000, () => prisma.user.count({ where: { online: "1" } }));
|
||||
return apiJson({ count });
|
||||
} catch {
|
||||
return apiJson({ count: 0 });
|
||||
|
||||
@@ -2,11 +2,7 @@ import { NextResponse } from "next/server";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { sessionUserId } from "@/lib/auth/session-user";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import {
|
||||
captureOrder,
|
||||
creditsPerUnit,
|
||||
isPayPalConfigured,
|
||||
} from "@/lib/services/paypal";
|
||||
import { captureOrder, creditsPerUnit, isPayPalConfigured } from "@/lib/services/paypal";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import { sendCurrency } from "@/lib/services/send-currency";
|
||||
import { env } from "@/env";
|
||||
|
||||
@@ -39,9 +39,6 @@ export async function GET(req: Request) {
|
||||
});
|
||||
} catch {
|
||||
// DB unavailable — return an empty payload instead of a 500.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
return apiJson({ data: [], meta: { page, perPage, total: 0, lastPage: 1 } }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,7 @@ import { prisma } from "@/lib/prisma";
|
||||
// flat { key: value } map. None of these keys are secrets.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const EMBED_KEYS = [
|
||||
"radio_enabled",
|
||||
"radio_name",
|
||||
"radio_stream_url",
|
||||
"radio_auto_play",
|
||||
];
|
||||
const EMBED_KEYS = ["radio_enabled", "radio_name", "radio_stream_url", "radio_auto_play"];
|
||||
|
||||
export async function GET(_req: Request) {
|
||||
try {
|
||||
|
||||
@@ -80,9 +80,7 @@ export async function GET(_req: Request) {
|
||||
const aborted = e instanceof Error && e.name === "AbortError";
|
||||
return apiJson({
|
||||
listeners: null,
|
||||
error: aborted
|
||||
? `Timed out after ${FETCH_TIMEOUT_MS / 1000}s`
|
||||
: "Fetch failed",
|
||||
error: aborted ? `Timed out after ${FETCH_TIMEOUT_MS / 1000}s` : "Fetch failed",
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
|
||||
@@ -44,9 +44,7 @@ export async function GET(_req: Request) {
|
||||
} catch (e) {
|
||||
const aborted = e instanceof Error && e.name === "AbortError";
|
||||
return apiJson({
|
||||
error: aborted
|
||||
? `Timed out after ${FETCH_TIMEOUT_MS / 1000}s`
|
||||
: "Fetch failed",
|
||||
error: aborted ? `Timed out after ${FETCH_TIMEOUT_MS / 1000}s` : "Fetch failed",
|
||||
});
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
|
||||
@@ -53,9 +53,6 @@ export async function GET(req: Request) {
|
||||
});
|
||||
} catch {
|
||||
// DB unreachable — never 500; return an empty, well-formed payload.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
return apiJson({ data: [], meta: { page, perPage, total: 0, lastPage: 1 } }, { status: 200 });
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
||||
if (!ticketId) return apiError("Invalid ticket id", 422);
|
||||
|
||||
const body = (await req.json().catch(() => ({}))) as { content?: unknown };
|
||||
const content = String(body.content ?? "").trim().slice(0, 5000);
|
||||
const content = String(body.content ?? "")
|
||||
.trim()
|
||||
.slice(0, 5000);
|
||||
if (!content) return apiError("Content is required");
|
||||
|
||||
try {
|
||||
|
||||
@@ -46,8 +46,12 @@ export async function POST(req: Request) {
|
||||
categoryId?: unknown;
|
||||
};
|
||||
|
||||
const title = String(body.title ?? "").trim().slice(0, 255);
|
||||
const content = String(body.content ?? "").trim().slice(0, 5000);
|
||||
const title = String(body.title ?? "")
|
||||
.trim()
|
||||
.slice(0, 255);
|
||||
const content = String(body.content ?? "")
|
||||
.trim()
|
||||
.slice(0, 5000);
|
||||
if (!title) return apiError("Title is required");
|
||||
if (!content) return apiError("Content is required");
|
||||
|
||||
|
||||
@@ -10,10 +10,7 @@ import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ username: string }> },
|
||||
) {
|
||||
export async function GET(_req: Request, { params }: { params: Promise<{ username: string }> }) {
|
||||
const { username } = await params;
|
||||
|
||||
try {
|
||||
|
||||
@@ -6,10 +6,7 @@ import { prisma } from "@/lib/prisma";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
|
||||
// The primary key is a BigInt; reject non-numeric ids up front.
|
||||
|
||||
@@ -46,9 +46,6 @@ export async function GET(req: Request) {
|
||||
});
|
||||
} catch {
|
||||
// DB unreachable — never 500; return an empty, well-formed payload.
|
||||
return apiJson(
|
||||
{ data: [], meta: { page, perPage, total: 0, lastPage: 1 } },
|
||||
{ status: 200 },
|
||||
);
|
||||
return apiJson({ data: [], meta: { page, perPage, total: 0, lastPage: 1 } }, { status: 200 });
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user