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",