UNPKG

@paroicms/server

Version:
156 lines 6.73 kB
import { getDocumentTypeByName, getPartTypeByName } from "@paroicms/internal-anywhere-lib"; import { isDef } from "@paroicms/public-anywhere-lib"; import { ApiError } from "@paroicms/public-server-lib"; import { type } from "arktype"; import { invalidateNodeInCache } from "../../common/text-cache.js"; import { countPartsOfList } from "./part.queries.js"; export async function movePart(siteContext, { partNodeId, newParentNodeId, newListName, }) { const partInfo = await getPartInfoForMove(siteContext, partNodeId); const parentInfo = await getParentInfoForMove(siteContext, newParentNodeId); const resolvedDocumentNodeId = parentInfo.parentDocumentNodeId ?? newParentNodeId; validateMoveWithinSameDocument(partInfo.documentNodeId, resolvedDocumentNodeId); const targetListType = await getAndValidateTargetListType(siteContext.siteSchema, parentInfo, newListName, partInfo.partTypeName); const shouldMove = await checkIfMoveIsNeeded(siteContext, partNodeId, newParentNodeId, partInfo.currentListName, newListName); if (!shouldMove) { return; } await validateListLimit(siteContext, newParentNodeId, newListName, targetListType); await executePartMove(siteContext, partNodeId, newParentNodeId, newListName, targetListType); await invalidateNodeInCache(siteContext, { nodeId: partInfo.documentNodeId }); } const BeforeMovePartAT = type({ partTypeName: "string", currentListName: "string", documentNodeId: "number", "+": "reject", }).pipe((r) => ({ partTypeName: r.partTypeName, currentListName: r.currentListName, documentNodeId: String(r.documentNodeId), })); async function getPartInfoForMove(siteContext, partNodeId) { const { cn } = siteContext; const partRow = await cn("PaNode as n") .select([ "n.typeName as partTypeName", "p.listName as currentListName", "p.documentNodeId as documentNodeId", ]) .innerJoin("PaPartNode as p", "p.nodeId", "n.id") .where("n.id", partNodeId) .first(); if (!partRow) { throw new ApiError(`cannot find part-node '${partNodeId}'`, 404); } return BeforeMovePartAT.assert(partRow); } const ParentMovePartAT = type({ parentTypeName: "string", parentDocumentNodeId: "number|null", "+": "reject", }).pipe((r) => ({ parentTypeName: r.parentTypeName, parentDocumentNodeId: isDef(r.parentDocumentNodeId) ? String(r.parentDocumentNodeId) : undefined, })); async function getParentInfoForMove(siteContext, newParentNodeId) { const { cn } = siteContext; const parentRow = await cn("PaNode as n") .select(["n.typeName as parentTypeName", "p.documentNodeId as parentDocumentNodeId"]) .leftJoin("PaPartNode as p", "p.nodeId", "n.id") .where("n.id", newParentNodeId) .first(); if (!parentRow) { throw new ApiError(`cannot find parent node '${newParentNodeId}'`, 404); } return ParentMovePartAT.assert(parentRow); } function validateMoveWithinSameDocument(documentNodeId, resolvedDocumentNodeId) { if (documentNodeId !== resolvedDocumentNodeId) { throw new ApiError("cannot move part to a different document", 400); } } async function getAndValidateTargetListType(siteSchema, parentInfo, newListName, partTypeName) { const { parentTypeName, parentDocumentNodeId } = parentInfo; let targetListType; if (parentDocumentNodeId === undefined) { const parentDocumentType = getDocumentTypeByName(siteSchema, parentTypeName); targetListType = parentDocumentType.lists?.find((l) => l.listName === newListName); } else { const parentPartType = getPartTypeByName(siteSchema, parentTypeName); targetListType = parentPartType.list; if (targetListType && targetListType.listName !== newListName) { throw new ApiError(`list name mismatch: expected '${targetListType.listName}' but got '${newListName}'`, 400); } } if (!targetListType) { throw new ApiError(`unknown list '${newListName}' in document or part type '${parentTypeName}'`, 400); } if (!targetListType.parts.includes(partTypeName)) { throw new ApiError(`part type '${partTypeName}' is not compatible with list '${newListName}'`, 400); } return targetListType; } async function checkIfMoveIsNeeded(siteContext, partNodeId, newParentNodeId, currentListName, newListName) { const { cn } = siteContext; const currentParentRow = await cn("PaNode") .select("parentId") .where("id", partNodeId) .first(); const currentParentId = currentParentRow?.parentId; return !(currentParentId === Number(newParentNodeId) && currentListName === newListName); } async function validateListLimit(siteContext, newParentNodeId, newListName, targetListType) { if (targetListType.limit !== undefined) { const count = await countPartsOfList(siteContext, { listName: newListName, parentNodeId: newParentNodeId, }); if (count >= targetListType.limit) { throw new ApiError("maximum parts reached in target list", 400); } } } async function executePartMove(siteContext, partNodeId, newParentNodeId, newListName, targetListType) { const { cn } = siteContext; await cn.transaction(async (tx) => { await tx("PaNode").where("id", partNodeId).update({ parentId: newParentNodeId, }); await tx("PaPartNode").where("nodeId", partNodeId).update({ listName: newListName, }); if (targetListType.sorting === "manual") { await updatePartOrdering(tx, partNodeId, newParentNodeId, newListName); } }); } async function updatePartOrdering(tx, partNodeId, newParentNodeId, newListName) { const orderRow = await tx("PaNode as n") .select(tx.raw("max(o.orderNum) as orderNum")) .innerJoin("PaPartNode as p", "p.nodeId", "n.id") .innerJoin("PaOrderedNode as o", "o.nodeId", "n.id") .where("n.parentId", newParentNodeId) .andWhere("p.listName", newListName) .andWhereNot("n.id", partNodeId) .first(); const maxOrderNum = Number(orderRow?.orderNum) || 0; const existingOrder = await tx("PaOrderedNode") .select("nodeId") .where("nodeId", partNodeId) .first(); if (existingOrder) { await tx("PaOrderedNode") .where("nodeId", partNodeId) .update({ orderNum: maxOrderNum + 1, }); } else { await tx("PaOrderedNode").insert({ nodeId: partNodeId, orderNum: maxOrderNum + 1, }); } } //# sourceMappingURL=part-moving.queries.js.map