Finish fine-grained ACL across remaining admin pages and actions.
Local Build and Deploy / deploy (push) Successful in 56s
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:
1 parent
3c8a8ff888
commit
0e89d03940
63 files changed
+420
-163
No files matched your search
@@ -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;
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,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) {
|
||||
|
||||
@@ -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") ?? "")
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 } });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() },
|
||||
|
||||
@@ -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 }> = [];
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 },
|
||||
|
||||
Reference in new issue
Block a user