diff --git a/src/app/(frontend)/account/actions.ts b/src/app/(frontend)/account/actions.ts
index 3f8f5cf..f3bf76f 100644
--- a/src/app/(frontend)/account/actions.ts
+++ b/src/app/(frontend)/account/actions.ts
@@ -144,26 +144,34 @@ export async function requestDiscordUsernameChange(input: {
return { success: false, error: "That Discord username is already in use." };
}
- const staffRes = await payload.find({
- collection: "users",
- where: {
- or: [{ roles: { contains: "admin" } }, { roles: { contains: "developer" } }],
- },
- limit: 100,
+ const staffRoles = await payload.find({
+ collection: "roles",
+ where: { slug: { in: ["admin", "developer"] } },
+ limit: 2,
depth: 0,
overrideAccess: true,
});
- const staffIds = [...new Set(staffRes.docs.map((doc) => doc.id as number))].filter(
- (id) => id !== userId,
- );
- for (const staffId of staffIds) {
- await notifyUser(payload, {
- userId: staffId,
- type: "account:discord-request",
- title: `Discord username change requested by ${user.username}`,
- message: `wants to change their Discord username to "${trimmed}".`,
- link: `/admin/collections/users/${userId}`,
+ const staffRoleIds = staffRoles.docs.map((d) => d.id);
+ if (staffRoleIds.length > 0) {
+ const staffRes = await payload.find({
+ collection: "users",
+ where: { roleDocs: { in: staffRoleIds } },
+ limit: 100,
+ depth: 0,
+ overrideAccess: true,
});
+ const staffIds = [...new Set(staffRes.docs.map((doc) => doc.id as number))].filter(
+ (id) => id !== userId,
+ );
+ for (const staffId of staffIds) {
+ await notifyUser(payload, {
+ userId: staffId,
+ type: "account:discord-request",
+ title: `Discord username change requested by ${user.username}`,
+ message: `wants to change their Discord username to "${trimmed}".`,
+ link: `/admin/collections/users/${userId}`,
+ });
+ }
}
await notifyUser(payload, {
diff --git a/src/app/(frontend)/helpdesk/[id]/page.tsx b/src/app/(frontend)/helpdesk/[id]/page.tsx
index a50dbb2..ca104fd 100644
--- a/src/app/(frontend)/helpdesk/[id]/page.tsx
+++ b/src/app/(frontend)/helpdesk/[id]/page.tsx
@@ -4,7 +4,7 @@ import { notFound, redirect } from "next/navigation";
import { getPayload } from "payload";
import type { Ticket } from "@/payload-types";
import { TicketDetail } from "@/components/frontend/helpdesk/TicketDetail";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { ArrowLeftIcon, LifeBuoyIcon } from "lucide-react";
import Link from "next/link";
@@ -18,14 +18,16 @@ interface TicketPageProps {
export default async function TicketPage({ params }: TicketPageProps) {
const { id } = await params;
+ const headers = await nextHeaders();
const payload = await getPayload({ config });
const { user } = await payload.auth({
- headers: await nextHeaders(),
+ headers,
canSetHeaders: false,
});
if (!user) {
- redirect("/login");
+ const pathname = headers.get("x-invoke-path") || `/helpdesk/${id}`;
+ redirect(`/login?returnTo=${encodeURIComponent(pathname)}`);
}
const ticket = await payload
@@ -40,8 +42,8 @@ export default async function TicketPage({ params }: TicketPageProps) {
notFound();
}
- const typedTicket = ticket as unknown as Ticket;
- const isStaff = hasRoles(["admin", "developer"], user);
+ const typedTicket = ticket as unknown as Ticket;
+ const isStaff = await hasPermission(payload, user, "tickets:staff");
const assigneeOptions = isStaff
? (
await payload.find({
diff --git a/src/app/(frontend)/helpdesk/actions.ts b/src/app/(frontend)/helpdesk/actions.ts
index 81f09ed..b41a3a6 100644
--- a/src/app/(frontend)/helpdesk/actions.ts
+++ b/src/app/(frontend)/helpdesk/actions.ts
@@ -3,7 +3,7 @@
import config from "@payload-config";
import { getPayload } from "payload";
import type { Ticket } from "@/payload-types";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
import { notifyUser } from "@/lib/notifications";
@@ -144,7 +144,7 @@ export async function replyToTicket(ticketId: number, content: string): Promise<
const userId = user.id as number;
const ticket = await getTicketOrThrow(payload, ticketId);
- const isStaff = hasRoles(["admin", "developer"], user);
+ const isStaff = await hasPermission(payload, user, "tickets:staff");
const reporterId = reporterIdOf(ticket);
if (!isStaff && reporterId !== userId) {
throw new Error("You don't have access to this ticket.");
@@ -241,7 +241,7 @@ export async function updateTicketStatus(
const ticket = await getTicketOrThrow(payload, ticketId);
if (status === ticket.status) return { success: true };
- const isStaff = hasRoles(["admin", "developer"], user);
+ const isStaff = await hasPermission(payload, user, "tickets:staff");
const reporterId = reporterIdOf(ticket);
if (!isStaff) {
const reporterCancel =
@@ -299,7 +299,7 @@ export async function assignTicket(
const { payload, user } = await authenticate();
const userId = user.id as number;
- if (!hasRoles(["admin", "developer"], user)) {
+ if (!(await hasPermission(payload, user, "tickets:staff"))) {
throw new Error("Only staff can assign tickets.");
}
diff --git a/src/app/(frontend)/helpdesk/page.tsx b/src/app/(frontend)/helpdesk/page.tsx
index 75ce25c..0fc1981 100644
--- a/src/app/(frontend)/helpdesk/page.tsx
+++ b/src/app/(frontend)/helpdesk/page.tsx
@@ -4,7 +4,7 @@ import { redirect } from "next/navigation";
import { getPayload } from "payload";
import type { Ticket } from "@/payload-types";
import { TicketsView } from "@/components/frontend/helpdesk/TicketsView";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { LifeBuoyIcon } from "lucide-react";
export const metadata = {
@@ -12,14 +12,16 @@ export const metadata = {
};
export default async function HelpdeskPage() {
+ const headers = await nextHeaders();
const payload = await getPayload({ config });
const { user } = await payload.auth({
- headers: await nextHeaders(),
+ headers,
canSetHeaders: false,
});
if (!user) {
- redirect("/login");
+ const pathname = headers.get("x-invoke-path") || "/helpdesk";
+ redirect(`/login?returnTo=${encodeURIComponent(pathname)}`);
}
const ticketsRes = await payload.find({
@@ -28,8 +30,8 @@ export default async function HelpdeskPage() {
limit: 200,
depth: 2,
});
- const tickets = ticketsRes.docs as unknown as Ticket[];
- const isStaff = hasRoles(["admin", "developer"], user);
+ const tickets = ticketsRes.docs as unknown as Ticket[];
+ const isStaff = await hasPermission(payload, user, "tickets:staff");
return (
diff --git a/src/app/(frontend)/locker/actions.ts b/src/app/(frontend)/locker/actions.ts
index 396fbbe..0c20d18 100644
--- a/src/app/(frontend)/locker/actions.ts
+++ b/src/app/(frontend)/locker/actions.ts
@@ -3,7 +3,7 @@
import config from "@payload-config";
import { getPayload } from "payload";
import type { Asset, Loadout, LockerStorage, User } from "@/payload-types";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
import {
@@ -24,6 +24,7 @@ import {
skinAppliesTo,
toLockerGridItems,
} from "@/lib/locker";
+import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
export interface ActionResult
{
success: boolean;
@@ -45,8 +46,9 @@ async function authenticate() {
return { payload, user };
}
-function isLockerManager(user: User): boolean {
- return hasRoles(["admin", "developer"], user);
+async function isLockerManager(payload: Awaited>, user: User): Promise {
+ if (await hasPermission(payload, user, "locker-storages:update")) return true;
+ return hasLogisticsQualification(payload, user);
}
async function getLockerWithItems(
@@ -114,7 +116,7 @@ export async function addItemToLocker(
): Promise> {
try {
const { payload, user } = await authenticate();
- if (!isLockerManager(user)) {
+ if (!(await hasPermission(payload, user, "locker-storages:update"))) {
return {
success: false,
error: "Insufficient permissions. Admin or Developer role required to add items.",
@@ -759,7 +761,7 @@ async function getOwnedLoadout(
? (loadout.ownerUser as { id: number }).id
: (loadout.ownerUser as number);
- if (ownerId !== user.id && !isLockerManager(user)) return null;
+ if (ownerId !== user.id && !(await hasPermission(payload, user, "locker-storages:update"))) return null;
return loadout;
}
diff --git a/src/app/(frontend)/locker/page.tsx b/src/app/(frontend)/locker/page.tsx
index d80a54d..9e390e8 100644
--- a/src/app/(frontend)/locker/page.tsx
+++ b/src/app/(frontend)/locker/page.tsx
@@ -5,7 +5,7 @@ import type { Asset, Loadout, LockerStorage } from "@/payload-types";
import { LockKeyholeIcon } from "lucide-react";
import { ensureLockerStorage, getLockerGridDimensions } from "@/lib/locker";
import { LockerView } from "@/components/frontend/locker/LockerView";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
export const metadata = {
title: "Locker — Polaris Task Force",
@@ -34,9 +34,9 @@ export default async function LockerPage() {
limit: 100,
depth: 1,
});
- const loadouts = loadoutsRes.docs as unknown as Loadout[];
+ const loadouts = loadoutsRes.docs as unknown as Loadout[];
- const isManager = user ? hasRoles(["admin", "developer"], user) : false;
+ const isManager = user ? (await hasPermission(payload, user, "locker-storages:update")) : false;
let assetsCatalog: Asset[] = [];
if (isManager) {
diff --git a/src/app/(frontend)/logistics/banking/[id]/page.tsx b/src/app/(frontend)/logistics/banking/[id]/page.tsx
index c9f9b5e..e5aed6b 100644
--- a/src/app/(frontend)/logistics/banking/[id]/page.tsx
+++ b/src/app/(frontend)/logistics/banking/[id]/page.tsx
@@ -4,7 +4,7 @@ import { headers as nextHeaders } from "next/headers";
import { notFound } from "next/navigation";
import type { BankAccount, LedgerEntry } from "@/payload-types";
import { AccountDetail } from "@/components/frontend/banking/AccountDetail";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
export const metadata = {
@@ -35,12 +35,12 @@ export default async function AccountPage({ params }: AccountPageProps) {
notFound();
}
- const typedAccount = account as unknown as BankAccount;
+ const typedAccount = account as unknown as BankAccount;
- const isManager = user
- ? hasRoles(["admin", "developer"], user) ||
- (await hasLogisticsQualification(payload, user).catch(() => false))
- : false;
+ const isManager = user
+ ? (await hasPermission(payload, user, "banking:manage")) ||
+ (await hasLogisticsQualification(payload, user).catch(() => false))
+ : false;
const ownerId =
typeof typedAccount.ownerUser === "object"
diff --git a/src/app/(frontend)/logistics/banking/actions.ts b/src/app/(frontend)/logistics/banking/actions.ts
index 87ee51e..aaf249a 100644
--- a/src/app/(frontend)/logistics/banking/actions.ts
+++ b/src/app/(frontend)/logistics/banking/actions.ts
@@ -3,7 +3,7 @@
import config from "@payload-config";
import { getPayload } from "payload";
import type { User } from "@/payload-types";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
@@ -42,7 +42,7 @@ async function isBankingManager(
payload: Awaited>,
user: User,
): Promise {
- if (hasRoles(["admin", "developer"], user)) return true;
+ if (await hasPermission(payload, user, "banking:manage")) return true;
return hasLogisticsQualification(payload, user);
}
@@ -188,7 +188,7 @@ async function moveFunds(
): Promise> {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "bank-accounts:create"))) {
return { success: false, error: "Insufficient permissions." };
}
if (!amount || amount <= 0) {
diff --git a/src/app/(frontend)/logistics/banking/page.tsx b/src/app/(frontend)/logistics/banking/page.tsx
index 5aba39a..cdebf8b 100644
--- a/src/app/(frontend)/logistics/banking/page.tsx
+++ b/src/app/(frontend)/logistics/banking/page.tsx
@@ -4,7 +4,7 @@ import { headers as nextHeaders } from "next/headers";
import type { BankAccount, BankTransaction, Faction, Resource } from "@/payload-types";
import { LandmarkIcon } from "lucide-react";
import { BankingOverview } from "@/components/frontend/banking/BankingOverview";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
export const metadata = {
@@ -34,20 +34,20 @@ export default async function BankingPage() {
});
const recentTransactions = transactionsRes.docs as unknown as BankTransaction[];
- const factionsRes = await payload.find({
- collection: "factions",
- limit: 100,
- depth: 0,
- });
- const factions = (factionsRes.docs as unknown as Faction[]).map((f) => ({
- id: f.id,
- name: f.name,
- }));
+ const factionsRes = await payload.find({
+ collection: "factions",
+ limit: 100,
+ depth: 0,
+ });
+ const factions = (factionsRes.docs as unknown as Faction[]).map((f) => ({
+ id: f.id,
+ name: f.name,
+ }));
- const isManager = user
- ? hasRoles(["admin", "developer"], user) ||
- (await hasLogisticsQualification(payload, user).catch(() => false))
- : false;
+ const isManager = user
+ ? (await hasPermission(payload, user, "banking:manage")) ||
+ (await hasLogisticsQualification(payload, user).catch(() => false))
+ : false;
const currentUser = user
? {
diff --git a/src/app/(frontend)/logistics/market/actions.ts b/src/app/(frontend)/logistics/market/actions.ts
index 3b3b956..e1cc99c 100644
--- a/src/app/(frontend)/logistics/market/actions.ts
+++ b/src/app/(frontend)/logistics/market/actions.ts
@@ -3,7 +3,7 @@
import config from "@payload-config";
import { getPayload } from "payload";
import type { Asset, LockerStorage, MarketListing, MarketNegotiation, User } from "@/payload-types";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
import {
@@ -44,8 +44,8 @@ async function authenticate() {
return { payload, user };
}
-function isMarketManager(user: User): boolean {
- return hasRoles(["admin", "developer"], user);
+async function isMarketManager(payload: Awaited>, user: User): Promise {
+ return await hasPermission(payload, user, "market-listings:update");
}
function sellerIdOf(listing: MarketListing): number | null {
@@ -467,7 +467,7 @@ export async function cancelMarketListing(listingId: number): Promise
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "shipments:update"))) {
return { success: false, error: "Insufficient permissions." };
}
@@ -501,7 +501,7 @@ export async function toggleAutoReturn(
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "shipments:update"))) {
return { success: false, error: "Insufficient permissions." };
}
diff --git a/src/app/(frontend)/logistics/structures/actions.ts b/src/app/(frontend)/logistics/structures/actions.ts
index 226bd39..45e7c63 100644
--- a/src/app/(frontend)/logistics/structures/actions.ts
+++ b/src/app/(frontend)/logistics/structures/actions.ts
@@ -3,7 +3,7 @@
import config from "@payload-config";
import { getPayload } from "payload";
import { GameStructure, Resource, Structure } from "@/payload-types";
-import hasRoles from "@/utils/access-control/hasRoles";
+import { hasPermission } from "@/utils/access-control/hasPermission";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
import { checkStorageDeposit, storageViolationMessage } from "@/lib/storageRules";
@@ -74,10 +74,10 @@ export async function addResource(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
- error: "Insufficient permissions. Admin or Developer role required for deposit.",
+ error: "Insufficient permissions. User, Admin, or Developer role required.",
};
}
@@ -264,7 +264,7 @@ export async function removeResource(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. Admin or Developer role required for withdrawal.",
@@ -373,7 +373,7 @@ export async function transferResource(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required for transfer.",
@@ -524,7 +524,7 @@ export async function placeResourceOnGrid(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. Admin or Developer role required for placement.",
@@ -641,7 +641,7 @@ export async function moveGridItem(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required.",
@@ -730,7 +730,7 @@ export async function mergeGridStacks(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required.",
@@ -812,7 +812,7 @@ export async function rotateGridItem(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required.",
@@ -899,7 +899,7 @@ export async function removeGridItem(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. Admin or Developer role required for removal.",
@@ -953,7 +953,7 @@ export async function splitGridStack(
): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required.",
@@ -1061,7 +1061,7 @@ export async function splitGridStack(
export async function retrieveFromVoid(structureId: number): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return { success: false, error: "Insufficient permissions." };
}
const structure = await getStructure(payload, structureId);
@@ -1159,7 +1159,7 @@ export async function retrieveFromVoid(structureId: number): Promise {
try {
const { payload, user } = await authenticate();
- if (!hasRoles(["admin"], user) && !hasRoles(["user"], user)) {
+ if (!(await hasPermission(payload, user, "structures:update"))) {
return {
success: false,
error: "Insufficient permissions. User, Admin, or Developer role required.",
diff --git a/src/app/(frontend)/profile/[username]/page.tsx b/src/app/(frontend)/profile/[username]/page.tsx
index dcc1e66..edf76a6 100644
--- a/src/app/(frontend)/profile/[username]/page.tsx
+++ b/src/app/(frontend)/profile/[username]/page.tsx
@@ -107,7 +107,7 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
const totalKills = infantryKills + vehicleKills + armorKills + airKills;
const kdRatio = deaths > 0 ? (totalKills / deaths).toFixed(2) : totalKills > 0 ? "∞" : "—";
- const enlistDate = new Date(profile.createdAt);
+ const enlistDate = new Date(profile.dossier.enlistmentDate);
const enlistDateStr = enlistDate.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
diff --git a/src/lib/tickets/staff.ts b/src/lib/tickets/staff.ts
index 35e3811..596948b 100644
--- a/src/lib/tickets/staff.ts
+++ b/src/lib/tickets/staff.ts
@@ -8,11 +8,19 @@ type PayloadType = Awaited>;
export async function getStaffUserIds(payload: PayloadType): Promise {
try {
+ const adminRole = await payload.find({
+ collection: "roles",
+ where: { slug: { in: ["admin", "developer"] } },
+ limit: 2,
+ depth: 0,
+ overrideAccess: true,
+ });
+ const roleIds = adminRole.docs.map((d) => d.id);
+ if (roleIds.length === 0) return [];
+
const res = await payload.find({
collection: "users",
- where: {
- or: [{ roles: { contains: "admin" } }, { roles: { contains: "developer" } }],
- },
+ where: { roleDocs: { in: roleIds } },
limit: 50,
depth: 0,
select: { username: true },
diff --git a/src/utils/access-control/hasIntelligenceQualification.ts b/src/utils/access-control/hasIntelligenceQualification.ts
index e27965c..1f25172 100644
--- a/src/utils/access-control/hasIntelligenceQualification.ts
+++ b/src/utils/access-control/hasIntelligenceQualification.ts
@@ -1,4 +1,26 @@
import type { Payload } from "payload";
+import { hasAnyPermission } from "@/utils/access-control/hasPermission";
+
+const INTELLIGENCE_PERMISSIONS = [
+ "intelligence:manage",
+ "missions:read",
+ "missions:create",
+ "missions:update",
+ "missions:delete",
+ "missions:read-sensitive",
+ "campaigns:read",
+ "campaigns:create",
+ "campaigns:update",
+ "campaigns:delete",
+ "factions:read",
+ "factions:create",
+ "factions:update",
+ "factions:delete",
+ "technologies:read",
+ "technologies:create",
+ "technologies:update",
+ "technologies:delete",
+] as const;
export async function hasIntelligenceQualification(
payload: Payload,
@@ -6,10 +28,7 @@ export async function hasIntelligenceQualification(
): Promise {
if (!user) return false;
- const roles = user.roles as string[] | undefined;
- if (roles?.includes("developer") || roles?.includes("admin")) {
- return true;
- }
+ if (await hasAnyPermission(payload, user, ...INTELLIGENCE_PERMISSIONS)) return true;
const profile = (await payload.find({
collection: "profiles",
diff --git a/src/utils/access-control/hasLogisticsQualification.ts b/src/utils/access-control/hasLogisticsQualification.ts
index 410dc9e..abca30d 100644
--- a/src/utils/access-control/hasLogisticsQualification.ts
+++ b/src/utils/access-control/hasLogisticsQualification.ts
@@ -1,9 +1,55 @@
import type { Payload } from "payload";
+import { hasAnyPermission } from "@/utils/access-control/hasPermission";
+
+const LOGISTICS_PERMISSIONS = [
+ "logistics:manage",
+ "assets:read",
+ "assets:create",
+ "assets:update",
+ "assets:delete",
+ "resources:read",
+ "resources:create",
+ "resources:update",
+ "resources:delete",
+ "vehicles:read",
+ "vehicles:create",
+ "vehicles:update",
+ "vehicles:delete",
+ "structures:read",
+ "structures:create",
+ "structures:update",
+ "structures:delete",
+ "shipments:read",
+ "shipments:create",
+ "shipments:update",
+ "shipments:delete",
+ "bank-accounts:read",
+ "bank-accounts:create",
+ "bank-accounts:update",
+ "bank-accounts:delete",
+ "banking:manage",
+ "market-listings:read",
+ "market-listings:create",
+ "market-listings:update",
+ "market-listings:delete",
+ "game-structures:read",
+ "game-structures:create",
+ "game-structures:update",
+ "game-structures:delete",
+ "game-vehicles:read",
+ "game-vehicles:create",
+ "game-vehicles:update",
+ "game-vehicles:delete",
+ "game-npcs:read",
+ "game-npcs:create",
+ "game-npcs:update",
+ "game-npcs:delete",
+] as const;
/**
* Checks if a user has the Logistics qualification.
* Queries the user's profile for qualifications matching "Logistics" (case-insensitive).
- * Developers and admins always pass.
+ * Users with ANY logistics-domain RBAC permission always pass.
*/
export async function hasLogisticsQualification(
payload: Payload,
@@ -11,10 +57,7 @@ export async function hasLogisticsQualification(
): Promise {
if (!user) return false;
- const roles = user.roles as string[] | undefined;
- if (roles?.includes("developer") || roles?.includes("admin")) {
- return true;
- }
+ if (await hasAnyPermission(payload, user, ...LOGISTICS_PERMISSIONS)) return true;
const profileRes = await payload.find({
collection: "profiles",