From afeabdad1b325f921105881d12201b41d8bcd24f Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 18 Jul 2026 22:24:10 +0200 Subject: [PATCH 1/2] Fix admin chat/command logs crashing on missing Prisma models. chatlogs_room and commandlogs are @@ignore (no client delegates); load them via raw SQL with user joins instead of fetchAdminList(chatLog/commandLog). Co-authored-by: Cursor --- src/app/admin/logs/_lib/load-ignored-logs.ts | 160 +++++++++++++++++++ src/app/admin/logs/chat/page.tsx | 23 +-- src/app/admin/logs/commands/page.tsx | 23 +-- 3 files changed, 164 insertions(+), 42 deletions(-) create mode 100644 src/app/admin/logs/_lib/load-ignored-logs.ts diff --git a/src/app/admin/logs/_lib/load-ignored-logs.ts b/src/app/admin/logs/_lib/load-ignored-logs.ts new file mode 100644 index 00000000..e9083100 --- /dev/null +++ b/src/app/admin/logs/_lib/load-ignored-logs.ts @@ -0,0 +1,160 @@ +import { redirect } from "next/navigation"; +import { calcPagination, parseListParams } from "@/lib/admin-helpers"; +import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; +import { prisma } from "@/lib/prisma"; + +type ListParamsInput = { + searchParams: Promise>; + defaultPerPage?: number; +}; + +async function requireLogsView() { + const { session, permissions } = await getAdminContext(); + if (!canAccess(permissions, PERMS.LOGS_VIEW, session.user.rank)) { + redirect("/admin"); + } +} + +function parsePageParams( + rawParams: Record, + defaultPerPage: number, +) { + const sp = new URLSearchParams(rawParams); + const parsed = parseListParams(sp); + const perPage = Number(rawParams.perPage) || defaultPerPage; + return { search: parsed.search.trim(), page: parsed.page, perPage }; +} + +export type ChatLogListRow = { + id: number; + username: string; + message: string; + timestamp: number; + roomId: number; +}; + +export async function loadChatLogList({ + searchParams, + defaultPerPage = 50, +}: ListParamsInput) { + await requireLogsView(); + + const rawParams = await searchParams; + const { search, page, perPage } = parsePageParams(rawParams, defaultPerPage); + const offset = (page - 1) * perPage; + const like = `%${search}%`; + + type RawRow = { + roomId: number; + message: string; + timestamp: number; + username: string | null; + }; + + const [rawRows, countRows] = search + ? await Promise.all([ + prisma.$queryRaw` + SELECT c.room_id AS roomId, c.message, c.timestamp, u.username AS username + FROM chatlogs_room c + LEFT JOIN users u ON u.id = c.user_from_id + WHERE c.message LIKE ${like} OR u.username LIKE ${like} + ORDER BY c.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count + FROM chatlogs_room c + LEFT JOIN users u ON u.id = c.user_from_id + WHERE c.message LIKE ${like} OR u.username LIKE ${like}`, + ]) + : await Promise.all([ + prisma.$queryRaw` + SELECT c.room_id AS roomId, c.message, c.timestamp, u.username AS username + FROM chatlogs_room c + LEFT JOIN users u ON u.id = c.user_from_id + ORDER BY c.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count FROM chatlogs_room`, + ]); + + const total = Number(countRows[0]?.count ?? 0); + const pagination = calcPagination(total, page, perPage); + const rows: ChatLogListRow[] = rawRows.map((row, index) => ({ + id: pagination.offset + index + 1, + username: row.username ?? "-", + message: row.message, + timestamp: row.timestamp, + roomId: row.roomId, + })); + + return { rows, ...pagination }; +} + +export type CommandLogListRow = { + id: number; + username: string; + command: string; + params: string; + timestamp: number; +}; + +export async function loadCommandLogList({ + searchParams, + defaultPerPage = 50, +}: ListParamsInput) { + await requireLogsView(); + + const rawParams = await searchParams; + const { search, page, perPage } = parsePageParams(rawParams, defaultPerPage); + const offset = (page - 1) * perPage; + const like = `%${search}%`; + + type RawRow = { + command: string; + params: string; + timestamp: number; + username: string | null; + }; + + const [rawRows, countRows] = search + ? await Promise.all([ + prisma.$queryRaw` + SELECT c.command, c.params, c.timestamp, u.username AS username + FROM commandlogs c + LEFT JOIN users u ON u.id = c.user_id + WHERE c.command LIKE ${like} + OR c.params LIKE ${like} + OR u.username LIKE ${like} + ORDER BY c.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count + FROM commandlogs c + LEFT JOIN users u ON u.id = c.user_id + WHERE c.command LIKE ${like} + OR c.params LIKE ${like} + OR u.username LIKE ${like}`, + ]) + : await Promise.all([ + prisma.$queryRaw` + SELECT c.command, c.params, c.timestamp, u.username AS username + FROM commandlogs c + LEFT JOIN users u ON u.id = c.user_id + ORDER BY c.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count FROM commandlogs`, + ]); + + const total = Number(countRows[0]?.count ?? 0); + const pagination = calcPagination(total, page, perPage); + const rows: CommandLogListRow[] = rawRows.map((row, index) => ({ + id: pagination.offset + index + 1, + username: row.username ?? "-", + command: row.command, + params: row.params, + timestamp: row.timestamp, + })); + + return { rows, ...pagination }; +} diff --git a/src/app/admin/logs/chat/page.tsx b/src/app/admin/logs/chat/page.tsx index 5b3dad05..81e7c931 100644 --- a/src/app/admin/logs/chat/page.tsx +++ b/src/app/admin/logs/chat/page.tsx @@ -1,32 +1,13 @@ -import { fetchAdminList } from "@/lib/admin-list"; -import { PERMS } from "@/lib/permissions"; +import { loadChatLogList } from "../_lib/load-ignored-logs"; import { ChatTable } from "./chat-table"; export default async function ChatLogsPage({ - params: paramsPromise, searchParams, }: { params: Promise<{ locale: string }>; searchParams: Promise>; }) { - const result = await fetchAdminList( - { - permission: PERMS.LOGS_VIEW, - model: "chatLog", - searchFields: ["message", "user.username"], - defaultPerPage: 50, - include: { user: { select: { username: true } } }, - mapRow: (l) => ({ - id: l.id, - username: l.user.username, - message: l.message, - timestamp: l.timestamp, - roomId: l.roomId, - }), - }, - paramsPromise, - searchParams, - ); + const result = await loadChatLogList({ searchParams, defaultPerPage: 50 }); return (
diff --git a/src/app/admin/logs/commands/page.tsx b/src/app/admin/logs/commands/page.tsx index 59628e22..a2c0f0ac 100644 --- a/src/app/admin/logs/commands/page.tsx +++ b/src/app/admin/logs/commands/page.tsx @@ -1,32 +1,13 @@ -import { fetchAdminList } from "@/lib/admin-list"; -import { PERMS } from "@/lib/permissions"; +import { loadCommandLogList } from "../_lib/load-ignored-logs"; import { CommandsTable } from "./commands-table"; export default async function CommandLogsPage({ - params: paramsPromise, searchParams, }: { params: Promise<{ locale: string }>; searchParams: Promise>; }) { - const result = await fetchAdminList( - { - permission: PERMS.LOGS_VIEW, - model: "commandLog", - searchFields: ["command", "params", "user.username"], - defaultPerPage: 50, - include: { user: { select: { username: true } } }, - mapRow: (l) => ({ - id: l.id, - username: l.user.username, - command: l.command, - params: l.params, - timestamp: l.timestamp, - }), - }, - paramsPromise, - searchParams, - ); + const result = await loadCommandLogList({ searchParams, defaultPerPage: 50 }); return (
From 3dcd100c9ff33710fa5c6ba1d1ddede27b8d3c0a Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 18 Jul 2026 22:25:38 +0200 Subject: [PATCH 2/2] Fix admin trade logs: wrong Prisma model and missing relations. Load room_trade_log via SQL with user joins; status is always completed (no column in DB). Co-authored-by: Cursor --- src/app/admin/logs/_lib/load-ignored-logs.ts | 71 ++++++++++++++++++++ src/app/admin/logs/trades/page.tsx | 26 +------ 2 files changed, 73 insertions(+), 24 deletions(-) diff --git a/src/app/admin/logs/_lib/load-ignored-logs.ts b/src/app/admin/logs/_lib/load-ignored-logs.ts index e9083100..2abe8af7 100644 --- a/src/app/admin/logs/_lib/load-ignored-logs.ts +++ b/src/app/admin/logs/_lib/load-ignored-logs.ts @@ -158,3 +158,74 @@ export async function loadCommandLogList({ return { rows, ...pagination }; } + +export type TradeLogListRow = { + id: number; + userOneName: string; + userTwoName: string; + status: string; + timestamp: number; +}; + +export async function loadTradeLogList({ + searchParams, + defaultPerPage = 50, +}: ListParamsInput) { + await requireLogsView(); + + const rawParams = await searchParams; + const { search, page, perPage } = parsePageParams(rawParams, defaultPerPage); + const offset = (page - 1) * perPage; + const like = `%${search}%`; + + type RawRow = { + id: number; + timestamp: number; + userOneName: string | null; + userTwoName: string | null; + }; + + const [rawRows, countRows] = search + ? await Promise.all([ + prisma.$queryRaw` + SELECT t.id, t.timestamp, + u1.username AS userOneName, u2.username AS userTwoName + FROM room_trade_log t + LEFT JOIN users u1 ON u1.id = t.user_one_id + LEFT JOIN users u2 ON u2.id = t.user_two_id + WHERE u1.username LIKE ${like} OR u2.username LIKE ${like} + ORDER BY t.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count + FROM room_trade_log t + LEFT JOIN users u1 ON u1.id = t.user_one_id + LEFT JOIN users u2 ON u2.id = t.user_two_id + WHERE u1.username LIKE ${like} OR u2.username LIKE ${like}`, + ]) + : await Promise.all([ + prisma.$queryRaw` + SELECT t.id, t.timestamp, + u1.username AS userOneName, u2.username AS userTwoName + FROM room_trade_log t + LEFT JOIN users u1 ON u1.id = t.user_one_id + LEFT JOIN users u2 ON u2.id = t.user_two_id + ORDER BY t.timestamp DESC + LIMIT ${perPage} OFFSET ${offset}`, + prisma.$queryRaw<{ count: bigint }[]>` + SELECT COUNT(*) AS count FROM room_trade_log`, + ]); + + const total = Number(countRows[0]?.count ?? 0); + const pagination = calcPagination(total, page, perPage); + const rows: TradeLogListRow[] = rawRows.map((row) => ({ + id: row.id, + userOneName: row.userOneName ?? "-", + userTwoName: row.userTwoName ?? "-", + // room_trade_log only stores completed trades; no status column exists + status: "completed", + timestamp: row.timestamp, + })); + + return { rows, ...pagination }; +} diff --git a/src/app/admin/logs/trades/page.tsx b/src/app/admin/logs/trades/page.tsx index 69e5cd0e..2c470b9c 100644 --- a/src/app/admin/logs/trades/page.tsx +++ b/src/app/admin/logs/trades/page.tsx @@ -1,35 +1,13 @@ -import { fetchAdminList } from "@/lib/admin-list"; -import { PERMS } from "@/lib/permissions"; +import { loadTradeLogList } from "../_lib/load-ignored-logs"; import { TradesTable } from "./trades-table"; export default async function TradeLogsPage({ - params: paramsPromise, searchParams, }: { params: Promise<{ locale: string }>; searchParams: Promise>; }) { - const result = await fetchAdminList( - { - permission: PERMS.LOGS_VIEW, - model: "trade", - searchFields: ["userOne.username", "userTwo.username"], - defaultPerPage: 50, - include: { - userOne: { select: { username: true } }, - userTwo: { select: { username: true } }, - }, - mapRow: (t) => ({ - id: t.id, - userOneName: t.userOne.username, - userTwoName: t.userTwo.username, - status: t.status, - timestamp: t.timestamp, - }), - }, - paramsPromise, - searchParams, - ); + const result = await loadTradeLogList({ searchParams, defaultPerPage: 50 }); return (