diff --git a/src/collections/game/GameRules.ts b/src/collections/game/GameRules.ts index 29274f9..f5217a5 100644 --- a/src/collections/game/GameRules.ts +++ b/src/collections/game/GameRules.ts @@ -18,5 +18,25 @@ export const GameRules: GlobalConfig = { 'The base currency ("gold standard") used by all factions to pay for goods and services.', }, }, + { + name: "commandAssignments", + type: "relationship", + relationTo: "assignments", + hasMany: true, + admin: { + description: + "Assignment(s) considered unit command. Members and leaders of these assignments get elevated access to draft missions and other restricted content.", + }, + }, + { + name: "intelAssignments", + type: "relationship", + relationTo: "assignments", + hasMany: true, + admin: { + description: + "Assignment(s) considered the intelligence division. Members and leaders of these assignments get elevated access to draft missions and intelligence content.", + }, + }, ], }; diff --git a/src/collections/intelligence/Missions.ts b/src/collections/intelligence/Missions.ts index b8d09d4..48af913 100644 --- a/src/collections/intelligence/Missions.ts +++ b/src/collections/intelligence/Missions.ts @@ -1,5 +1,40 @@ -import { CollectionConfig } from "payload"; +import type { CollectionConfig, Payload, User } from "payload"; import { isDeveloper } from "@/utils/access-control/isRole"; +import hasRoles from "@/utils/access-control/hasRoles"; + +type AssignmentRef = { id: number } | number; + +async function isInAssignments( + payload: Payload, + userId: number, + assignmentIds: number[], +): Promise { + if (assignmentIds.length === 0) return false; + const assignments = await payload.find({ + collection: "assignments", + where: { id: { in: assignmentIds } }, + limit: assignmentIds.length, + depth: 0, + select: { leader: true, members: true }, + }); + return assignments.docs.some((a) => { + const leaderId = + a.leader && typeof a.leader === "object" ? a.leader.id : a.leader; + const memberIds = (a.members ?? []).map((m) => + typeof m === "object" ? m.id : m, + ); + return ( + (leaderId !== null && leaderId !== undefined && leaderId === userId) || + memberIds.includes(userId) + ); + }); +} + +function extractIds(refs: unknown[] | undefined): number[] { + return (refs ?? []).map((r) => + typeof r === "object" && r !== null ? (r as { id: number }).id : (r as number), + ); +} export const Missions: CollectionConfig = { slug: "missions", @@ -10,7 +45,55 @@ export const Missions: CollectionConfig = { create: isDeveloper, update: isDeveloper, delete: isDeveloper, - read: isDeveloper, + read: async ({ req, data }) => { + if (!req.user) return false; + if (hasRoles(["developer", "admin"], req.user)) return true; + + const userId = req.user.id; + + const gameRules = await req.payload.findGlobal({ slug: "game-rules" }); + const commandIds = extractIds( + gameRules.commandAssignments as AssignmentRef[] | undefined, + ); + const intelIds = extractIds( + gameRules.intelAssignments as AssignmentRef[] | undefined, + ); + + const [isCommand, isIntel] = await Promise.all([ + isInAssignments(req.payload, userId, commandIds), + isInAssignments(req.payload, userId, intelIds), + ]); + + const authorIds = extractIds( + data?.ownershipAndStatus?.authors as unknown[] | undefined, + ); + const zeusIds = extractIds( + data?.ownershipAndStatus?.zeus as unknown[] | undefined, + ); + const isAuthorOrZeus = + authorIds.includes(userId) || zeusIds.includes(userId); + + const status = data?.ownershipAndStatus?.status; + const isDraft = status === "Concept" || status === "Planning"; + + if (isDraft) { + return isAuthorOrZeus || isCommand || isIntel; + } + + const visibility = data?.ownershipAndStatus?.visibility; + switch (visibility) { + case "unit": + return true; + case "leadership": + return isCommand || isIntel || isAuthorOrZeus; + case "intel": + return isIntel || isAuthorOrZeus; + case "private": + return isAuthorOrZeus; + default: + return true; + } + }, }, fields: [ { @@ -344,8 +427,9 @@ export const Missions: CollectionConfig = { type: "array", fields: [ { - name: "factionName", - type: "text", + name: "faction", + type: "relationship", + relationTo: "factions", required: true, }, { @@ -406,6 +490,9 @@ export const Missions: CollectionConfig = { type: "text", hidden: true, defaultValue: "ptf313", + access: { + read: isDeveloper, + }, }, ], }, @@ -417,7 +504,6 @@ export const Missions: CollectionConfig = { name: "missionFile", type: "upload", relationTo: "mission-files", - required: true, }, { name: "requiredMods",