This commit is contained in:
NextJS
2026-07-12 17:55:44 +00:00
parent 4ed4a10eae
commit e324a95625
28 changed files with 1087 additions and 114 deletions
+2 -6
View File
@@ -1,8 +1,7 @@
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { requireWriteAccess } from "@/lib/auth/require-session";
import { applyCoinsDeltaToRcBalance } from "@/lib/coins-rc";
import {
buildDashboardHref,
@@ -25,10 +24,7 @@ function parsePositiveCoins(raw: FormDataEntryValue | null): bigint | null {
}
export async function addSystemSupply(formData: FormData) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireWriteAccess();
const tabRaw = String(formData.get("tab") ?? "");
const tab: AdminDashboardTab =
+4 -14
View File
@@ -1,8 +1,7 @@
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { requireWriteAccess } from "@/lib/auth/require-session";
import { parseStoredCoins } from "@/lib/coins-rc";
import { createAdminSupabase } from "@/lib/supabase/admin";
@@ -38,10 +37,7 @@ function normalizeSettingValue(
}
export async function updateSetting(formData: FormData) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireWriteAccess();
const key = String(formData.get("key") ?? "").trim();
if (!key) {
@@ -71,10 +67,7 @@ export async function updateSetting(formData: FormData) {
}
export async function insertSetting(formData: FormData) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireWriteAccess();
const key = String(formData.get("newKey") ?? "").trim();
const value = normalizeValue(formData.get("newValue"));
@@ -102,10 +95,7 @@ export async function insertSetting(formData: FormData) {
}
export async function deleteSetting(formData: FormData) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireWriteAccess();
const key = String(formData.get("key") ?? "").trim();
if (!key) {
+2 -6
View File
@@ -1,8 +1,7 @@
"use server";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { requireWriteAccess } from "@/lib/auth/require-session";
import {
buildDashboardHref,
type AdminDashboardTab,
@@ -19,10 +18,7 @@ function parseScoreField(raw: FormDataEntryValue | null): number | null {
}
export async function updateUserCcRc(formData: FormData) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireWriteAccess();
const userId = Number(formData.get("userId"));
const tabRaw = String(formData.get("tab") ?? "");
+8 -4
View File
@@ -1,6 +1,9 @@
import { NextResponse } from "next/server";
import { getClientIp } from "@/lib/auth/client-ip";
import { verifyAdminCredentials } from "@/lib/auth/credentials";
import { verifyCredentials } from "@/lib/auth/credentials";
import {
roleToSessionCookieValue,
} from "@/lib/auth/roles";
import {
checkLoginRateLimit,
clearLoginAttempts,
@@ -54,7 +57,8 @@ export async function POST(request: Request) {
password = String(formData.get("password") ?? "");
}
if (!verifyAdminCredentials(username, password)) {
const role = verifyCredentials(username, password);
if (!role) {
recordFailedLogin(clientIp);
if (contentType.includes("application/json")) {
return invalidJson();
@@ -68,13 +72,13 @@ export async function POST(request: Request) {
const res = NextResponse.json({ ok: true });
res.cookies.set(
ADMIN_SESSION_COOKIE,
"1",
roleToSessionCookieValue(role),
getSessionCookieSetOptions(request),
);
return res;
}
const res = NextResponse.redirect(publicRequestUrl(request, "/"));
applySessionCookie(res, request);
applySessionCookie(res, request, role);
return res;
}
+3 -4
View File
@@ -1,5 +1,4 @@
import { cookies } from "next/headers";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { getSessionRole } from "@/lib/auth/require-session";
import {
parseMatchIdParam,
readMatchLogFile,
@@ -9,8 +8,8 @@ export async function GET(
_request: Request,
context: { params: Promise<{ matchId: string }> },
) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
const role = await getSessionRole();
if (!role) {
return Response.json({ error: "Unauthorized" }, { status: 401 });
}
+3 -4
View File
@@ -1,13 +1,12 @@
import { cookies } from "next/headers";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { getSessionRole } from "@/lib/auth/require-session";
import { parseMatchmakerLogParam } from "@/lib/matchmaker-log-source";
import { readMatchmakerLogFile } from "@/lib/matchmaker-logs-server";
const NO_STORE = { "Cache-Control": "no-store, max-age=0" };
export async function GET(request: Request) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
const role = await getSessionRole();
if (!role) {
return Response.json(
{ error: "Unauthorized" },
{ status: 401, headers: NO_STORE },
+2 -6
View File
@@ -1,10 +1,9 @@
import { Fragment } from "react";
import type { Metadata } from "next";
import Link from "next/link";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { AdminHeader } from "@/components/admin-header";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { requireSession } from "@/lib/auth/require-session";
import {
formatRcDecimalFromCoinsBigInt,
formatRcLabelFromCoinsBigInt,
@@ -58,10 +57,7 @@ export default async function LedgerBookPage({
}: {
searchParams: Promise<{ from?: string | string[]; to?: string | string[] }>;
}) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireSession();
const sp = await searchParams;
let fromStr = firstSearchParam(sp.from);
+3 -7
View File
@@ -1,8 +1,7 @@
import type { Metadata } from "next";
import Link from "next/link";
import { cookies } from "next/headers";
import { notFound, redirect } from "next/navigation";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { notFound } from "next/navigation";
import { requireSession } from "@/lib/auth/require-session";
import { MatchLogColoredBody } from "@/components/match-log-colored-body";
import {
parseMatchIdParam,
@@ -25,10 +24,7 @@ export default async function MatchLogPage({
}: {
params: Promise<{ matchId: string }>;
}) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireSession();
const { matchId: raw } = await params;
const matchId = parseMatchIdParam(raw);
+2 -7
View File
@@ -1,8 +1,6 @@
import type { Metadata } from "next";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { MatchmakerLogTerminal } from "@/components/matchmaker-log-terminal";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { requireSession } from "@/lib/auth/require-session";
import { parseMatchmakerLogParam } from "@/lib/matchmaker-log-source";
import { readMatchmakerLogFile } from "@/lib/matchmaker-logs-server";
@@ -17,10 +15,7 @@ export default async function MatchmakerLogsPage({
}: {
searchParams: Promise<{ mklog?: string | string[] }>;
}) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
await requireSession();
const sp = await searchParams;
const raw =
+27 -3
View File
@@ -1,6 +1,8 @@
import { AdminDashboard } from "@/components/admin-dashboard";
import { AdminHeader } from "@/components/admin-header";
import { EditUserCcRcOverlay } from "@/components/edit-user-cc-rc-overlay";
import { isReadOnlyRole } from "@/lib/auth/roles";
import { requireSession } from "@/lib/auth/require-session";
import { loadDashboardStatsBundle } from "@/lib/dashboard-stats";
import {
normalizeLedgerDateRange,
@@ -25,6 +27,10 @@ import {
} from "@/lib/match-log-analysis-server";
import type { MatchLogAnalysisResult } from "@/lib/match-log-parser";
import { emptyMatchLogAnalysisResult } from "@/lib/match-log-parser";
import {
loadGeolocationAnalytics,
type GeolocationAnalyticsResult,
} from "@/lib/geolocation-analytics";
import {
loadPingAnalytics,
type PingAnalyticsResult,
@@ -78,6 +84,9 @@ export default async function Home({
aplayers?: string | string[];
}>;
}) {
const role = await requireSession();
const readOnly = isReadOnlyRole(role);
const sp = await searchParams;
const tabParam = firstSearchParam(sp.tab);
const tab: AdminDashboardTab =
@@ -138,6 +147,11 @@ export default async function Home({
byCountries: [],
error: null,
};
let geolocationAnalytics: GeolocationAnalyticsResult = {
userAccounts: [],
matches: [],
error: null,
};
let statsBundle: Awaited<ReturnType<typeof loadDashboardStatsBundle>> | null =
null;
@@ -272,7 +286,7 @@ export default async function Home({
analysisFrom = range.from;
analysisTo = range.to;
analysisPlayerIds = parseAnalysisPlayerIds(sp.aplayers);
const [matchLogResult, pingResult] = await Promise.all([
const [matchLogResult, pingResult, geoResult] = await Promise.all([
analyzeMatchLogsForMatches(
matches,
analysisFrom,
@@ -280,20 +294,28 @@ export default async function Home({
analysisPlayerIds,
),
loadPingAnalytics(supabase, analysisFrom, analysisTo, analysisPlayerIds),
loadGeolocationAnalytics(
supabase,
users,
analysisFrom,
analysisTo,
analysisPlayerIds,
),
]);
matchAnalysis = matchLogResult;
pingAnalytics = pingResult;
geolocationAnalytics = geoResult;
}
}
let editUser: DbUser | null = null;
if (Number.isInteger(editIdNum) && editIdNum >= 1) {
if (!readOnly && Number.isInteger(editIdNum) && editIdNum >= 1) {
editUser = users.find((u) => Number(u.id) === editIdNum) ?? null;
}
return (
<div className="flex min-h-full flex-1 flex-col bg-zinc-50 dark:bg-zinc-950">
<AdminHeader />
<AdminHeader readOnly={readOnly} />
{configError ? (
<div className="px-6 pt-8">
<div
@@ -335,6 +357,8 @@ export default async function Home({
analysisPlayerIds={analysisPlayerIds}
matchAnalysis={matchAnalysis}
pingAnalytics={pingAnalytics}
geolocationAnalytics={geolocationAnalytics}
readOnly={readOnly}
/>
{editUser ? (
<EditUserCcRcOverlay
+6 -8
View File
@@ -1,9 +1,8 @@
import type { Metadata } from "next";
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { requireSession } from "@/lib/auth/require-session";
import { isReadOnlyRole } from "@/lib/auth/roles";
import { AdminHeader } from "@/components/admin-header";
import { AdminSettingsEditor } from "@/components/admin-settings-editor";
import { ADMIN_SESSION_COOKIE } from "@/lib/auth/session";
import { createAdminSupabase } from "@/lib/supabase/admin";
import type { DbSetting } from "@/types/database";
@@ -31,10 +30,8 @@ export default async function SettingsPage({
addError?: string | string[];
}>;
}) {
const cookieStore = await cookies();
if (cookieStore.get(ADMIN_SESSION_COOKIE)?.value !== "1") {
redirect("/login");
}
const role = await requireSession();
const readOnly = isReadOnlyRole(role);
const sp = await searchParams;
const saveError = firstSearchParam(sp.saveError) === "1";
@@ -63,7 +60,7 @@ export default async function SettingsPage({
return (
<div className="flex min-h-full flex-1 flex-col bg-zinc-50 dark:bg-zinc-950">
<AdminHeader />
<AdminHeader readOnly={readOnly} />
<main className="flex-1 px-6 py-8">
<div className="mx-auto mb-8 max-w-[900px]">
<h1 className="text-xl font-semibold tracking-tight text-zinc-900 dark:text-zinc-50">
@@ -96,6 +93,7 @@ export default async function SettingsPage({
rows={rows}
saveError={saveError}
addError={addError}
readOnly={readOnly}
/>
)}
</main>