Files
Epicnabbo-Catalogus-Updated…/src/app/admin/users/_lib/load-user-overview.ts
T

138 lines
4.2 KiB
TypeScript

import { type SQL, sql } from "drizzle-orm";
import { db } from "@/lib/db";
import { canAccess, PERMS, type PermissionSet } from "@/lib/permissions";
export type OverviewRow = {
id: string | number;
title: string;
detail?: string | null;
status?: string | null;
at?: string | number | Date | null;
href?: string;
amount?: number | null;
credits?: number | null;
points?: number | null;
pointsType?: number | null;
};
export type OverviewSection = {
key: string;
href?: string;
state: "ready" | "error";
rows: OverviewRow[];
};
export async function loadUserOverview(
userId: number,
permissions: PermissionSet,
rank: number,
): Promise<OverviewSection[]> {
const read = async (query: SQL): Promise<OverviewRow[]> => {
const [rows] = await db.execute(query);
return rows as unknown as OverviewRow[];
};
const sources: {
key: string;
permission: string;
href?: string;
query: () => Promise<OverviewRow[]>;
rowHref?: (id: string | number) => string;
}[] = [
{
key: "sanctions",
permission: PERMS.BANS_VIEW,
href: "/admin/bans",
query: () =>
read(
sql`SELECT id, type AS title, LEFT(ban_reason, 180) AS detail, timestamp AS at, CASE WHEN ban_expire = 0 OR ban_expire > UNIX_TIMESTAMP() THEN 'active' ELSE 'expired' END AS status FROM bans WHERE user_id = ${userId} ORDER BY timestamp DESC, id DESC LIMIT 5`,
),
},
{
key: "mute",
permission: PERMS.MODERATION_VIEW,
query: () =>
read(
sql`SELECT user_id AS id, '' AS title, mute_end_timestamp AS at FROM users_settings WHERE user_id = ${userId} AND mute_end_timestamp > UNIX_TIMESTAMP() LIMIT 5`,
),
},
{
key: "tickets",
permission: PERMS.TICKETS_VIEW,
query: () =>
read(
sql`SELECT id, subject AS title, status, updated_at AS at FROM website_tickets WHERE creator_id = ${userId} ORDER BY updated_at DESC, id DESC LIMIT 5`,
),
rowHref: (id) => `/admin/tickets/${id}`,
},
{
key: "help",
permission: PERMS.TICKETS_VIEW,
query: () =>
read(
sql`SELECT id, title, CASE WHEN open = 1 THEN 'open' ELSE 'closed' END AS status, created_at AS at FROM website_help_center_tickets WHERE user_id = ${userId} ORDER BY created_at DESC, id DESC LIMIT 5`,
),
rowHref: (id) => `/admin/help-tickets/${id}`,
},
{
key: "reports",
permission: PERMS.MODERATION_VIEW,
query: () =>
read(
sql`SELECT id, LEFT(issue, 180) AS title, CASE WHEN reported_id = ${userId} THEN 'reported' ELSE 'reporter' END AS status, timestamp AS at FROM support_tickets WHERE reported_id = ${userId} OR sender_id = ${userId} ORDER BY timestamp DESC, id DESC LIMIT 5`,
),
rowHref: (id) => `/admin/moderation/cfh/${id}`,
},
{
key: "transactions",
permission: PERMS.SHOP_VIEW,
href: "/admin/transactions",
query: () =>
read(
sql`SELECT id, COALESCE(description, transaction_id) AS title, CONCAT(amount, ' ', currency) AS detail, status, created_at AS at FROM website_paypal_transactions WHERE user_id = ${userId} ORDER BY created_at DESC, id DESC LIMIT 5`,
),
},
{
key: "purchases",
permission: PERMS.LOGS_VIEW,
query: () =>
read(
sql`SELECT id, catalog_name AS title, amount, cost_credits AS credits, cost_points AS points, points_type AS pointsType, timestamp AS at FROM logs_shop_purchases WHERE user_id = ${userId} ORDER BY timestamp DESC, id DESC LIMIT 5`,
),
},
{
key: "activity",
permission: PERMS.LOGS_VIEW,
href: "/admin/logs/audit",
query: () =>
read(
sql`SELECT id, action AS title, created_at AS at FROM admin_audit_log WHERE target = 'User' AND target_id = ${userId} ORDER BY id DESC LIMIT 5`,
),
},
];
// Filter before invoking the query; an inaccessible source must never touch its table.
return Promise.all(
sources
.filter((source) => canAccess(permissions, source.permission, rank))
.map(async (source) => {
try {
const rows = await source.query();
return {
key: source.key,
href: source.href,
state: "ready" as const,
rows: rows.map((row) => ({
...row,
href: source.rowHref?.(row.id),
})),
};
} catch {
return {
key: source.key,
href: source.href,
state: "error" as const,
rows: [],
};
}
}),
);
}