Finish fine-grained ACL across remaining admin pages and actions.
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 { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
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.
|
// table; it only stores an image URL rendered in the site layout/widgets.
|
||||||
|
|
||||||
export async function createAd(formData: FormData): Promise<void> {
|
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") ?? "")
|
const image = String(formData.get("image") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -39,7 +40,7 @@ export async function createAd(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateAd(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");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!/^\d+$/.test(raw)) return;
|
if (!/^\d+$/.test(raw)) return;
|
||||||
const id = BigInt(raw);
|
const id = BigInt(raw);
|
||||||
@@ -70,7 +71,7 @@ export async function updateAd(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteAd(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");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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";
|
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.
|
* `message` payload. Staff-gated; the message is trimmed/bounded before send.
|
||||||
*/
|
*/
|
||||||
export async function sendHotelAlert(formData: FormData): Promise<void> {
|
export async function sendHotelAlert(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.NOTIFICATIONS_EDIT);
|
||||||
|
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function dismissApplication(formData: FormData): Promise<void> {
|
export async function dismissApplication(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.USERS_EDIT);
|
||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import { writeFile } from "node:fs/promises";
|
import { writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { redirect } from "next/navigation";
|
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";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
|
|
||||||
// Writes a badge image to the configured emulator badge directory. The path is
|
// 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> {
|
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;
|
const dir = process.env.BADGE_UPLOAD_DIR;
|
||||||
if (!dir) {
|
if (!dir) {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
|
|
||||||
export async function giveBadge(formData: FormData): Promise<void> {
|
export async function giveBadge(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
|
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const code = String(formData.get("code") ?? "")
|
const code = String(formData.get("code") ?? "")
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function createEmailTemplate(formData: FormData): Promise<void> {
|
export async function createEmailTemplate(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -35,7 +36,7 @@ export async function createEmailTemplate(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateEmailTemplate(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");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
let id: bigint;
|
let id: bigint;
|
||||||
@@ -68,7 +69,7 @@ export async function updateEmailTemplate(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteEmailTemplate(formData: FormData): Promise<void> {
|
export async function deleteEmailTemplate(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
await prisma.emailTemplates.delete({ where: { id } });
|
await prisma.emailTemplates.delete({ where: { id } });
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
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> {
|
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") ?? "")
|
const name = String(formData.get("name") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -82,7 +83,7 @@ export async function createHelpQuestion(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateHelpQuestion(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");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ export async function updateHelpQuestion(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteHelpQuestion(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");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +10,7 @@ import { prisma } from "@/lib/prisma";
|
|||||||
* string). Mirrors AtomCMS' housekeeping permission management.
|
* string). Mirrors AtomCMS' housekeeping permission management.
|
||||||
*/
|
*/
|
||||||
export async function upsertPermission(formData: FormData): Promise<void> {
|
export async function upsertPermission(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
||||||
|
|
||||||
const permission = String(formData.get("permission") ?? "")
|
const permission = String(formData.get("permission") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -38,7 +39,7 @@ export async function upsertPermission(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePermission(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");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
function parseIp(formData: FormData): string {
|
function parseIp(formData: FormData): string {
|
||||||
@@ -20,7 +21,7 @@ function parseAsn(formData: FormData): string | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addWhitelist(formData: FormData): Promise<void> {
|
export async function addWhitelist(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
||||||
const ipAddress = parseIp(formData);
|
const ipAddress = parseIp(formData);
|
||||||
if (!ipAddress) return;
|
if (!ipAddress) return;
|
||||||
const asn = parseAsn(formData);
|
const asn = parseAsn(formData);
|
||||||
@@ -31,7 +32,7 @@ export async function addWhitelist(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteWhitelist(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") ?? "")
|
const raw = String(formData.get("id") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim();
|
.trim();
|
||||||
@@ -41,7 +42,7 @@ export async function deleteWhitelist(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function addBlacklist(formData: FormData): Promise<void> {
|
export async function addBlacklist(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SETTINGS_EDIT);
|
||||||
const ipAddress = parseIp(formData);
|
const ipAddress = parseIp(formData);
|
||||||
if (!ipAddress) return;
|
if (!ipAddress) return;
|
||||||
const asn = parseAsn(formData);
|
const asn = parseAsn(formData);
|
||||||
@@ -52,7 +53,7 @@ export async function addBlacklist(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBlacklist(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") ?? "")
|
const raw = String(formData.get("id") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim();
|
.trim();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
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> {
|
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
|
// Checkbox: present only when ticked. Normalise to the '1'/'0' string the
|
||||||
// emulator/Laravel side expects.
|
// emulator/Laravel side expects.
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
import { mkdir, writeFile } from "node:fs/promises";
|
import { mkdir, writeFile } from "node:fs/promises";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { revalidatePath } from "next/cache";
|
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 MEDIA_DIR = "public/assets/images/media";
|
||||||
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
const MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
||||||
const ALLOWED = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
const ALLOWED = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
||||||
|
|
||||||
export async function uploadMedia(formData: FormData): Promise<void> {
|
export async function uploadMedia(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const file = formData.get("file") as File | null;
|
const file = formData.get("file") as File | null;
|
||||||
if (!file || file.size === 0) return;
|
if (!file || file.size === 0) return;
|
||||||
if (file.size > MAX_SIZE) 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> {
|
export async function deleteMedia(name: string): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const { unlink } = await import("node:fs/promises");
|
const { unlink } = await import("node:fs/promises");
|
||||||
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
|
const baseDir = path.resolve(process.cwd(), MEDIA_DIR);
|
||||||
const filePath = path.resolve(baseDir, name);
|
const filePath = path.resolve(baseDir, name);
|
||||||
@@ -50,7 +51,7 @@ export async function deleteMedia(name: string): Promise<void> {
|
|||||||
export async function uploadMediaAndReturn(
|
export async function uploadMediaAndReturn(
|
||||||
formData: FormData,
|
formData: FormData,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const file = formData.get("file") as File | null;
|
const file = formData.get("file") as File | null;
|
||||||
if (!file || file.size === 0) return "";
|
if (!file || file.size === 0) return "";
|
||||||
if (file.size > MAX_SIZE) return "";
|
if (file.size > MAX_SIZE) return "";
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { logServerError } from "@/lib/server-log";
|
import { logServerError } from "@/lib/server-log";
|
||||||
@@ -23,7 +24,7 @@ function parseMinRank(formData: FormData): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createPermission(formData: FormData): Promise<void> {
|
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") ?? "")
|
const permission = String(formData.get("permission") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -67,7 +68,7 @@ export async function createPermission(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updatePermission(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");
|
const raw = String(formData.get("id") ?? "").normalize("NFC");
|
||||||
if (!raw) return;
|
if (!raw) return;
|
||||||
const id = BigInt(raw);
|
const id = BigInt(raw);
|
||||||
@@ -106,7 +107,7 @@ export async function updatePermission(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePermission(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");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function deletePhoto(formData: FormData): Promise<void> {
|
export async function deletePhoto(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.PAGES_EDIT);
|
||||||
const id = Number(formData.get("id"));
|
const id = Number(formData.get("id"));
|
||||||
if (!(id > 0)) return;
|
if (!(id > 0)) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function createCategory(formData: FormData): Promise<void> {
|
export async function createCategory(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
const name = String(formData.get("name") ?? "")
|
const name = String(formData.get("name") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
.trim()
|
.trim()
|
||||||
@@ -33,7 +34,7 @@ export async function createCategory(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCategory(formData: FormData): Promise<void> {
|
export async function deleteCategory(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ export async function deleteCategory(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createValue(formData: FormData): Promise<void> {
|
export async function createValue(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
const categoryId = formPositiveBigInt(formData, "categoryId");
|
const categoryId = formPositiveBigInt(formData, "categoryId");
|
||||||
if (!categoryId) return;
|
if (!categoryId) return;
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ export async function createValue(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteValue(formData: FormData): Promise<void> {
|
export async function deleteValue(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ function normaliseColor(raw: string): string {
|
|||||||
// ── Tags CRUD (tags + taggables, AtomCMS article tags/categories) ──────────
|
// ── Tags CRUD (tags + taggables, AtomCMS article tags/categories) ──────────
|
||||||
|
|
||||||
export async function createTag(formData: FormData): Promise<void> {
|
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);
|
const name = str(formData.get("name")).trim().slice(0, 255);
|
||||||
if (!name) return;
|
if (!name) return;
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ export async function createTag(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateTag(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"));
|
const id = parseId(formData.get("id"));
|
||||||
if (id === null) return;
|
if (id === null) return;
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ export async function updateTag(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTag(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"));
|
const id = parseId(formData.get("id"));
|
||||||
if (id === null) return;
|
if (id === null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function createTeam(formData: FormData): Promise<void> {
|
export async function createTeam(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.USERS_EDIT);
|
||||||
|
|
||||||
const rankName = String(formData.get("rankName") ?? "")
|
const rankName = String(formData.get("rankName") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -41,7 +42,7 @@ export async function createTeam(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteTeam(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")));
|
const id = BigInt(String(formData.get("id")));
|
||||||
await prisma.websiteTeams.delete({ where: { id } });
|
await prisma.websiteTeams.delete({ where: { id } });
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { type CurrencyName, sendCurrency } from "@/lib/services/send-currency";
|
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> {
|
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 userId = Number(formData.get("userId"));
|
||||||
const type = String(formData.get("type"));
|
const type = String(formData.get("type"));
|
||||||
const amount = Number(formData.get("amount"));
|
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> {
|
export async function setMotto(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.USERS_EDIT);
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const motto = String(formData.get("motto") ?? "")
|
const motto = String(formData.get("motto") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -51,7 +52,7 @@ export async function setMotto(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function setRank(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 userId = Number(formData.get("userId"));
|
||||||
const rank = Number(formData.get("rank"));
|
const rank = Number(formData.get("rank"));
|
||||||
if (userId > 0 && rank > 0) {
|
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> {
|
export async function alertUser(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.USERS_EDIT);
|
||||||
const userId = Number(formData.get("userId"));
|
const userId = Number(formData.get("userId"));
|
||||||
const message = String(formData.get("message") ?? "")
|
const message = String(formData.get("message") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -78,7 +79,7 @@ export async function alertUser(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function disconnectUser(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 userId = Number(formData.get("userId"));
|
||||||
const username = String(formData.get("username") ?? "").normalize("NFC");
|
const username = String(formData.get("username") ?? "").normalize("NFC");
|
||||||
if (userId > 0) await rcon.disconnectUser(userId, username);
|
if (userId > 0) await rcon.disconnectUser(userId, username);
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { formPositiveBigInt } from "@/lib/form-data";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { logServerError } from "@/lib/server-log";
|
import { logServerError } from "@/lib/server-log";
|
||||||
|
|
||||||
export async function createVoucher(formData: FormData): Promise<void> {
|
export async function createVoucher(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
|
|
||||||
const code = String(formData.get("code") ?? "")
|
const code = String(formData.get("code") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -53,7 +54,7 @@ export async function createVoucher(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteVoucher(formData: FormData): Promise<void> {
|
export async function deleteVoucher(formData: FormData): Promise<void> {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.SHOP_EDIT);
|
||||||
|
|
||||||
const id = formPositiveBigInt(formData, "id");
|
const id = formPositiveBigInt(formData, "id");
|
||||||
if (!id) return;
|
if (!id) return;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
@@ -28,7 +29,7 @@ async function writeSetting(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function saveVpn(formData: FormData): Promise<void> {
|
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.
|
// Toggle: an unchecked checkbox submits nothing, so absence === disabled.
|
||||||
const enabled =
|
const enabled =
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ function revalidate(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function createBox(formData: FormData): Promise<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") ?? "")
|
const title = String(formData.get("title") ?? "")
|
||||||
.normalize("NFC")
|
.normalize("NFC")
|
||||||
@@ -82,7 +83,7 @@ export async function createBox(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function updateBox(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);
|
const id = parseId(formData);
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
@@ -125,7 +126,7 @@ export async function updateBox(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBox(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);
|
const id = parseId(formData);
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
@@ -147,7 +148,7 @@ export async function deleteBox(formData: FormData): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function toggleBox(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);
|
const id = parseId(formData);
|
||||||
if (id == null) return;
|
if (id == null) return;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function getBadgeData({ code }: { code: string }) {
|
export async function getBadgeData({ code }: { code: string }) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const badge = await prisma.websiteBadges.findUnique({
|
const badge = await prisma.websiteBadges.findUnique({
|
||||||
where: { badgeKey: code },
|
where: { badgeKey: code },
|
||||||
select: { badgeName: true, badgeDescription: true },
|
select: { badgeName: true, badgeDescription: true },
|
||||||
@@ -26,7 +27,7 @@ export async function updateBadge({
|
|||||||
name: string;
|
name: string;
|
||||||
desc: string;
|
desc: string;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.websiteBadges.upsert({
|
await prisma.websiteBadges.upsert({
|
||||||
where: { badgeKey: code },
|
where: { badgeKey: code },
|
||||||
update: { badgeName: name, badgeDescription: desc, updatedAt: new Date() },
|
update: { badgeName: name, badgeDescription: desc, updatedAt: new Date() },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use server";
|
"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 { prisma } from "@/lib/prisma";
|
||||||
import type { ActionResult } from "@/lib/safe-action-shared";
|
import type { ActionResult } from "@/lib/safe-action-shared";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
@@ -11,7 +12,7 @@ export async function bulkUnban({
|
|||||||
}: {
|
}: {
|
||||||
userIds: number[];
|
userIds: number[];
|
||||||
}): Promise<ActionResult<{ unbanned: number; total: number }>> {
|
}): Promise<ActionResult<{ unbanned: number; total: number }>> {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||||
const result = await prisma.ban.deleteMany({
|
const result = await prisma.ban.deleteMany({
|
||||||
where: { userId: { in: userIds } },
|
where: { userId: { in: userIds } },
|
||||||
});
|
});
|
||||||
@@ -36,7 +37,7 @@ export async function bulkBan({
|
|||||||
reason: string;
|
reason: string;
|
||||||
duration: number;
|
duration: number;
|
||||||
}): Promise<ActionResult<{ banned: number }>> {
|
}): Promise<ActionResult<{ banned: number }>> {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
let banned = 0;
|
let banned = 0;
|
||||||
|
|
||||||
@@ -84,7 +85,7 @@ export async function bulkGiveCurrency({
|
|||||||
failedIds: Array<{ userId: number; reason: string }>;
|
failedIds: Array<{ userId: number; reason: string }>;
|
||||||
}>
|
}>
|
||||||
> {
|
> {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||||
let given = 0;
|
let given = 0;
|
||||||
const failedIds: Array<{ userId: number; reason: string }> = [];
|
const failedIds: Array<{ userId: number; reason: string }> = [];
|
||||||
|
|
||||||
@@ -142,7 +143,7 @@ export async function bulkGiveBadge({
|
|||||||
failedIds: Array<{ userId: number; reason: string }>;
|
failedIds: Array<{ userId: number; reason: string }>;
|
||||||
}>
|
}>
|
||||||
> {
|
> {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.USERS_EDIT);
|
||||||
let given = 0;
|
let given = 0;
|
||||||
const failedIds: Array<{ userId: number; reason: string }> = [];
|
const failedIds: Array<{ userId: number; reason: string }> = [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
@@ -10,7 +11,7 @@ export async function updateBcPage({
|
|||||||
id,
|
id,
|
||||||
...fields
|
...fields
|
||||||
}: { id: number } & Record<string, unknown>) {
|
}: { 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 prisma.catalogPagesBc.update({ where: { id }, data: fields as any });
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
@@ -25,7 +26,7 @@ export async function updateBcPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteBcItem({ id }: { id: number }) {
|
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 prisma.catalogItemsBc.delete({ where: { id } });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
@@ -47,7 +48,7 @@ export async function updateBcItem({
|
|||||||
orderNumber?: number;
|
orderNumber?: number;
|
||||||
extradata?: string;
|
extradata?: string;
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.catalogItemsBc.update({ where: { id }, data: data as any });
|
await prisma.catalogItemsBc.update({ where: { id }, data: data as any });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
@@ -69,7 +70,7 @@ export async function createBcItem({
|
|||||||
orderNumber: number;
|
orderNumber: number;
|
||||||
extradata: string;
|
extradata: string;
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const created = await prisma.catalogItemsBc.create({
|
const created = await prisma.catalogItemsBc.create({
|
||||||
data: { pageId, ...data },
|
data: { pageId, ...data },
|
||||||
});
|
});
|
||||||
@@ -90,7 +91,7 @@ export async function toggleBcPage({
|
|||||||
id: number;
|
id: number;
|
||||||
field: "enabled" | "visible";
|
field: "enabled" | "visible";
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const page = await prisma.catalogPagesBc.findUnique({
|
const page = await prisma.catalogPagesBc.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
select: { enabled: true, visible: true },
|
select: { enabled: true, visible: true },
|
||||||
@@ -108,7 +109,7 @@ export async function createBcPage(input: {
|
|||||||
parentId: number;
|
parentId: number;
|
||||||
pageLayout: string;
|
pageLayout: string;
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const created = await prisma.catalogPagesBc.create({
|
const created = await prisma.catalogPagesBc.create({
|
||||||
data: {
|
data: {
|
||||||
caption: input.caption,
|
caption: input.caption,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
@@ -23,7 +24,7 @@ export async function createCatalogItem(data: {
|
|||||||
haveOffer: "0" | "1";
|
haveOffer: "0" | "1";
|
||||||
clubOnly: "0" | "1";
|
clubOnly: "0" | "1";
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const created = await prisma.catalogItems.create({ data });
|
const created = await prisma.catalogItems.create({ data });
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
@@ -38,7 +39,7 @@ export async function createCatalogItem(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCatalogItems({ ids }: { ids: number[] }) {
|
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 prisma.catalogItems.deleteMany({ where: { id: { in: ids } } });
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
@@ -58,7 +59,7 @@ export async function moveCatalogItems({
|
|||||||
ids: number[];
|
ids: number[];
|
||||||
targetPageId: number;
|
targetPageId: number;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.catalogItems.updateMany({
|
await prisma.catalogItems.updateMany({
|
||||||
where: { id: { in: ids } },
|
where: { id: { in: ids } },
|
||||||
data: { pageId: targetPageId },
|
data: { pageId: targetPageId },
|
||||||
@@ -73,7 +74,7 @@ export async function reorderCatalogItems({
|
|||||||
}: {
|
}: {
|
||||||
orders: Array<{ id: number; orderNumber: number }>;
|
orders: Array<{ id: number; orderNumber: number }>;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
for (const { id, orderNumber } of orders) {
|
for (const { id, orderNumber } of orders) {
|
||||||
await prisma.catalogItems.update({ where: { id }, data: { orderNumber } });
|
await prisma.catalogItems.update({ where: { id }, data: { orderNumber } });
|
||||||
}
|
}
|
||||||
@@ -91,7 +92,7 @@ export async function updateCatalogItem({
|
|||||||
catalogFields: Record<string, unknown>;
|
catalogFields: Record<string, unknown>;
|
||||||
baseItem?: { id: number; fields: 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({
|
await prisma.catalogItems.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: catalogFields as any,
|
data: catalogFields as any,
|
||||||
@@ -119,7 +120,7 @@ export async function translateCatalogItems({
|
|||||||
}: {
|
}: {
|
||||||
items: Array<{ id: number; publicName: string; description: string }>;
|
items: Array<{ id: number; publicName: string; description: string }>;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
let namesUpdated = 0;
|
let namesUpdated = 0;
|
||||||
let descriptionsUpdated = 0;
|
let descriptionsUpdated = 0;
|
||||||
let furniDataUpdated = 0;
|
let furniDataUpdated = 0;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import type { ActionResult } from "@/lib/safe-action-shared";
|
import type { ActionResult } from "@/lib/safe-action-shared";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
@@ -11,7 +12,7 @@ export async function updateCatalogPage({
|
|||||||
id,
|
id,
|
||||||
...fields
|
...fields
|
||||||
}: { id: number } & Record<string, unknown>): Promise<ActionResult> {
|
}: { 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 prisma.catalogPages.update({ where: { id }, data: fields as any });
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
@@ -26,7 +27,7 @@ export async function updateCatalogPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteCatalogPage({ id }: { id: number }) {
|
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 prisma.catalogPages.delete({ where: { id } });
|
||||||
await rcon.updateCatalog();
|
await rcon.updateCatalog();
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
@@ -47,7 +48,7 @@ export async function toggleCatalogPage({
|
|||||||
id: number;
|
id: number;
|
||||||
action: "toggleEnabled" | "toggleVisible";
|
action: "toggleEnabled" | "toggleVisible";
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const page = await prisma.catalogPages.findUnique({
|
const page = await prisma.catalogPages.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
select: { enabled: true, visible: true },
|
select: { enabled: true, visible: true },
|
||||||
@@ -74,7 +75,7 @@ export async function createCatalogPage(input: {
|
|||||||
minRank?: number;
|
minRank?: number;
|
||||||
orderNum?: number;
|
orderNum?: number;
|
||||||
}): Promise<ActionResult<{ id: number }>> {
|
}): Promise<ActionResult<{ id: number }>> {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
const created = await prisma.catalogPages.create({
|
const created = await prisma.catalogPages.create({
|
||||||
data: {
|
data: {
|
||||||
caption: input.caption,
|
caption: input.caption,
|
||||||
@@ -110,7 +111,7 @@ export async function reorderTreePage(input: {
|
|||||||
newParentId?: number;
|
newParentId?: number;
|
||||||
newOrderNum: number;
|
newOrderNum: number;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.catalogPages.update({
|
await prisma.catalogPages.update({
|
||||||
where: { id: input.pageId },
|
where: { id: input.pageId },
|
||||||
data: {
|
data: {
|
||||||
@@ -129,7 +130,7 @@ export async function deleteTreePage(input: {
|
|||||||
pageId: number;
|
pageId: number;
|
||||||
mode: "reparent" | "cascade";
|
mode: "reparent" | "cascade";
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
if (input.mode === "cascade") {
|
if (input.mode === "cascade") {
|
||||||
const children = await prisma.catalogPages.findMany({
|
const children = await prisma.catalogPages.findMany({
|
||||||
where: { parentId: input.pageId },
|
where: { parentId: input.pageId },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use server";
|
"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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function importBadgeFromRemote({
|
export async function importBadgeFromRemote({
|
||||||
@@ -12,7 +13,7 @@ export async function importBadgeFromRemote({
|
|||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.ASSETS_IMPORT);
|
||||||
try {
|
try {
|
||||||
await prisma.websiteBadges.upsert({
|
await prisma.websiteBadges.upsert({
|
||||||
where: { badgeKey: code },
|
where: { badgeKey: code },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use server";
|
"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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export interface MultiAccountCluster {
|
export interface MultiAccountCluster {
|
||||||
@@ -22,7 +23,7 @@ export async function detectMultiAccounts({
|
|||||||
minAccounts: number;
|
minAccounts: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.USERS_VIEW);
|
||||||
const clusters: MultiAccountCluster[] = [];
|
const clusters: MultiAccountCluster[] = [];
|
||||||
|
|
||||||
const ipGroups = await prisma.user.groupBy({
|
const ipGroups = await prisma.user.groupBy({
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
import { rcon } from "@/lib/services/rcon";
|
import { rcon } from "@/lib/services/rcon";
|
||||||
import { logStaffActivity } from "@/lib/services/staff-activity";
|
import { logStaffActivity } from "@/lib/services/staff-activity";
|
||||||
|
|
||||||
export async function updateRoomItem(payload: Record<string, unknown>) {
|
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 {
|
const { roomId, itemId, ...data } = payload as {
|
||||||
roomId: number;
|
roomId: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
@@ -31,7 +32,7 @@ export async function bulkDeleteRoomItems({
|
|||||||
roomId: number;
|
roomId: number;
|
||||||
itemIds: number[];
|
itemIds: number[];
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.ROOMS_EDIT);
|
||||||
await prisma.items.deleteMany({ where: { id: { in: itemIds }, roomId } });
|
await prisma.items.deleteMany({ where: { id: { in: itemIds }, roomId } });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
@@ -49,7 +50,7 @@ export async function deleteRoomItem({
|
|||||||
roomId: number;
|
roomId: number;
|
||||||
itemId: number;
|
itemId: number;
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.ROOMS_EDIT);
|
||||||
await prisma.items.delete({ where: { id: itemId } });
|
await prisma.items.delete({ where: { id: itemId } });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
@@ -68,7 +69,7 @@ export async function roomRconAction({
|
|||||||
roomId: number;
|
roomId: number;
|
||||||
action: string;
|
action: string;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.ROOMS_EDIT);
|
||||||
if (action === "reload") {
|
if (action === "reload") {
|
||||||
await rcon.send("reloadroom", { room_id: roomId });
|
await rcon.send("reloadroom", { room_id: roomId });
|
||||||
} else if (action === "kick") {
|
} else if (action === "kick") {
|
||||||
@@ -81,7 +82,7 @@ export async function roomRconAction({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteRoom({ id }: { id: number }) {
|
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 prisma.rooms.delete({ where: { id } });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
@@ -103,7 +104,7 @@ export async function updateRoom({
|
|||||||
state?: string;
|
state?: string;
|
||||||
usersMax?: number;
|
usersMax?: number;
|
||||||
}) {
|
}) {
|
||||||
const staff = await requireStaff();
|
const staff = await requirePermission(PERMS.ROOMS_EDIT);
|
||||||
await prisma.rooms.update({ where: { id }, data: data as any });
|
await prisma.rooms.update({ where: { id }, data: data as any });
|
||||||
await logStaffActivity({
|
await logStaffActivity({
|
||||||
staffId: staff.id,
|
staffId: staff.id,
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
"use server";
|
"use server";
|
||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
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 { prisma } from "@/lib/prisma";
|
||||||
|
|
||||||
export async function deleteSoundtrack({ id }: { id: number }) {
|
export async function deleteSoundtrack({ id }: { id: number }) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.soundtracks.delete({ where: { id } });
|
await prisma.soundtracks.delete({ where: { id } });
|
||||||
revalidatePath("/admin/sounds");
|
revalidatePath("/admin/sounds");
|
||||||
}
|
}
|
||||||
@@ -23,7 +24,7 @@ export async function updateSoundtrack({
|
|||||||
track: string;
|
track: string;
|
||||||
length: number;
|
length: number;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
await requirePermission(PERMS.CATALOG_EDIT);
|
||||||
await prisma.soundtracks.update({
|
await prisma.soundtracks.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
data: { name, author, track, length },
|
data: { name, author, track, length },
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Award } from "lucide-react";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -16,6 +18,11 @@ type Achievement = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function AdminAchievements() {
|
export default async function AdminAchievements() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.achievements");
|
const t = await getTranslations("pages.admin.achievements");
|
||||||
|
|
||||||
let achievements: Achievement[];
|
let achievements: Achievement[];
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { Megaphone } from "lucide-react";
|
import { Megaphone } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { deleteAd, updateAd } from "@/actions/admin-ads";
|
import { deleteAd, updateAd } from "@/actions/admin-ads";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -11,6 +12,11 @@ export default async function EditAd({
|
|||||||
}: {
|
}: {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
let ad: Awaited<ReturnType<typeof prisma.websiteAds.findUnique>> = null;
|
let ad: Awaited<ReturnType<typeof prisma.websiteAds.findUnique>> = null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
import { Megaphone } from "lucide-react";
|
import { Megaphone } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
import { createAd } from "@/actions/admin-ads";
|
import { createAd } from "@/actions/admin-ads";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
|
export default async function NewAd() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_EDIT, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
export default function NewAd() {
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
|
|||||||
@@ -3,10 +3,17 @@ import Link from "next/link";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function AdminAds() {
|
export default async function AdminAds() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.ads");
|
const t = await getTranslations("pages.admin.ads");
|
||||||
let ads: Awaited<ReturnType<typeof prisma.websiteAds.findMany>>;
|
let ads: Awaited<ReturnType<typeof prisma.websiteAds.findMany>>;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { sendHotelAlert } from "@/actions/admin-alerts";
|
import { sendHotelAlert } from "@/actions/admin-alerts";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -54,6 +56,11 @@ function Badge({ label, color }: { label: string; color: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminAlerts() {
|
export default async function AdminAlerts() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.NOTIFICATIONS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.alerts");
|
const t = await getTranslations("pages.admin.alerts");
|
||||||
|
|
||||||
let alerts: Awaited<ReturnType<typeof prisma.alertLogs.findMany>>;
|
let alerts: Awaited<ReturnType<typeof prisma.alertLogs.findMany>>;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { dismissApplication } from "@/actions/admin-applications";
|
import { dismissApplication } from "@/actions/admin-applications";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -20,6 +22,11 @@ function formatDate(d: Date | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminApplications() {
|
export default async function AdminApplications() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.USERS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.applications");
|
const t = await getTranslations("pages.admin.applications");
|
||||||
let applications: ApplicationRow[];
|
let applications: ApplicationRow[];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { uploadBadge } from "@/actions/admin-badge-upload";
|
import { uploadBadge } from "@/actions/admin-badge-upload";
|
||||||
import { giveBadge } from "@/actions/admin-badges";
|
import { giveBadge } from "@/actions/admin-badges";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -13,6 +15,11 @@ export default async function AdminBadges({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{ uploaded?: string; error?: string }>;
|
searchParams: Promise<{ uploaded?: string; error?: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.CATALOG_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.badges");
|
const t = await getTranslations("pages.admin.badges");
|
||||||
const { uploaded, error } = await searchParams;
|
const { uploaded, error } = await searchParams;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -40,6 +41,11 @@ export default async function AdminCalendarCampaign({
|
|||||||
}: {
|
}: {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.calendar");
|
const t = await getTranslations("pages.admin.calendar");
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import Link from "next/link";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -22,6 +24,11 @@ function fmtDate(ts: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminCalendar() {
|
export default async function AdminCalendar() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.calendar");
|
const t = await getTranslations("pages.admin.calendar");
|
||||||
|
|
||||||
let campaigns: Campaign[] = [];
|
let campaigns: Campaign[] = [];
|
||||||
|
|||||||
@@ -5,10 +5,17 @@ import {
|
|||||||
updateEmailTemplate,
|
updateEmailTemplate,
|
||||||
} from "@/actions/admin-email-templates";
|
} from "@/actions/admin-email-templates";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function AdminEmailTemplates() {
|
export default async function AdminEmailTemplates() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.emailTemplates");
|
const t = await getTranslations("pages.admin.emailTemplates");
|
||||||
|
|
||||||
let templates: Awaited<ReturnType<typeof prisma.emailTemplates.findMany>> =
|
let templates: Awaited<ReturnType<typeof prisma.emailTemplates.findMany>> =
|
||||||
|
|||||||
@@ -2,10 +2,17 @@ import { Image } from "lucide-react";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
import { FaviconForm } from "./favicon-form";
|
import { FaviconForm } from "./favicon-form";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function AdminFaviconPage() {
|
export default async function AdminFaviconPage() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.favicon");
|
const t = await getTranslations("pages.admin.favicon");
|
||||||
|
|
||||||
let faviconUrl: string | null = null;
|
let faviconUrl: string | null = null;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { HelpCircle } from "lucide-react";
|
import { HelpCircle } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
import { deleteHelpQuestion, updateHelpQuestion } from "@/actions/admin-help";
|
import { deleteHelpQuestion, updateHelpQuestion } from "@/actions/admin-help";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -13,6 +14,11 @@ export default async function EditHelpQuestion({
|
|||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
searchParams: Promise<{ error?: string }>;
|
searchParams: Promise<{ error?: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const { error } = await searchParams;
|
const { error } = await searchParams;
|
||||||
let entry: Awaited<
|
let entry: Awaited<
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
import { HelpCircle } from "lucide-react";
|
import { HelpCircle } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { createHelpQuestion } from "@/actions/admin-help";
|
import { createHelpQuestion } from "@/actions/admin-help";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export default async function NewHelpQuestion({
|
export default async function NewHelpQuestion({
|
||||||
searchParams,
|
searchParams,
|
||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{ error?: string }>;
|
searchParams: Promise<{ error?: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_EDIT, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const { error } = await searchParams;
|
const { error } = await searchParams;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,10 +3,17 @@ import Link from "next/link";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function AdminHelpQuestions() {
|
export default async function AdminHelpQuestions() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.helpCenter");
|
const t = await getTranslations("pages.admin.helpCenter");
|
||||||
let entries: Awaited<
|
let entries: Awaited<
|
||||||
ReturnType<typeof prisma.websiteHelpCenterCategories.findMany>
|
ReturnType<typeof prisma.websiteHelpCenterCategories.findMany>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
upsertPermission,
|
upsertPermission,
|
||||||
} from "@/actions/admin-housekeeping";
|
} from "@/actions/admin-housekeeping";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -13,6 +15,11 @@ export default async function AdminHousekeeping({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{ q?: string; mode?: "manage" | "import" }>;
|
searchParams: Promise<{ q?: string; mode?: "manage" | "import" }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions: acl } = await getAdminContext();
|
||||||
|
if (!canAccess(acl, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.housekeeping");
|
const t = await getTranslations("pages.admin.housekeeping");
|
||||||
|
|
||||||
const sp = await searchParams;
|
const sp = await searchParams;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import {
|
|||||||
} from "@/actions/admin-ip";
|
} from "@/actions/admin-ip";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -23,6 +25,11 @@ function formatDate(d: Date | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminIp() {
|
export default async function AdminIp() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.ip");
|
const t = await getTranslations("pages.admin.ip");
|
||||||
let whitelist: IpRow[] = [];
|
let whitelist: IpRow[] = [];
|
||||||
let blacklist: IpRow[] = [];
|
let blacklist: IpRow[] = [];
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { buildStaffActivityWhere } from "@/lib/admin/log-filters";
|
import { buildStaffActivityWhere } from "@/lib/admin/log-filters";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -23,6 +25,11 @@ export default async function AdminStaffActivities({
|
|||||||
authorization?: string;
|
authorization?: string;
|
||||||
}>;
|
}>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.LOGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.logs");
|
const t = await getTranslations("pages.admin.logs");
|
||||||
|
|
||||||
const sp = await searchParams;
|
const sp = await searchParams;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { saveMaintenance } from "@/actions/admin-maintenance";
|
import { saveMaintenance } from "@/actions/admin-maintenance";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -13,6 +15,11 @@ const KEYS = [
|
|||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export default async function AdminMaintenance() {
|
export default async function AdminMaintenance() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.maintenance");
|
const t = await getTranslations("pages.admin.maintenance");
|
||||||
|
|
||||||
let rows: { key: string; value: string }[] = [];
|
let rows: { key: string; value: string }[] = [];
|
||||||
|
|||||||
@@ -2,8 +2,15 @@ import { Image } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { AdminMediaGrid } from "@/components/admin/media-grid";
|
import { AdminMediaGrid } from "@/components/admin/media-grid";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export default async function AdminMediaPage() {
|
export default async function AdminMediaPage() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.media");
|
const t = await getTranslations("pages.admin.media");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { Navigation } from "lucide-react";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -40,6 +42,11 @@ function YesNo({ on }: { on: boolean }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminNavigation() {
|
export default async function AdminNavigation() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.navigation");
|
const t = await getTranslations("pages.admin.navigation");
|
||||||
let flatcats: Flatcat[] = [];
|
let flatcats: Flatcat[] = [];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -3,10 +3,17 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { deletePhoto } from "@/actions/admin-photos";
|
import { deletePhoto } from "@/actions/admin-photos";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function AdminPhotos() {
|
export default async function AdminPhotos() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.photos");
|
const t = await getTranslations("pages.admin.photos");
|
||||||
let photos: Awaited<ReturnType<typeof prisma.cameraWeb.findMany>> = [];
|
let photos: Awaited<ReturnType<typeof prisma.cameraWeb.findMany>> = [];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ import {
|
|||||||
} from "@/actions/admin-rare-values";
|
} from "@/actions/admin-rare-values";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"];
|
const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"];
|
||||||
|
|
||||||
export default async function AdminRareValues() {
|
export default async function AdminRareValues() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.rareValues");
|
const t = await getTranslations("pages.admin.rareValues");
|
||||||
|
|
||||||
let categories: Awaited<
|
let categories: Awaited<
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export default async function RoomFurniPage({
|
|||||||
ownerUsername: room.owner?.username ?? room.ownerName,
|
ownerUsername: room.owner?.username ?? room.ownerName,
|
||||||
}}
|
}}
|
||||||
items={rows}
|
items={rows}
|
||||||
canEdit={session.user.rank >= 4}
|
canEdit={canAccess(permissions, PERMS.ROOMS_EDIT, session.user.rank)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ export default async function RoomEditPage({
|
|||||||
}) {
|
}) {
|
||||||
const { id } = await params;
|
const { id } = await params;
|
||||||
const { session, permissions } = await getAdminContext();
|
const { session, permissions } = await getAdminContext();
|
||||||
if (
|
if (!canAccess(permissions, PERMS.ROOMS_EDIT, session.user.rank)) {
|
||||||
!canAccess(permissions, PERMS.ROOMS_VIEW, session.user.rank) ||
|
|
||||||
session.user.rank < 4
|
|
||||||
) {
|
|
||||||
redirect("/admin");
|
redirect("/admin");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,7 @@ export default async function RoomsPage({
|
|||||||
searchParams,
|
searchParams,
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEdit =
|
const canEdit = canAccess(result.permissions, PERMS.ROOMS_EDIT, result.rank);
|
||||||
canAccess(result.permissions, PERMS.ROOMS_VIEW, result.rank) &&
|
|
||||||
result.rank >= 4;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default async function RoomShowPage({
|
|||||||
redirect("/admin");
|
redirect("/admin");
|
||||||
|
|
||||||
const room = await loadRoom(id);
|
const room = await loadRoom(id);
|
||||||
const canEdit = session.user.rank >= 4;
|
const canEdit = canAccess(permissions, PERMS.ROOMS_EDIT, session.user.rank);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { CreditCard } from "lucide-react";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -17,6 +19,11 @@ function fmt(ts: number): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminSubscriptions() {
|
export default async function AdminSubscriptions() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.subscriptions");
|
const t = await getTranslations("pages.admin.subscriptions");
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { createTag, deleteTag, updateTag } from "@/actions/admin-tags";
|
import { createTag, deleteTag, updateTag } from "@/actions/admin-tags";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -18,6 +20,11 @@ function formatDate(d: Date | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminTagsPage() {
|
export default async function AdminTagsPage() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.tags");
|
const t = await getTranslations("pages.admin.tags");
|
||||||
let tags: Tag[] = [];
|
let tags: Tag[] = [];
|
||||||
let usage = new Map<string, number>();
|
let usage = new Map<string, number>();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { createTeam, deleteTeam } from "@/actions/admin-teams";
|
import { createTeam, deleteTeam } from "@/actions/admin-teams";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -16,6 +18,11 @@ type TeamRow = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function AdminTeams() {
|
export default async function AdminTeams() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.USERS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.teams");
|
const t = await getTranslations("pages.admin.teams");
|
||||||
let teams: TeamRow[] = [];
|
let teams: TeamRow[] = [];
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { ArrowLeftRight } from "lucide-react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { requireStaff } from "@/lib/admin/guard";
|
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -47,7 +48,10 @@ export default async function AdminTransactions({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{ page?: string }>;
|
searchParams: Promise<{ page?: string }>;
|
||||||
}) {
|
}) {
|
||||||
await requireStaff();
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.transactions");
|
const t = await getTranslations("pages.admin.transactions");
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { createVoucher, deleteVoucher } from "@/actions/admin-vouchers";
|
import { createVoucher, deleteVoucher } from "@/actions/admin-vouchers";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -22,6 +24,11 @@ function fmtDate(d: Date | null): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default async function AdminVouchers() {
|
export default async function AdminVouchers() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SHOP_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.vouchers");
|
const t = await getTranslations("pages.admin.vouchers");
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { getTranslations } from "next-intl/server";
|
|||||||
import { saveVpn } from "@/actions/admin-vpn";
|
import { saveVpn } from "@/actions/admin-vpn";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { siteSettings } from "@/lib/services/site-settings";
|
import { siteSettings } from "@/lib/services/site-settings";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
export const metadata = { title: "VPN" };
|
export const metadata = { title: "VPN" };
|
||||||
@@ -19,6 +21,11 @@ export default async function AdminVpn({
|
|||||||
}: {
|
}: {
|
||||||
searchParams: Promise<{ saved?: string }>;
|
searchParams: Promise<{ saved?: string }>;
|
||||||
}) {
|
}) {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.SETTINGS_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const sp = await searchParams;
|
const sp = await searchParams;
|
||||||
const t = await getTranslations("pages.admin.vpn");
|
const t = await getTranslations("pages.admin.vpn");
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,19 @@ import {
|
|||||||
} from "@/actions/admin-writeable-boxes";
|
} from "@/actions/admin-writeable-boxes";
|
||||||
import { StatusCard } from "@/components/admin/dashboard";
|
import { StatusCard } from "@/components/admin/dashboard";
|
||||||
import { prisma } from "@/lib/prisma";
|
import { prisma } from "@/lib/prisma";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import { canAccess, getAdminContext, PERMS } from "@/lib/permissions";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export const metadata = { title: "Writeable boxes" };
|
export const metadata = { title: "Writeable boxes" };
|
||||||
|
|
||||||
export default async function AdminWriteableBoxes() {
|
export default async function AdminWriteableBoxes() {
|
||||||
|
const { session, permissions } = await getAdminContext();
|
||||||
|
if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) {
|
||||||
|
redirect("/admin");
|
||||||
|
}
|
||||||
|
|
||||||
const t = await getTranslations("pages.admin.writeableBoxes");
|
const t = await getTranslations("pages.admin.writeableBoxes");
|
||||||
const boxes = await prisma.websiteWriteableBoxes
|
const boxes = await prisma.websiteWriteableBoxes
|
||||||
.findMany({
|
.findMany({
|
||||||
|
|||||||
@@ -1,21 +1,14 @@
|
|||||||
import { existsSync, readFileSync } from "node:fs";
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||||
|
import { join, relative } from "node:path";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
const ROUTES: Array<[string, string]> = [
|
const ROUTES: Array<[string, string]> = [
|
||||||
["moderation", "PERMS.MODERATION_VIEW"],
|
["moderation", "PERMS.MODERATION_VIEW"],
|
||||||
["moderation/actions", "PERMS.MODERATION_EDIT"],
|
["moderation/actions", "PERMS.MODERATION_EDIT"],
|
||||||
["moderation/cfh", "PERMS.MODERATION_VIEW"],
|
["moderation/cfh", "PERMS.MODERATION_VIEW"],
|
||||||
["moderation/cfh/[id]", "PERMS.MODERATION_VIEW"],
|
|
||||||
["moderation/team", "PERMS.MODERATION_VIEW"],
|
|
||||||
["logs/audit", "PERMS.LOGS_VIEW"],
|
["logs/audit", "PERMS.LOGS_VIEW"],
|
||||||
["logs/chat", "PERMS.LOGS_VIEW"],
|
|
||||||
["logs/commands", "PERMS.LOGS_VIEW"],
|
|
||||||
["logs/trades", "PERMS.LOGS_VIEW"],
|
|
||||||
["analytics", "PERMS.ANALYTICS_VIEW"],
|
["analytics", "PERMS.ANALYTICS_VIEW"],
|
||||||
["analytics/activity", "PERMS.ANALYTICS_VIEW"],
|
|
||||||
["analytics/economy", "PERMS.ANALYTICS_VIEW"],
|
|
||||||
["devops", "PERMS.DEVOPS_VIEW"],
|
["devops", "PERMS.DEVOPS_VIEW"],
|
||||||
["devops/errors", "PERMS.DEVOPS_VIEW"],
|
|
||||||
["online", "PERMS.USERS_VIEW"],
|
["online", "PERMS.USERS_VIEW"],
|
||||||
["commandocentrum", "PERMS.RCON_EXECUTE"],
|
["commandocentrum", "PERMS.RCON_EXECUTE"],
|
||||||
["users/edit/[id]", "PERMS.USERS_EDIT"],
|
["users/edit/[id]", "PERMS.USERS_EDIT"],
|
||||||
@@ -26,6 +19,43 @@ const ROUTES: Array<[string, string]> = [
|
|||||||
["wordfilter", "PERMS.WORDFILTER_VIEW"],
|
["wordfilter", "PERMS.WORDFILTER_VIEW"],
|
||||||
["articles", "PERMS.NEWS_VIEW"],
|
["articles", "PERMS.NEWS_VIEW"],
|
||||||
["shop", "PERMS.SHOP_VIEW"],
|
["shop", "PERMS.SHOP_VIEW"],
|
||||||
|
["transactions", "PERMS.SHOP_VIEW"],
|
||||||
|
["vouchers", "PERMS.SHOP_VIEW"],
|
||||||
|
["vpn", "PERMS.SETTINGS_VIEW"],
|
||||||
|
["ip", "PERMS.SETTINGS_VIEW"],
|
||||||
|
["maintenance", "PERMS.SETTINGS_VIEW"],
|
||||||
|
["alerts", "PERMS.NOTIFICATIONS_VIEW"],
|
||||||
|
["tags", "PERMS.PAGES_VIEW"],
|
||||||
|
["ads", "PERMS.PAGES_VIEW"],
|
||||||
|
["media", "PERMS.PAGES_VIEW"],
|
||||||
|
["photos", "PERMS.PAGES_VIEW"],
|
||||||
|
["badges", "PERMS.CATALOG_VIEW"],
|
||||||
|
["teams", "PERMS.USERS_VIEW"],
|
||||||
|
["applications", "PERMS.USERS_VIEW"],
|
||||||
|
["logs", "PERMS.LOGS_VIEW"],
|
||||||
|
["catalog", "PERMS.CATALOG_VIEW"],
|
||||||
|
["rooms/edit/[id]", "PERMS.ROOMS_EDIT"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const ACTION_GATES: Array<[string, string]> = [
|
||||||
|
["admin-settings.ts", "PERMS.SETTINGS_EDIT"],
|
||||||
|
["admin-theme.ts", "PERMS.SETTINGS_EDIT"],
|
||||||
|
["admin-emulator.ts", "PERMS.SETTINGS_EDIT"],
|
||||||
|
["admin-bans.ts", "PERMS.USERS_BAN"],
|
||||||
|
["admin-wordfilter.ts", "PERMS.WORDFILTER_EDIT"],
|
||||||
|
["admin-articles.ts", "PERMS.NEWS_EDIT"],
|
||||||
|
["admin-shop.ts", "PERMS.SHOP_EDIT"],
|
||||||
|
["admin-radio-autodj.ts", "PERMS.RADIO_EDIT"],
|
||||||
|
["catalog.ts", "PERMS.CATALOG_EDIT"],
|
||||||
|
["rooms.ts", "PERMS.ROOMS_EDIT"],
|
||||||
|
["admin-users.ts", "PERMS.USERS_EDIT"],
|
||||||
|
["admin-vpn.ts", "PERMS.SETTINGS_EDIT"],
|
||||||
|
["admin-ads.ts", "PERMS.PAGES_EDIT"],
|
||||||
|
["admin-vouchers.ts", "PERMS.SHOP_EDIT"],
|
||||||
|
["admin-alerts.ts", "PERMS.NOTIFICATIONS_EDIT"],
|
||||||
|
["admin-permissions.ts", "PERMS.PERMISSIONS_MANAGE"],
|
||||||
|
["commandocentrum.ts", "PERMS.RCON_EXECUTE"],
|
||||||
|
["translations.ts", "PERMS.SETTINGS_EDIT"],
|
||||||
];
|
];
|
||||||
|
|
||||||
describe("admin operations route contract", () => {
|
describe("admin operations route contract", () => {
|
||||||
@@ -51,36 +81,42 @@ describe("admin operations route contract", () => {
|
|||||||
expect(readFileSync(path, "utf8"), path).toContain(permission);
|
expect(readFileSync(path, "utf8"), path).toContain(permission);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("guards moderation mutations separately from page navigation", () => {
|
it.each(ACTION_GATES)("guards %s with %s", (file, permission) => {
|
||||||
const source = readFileSync("src/actions/moderation.ts", "utf8");
|
|
||||||
expect(source).toContain("PERMS.MODERATION_EDIT");
|
|
||||||
expect(source).toContain("adminAction");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("guards translation writes with SETTINGS_EDIT", () => {
|
|
||||||
const source = readFileSync("src/actions/translations.ts", "utf8");
|
|
||||||
expect(source).toContain("PERMS.SETTINGS_EDIT");
|
|
||||||
expect(source).not.toContain("rank < 7");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("guards commandocentrum mutations with RCON_EXECUTE", () => {
|
|
||||||
const source = readFileSync("src/actions/commandocentrum.ts", "utf8");
|
|
||||||
expect(source).toContain("PERMS.RCON_EXECUTE");
|
|
||||||
expect(source).not.toContain("requireStaff()");
|
|
||||||
});
|
|
||||||
|
|
||||||
it.each([
|
|
||||||
["admin-settings.ts", "PERMS.SETTINGS_EDIT"],
|
|
||||||
["admin-theme.ts", "PERMS.SETTINGS_EDIT"],
|
|
||||||
["admin-emulator.ts", "PERMS.SETTINGS_EDIT"],
|
|
||||||
["admin-bans.ts", "PERMS.USERS_BAN"],
|
|
||||||
["admin-wordfilter.ts", "PERMS.WORDFILTER_EDIT"],
|
|
||||||
["admin-articles.ts", "PERMS.NEWS_EDIT"],
|
|
||||||
["admin-shop.ts", "PERMS.SHOP_EDIT"],
|
|
||||||
["admin-radio-autodj.ts", "PERMS.RADIO_EDIT"],
|
|
||||||
])("guards %s mutations with %s", (file, permission) => {
|
|
||||||
const source = readFileSync(`src/actions/${file}`, "utf8");
|
const source = readFileSync(`src/actions/${file}`, "utf8");
|
||||||
expect(source).toContain(permission);
|
expect(source).toContain(permission);
|
||||||
expect(source).not.toMatch(/await requireStaff\(\)/);
|
expect(source).not.toMatch(/await requireStaff\(\)/);
|
||||||
|
expect(source).not.toMatch(/await requireStaffRateLimited\(\)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has no requireStaff left in admin action modules", () => {
|
||||||
|
const dir = "src/actions";
|
||||||
|
const offenders: string[] = [];
|
||||||
|
for (const name of readdirSync(dir)) {
|
||||||
|
if (!name.endsWith(".ts") || name.endsWith(".test.ts")) continue;
|
||||||
|
const source = readFileSync(join(dir, name), "utf8");
|
||||||
|
if (/await requireStaff(RateLimited)?\(\)/.test(source)) {
|
||||||
|
offenders.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(offenders).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has no requireStaff left in admin pages", () => {
|
||||||
|
const root = "src/app/admin";
|
||||||
|
const offenders: string[] = [];
|
||||||
|
function walk(dir: string) {
|
||||||
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = join(dir, entry.name);
|
||||||
|
if (entry.isDirectory()) walk(full);
|
||||||
|
else if (entry.name === "page.tsx") {
|
||||||
|
const source = readFileSync(full, "utf8");
|
||||||
|
if (/await requireStaff\(/.test(source)) {
|
||||||
|
offenders.push(relative(process.cwd(), full).replaceAll("\\", "/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
walk(root);
|
||||||
|
expect(offenders).toEqual([]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user