Finish fine-grained ACL across remaining admin pages and actions.
Local Build and Deploy / deploy (push) Successful in 56s

Replace leftover requireStaff gates with module PERMS, drop hardcoded room rank thresholds, and expand contract tests so admin mutations cannot regress to dashboard-only checks.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
SimoandCursor committed 2026-07-15 20:15:22 +02:00
1 parent 3c8a8ff888
commit 0e89d03940
63 files changed
+420 -163

No files matched your search

+5 -4
View File
@@ -2,7 +2,8 @@
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -11,7 +12,7 @@ import { logStaffActivity } from "@/lib/services/staff-activity";
// table; it only stores an image URL rendered in the site layout/widgets.
export async function createAd(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const image = String(formData.get("image") ?? "")
.normalize("NFC")
.trim()
@@ -39,7 +40,7 @@ export async function createAd(formData: FormData): Promise<void> {
}
export async function updateAd(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const raw = String(formData.get("id") ?? "").normalize("NFC");
if (!/^\d+$/.test(raw)) return;
const id = BigInt(raw);
@@ -70,7 +71,7 @@ export async function updateAd(formData: FormData): Promise<void> {
}
export async function deleteAd(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+3 -2
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { rcon } from "@/lib/services/rcon";
/**
@@ -11,7 +12,7 @@ import { rcon } from "@/lib/services/rcon";
* `message` payload. Staff-gated; the message is trimmed/bounded before send.
*/
export async function sendHotelAlert(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.NOTIFICATIONS_EDIT);
const message = String(formData.get("message") ?? "")
.normalize("NFC")
+3 -2
View File
@@ -1,12 +1,13 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
export async function dismissApplication(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+3 -2
View File
@@ -3,7 +3,8 @@
import { writeFile } from "node:fs/promises";
import path from "node:path";
import { redirect } from "next/navigation";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { logStaffActivity } from "@/lib/services/staff-activity";
// Writes a badge image to the configured emulator badge directory. The path is
@@ -20,7 +21,7 @@ function back(param: string, value: string): never {
}
export async function uploadBadge(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const dir = process.env.BADGE_UPLOAD_DIR;
if (!dir) {
+3 -2
View File
@@ -1,12 +1,13 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
export async function giveBadge(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
const userId = Number(formData.get("userId"));
const code = String(formData.get("code") ?? "")
+5 -4
View File
@@ -1,12 +1,13 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
export async function createEmailTemplate(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const name = String(formData.get("name") ?? "")
.normalize("NFC")
.trim()
@@ -35,7 +36,7 @@ export async function createEmailTemplate(formData: FormData): Promise<void> {
}
export async function updateEmailTemplate(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const raw = String(formData.get("id") ?? "").normalize("NFC");
if (!raw) return;
let id: bigint;
@@ -68,7 +69,7 @@ export async function updateEmailTemplate(formData: FormData): Promise<void> {
}
export async function deleteEmailTemplate(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
await prisma.emailTemplates.delete({ where: { id } });
+5 -4
View File
@@ -2,7 +2,8 @@
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -16,7 +17,7 @@ function parsePosition(value: FormDataEntryValue | null): number {
}
export async function createHelpQuestion(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const name = String(formData.get("name") ?? "")
.normalize("NFC")
.trim()
@@ -82,7 +83,7 @@ export async function createHelpQuestion(formData: FormData): Promise<void> {
}
export async function updateHelpQuestion(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
@@ -149,7 +150,7 @@ export async function updateHelpQuestion(formData: FormData): Promise<void> {
}
export async function deleteHelpQuestion(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+4 -3
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
/**
@@ -9,7 +10,7 @@ import { prisma } from "@/lib/prisma";
* string). Mirrors AtomCMS' housekeeping permission management.
*/
export async function upsertPermission(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const permission = String(formData.get("permission") ?? "")
.normalize("NFC")
@@ -38,7 +39,7 @@ export async function upsertPermission(formData: FormData): Promise<void> {
}
export async function deletePermission(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const raw = String(formData.get("id") ?? "").normalize("NFC");
if (!raw) return;
+6 -5
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
function parseIp(formData: FormData): string {
@@ -20,7 +21,7 @@ function parseAsn(formData: FormData): string | null {
}
export async function addWhitelist(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const ipAddress = parseIp(formData);
if (!ipAddress) return;
const asn = parseAsn(formData);
@@ -31,7 +32,7 @@ export async function addWhitelist(formData: FormData): Promise<void> {
}
export async function deleteWhitelist(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const raw = String(formData.get("id") ?? "")
.normalize("NFC")
.trim();
@@ -41,7 +42,7 @@ export async function deleteWhitelist(formData: FormData): Promise<void> {
}
export async function addBlacklist(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const ipAddress = parseIp(formData);
if (!ipAddress) return;
const asn = parseAsn(formData);
@@ -52,7 +53,7 @@ export async function addBlacklist(formData: FormData): Promise<void> {
}
export async function deleteBlacklist(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
const raw = String(formData.get("id") ?? "")
.normalize("NFC")
.trim();
+3 -2
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
@@ -35,7 +36,7 @@ async function upsertSetting(key: string, value: string): Promise<void> {
}
export async function saveMaintenance(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SETTINGS_EDIT);
// Checkbox: present only when ticked. Normalise to the '1'/'0' string the
// emulator/Laravel side expects.
+5 -4
View File
@@ -3,14 +3,15 @@
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
const MEDIA_DIR = "public/assets/images/media";
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
const ALLOWED = ["image/png", "image/jpeg", "image/gif", "image/webp"];
export async function uploadMedia(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const file = formData.get("file") as File | null;
if (!file || file.size === 0) return;
if (file.size > MAX_SIZE) return;
@@ -33,7 +34,7 @@ export async function uploadMedia(formData: FormData): Promise<void> {
}
export async function deleteMedia(name: string): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const { unlink } = await import("node:fs/promises");
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
const filePath = path.resolve(baseDir, name);
@@ -50,7 +51,7 @@ export async function deleteMedia(name: string): Promise<void> {
export async function uploadMediaAndReturn(
formData: FormData,
): Promise<string> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const file = formData.get("file") as File | null;
if (!file || file.size === 0) return "";
if (file.size > MAX_SIZE) return "";
+5 -4
View File
@@ -2,7 +2,8 @@
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
import { logServerError } from "@/lib/server-log";
@@ -23,7 +24,7 @@ function parseMinRank(formData: FormData): number {
}
export async function createPermission(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PERMISSIONS_MANAGE);
const permission = String(formData.get("permission") ?? "")
.normalize("NFC")
.trim()
@@ -67,7 +68,7 @@ export async function createPermission(formData: FormData): Promise<void> {
}
export async function updatePermission(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PERMISSIONS_MANAGE);
const raw = String(formData.get("id") ?? "").normalize("NFC");
if (!raw) return;
const id = BigInt(raw);
@@ -106,7 +107,7 @@ export async function updatePermission(formData: FormData): Promise<void> {
}
export async function deletePermission(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PERMISSIONS_MANAGE);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+3 -2
View File
@@ -1,11 +1,12 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export async function deletePhoto(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.PAGES_EDIT);
const id = Number(formData.get("id"));
if (!(id > 0)) return;
+6 -5
View File
@@ -1,12 +1,13 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
export async function createCategory(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const name = String(formData.get("name") ?? "")
.normalize("NFC")
.trim()
@@ -33,7 +34,7 @@ export async function createCategory(formData: FormData): Promise<void> {
}
export async function deleteCategory(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
@@ -48,7 +49,7 @@ export async function deleteCategory(formData: FormData): Promise<void> {
}
export async function createValue(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const categoryId = formPositiveBigInt(formData, "categoryId");
if (!categoryId) return;
@@ -98,7 +99,7 @@ export async function createValue(formData: FormData): Promise<void> {
}
export async function deleteValue(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+5 -4
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -31,7 +32,7 @@ function normaliseColor(raw: string): string {
// ── Tags CRUD (tags + taggables, AtomCMS article tags/categories) ──────────
export async function createTag(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const name = str(formData.get("name")).trim().slice(0, 255);
if (!name) return;
@@ -56,7 +57,7 @@ export async function createTag(formData: FormData): Promise<void> {
}
export async function updateTag(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = parseId(formData.get("id"));
if (id === null) return;
@@ -83,7 +84,7 @@ export async function updateTag(formData: FormData): Promise<void> {
}
export async function deleteTag(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = parseId(formData.get("id"));
if (id === null) return;
+4 -3
View File
@@ -1,11 +1,12 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export async function createTeam(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const rankName = String(formData.get("rankName") ?? "")
.normalize("NFC")
@@ -41,7 +42,7 @@ export async function createTeam(formData: FormData): Promise<void> {
}
export async function deleteTeam(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const id = BigInt(String(formData.get("id")));
await prisma.websiteTeams.delete({ where: { id } });
+7 -6
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff, requireStaffRateLimited } from "@/lib/admin/guard";
import { requirePermission, requirePermissionRateLimited } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
import { type CurrencyName, sendCurrency } from "@/lib/services/send-currency";
@@ -15,7 +16,7 @@ const CURRENCIES: ReadonlySet<string> = new Set([
]);
export async function giveCurrency(formData: FormData): Promise<void> {
const staff = await requireStaffRateLimited();
const staff = await requirePermissionRateLimited(PERMS.USERS_EDIT);
const userId = Number(formData.get("userId"));
const type = String(formData.get("type"));
const amount = Number(formData.get("amount"));
@@ -38,7 +39,7 @@ export async function giveCurrency(formData: FormData): Promise<void> {
}
export async function setMotto(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const userId = Number(formData.get("userId"));
const motto = String(formData.get("motto") ?? "")
.normalize("NFC")
@@ -51,7 +52,7 @@ export async function setMotto(formData: FormData): Promise<void> {
}
export async function setRank(formData: FormData): Promise<void> {
const staff = await requireStaffRateLimited();
const staff = await requirePermissionRateLimited(PERMS.USERS_EDIT);
const userId = Number(formData.get("userId"));
const rank = Number(formData.get("rank"));
if (userId > 0 && rank > 0) {
@@ -69,7 +70,7 @@ export async function setRank(formData: FormData): Promise<void> {
}
export async function alertUser(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const userId = Number(formData.get("userId"));
const message = String(formData.get("message") ?? "")
.normalize("NFC")
@@ -78,7 +79,7 @@ export async function alertUser(formData: FormData): Promise<void> {
}
export async function disconnectUser(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.USERS_EDIT);
const userId = Number(formData.get("userId"));
const username = String(formData.get("username") ?? "").normalize("NFC");
if (userId > 0) await rcon.disconnectUser(userId, username);
+4 -3
View File
@@ -1,13 +1,14 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { formPositiveBigInt } from "@/lib/form-data";
import { prisma } from "@/lib/prisma";
import { logServerError } from "@/lib/server-log";
export async function createVoucher(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const code = String(formData.get("code") ?? "")
.normalize("NFC")
@@ -53,7 +54,7 @@ export async function createVoucher(formData: FormData): Promise<void> {
}
export async function deleteVoucher(formData: FormData): Promise<void> {
await requireStaff();
await requirePermission(PERMS.SHOP_EDIT);
const id = formPositiveBigInt(formData, "id");
if (!id) return;
+3 -2
View File
@@ -2,7 +2,8 @@
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { siteSettings } from "@/lib/services/site-settings";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -28,7 +29,7 @@ async function writeSetting(
}
export async function saveVpn(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.SETTINGS_EDIT);
// Toggle: an unchecked checkbox submits nothing, so absence === disabled.
const enabled =
+6 -5
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -40,7 +41,7 @@ function revalidate(): void {
}
export async function createBox(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const title = String(formData.get("title") ?? "")
.normalize("NFC")
@@ -82,7 +83,7 @@ export async function createBox(formData: FormData): Promise<void> {
}
export async function updateBox(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = parseId(formData);
if (id == null) return;
@@ -125,7 +126,7 @@ export async function updateBox(formData: FormData): Promise<void> {
}
export async function deleteBox(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = parseId(formData);
if (id == null) return;
@@ -147,7 +148,7 @@ export async function deleteBox(formData: FormData): Promise<void> {
}
export async function toggleBox(formData: FormData): Promise<void> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.PAGES_EDIT);
const id = parseId(formData);
if (id == null) return;
+4 -3
View File
@@ -1,11 +1,12 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export async function getBadgeData({ code }: { code: string }) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
const badge = await prisma.websiteBadges.findUnique({
where: { badgeKey: code },
select: { badgeName: true, badgeDescription: true },
@@ -26,7 +27,7 @@ export async function updateBadge({
name: string;
desc: string;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.websiteBadges.upsert({
where: { badgeKey: code },
update: { badgeName: name, badgeDescription: desc, updatedAt: new Date() },
+6 -5
View File
@@ -1,6 +1,7 @@
"use server";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import type { ActionResult } from "@/lib/safe-action-shared";
import { rcon } from "@/lib/services/rcon";
@@ -11,7 +12,7 @@ export async function bulkUnban({
}: {
userIds: number[];
}): Promise<ActionResult<{ unbanned: number; total: number }>> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.USERS_EDIT);
const result = await prisma.ban.deleteMany({
where: { userId: { in: userIds } },
});
@@ -36,7 +37,7 @@ export async function bulkBan({
reason: string;
duration: number;
}): Promise<ActionResult<{ banned: number }>> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.USERS_EDIT);
const now = Math.floor(Date.now() / 1000);
let banned = 0;
@@ -84,7 +85,7 @@ export async function bulkGiveCurrency({
failedIds: Array<{ userId: number; reason: string }>;
}>
> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.USERS_EDIT);
let given = 0;
const failedIds: Array<{ userId: number; reason: string }> = [];
@@ -142,7 +143,7 @@ export async function bulkGiveBadge({
failedIds: Array<{ userId: number; reason: string }>;
}>
> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.USERS_EDIT);
let given = 0;
const failedIds: Array<{ userId: number; reason: string }> = [];
+8 -7
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -10,7 +11,7 @@ export async function updateBcPage({
id,
...fields
}: { id: number } & Record<string, unknown>) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogPagesBc.update({ where: { id }, data: fields as any });
await rcon.updateCatalog();
await logStaffActivity({
@@ -25,7 +26,7 @@ export async function updateBcPage({
}
export async function deleteBcItem({ id }: { id: number }) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItemsBc.delete({ where: { id } });
await logStaffActivity({
staffId: staff.id,
@@ -47,7 +48,7 @@ export async function updateBcItem({
orderNumber?: number;
extradata?: string;
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItemsBc.update({ where: { id }, data: data as any });
await logStaffActivity({
staffId: staff.id,
@@ -69,7 +70,7 @@ export async function createBcItem({
orderNumber: number;
extradata: string;
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const created = await prisma.catalogItemsBc.create({
data: { pageId, ...data },
});
@@ -90,7 +91,7 @@ export async function toggleBcPage({
id: number;
field: "enabled" | "visible";
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
const page = await prisma.catalogPagesBc.findUnique({
where: { id },
select: { enabled: true, visible: true },
@@ -108,7 +109,7 @@ export async function createBcPage(input: {
parentId: number;
pageLayout: string;
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const created = await prisma.catalogPagesBc.create({
data: {
caption: input.caption,
+8 -7
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
@@ -23,7 +24,7 @@ export async function createCatalogItem(data: {
haveOffer: "0" | "1";
clubOnly: "0" | "1";
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const created = await prisma.catalogItems.create({ data });
await rcon.updateCatalog();
await logStaffActivity({
@@ -38,7 +39,7 @@ export async function createCatalogItem(data: {
}
export async function deleteCatalogItems({ ids }: { ids: number[] }) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItems.deleteMany({ where: { id: { in: ids } } });
await rcon.updateCatalog();
await logStaffActivity({
@@ -58,7 +59,7 @@ export async function moveCatalogItems({
ids: number[];
targetPageId: number;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItems.updateMany({
where: { id: { in: ids } },
data: { pageId: targetPageId },
@@ -73,7 +74,7 @@ export async function reorderCatalogItems({
}: {
orders: Array<{ id: number; orderNumber: number }>;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
for (const { id, orderNumber } of orders) {
await prisma.catalogItems.update({ where: { id }, data: { orderNumber } });
}
@@ -91,7 +92,7 @@ export async function updateCatalogItem({
catalogFields: Record<string, unknown>;
baseItem?: { id: number; fields: Record<string, unknown> };
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogItems.update({
where: { id },
data: catalogFields as any,
@@ -119,7 +120,7 @@ export async function translateCatalogItems({
}: {
items: Array<{ id: number; publicName: string; description: string }>;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
let namesUpdated = 0;
let descriptionsUpdated = 0;
let furniDataUpdated = 0;
+8 -7
View File
@@ -1,7 +1,8 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import type { ActionResult } from "@/lib/safe-action-shared";
import { rcon } from "@/lib/services/rcon";
@@ -11,7 +12,7 @@ export async function updateCatalogPage({
id,
...fields
}: { id: number } & Record<string, unknown>): Promise<ActionResult> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogPages.update({ where: { id }, data: fields as any });
await rcon.updateCatalog();
await logStaffActivity({
@@ -26,7 +27,7 @@ export async function updateCatalogPage({
}
export async function deleteCatalogPage({ id }: { id: number }) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogPages.delete({ where: { id } });
await rcon.updateCatalog();
await logStaffActivity({
@@ -47,7 +48,7 @@ export async function toggleCatalogPage({
id: number;
action: "toggleEnabled" | "toggleVisible";
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
const page = await prisma.catalogPages.findUnique({
where: { id },
select: { enabled: true, visible: true },
@@ -74,7 +75,7 @@ export async function createCatalogPage(input: {
minRank?: number;
orderNum?: number;
}): Promise<ActionResult<{ id: number }>> {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.CATALOG_EDIT);
const created = await prisma.catalogPages.create({
data: {
caption: input.caption,
@@ -110,7 +111,7 @@ export async function reorderTreePage(input: {
newParentId?: number;
newOrderNum: number;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.catalogPages.update({
where: { id: input.pageId },
data: {
@@ -129,7 +130,7 @@ export async function deleteTreePage(input: {
pageId: number;
mode: "reparent" | "cascade";
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
if (input.mode === "cascade") {
const children = await prisma.catalogPages.findMany({
where: { parentId: input.pageId },
+3 -2
View File
@@ -1,6 +1,7 @@
"use server";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export async function importBadgeFromRemote({
@@ -12,7 +13,7 @@ export async function importBadgeFromRemote({
name: string;
description: string;
}) {
await requireStaff();
await requirePermission(PERMS.ASSETS_IMPORT);
try {
await prisma.websiteBadges.upsert({
where: { badgeKey: code },
+3 -2
View File
@@ -1,6 +1,7 @@
"use server";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export interface MultiAccountCluster {
@@ -22,7 +23,7 @@ export async function detectMultiAccounts({
minAccounts: number;
limit: number;
}) {
await requireStaff();
await requirePermission(PERMS.USERS_VIEW);
const clusters: MultiAccountCluster[] = [];
const ipGroups = await prisma.user.groupBy({
+8 -7
View File
@@ -1,13 +1,14 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
import { rcon } from "@/lib/services/rcon";
import { logStaffActivity } from "@/lib/services/staff-activity";
export async function updateRoomItem(payload: Record<string, unknown>) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.ROOMS_EDIT);
const { roomId, itemId, ...data } = payload as {
roomId: number;
itemId: number;
@@ -31,7 +32,7 @@ export async function bulkDeleteRoomItems({
roomId: number;
itemIds: number[];
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.ROOMS_EDIT);
await prisma.items.deleteMany({ where: { id: { in: itemIds }, roomId } });
await logStaffActivity({
staffId: staff.id,
@@ -49,7 +50,7 @@ export async function deleteRoomItem({
roomId: number;
itemId: number;
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.ROOMS_EDIT);
await prisma.items.delete({ where: { id: itemId } });
await logStaffActivity({
staffId: staff.id,
@@ -68,7 +69,7 @@ export async function roomRconAction({
roomId: number;
action: string;
}) {
await requireStaff();
await requirePermission(PERMS.ROOMS_EDIT);
if (action === "reload") {
await rcon.send("reloadroom", { room_id: roomId });
} else if (action === "kick") {
@@ -81,7 +82,7 @@ export async function roomRconAction({
}
export async function deleteRoom({ id }: { id: number }) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.ROOMS_DELETE);
await prisma.rooms.delete({ where: { id } });
await logStaffActivity({
staffId: staff.id,
@@ -103,7 +104,7 @@ export async function updateRoom({
state?: string;
usersMax?: number;
}) {
const staff = await requireStaff();
const staff = await requirePermission(PERMS.ROOMS_EDIT);
await prisma.rooms.update({ where: { id }, data: data as any });
await logStaffActivity({
staffId: staff.id,
+4 -3
View File
@@ -1,11 +1,12 @@
"use server";
import { revalidatePath } from "next/cache";
import { requireStaff } from "@/lib/admin/guard";
import { requirePermission } from "@/lib/admin/guard";
import { PERMS } from "@/lib/permissions";
import { prisma } from "@/lib/prisma";
export async function deleteSoundtrack({ id }: { id: number }) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.soundtracks.delete({ where: { id } });
revalidatePath("/admin/sounds");
}
@@ -23,7 +24,7 @@ export async function updateSoundtrack({
track: string;
length: number;
}) {
await requireStaff();
await requirePermission(PERMS.CATALOG_EDIT);
await prisma.soundtracks.update({
where: { id },
data: { name, author, track, length },