@paroicms/server
Version:
The ParoiCMS server
156 lines • 6.73 kB
JavaScript
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