import { desc, eq, gt, or } from "drizzle-orm"; import { getTranslations } from "next-intl/server"; import { StatusCard } from "@/components/admin/dashboard"; import { requireModPermission } from "@/lib/admin/guard"; import { unixNow } from "@/lib/bans"; import { Ban, db } from "@/lib/db"; import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; type BanRow = { id: number; userId: number; type: "account" | "ip" | "machine" | "super"; banReason: string; banExpire: number; }; export default async function ModBansPage() { await requireModPermission([PERMS.MOD_BANS_VIEW, PERMS.BANS_VIEW]); const t = await getTranslations("pages.admin.bans"); const now = unixNow(); let bans: BanRow[] = []; try { bans = await db .select({ id: Ban.id, userId: Ban.userId, type: Ban.type, banReason: Ban.banReason, banExpire: Ban.banExpire, }) .from(Ban) .where(or(eq(Ban.banExpire, 0), gt(Ban.banExpire, now))) .orderBy(desc(Ban.timestamp)) .limit(100); } catch { bans = []; } const permanent = bans.filter((b) => b.banExpire === 0).length; const accountBans = bans.filter((b) => b.type === "account").length; return (

Read-only ban list for moderators. Create/lift bans requires the full admin panel.

0 ? "warn" : "ok"} icon="🔨" /> 0 ? "danger" : "neutral"} icon="⛔" />

{t("activeBans")} ({bans.length})

{bans.length === 0 ? (
{t("noBans")}
) : (
{bans.map((b) => ( ))}
{t("colUserId")} {t("colType")} {t("colReason")} {t("colExpires")}
{b.userId} {b.type} {b.banReason || "—"} {b.banExpire === 0 ? ( {t("permanent")} ) : ( formatDate(new Date(b.banExpire * 1000)) )}
)}
); }