UNPKG

@lucidcms/core

Version:

The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.

1 lines 10 kB
{"version":3,"file":"index.mjs","names":["constants"],"sources":["../../../../src/services/document-publish-operations/helpers/index.ts"],"sourcesContent":["import constants from \"../../../constants/constants.js\";\nimport type CollectionBuilder from \"../../../libs/collection/builders/collection-builder/index.js\";\nimport { copy } from \"../../../libs/i18n/index.js\";\nimport { resolveCollectionPermission } from \"../../../libs/permission/collection-permissions.js\";\nimport hasAccess from \"../../../libs/permission/has-access.js\";\nimport type { QueueEvent } from \"../../../libs/queue/types.js\";\nimport type { Config } from \"../../../types/config.js\";\nimport type { LucidErrorData } from \"../../../types/errors.js\";\nimport type { LucidAuth } from \"../../../types/hono.js\";\nimport { tenantAccessAllowed } from \"../../../utils/helpers/index.js\";\n\n/** Approval states that still represent an unresolved release for a document target. */\nexport const activePublishOperationStatuses = [\"pending\", \"approved\"] as const;\n\n/** Execution states that can still be completed by approval, queue dispatch, retry, or cancellation. */\nexport const unresolvedPublishOperationExecutionStatuses = [\n\t\"awaiting_approval\",\n\t\"scheduled\",\n\t\"executing\",\n\t\"failed\",\n] as const;\n\n/** Version type used to freeze publish operation snapshots before execution. */\nexport const snapshotVersionType =\n\tconstants.collectionBuilder.publishing.snapshotVersionType;\n\n/** Queue event used to execute a stored publish operation from delayed dispatch. */\nexport const publishOperationExecuteEvent =\n\t\"document-publish-operation:execute\" satisfies QueueEvent;\n\n/** Lookahead for queue dispatch; keeps scheduling comfortably below provider delay limits. */\nexport const schedulingDispatchWindowHours = 6;\n\nexport const schedulingDispatchWindowMs =\n\tschedulingDispatchWindowHours * 60 * 60 * 1000; // 6 hours\n\n/** Returns environment targets that require release review for the collection. */\nexport const getPublishOperationTargets = (collection: CollectionBuilder) => {\n\tconst targets = collection.getData.review?.requiredFor;\n\tif (targets === undefined) return [];\n\n\tconst environmentKeys = collection.getData.environments.map(\n\t\t(environment) => environment.key,\n\t);\n\treturn targets.filter((target) => environmentKeys.includes(target));\n};\n\n/** Checks whether the target participates in the review-backed publish operation flow. */\nexport const canUsePublishOperationsForTarget = (params: {\n\tcollection: CollectionBuilder;\n\ttarget: string;\n}) => getPublishOperationTargets(params.collection).includes(params.target);\n\n/** Returns environment targets that must be current before a release can be created. */\nexport const getReleaseRequirementTargets = (params: {\n\tcollection: CollectionBuilder;\n\ttarget: string;\n}) => {\n\tconst targetEnvironment = params.collection.getData.environments.find(\n\t\t(environment) => environment.key === params.target,\n\t);\n\n\treturn Array.from(new Set(targetEnvironment?.requires ?? []));\n};\n\n/** Returns configured release requirements whose content does not match the submitted source. */\nexport const getUnmetReleaseRequirementTargets = (params: {\n\tcollection: CollectionBuilder;\n\ttarget: string;\n\tsourceContentId: string;\n\tcontentIdsByTarget: Map<string, string | null | undefined>;\n}) =>\n\tgetReleaseRequirementTargets({\n\t\tcollection: params.collection,\n\t\ttarget: params.target,\n\t}).filter(\n\t\t(requiredTarget) =>\n\t\t\tparams.contentIdsByTarget.get(requiredTarget) !== params.sourceContentId,\n\t);\n\n/** Checks scheduling support across collection config, target type, and runtime queue capability. */\nexport const collectionTargetSupportsScheduling = (params: {\n\tcollection: CollectionBuilder;\n\ttarget: string;\n\tqueueSupportsScheduling: boolean;\n}) => {\n\tconst targetIsEnvironment = params.collection.getData.environments.some(\n\t\t(environment) => environment.key === params.target,\n\t);\n\n\treturn (\n\t\ttargetIsEnvironment &&\n\t\tparams.collection.getData.scheduling === true &&\n\t\tparams.queueSupportsScheduling === true\n\t);\n};\n\n/** Determines whether a scheduled operation is close enough for a delayed queue job. */\nexport const isInSchedulingDispatchWindow = (params: {\n\tscheduledAt: Date;\n\tnow?: Date;\n}) => {\n\tconst now = params.now ?? new Date();\n\treturn (\n\t\tparams.scheduledAt.getTime() <= now.getTime() + schedulingDispatchWindowMs\n\t);\n};\n\n/** Normalizes optional schedule input and validates UTC minute precision plus IANA timezone. */\nexport const parseScheduleInput = (params: {\n\tscheduledAt?: string | null;\n\tscheduledTimezone?: string | null;\n}): {\n\terror?: LucidErrorData;\n\tdata?: {\n\t\tscheduledAt: string | null;\n\t\tscheduledTimezone: string | null;\n\t\tscheduledDate: Date | null;\n\t\tprovided: boolean;\n\t};\n} => {\n\tconst scheduledAtProvided = params.scheduledAt !== undefined;\n\tconst timezoneProvided = params.scheduledTimezone !== undefined;\n\n\tif (!scheduledAtProvided && !timezoneProvided) {\n\t\treturn {\n\t\t\tdata: {\n\t\t\t\tscheduledAt: null,\n\t\t\t\tscheduledTimezone: null,\n\t\t\t\tscheduledDate: null,\n\t\t\t\tprovided: false,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (params.scheduledAt === null) {\n\t\treturn {\n\t\t\tdata: {\n\t\t\t\tscheduledAt: null,\n\t\t\t\tscheduledTimezone: null,\n\t\t\t\tscheduledDate: null,\n\t\t\t\tprovided: true,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (!params.scheduledAt || !params.scheduledTimezone) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\"server:core.publish.operations.schedule.incomplete\"),\n\t\t\t\tstatus: 400,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst scheduledDate = new Date(params.scheduledAt);\n\tif (Number.isNaN(scheduledDate.getTime())) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\"server:core.publish.operations.schedule.invalid\"),\n\t\t\t\tstatus: 400,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (\n\t\tscheduledDate.getUTCSeconds() !== 0 ||\n\t\tscheduledDate.getUTCMilliseconds() !== 0\n\t) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\n\t\t\t\t\t\"server:core.publish.operations.schedule.minute.precision\",\n\t\t\t\t),\n\t\t\t\tstatus: 400,\n\t\t\t},\n\t\t};\n\t}\n\n\ttry {\n\t\tnew Intl.DateTimeFormat(\"en-US\", {\n\t\t\ttimeZone: params.scheduledTimezone,\n\t\t}).format(scheduledDate);\n\t} catch {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tmessage: copy(\n\t\t\t\t\t\"server:core.publish.operations.schedule.timezone.invalid\",\n\t\t\t\t),\n\t\t\t\tstatus: 400,\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tdata: {\n\t\t\tscheduledAt: scheduledDate.toISOString(),\n\t\t\tscheduledTimezone: params.scheduledTimezone,\n\t\t\tscheduledDate,\n\t\t\tprovided: true,\n\t\t},\n\t};\n};\n\n/** Resolves and checks the collection permission required for the action. */\nexport const hasCollectionPermission = (params: {\n\tuser: LucidAuth;\n\tcollection: CollectionBuilder;\n\taction: \"publish\" | \"review\";\n}) => {\n\tconst permission = resolveCollectionPermission({\n\t\tcollection: params.collection,\n\t\taction: params.action,\n\t});\n\n\treturn hasAccess({\n\t\tuser: params.user,\n\t\trequiredPermissions: [permission],\n\t});\n};\n\n/** Lists tenant-visible collections whose release requests the user may review. */\nexport const getReviewableCollectionKeys = (params: {\n\tconfig: Config;\n\tuser: LucidAuth;\n\ttenantKey?: string | null;\n}) => {\n\treturn params.config.collections\n\t\t.filter((collection) =>\n\t\t\ttenantAccessAllowed(collection.getData.tenants, params.tenantKey),\n\t\t)\n\t\t.filter((collection) =>\n\t\t\thasCollectionPermission({\n\t\t\t\tuser: params.user,\n\t\t\t\tcollection,\n\t\t\t\taction: \"review\",\n\t\t\t}),\n\t\t)\n\t\t.map((collection) => collection.key);\n};\n"],"mappings":"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"}