Local Build and Deploy / deploy (push) Successful in 1m14s
- Add group_name, depends_on and updated_by columns to website_housekeeping_permissions - Add migration 0016 for new columns - Rewrite housekeeping page with 7 tabs: Overview, Manage, Import/Export, Rank overview, Presets, Test, Audit log - Add permission groups/categories with filtering - Add bulk JSON import/export - Add rank overview grouped by permission category - Add permission presets for Moderator (rank 5), Admin (rank 7), Super Admin (rank 8) - Add audit logging for all permission changes via AdminAuditLog - Add test mode to check permissions by rank - Add dependency tracking with warnings for missing dependencies - Add overview dashboard with statistics - Add Dutch and English translations for all new features
898 lines
19 KiB
TypeScript
898 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<void> {
|
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
|
|
|
const raw = sanitize(String(formData.get("json") ?? ""));
|
|
const errors: string[] = [];
|
|
let count = 0;
|
|
|
|
if (!raw) return;
|
|
|
|
let parsed: unknown;
|
|
try {
|
|
parsed = JSON.parse(raw);
|
|
} catch {
|
|
return;
|
|
}
|
|
|
|
if (!Array.isArray(parsed)) {
|
|
return;
|
|
}
|
|
|
|
const userId = await currentUserId();
|
|
|
|
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");
|
|
}
|
|
|
|
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 testRankPermission(formData: FormData): Promise<void> {
|
|
await requirePermission(PERMS.SETTINGS_VIEW);
|
|
|
|
const rank = Number(formData.get("testRank"));
|
|
if (!Number.isFinite(rank) || rank < 0) return;
|
|
|
|
const perms = await prisma.websiteHousekeepingPermissions.findMany({
|
|
where: { minRank: { lte: rank } },
|
|
orderBy: [{ groupName: "asc" }, { permission: "asc" }],
|
|
select: { permission: true },
|
|
});
|
|
|
|
const granted = perms.map((p) => p.permission);
|
|
// Result is displayed via the revalidated page
|
|
revalidatePath(
|
|
"/admin/housekeeping?tab=test&result=" +
|
|
encodeURIComponent(JSON.stringify({ rank, permissions: granted })),
|
|
);
|
|
}
|
|
|
|
export async function getAuditHistory(): Promise<unknown[]> {
|
|
await requirePermission(PERMS.SETTINGS_VIEW);
|
|
|
|
const { getAuditLogs } = await import("@/lib/services/audit");
|
|
const result = await getAuditLogs({ search: "housekeeping", perPage: 50 });
|
|
return result.rows;
|
|
}
|