- Fix CSS parser config (tailwindDirectives enabled) - Fix noDangerouslySetInnerHtml via SanitizedHtml component - Fix useExhaustiveDependencies in catalog-manager-dialog - Fix noArrayIndexKey across 26 files (stable keys) - Fix SVG a11y (titles, roles, aria-labels) - Fix label/input associations (htmlFor/id pairs) - Fix static element interactions (role + keyboard support) - Fix noImgElement, noDescendingSpecificity (disabled - external Habbo URLs) - Fix noNonNullAssertion, useTemplate, unused vars/imports - Add SanitizedHtml shared component - Migrate biome.json to 2.5.4 schema
896 lines
19 KiB
TypeScript
896 lines
19 KiB
TypeScript
"use server";
|
|
|
|
import { revalidatePath } from "next/cache";
|
|
import { requirePermission } from "@/lib/admin/guard";
|
|
import { auth } from "@/lib/auth";
|
|
import { sessionUserId } from "@/lib/auth/session-user";
|
|
import { PERMS } from "@/lib/permissions";
|
|
import { prisma } from "@/lib/prisma";
|
|
import { logAudit } from "@/lib/services/audit";
|
|
|
|
function sanitize(str: string, max = 255): string {
|
|
return String(str ?? "")
|
|
.normalize("NFC")
|
|
.trim()
|
|
.slice(0, max);
|
|
}
|
|
|
|
async function currentUserId(): Promise<number | null> {
|
|
const session = await auth();
|
|
return session?.user ? sessionUserId(session.user.id) : null;
|
|
}
|
|
|
|
export async function upsertPermission(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const permission = sanitize(String(formData.get("permission") ?? ""));
|
|
const minRank = Number(formData.get("minRank"));
|
|
const descriptionRaw = sanitize(String(formData.get("description") ?? ""));
|
|
const description = descriptionRaw.length > 0 ? descriptionRaw : null;
|
|
const groupName =
|
|
sanitize(String(formData.get("groupName") ?? ""), 100) || null;
|
|
const dependsOn = sanitize(String(formData.get("dependsOn") ?? "")) || null;
|
|
|
|
if (!permission || !Number.isFinite(minRank) || minRank < 0) return;
|
|
|
|
const userId = await currentUserId();
|
|
|
|
try {
|
|
const existing = await prisma.websiteHousekeepingPermissions.findUnique({
|
|
where: { permission },
|
|
});
|
|
|
|
if (existing) {
|
|
const before = {
|
|
minRank: existing.minRank,
|
|
description: existing.description,
|
|
groupName: existing.groupName,
|
|
dependsOn: existing.dependsOn,
|
|
};
|
|
await prisma.websiteHousekeepingPermissions.update({
|
|
where: { permission },
|
|
data: {
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
dependsOn,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
});
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.permission.update",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
targetId: Number(existing.id),
|
|
before,
|
|
after: { minRank, description, groupName, dependsOn },
|
|
});
|
|
} else {
|
|
await prisma.websiteHousekeepingPermissions.create({
|
|
data: {
|
|
permission,
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
dependsOn,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
});
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.permission.create",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
targetId: undefined,
|
|
after: { permission, minRank, description, groupName, dependsOn },
|
|
});
|
|
}
|
|
} catch {
|
|
// Swallow
|
|
}
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
}
|
|
|
|
export async function deletePermission(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
|
if (!raw) return;
|
|
|
|
const userId = await currentUserId();
|
|
|
|
try {
|
|
const existing = await prisma.websiteHousekeepingPermissions.findUnique({
|
|
where: { id: BigInt(raw) },
|
|
});
|
|
if (existing) {
|
|
await prisma.websiteHousekeepingPermissions.delete({
|
|
where: { id: BigInt(raw) },
|
|
});
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.permission.delete",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
targetId: Number(existing.id),
|
|
before: {
|
|
permission: existing.permission,
|
|
minRank: existing.minRank,
|
|
description: existing.description,
|
|
groupName: existing.groupName,
|
|
},
|
|
});
|
|
}
|
|
} catch {
|
|
// Already gone / invalid id.
|
|
}
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
}
|
|
|
|
export async function bulkImportPermissions(
|
|
formData: FormData,
|
|
): Promise<{ count: number; errors: string[] }> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const raw = sanitize(String(formData.get("json") ?? ""));
|
|
if (!raw) return { count: 0, errors: ["Empty input"] };
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(raw);
|
|
} catch {
|
|
return { count: 0, errors: ["Invalid JSON"] };
|
|
}
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
return { count: 0, errors: ["Expected an array"] };
|
|
}
|
|
|
|
const userId = await currentUserId();
|
|
const errors: string[] = [];
|
|
let count = 0;
|
|
|
|
for (const item of parsed) {
|
|
const permission = sanitize(String(item.permission ?? ""));
|
|
const minRank = Number(item.minRank);
|
|
const description = sanitize(String(item.description ?? "")) || null;
|
|
const groupName = sanitize(String(item.groupName ?? ""), 100) || null;
|
|
const dependsOn = sanitize(String(item.dependsOn ?? "")) || null;
|
|
|
|
if (!permission || !Number.isFinite(minRank) || minRank < 0) {
|
|
errors.push(`Invalid entry: ${JSON.stringify(item)}`);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
await prisma.websiteHousekeepingPermissions.upsert({
|
|
where: { permission },
|
|
update: {
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
dependsOn,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
create: {
|
|
permission,
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
dependsOn,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
});
|
|
count++;
|
|
} catch {
|
|
errors.push(`Failed to upsert: ${permission}`);
|
|
}
|
|
}
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
return { count, errors };
|
|
}
|
|
|
|
export async function exportPermissions(): Promise<string> {
|
|
await requirePermission(PERMS.SETTINGS_VIEW);
|
|
|
|
const perms = await prisma.websiteHousekeepingPermissions.findMany({
|
|
orderBy: [{ groupName: "asc" }, { permission: "asc" }],
|
|
select: {
|
|
permission: true,
|
|
minRank: true,
|
|
description: true,
|
|
groupName: true,
|
|
dependsOn: true,
|
|
},
|
|
});
|
|
|
|
return JSON.stringify(perms, null, 2);
|
|
}
|
|
|
|
export async function applyPreset(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const preset = String(formData.get("preset") ?? "").trim();
|
|
const userId = await currentUserId();
|
|
|
|
const presets: Record<
|
|
string,
|
|
Array<{
|
|
permission: string;
|
|
minRank: number;
|
|
description?: string;
|
|
groupName?: string;
|
|
}>
|
|
> = {
|
|
moderator: [
|
|
{
|
|
permission: "admin.dashboard",
|
|
minRank: 5,
|
|
description: "Access admin dashboard",
|
|
groupName: "Core",
|
|
},
|
|
{
|
|
permission: "admin.users.view",
|
|
minRank: 5,
|
|
description: "View user profiles",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.ban",
|
|
minRank: 5,
|
|
description: "Ban users",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.room.view",
|
|
minRank: 5,
|
|
description: "View rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.bans.view",
|
|
minRank: 5,
|
|
description: "View bans",
|
|
groupName: "Bans",
|
|
},
|
|
{
|
|
permission: "admin.logs.view",
|
|
minRank: 5,
|
|
description: "View logs",
|
|
groupName: "Logs",
|
|
},
|
|
{
|
|
permission: "admin.wordfilter.view",
|
|
minRank: 5,
|
|
description: "View wordfilter",
|
|
groupName: "Wordfilter",
|
|
},
|
|
{
|
|
permission: "mod.dashboard",
|
|
minRank: 5,
|
|
description: "Mod dashboard",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.view",
|
|
minRank: 5,
|
|
description: "View CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.edit",
|
|
minRank: 5,
|
|
description: "Handle CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.actions",
|
|
minRank: 5,
|
|
description: "Mod actions",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.users.view",
|
|
minRank: 5,
|
|
description: "View users (mod)",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.bans.view",
|
|
minRank: 5,
|
|
description: "View bans (mod)",
|
|
groupName: "Moderation",
|
|
},
|
|
],
|
|
admin: [
|
|
{
|
|
permission: "admin.dashboard",
|
|
minRank: 7,
|
|
description: "Access admin dashboard",
|
|
groupName: "Core",
|
|
},
|
|
{
|
|
permission: "admin.users.view",
|
|
minRank: 7,
|
|
description: "View user profiles",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.edit",
|
|
minRank: 7,
|
|
description: "Edit users",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.ban",
|
|
minRank: 7,
|
|
description: "Ban users",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.reset_password",
|
|
minRank: 7,
|
|
description: "Reset passwords",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.room.view",
|
|
minRank: 7,
|
|
description: "View rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.room.edit",
|
|
minRank: 7,
|
|
description: "Edit rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.room.delete",
|
|
minRank: 7,
|
|
description: "Delete rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.bans.view",
|
|
minRank: 7,
|
|
description: "View bans",
|
|
groupName: "Bans",
|
|
},
|
|
{
|
|
permission: "admin.news.view",
|
|
minRank: 7,
|
|
description: "View news",
|
|
groupName: "News",
|
|
},
|
|
{
|
|
permission: "admin.news.edit",
|
|
minRank: 7,
|
|
description: "Edit news",
|
|
groupName: "News",
|
|
},
|
|
{
|
|
permission: "admin.logs.view",
|
|
minRank: 7,
|
|
description: "View logs",
|
|
groupName: "Logs",
|
|
},
|
|
{
|
|
permission: "admin.settings.view",
|
|
minRank: 7,
|
|
description: "View settings",
|
|
groupName: "Settings",
|
|
},
|
|
{
|
|
permission: "admin.wordfilter.view",
|
|
minRank: 7,
|
|
description: "View wordfilter",
|
|
groupName: "Wordfilter",
|
|
},
|
|
{
|
|
permission: "admin.wordfilter.edit",
|
|
minRank: 7,
|
|
description: "Edit wordfilter",
|
|
groupName: "Wordfilter",
|
|
},
|
|
{
|
|
permission: "admin.catalog.view",
|
|
minRank: 7,
|
|
description: "View catalog",
|
|
groupName: "Catalog",
|
|
},
|
|
{
|
|
permission: "admin.shop.view",
|
|
minRank: 7,
|
|
description: "View shop",
|
|
groupName: "Shop",
|
|
},
|
|
{
|
|
permission: "admin.events.view",
|
|
minRank: 7,
|
|
description: "View events",
|
|
groupName: "Events",
|
|
},
|
|
{
|
|
permission: "admin.tickets.view",
|
|
minRank: 7,
|
|
description: "View tickets",
|
|
groupName: "Tickets",
|
|
},
|
|
{
|
|
permission: "admin.moderation.view",
|
|
minRank: 7,
|
|
description: "View moderation",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "admin.polls.view",
|
|
minRank: 7,
|
|
description: "View polls",
|
|
groupName: "Polls",
|
|
},
|
|
{
|
|
permission: "admin.analytics.view",
|
|
minRank: 7,
|
|
description: "View analytics",
|
|
groupName: "Analytics",
|
|
},
|
|
{
|
|
permission: "admin.prefixes.view",
|
|
minRank: 7,
|
|
description: "View prefixes",
|
|
groupName: "Prefixes",
|
|
},
|
|
{
|
|
permission: "admin.banners.view",
|
|
minRank: 7,
|
|
description: "View banners",
|
|
groupName: "Banners",
|
|
},
|
|
{
|
|
permission: "admin.radio.view",
|
|
minRank: 7,
|
|
description: "View radio",
|
|
groupName: "Radio",
|
|
},
|
|
{
|
|
permission: "admin.rcon.execute",
|
|
minRank: 7,
|
|
description: "Execute RCON",
|
|
groupName: "RCON",
|
|
},
|
|
{
|
|
permission: "mod.dashboard",
|
|
minRank: 7,
|
|
description: "Mod dashboard",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.view",
|
|
minRank: 7,
|
|
description: "View CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.edit",
|
|
minRank: 7,
|
|
description: "Handle CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.actions",
|
|
minRank: 7,
|
|
description: "Mod actions",
|
|
groupName: "Moderation",
|
|
},
|
|
],
|
|
superadmin: [
|
|
{
|
|
permission: "admin.dashboard",
|
|
minRank: 8,
|
|
description: "Access admin dashboard",
|
|
groupName: "Core",
|
|
},
|
|
{
|
|
permission: "admin.users.view",
|
|
minRank: 8,
|
|
description: "View user profiles",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.edit",
|
|
minRank: 8,
|
|
description: "Edit users",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.ban",
|
|
minRank: 8,
|
|
description: "Ban users",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.users.reset_password",
|
|
minRank: 8,
|
|
description: "Reset passwords",
|
|
groupName: "Users",
|
|
},
|
|
{
|
|
permission: "admin.room.view",
|
|
minRank: 8,
|
|
description: "View rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.room.edit",
|
|
minRank: 8,
|
|
description: "Edit rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.room.delete",
|
|
minRank: 8,
|
|
description: "Delete rooms",
|
|
groupName: "Rooms",
|
|
},
|
|
{
|
|
permission: "admin.bans.view",
|
|
minRank: 8,
|
|
description: "View bans",
|
|
groupName: "Bans",
|
|
},
|
|
{
|
|
permission: "admin.news.view",
|
|
minRank: 8,
|
|
description: "View news",
|
|
groupName: "News",
|
|
},
|
|
{
|
|
permission: "admin.news.edit",
|
|
minRank: 8,
|
|
description: "Edit news",
|
|
groupName: "News",
|
|
},
|
|
{
|
|
permission: "admin.logs.view",
|
|
minRank: 8,
|
|
description: "View logs",
|
|
groupName: "Logs",
|
|
},
|
|
{
|
|
permission: "admin.settings.view",
|
|
minRank: 8,
|
|
description: "View settings",
|
|
groupName: "Settings",
|
|
},
|
|
{
|
|
permission: "admin.settings.edit",
|
|
minRank: 8,
|
|
description: "Edit settings",
|
|
groupName: "Settings",
|
|
},
|
|
{
|
|
permission: "admin.permissions.manage",
|
|
minRank: 8,
|
|
description: "Manage permissions",
|
|
groupName: "Settings",
|
|
},
|
|
{
|
|
permission: "admin.wordfilter.view",
|
|
minRank: 8,
|
|
description: "View wordfilter",
|
|
groupName: "Wordfilter",
|
|
},
|
|
{
|
|
permission: "admin.wordfilter.edit",
|
|
minRank: 8,
|
|
description: "Edit wordfilter",
|
|
groupName: "Wordfilter",
|
|
},
|
|
{
|
|
permission: "admin.catalog.view",
|
|
minRank: 8,
|
|
description: "View catalog",
|
|
groupName: "Catalog",
|
|
},
|
|
{
|
|
permission: "admin.catalog.edit",
|
|
minRank: 8,
|
|
description: "Edit catalog",
|
|
groupName: "Catalog",
|
|
},
|
|
{
|
|
permission: "admin.shop.view",
|
|
minRank: 8,
|
|
description: "View shop",
|
|
groupName: "Shop",
|
|
},
|
|
{
|
|
permission: "admin.shop.edit",
|
|
minRank: 8,
|
|
description: "Edit shop",
|
|
groupName: "Shop",
|
|
},
|
|
{
|
|
permission: "admin.events.view",
|
|
minRank: 8,
|
|
description: "View events",
|
|
groupName: "Events",
|
|
},
|
|
{
|
|
permission: "admin.events.edit",
|
|
minRank: 8,
|
|
description: "Edit events",
|
|
groupName: "Events",
|
|
},
|
|
{
|
|
permission: "admin.tickets.view",
|
|
minRank: 8,
|
|
description: "View tickets",
|
|
groupName: "Tickets",
|
|
},
|
|
{
|
|
permission: "admin.tickets.edit",
|
|
minRank: 8,
|
|
description: "Edit tickets",
|
|
groupName: "Tickets",
|
|
},
|
|
{
|
|
permission: "admin.moderation.view",
|
|
minRank: 8,
|
|
description: "View moderation",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "admin.moderation.edit",
|
|
minRank: 8,
|
|
description: "Edit moderation",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "admin.polls.view",
|
|
minRank: 8,
|
|
description: "View polls",
|
|
groupName: "Polls",
|
|
},
|
|
{
|
|
permission: "admin.polls.edit",
|
|
minRank: 8,
|
|
description: "Edit polls",
|
|
groupName: "Polls",
|
|
},
|
|
{
|
|
permission: "admin.analytics.view",
|
|
minRank: 8,
|
|
description: "View analytics",
|
|
groupName: "Analytics",
|
|
},
|
|
{
|
|
permission: "admin.analytics.export",
|
|
minRank: 8,
|
|
description: "Export analytics",
|
|
groupName: "Analytics",
|
|
},
|
|
{
|
|
permission: "admin.devops.view",
|
|
minRank: 8,
|
|
description: "View devops",
|
|
groupName: "DevOps",
|
|
},
|
|
{
|
|
permission: "admin.devops.edit",
|
|
minRank: 8,
|
|
description: "Edit devops",
|
|
groupName: "DevOps",
|
|
},
|
|
{
|
|
permission: "admin.prefixes.view",
|
|
minRank: 8,
|
|
description: "View prefixes",
|
|
groupName: "Prefixes",
|
|
},
|
|
{
|
|
permission: "admin.prefixes.edit",
|
|
minRank: 8,
|
|
description: "Edit prefixes",
|
|
groupName: "Prefixes",
|
|
},
|
|
{
|
|
permission: "admin.banners.view",
|
|
minRank: 8,
|
|
description: "View banners",
|
|
groupName: "Banners",
|
|
},
|
|
{
|
|
permission: "admin.banners.edit",
|
|
minRank: 8,
|
|
description: "Edit banners",
|
|
groupName: "Banners",
|
|
},
|
|
{
|
|
permission: "admin.radio.view",
|
|
minRank: 8,
|
|
description: "View radio",
|
|
groupName: "Radio",
|
|
},
|
|
{
|
|
permission: "admin.radio.edit",
|
|
minRank: 8,
|
|
description: "Edit radio",
|
|
groupName: "Radio",
|
|
},
|
|
{
|
|
permission: "admin.rcon.execute",
|
|
minRank: 8,
|
|
description: "Execute RCON",
|
|
groupName: "RCON",
|
|
},
|
|
{
|
|
permission: "admin.assets.import",
|
|
minRank: 8,
|
|
description: "Import assets",
|
|
groupName: "Assets",
|
|
},
|
|
{
|
|
permission: "admin.export",
|
|
minRank: 8,
|
|
description: "Export data",
|
|
groupName: "Export",
|
|
},
|
|
{
|
|
permission: "admin.notifications.view",
|
|
minRank: 8,
|
|
description: "View notifications",
|
|
groupName: "Notifications",
|
|
},
|
|
{
|
|
permission: "admin.notifications.edit",
|
|
minRank: 8,
|
|
description: "Edit notifications",
|
|
groupName: "Notifications",
|
|
},
|
|
{
|
|
permission: "admin.pages.view",
|
|
minRank: 8,
|
|
description: "View CMS pages",
|
|
groupName: "CMS Pages",
|
|
},
|
|
{
|
|
permission: "admin.pages.edit",
|
|
minRank: 8,
|
|
description: "Edit CMS pages",
|
|
groupName: "CMS Pages",
|
|
},
|
|
{
|
|
permission: "mod.dashboard",
|
|
minRank: 8,
|
|
description: "Mod dashboard",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.view",
|
|
minRank: 8,
|
|
description: "View CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.cfh.edit",
|
|
minRank: 8,
|
|
description: "Handle CFH",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.actions",
|
|
minRank: 8,
|
|
description: "Mod actions",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.team.view",
|
|
minRank: 8,
|
|
description: "View mod team",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.tickets.view",
|
|
minRank: 8,
|
|
description: "View mod tickets",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.tickets.edit",
|
|
minRank: 8,
|
|
description: "Edit mod tickets",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.users.view",
|
|
minRank: 8,
|
|
description: "View users (mod)",
|
|
groupName: "Moderation",
|
|
},
|
|
{
|
|
permission: "mod.bans.view",
|
|
minRank: 8,
|
|
description: "View bans (mod)",
|
|
groupName: "Moderation",
|
|
},
|
|
],
|
|
};
|
|
|
|
const selected = presets[preset];
|
|
if (!selected) return;
|
|
|
|
for (const item of selected) {
|
|
const { permission, minRank, description, groupName } = item;
|
|
await prisma.websiteHousekeepingPermissions.upsert({
|
|
where: { permission },
|
|
update: {
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
create: {
|
|
permission,
|
|
minRank,
|
|
description,
|
|
groupName,
|
|
updatedBy: userId ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.preset.apply",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
after: { preset, count: selected.length },
|
|
});
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
}
|
|
|
|
export async function clearAllPermissions(): Promise<void> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const userId = await currentUserId();
|
|
const count = await prisma.websiteHousekeepingPermissions.count();
|
|
|
|
await prisma.websiteHousekeepingPermissions.deleteMany();
|
|
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.permission.clear_all",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
after: { deletedCount: count },
|
|
});
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
}
|
|
|
|
export async function bulkDeletePermissions(
|
|
ids: bigint[],
|
|
): Promise<{ count: number }> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
if (!ids.length) return { count: 0 };
|
|
|
|
const userId = await currentUserId();
|
|
|
|
await prisma.websiteHousekeepingPermissions.deleteMany({
|
|
where: { id: { in: ids } },
|
|
});
|
|
|
|
await logAudit({
|
|
userId: userId ?? 0,
|
|
action: "housekeeping.permission.bulk_delete",
|
|
target: "WebsiteHousekeepingPermissions",
|
|
after: { deletedCount: ids.length },
|
|
});
|
|
|
|
revalidatePath("/admin/housekeeping");
|
|
return { count: ids.length };
|
|
}
|