Finish fine-grained ACL across remaining admin pages and actions.
Local Build and Deploy / deploy (push) Successful in 56s
Local Build and Deploy / deploy (push) Successful in 56s
Replace leftover requireStaff gates with module PERMS, drop hardcoded room rank thresholds, and expand contract tests so admin mutations cannot regress to dashboard-only checks. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
1 parent
3c8a8ff888
commit
0e89d03940
63 files changed
+420
-163
No files matched your search
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { 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