"use server"; import { revalidateTag } from "next/cache"; import { z } from "zod"; import { PERMS } from "@/lib/permission-slugs"; import { prisma } from "@/lib/prisma"; import { adminAction } from "@/lib/safe-action"; import { ActionError, actionOk } from "@/lib/safe-action-shared"; import { createEmulatorRank, deleteEmulatorRank, updateEmulatorRank, } from "@/lib/services/permission-ranks"; import { rcon } from "@/lib/services/rcon"; import { logStaffActivity } from "@/lib/services/staff-activity"; const createRankSchema = z.object({ rank_name: z.string().trim().min(1).max(25), level: z.coerce.number().int().min(1), }); export const createRank = adminAction( { schema: createRankSchema, permission: PERMS.PERMISSIONS_MANAGE }, async (ctx) => { const id = await createEmulatorRank(prisma, ctx.data); await prisma.aclRole.upsert({ where: { slug: `rank_${id}` }, create: { slug: `rank_${id}`, title: ctx.data.rank_name, description: "CMS role synchronized from permission_ranks", }, update: { title: ctx.data.rank_name }, }); await logStaffActivity({ staffId: ctx.session.user.id, action: "rank_create", description: `Created rank #${id}`, targetType: "rank", targetId: id, }); await rcon.send("updatepermissions"); revalidateTag("permissions", { expire: 0 }); return actionOk({ id }); }, ); const deleteRankSchema = z.object({ id: z.coerce.number().int().positive() }); export const deleteRank = adminAction( { schema: deleteRankSchema, permission: PERMS.PERMISSIONS_MANAGE }, async (ctx) => { const users = await prisma.user.count({ where: { rank: ctx.data.id } }); if (users > 0) throw new ActionError(`Cannot delete: ${users} users have this rank`); const role = await prisma.aclRole.findFirst({ where: { slug: `rank_${ctx.data.id}` }, }); await deleteEmulatorRank(prisma, ctx.data.id); if (role) { await prisma.$transaction([ prisma.aclModelPermission.deleteMany({ where: { modelId: role.id, modelType: "Role" }, }), prisma.aclModelRole.deleteMany({ where: { roleId: role.id } }), prisma.aclRole.delete({ where: { id: role.id } }), ]); } await logStaffActivity({ staffId: ctx.session.user.id, action: "rank_delete", description: `Deleted rank #${ctx.data.id}`, targetType: "rank", targetId: ctx.data.id, }); await rcon.send("updatepermissions"); revalidateTag("permissions", { expire: 0 }); return actionOk(); }, ); const saveRankSchema = z.object({ id: z.coerce.number().int().positive(), fields: z.record(z.string(), z.union([z.string(), z.number()])), }); export const saveRank = adminAction( { schema: saveRankSchema, permission: PERMS.PERMISSIONS_MANAGE }, async (ctx) => { await updateEmulatorRank(prisma, ctx.data.id, ctx.data.fields); if (typeof ctx.data.fields.rank_name === "string") { await prisma.aclRole.updateMany({ where: { slug: `rank_${ctx.data.id}` }, data: { title: ctx.data.fields.rank_name }, }); } await logStaffActivity({ staffId: ctx.session.user.id, action: "rank_update", description: `Updated rank #${ctx.data.id}`, targetType: "rank", targetId: ctx.data.id, }); await rcon.send("updatepermissions"); revalidateTag("permissions", { expire: 0 }); return actionOk(); }, ); const setCmsPermsSchema = z.object({ roleId: z.coerce.number().int().positive(), permissionSlugs: z.array(z.string().trim().min(1)).max(500), }); export const setCmsPermissions = adminAction( { schema: setCmsPermsSchema, permission: PERMS.PERMISSIONS_MANAGE }, async (ctx) => { const role = await prisma.aclRole.findUnique({ where: { id: ctx.data.roleId }, select: { id: true, slug: true }, }); if (!role) throw new ActionError("Role not found"); const permissions = await prisma.aclPermission.findMany({ where: { slug: { in: ctx.data.permissionSlugs } }, select: { id: true }, }); await prisma.$transaction(async (tx) => { await tx.aclModelPermission.deleteMany({ where: { modelId: role.id, modelType: "Role" }, }); if (permissions.length) { await tx.aclModelPermission.createMany({ data: permissions.map((permission) => ({ modelId: role.id, modelType: "Role", permissionId: permission.id, })), }); } }); await logStaffActivity({ staffId: ctx.session.user.id, action: "acl_role_permissions_update", description: `Updated ${permissions.length} permissions for ${role.slug}`, targetType: "acl_role", targetId: role.id, }); revalidateTag("permissions", { expire: 0 }); return actionOk(); }, ); /** * Re-apply the same grant repair as migration 0018: * - ranks with admin.dashboard get all admin.* * - ranks >= 6 get admin.*.view + dashboard * - ranks >= 7 get edit/manage/execute tools used by the sidebar */ export const repairAdminNavAclGrants = adminAction( { permission: PERMS.PERMISSIONS_MANAGE }, async (ctx) => { const dashboardFill = await prisma.$executeRaw` INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`) SELECT 'Role', ar.id, ap.id FROM \`acl_roles\` ar JOIN \`acl_permissions\` ap ON ap.slug LIKE 'admin.%' WHERE EXISTS ( SELECT 1 FROM \`acl_model_permissions\` amp JOIN \`acl_permissions\` apdash ON apdash.id = amp.permission_id WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND apdash.slug = 'admin.dashboard' ) AND NOT EXISTS ( SELECT 1 FROM \`acl_model_permissions\` amp2 WHERE amp2.model_type = 'Role' AND amp2.model_id = ar.id AND amp2.permission_id = ap.id ) `; const midRankViews = await prisma.$executeRaw` INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`) SELECT 'Role', ar.id, ap.id FROM \`permission_ranks\` pr JOIN \`acl_roles\` ar ON ar.slug = CONCAT('rank_', pr.id) JOIN \`acl_permissions\` ap ON ( ap.slug = 'admin.dashboard' OR (ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.view') ) WHERE pr.id >= 6 AND NOT EXISTS ( SELECT 1 FROM \`acl_model_permissions\` amp WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id ) `; const highRankTools = await prisma.$executeRaw` INSERT INTO \`acl_model_permissions\` (\`model_type\`, \`model_id\`, \`permission_id\`) SELECT 'Role', ar.id, ap.id FROM \`permission_ranks\` pr JOIN \`acl_roles\` ar ON ar.slug = CONCAT('rank_', pr.id) JOIN \`acl_permissions\` ap ON ( (ap.slug LIKE 'admin.%' AND ap.slug LIKE '%.edit') OR ap.slug IN ( 'admin.permissions.manage', 'admin.rcon.execute', 'admin.assets.import', 'admin.export', 'admin.analytics.export', 'admin.users.ban', 'admin.users.reset_password', 'admin.room.delete' ) ) WHERE pr.id >= 7 AND NOT EXISTS ( SELECT 1 FROM \`acl_model_permissions\` amp WHERE amp.model_type = 'Role' AND amp.model_id = ar.id AND amp.permission_id = ap.id ) `; const inserted = Number(dashboardFill) + Number(midRankViews) + Number(highRankTools); await logStaffActivity({ staffId: ctx.session.user.id, action: "acl_nav_grants_repair", description: `Repaired admin nav ACL grants (${inserted} rows inserted)`, targetType: "acl", targetId: 0, }); revalidateTag("permissions", { expire: 0 }); return actionOk({ inserted }); }, );