refactor(cms): deduplicate shared admin components and fix studio batch completion
- fix(studio): stop markBatchDone infinite recursion so batches complete - refactor(api): merge api-response into api and drop the duplicate module - refactor(media): extract shared media loader and URL validator - refactor(ui): extract shared LoadingSpinner for site and admin groups - refactor(dates): consolidate raw date formatting into formatDate util - refactor(logs): share a single generic log-list loader across tables - refactor(theme): merge both ColorField components and reuse contrast helpers - refactor(import): extract shared ImportErrorBanner and SearchInput - chore(): remove dead theme-editor-tabs after inlining tab components
This commit is contained in:
1 parent
b12e405b34
commit
8c12fc6c60
93 files changed
+1447
-1005
No files matched your search
@@ -9,6 +9,16 @@ type ListParamsInput = {
|
||||
defaultPerPage?: number;
|
||||
};
|
||||
|
||||
type SqlFragment = ReturnType<typeof sql>;
|
||||
|
||||
type LogListSpec<RawRow, Row> = {
|
||||
select: SqlFragment;
|
||||
from: SqlFragment;
|
||||
searchWhere: (like: string) => SqlFragment;
|
||||
orderBy: SqlFragment;
|
||||
mapRow: (raw: RawRow, index: number, offset: number) => Row;
|
||||
};
|
||||
|
||||
async function requireLogsView() {
|
||||
const { session, permissions } = await getAdminContext();
|
||||
if (!canAccess(permissions, PERMS.LOGS_VIEW, session.user.rank)) {
|
||||
@@ -26,11 +36,56 @@ function parsePageParams(
|
||||
return { search: parsed.search.trim(), page: parsed.page, perPage };
|
||||
}
|
||||
|
||||
async function rawRows<T>(query: ReturnType<typeof sql>): Promise<T[]> {
|
||||
async function rawRows<T>(query: SqlFragment): Promise<T[]> {
|
||||
const [rows] = (await db.execute(query)) as unknown as [T[], unknown];
|
||||
return rows ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared list loader for the chat / command / trade log tables. The SQL
|
||||
* fragments come from a per-table spec while the request handling, search
|
||||
* branching, count + pagination and row mapping stay in one place.
|
||||
*/
|
||||
async function loadLogList<RawRow, Row>(
|
||||
input: ListParamsInput,
|
||||
spec: LogListSpec<RawRow, Row>,
|
||||
): Promise<{
|
||||
rows: Row[];
|
||||
total: number;
|
||||
page: number;
|
||||
perPage: number;
|
||||
lastPage: number;
|
||||
offset: number;
|
||||
}> {
|
||||
await requireLogsView();
|
||||
|
||||
const rawParams = await input.searchParams;
|
||||
const { search, page, perPage } = parsePageParams(
|
||||
rawParams,
|
||||
input.defaultPerPage ?? 50,
|
||||
);
|
||||
const offset = (page - 1) * perPage;
|
||||
const like = `%${search}%`;
|
||||
const where = search ? sql`WHERE ${spec.searchWhere(like)}` : sql``;
|
||||
|
||||
const [rawRowsResult, countRows] = await Promise.all([
|
||||
rawRows<RawRow>(
|
||||
sql`${spec.select} ${spec.from} ${where} ${spec.orderBy} LIMIT ${perPage} OFFSET ${offset}`,
|
||||
),
|
||||
rawRows<{ count: bigint }>(
|
||||
sql`SELECT COUNT(*) AS count ${spec.from} ${where}`,
|
||||
),
|
||||
]);
|
||||
|
||||
const total = Number(countRows[0]?.count ?? 0);
|
||||
const pagination = calcPagination(total, page, perPage);
|
||||
const rows = rawRowsResult.map((row, index) =>
|
||||
spec.mapRow(row, index, pagination.offset),
|
||||
);
|
||||
|
||||
return { rows, ...pagination };
|
||||
}
|
||||
|
||||
export type ChatLogListRow = {
|
||||
id: number;
|
||||
username: string;
|
||||
@@ -39,17 +94,7 @@ export type ChatLogListRow = {
|
||||
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}%`;
|
||||
|
||||
export function loadChatLogList(input: ListParamsInput) {
|
||||
type RawRow = {
|
||||
roomId: number;
|
||||
message: string;
|
||||
@@ -57,43 +102,20 @@ export async function loadChatLogList({
|
||||
username: string | null;
|
||||
};
|
||||
|
||||
const [rawRowsResult, countRows] = search
|
||||
? await Promise.all([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
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([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
SELECT COUNT(*) AS count FROM chatlogs_room`),
|
||||
]);
|
||||
|
||||
const total = Number(countRows[0]?.count ?? 0);
|
||||
const pagination = calcPagination(total, page, perPage);
|
||||
const rows: ChatLogListRow[] = rawRowsResult.map((row, index) => ({
|
||||
id: pagination.offset + index + 1,
|
||||
username: row.username ?? "-",
|
||||
message: row.message,
|
||||
timestamp: row.timestamp,
|
||||
roomId: row.roomId,
|
||||
}));
|
||||
|
||||
return { rows, ...pagination };
|
||||
return loadLogList<RawRow, ChatLogListRow>(input, {
|
||||
select: sql`SELECT c.room_id AS roomId, c.message, c.timestamp, u.username AS username`,
|
||||
from: sql`FROM chatlogs_room c LEFT JOIN users u ON u.id = c.user_from_id`,
|
||||
searchWhere: (like) =>
|
||||
sql`c.message LIKE ${like} OR u.username LIKE ${like}`,
|
||||
orderBy: sql`ORDER BY c.timestamp DESC`,
|
||||
mapRow: (row, index, offset) => ({
|
||||
id: offset + index + 1,
|
||||
username: row.username ?? "-",
|
||||
message: row.message,
|
||||
timestamp: row.timestamp,
|
||||
roomId: row.roomId,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export type CommandLogListRow = {
|
||||
@@ -104,17 +126,7 @@ export type CommandLogListRow = {
|
||||
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}%`;
|
||||
|
||||
export function loadCommandLogList(input: ListParamsInput) {
|
||||
type RawRow = {
|
||||
command: string;
|
||||
params: string;
|
||||
@@ -122,47 +134,20 @@ export async function loadCommandLogList({
|
||||
username: string | null;
|
||||
};
|
||||
|
||||
const [rawRowsResult, countRows] = search
|
||||
? await Promise.all([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
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([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
SELECT COUNT(*) AS count FROM commandlogs`),
|
||||
]);
|
||||
|
||||
const total = Number(countRows[0]?.count ?? 0);
|
||||
const pagination = calcPagination(total, page, perPage);
|
||||
const rows: CommandLogListRow[] = rawRowsResult.map((row, index) => ({
|
||||
id: pagination.offset + index + 1,
|
||||
username: row.username ?? "-",
|
||||
command: row.command,
|
||||
params: row.params,
|
||||
timestamp: row.timestamp,
|
||||
}));
|
||||
|
||||
return { rows, ...pagination };
|
||||
return loadLogList<RawRow, CommandLogListRow>(input, {
|
||||
select: sql`SELECT c.command, c.params, c.timestamp, u.username AS username`,
|
||||
from: sql`FROM commandlogs c LEFT JOIN users u ON u.id = c.user_id`,
|
||||
searchWhere: (like) =>
|
||||
sql`c.command LIKE ${like} OR c.params LIKE ${like} OR u.username LIKE ${like}`,
|
||||
orderBy: sql`ORDER BY c.timestamp DESC`,
|
||||
mapRow: (row, index, offset) => ({
|
||||
id: offset + index + 1,
|
||||
username: row.username ?? "-",
|
||||
command: row.command,
|
||||
params: row.params,
|
||||
timestamp: row.timestamp,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
export type TradeLogListRow = {
|
||||
@@ -173,17 +158,7 @@ export type TradeLogListRow = {
|
||||
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}%`;
|
||||
|
||||
export function loadTradeLogList(input: ListParamsInput) {
|
||||
type RawRow = {
|
||||
id: number;
|
||||
timestamp: number;
|
||||
@@ -191,47 +166,19 @@ export async function loadTradeLogList({
|
||||
userTwoName: string | null;
|
||||
};
|
||||
|
||||
const [rawRowsResult, countRows] = search
|
||||
? await Promise.all([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
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([
|
||||
rawRows<RawRow>(sql`
|
||||
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}`),
|
||||
rawRows<{ count: bigint }>(sql`
|
||||
SELECT COUNT(*) AS count FROM room_trade_log`),
|
||||
]);
|
||||
|
||||
const total = Number(countRows[0]?.count ?? 0);
|
||||
const pagination = calcPagination(total, page, perPage);
|
||||
const rows: TradeLogListRow[] = rawRowsResult.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 };
|
||||
return loadLogList<RawRow, TradeLogListRow>(input, {
|
||||
select: sql`SELECT t.id, t.timestamp, u1.username AS userOneName, u2.username AS userTwoName`,
|
||||
from: sql`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`,
|
||||
searchWhere: (like) =>
|
||||
sql`u1.username LIKE ${like} OR u2.username LIKE ${like}`,
|
||||
orderBy: sql`ORDER BY t.timestamp DESC`,
|
||||
mapRow: (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,
|
||||
}),
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user