Merge pull request 'fix(admin): chat/command logs crash on ignored Prisma models' (#13) from fix/admin-chat-command-logs into main
Local Build and Deploy / deploy (push) Successful in 59s

Reviewed-on: #13
This commit is contained in:
Simo committed 2026-07-18 22:25:52 +02:00
commit 57c3fe601a
4 files changed
+237 -66

No files matched your search

@@ -0,0 +1,231 @@
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<Record<string, string>>;
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<string, string>,
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<RawRow[]>`
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<RawRow[]>`
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<RawRow[]>`
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<RawRow[]>`
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 };
}
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<RawRow[]>`
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<RawRow[]>`
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 };
}
+2 -21
View File
@@ -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<Record<string, string>>;
}) {
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 (
<div className="space-y-6">
+2 -21
View File
@@ -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<Record<string, string>>;
}) {
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 (
<div className="space-y-6">
+2 -24
View File
@@ -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<Record<string, string>>;
}) {
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 (
<div className="space-y-6">