feat(admin): extend housekeeping permissions page with groups, presets, audit, test, import/export, rank overview and dependency tracking
Local Build and Deploy / deploy (push) Successful in 1m14s
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
This commit is contained in:
1 parent
49d204e85c
commit
98d4b715df
7 files changed
+1636
-220
No files matched your search
@@ -2,37 +2,90 @@
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or update a housekeeping permission (keyed by the unique `permission`
|
||||
* string). Mirrors AtomCMS' housekeeping permission management.
|
||||
*/
|
||||
export async function upsertPermission(formData: FormData): Promise<void> {
|
||||
await requirePermission(PERMS.SETTINGS_EDIT);
|
||||
|
||||
const permission = String(formData.get("permission") ?? "")
|
||||
.normalize("NFC")
|
||||
.trim()
|
||||
.slice(0, 255);
|
||||
const permission = sanitize(String(formData.get("permission") ?? ""));
|
||||
const minRank = Number(formData.get("minRank"));
|
||||
const descriptionRaw = String(formData.get("description") ?? "")
|
||||
.normalize("NFC")
|
||||
.trim()
|
||||
.slice(0, 255);
|
||||
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 {
|
||||
await prisma.websiteHousekeepingPermissions.upsert({
|
||||
const existing = await prisma.websiteHousekeepingPermissions.findUnique({
|
||||
where: { permission },
|
||||
update: { minRank, description },
|
||||
create: { permission, minRank, description },
|
||||
});
|
||||
|
||||
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: duplicate/constraint issues shouldn't crash the action.
|
||||
// Swallow
|
||||
}
|
||||
|
||||
revalidatePath("/admin/housekeeping");
|
||||
@@ -44,10 +97,29 @@ export async function deletePermission(formData: FormData): Promise<void> {
|
||||
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||
if (!raw) return;
|
||||
|
||||
const userId = await currentUserId();
|
||||
|
||||
try {
|
||||
await prisma.websiteHousekeepingPermissions.delete({
|
||||
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.
|
||||
}
|
||||
@@ -55,25 +127,771 @@ export async function deletePermission(formData: FormData): Promise<void> {
|
||||
revalidatePath("/admin/housekeeping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Export housekeeping permissions as plain JSON-serializable rows
|
||||
* (used by the admin export UI).
|
||||
*/
|
||||
export async function exportPermissions(): Promise<
|
||||
{ permission: string; minRank: number; description: string | null }[]
|
||||
> {
|
||||
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);
|
||||
|
||||
try {
|
||||
return await prisma.websiteHousekeepingPermissions.findMany({
|
||||
orderBy: { permission: "asc" },
|
||||
select: {
|
||||
permission: true,
|
||||
minRank: true,
|
||||
description: true,
|
||||
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,
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in new issue
Block a user