UNPKG

@paroicms/server

Version:
294 lines 10.8 kB
import { getDocumentTypeByName, getPartTypeByName } from "@paroicms/internal-anywhere-lib"; import { parseSqliteDateTime } from "@paroicms/internal-server-lib"; import { encodeLNodeId, isDef, } from "@paroicms/public-anywhere-lib"; import { ApiError, getHandleOfFeaturedImage } from "@paroicms/public-server-lib"; import { type } from "arktype"; import { invalidateDocumentInCache } from "../../common/text-cache.js"; import { createNode } from "../../connector/db-init/init-node-queries.js"; import { isOrphanNode } from "../lnode/lnode.queries.js"; import { autoCreatePartFieldParts } from "./auto-create-part-field-parts.js"; import { createPartNode, shiftPartNodeOrdersAfterDeletion } from "./part-node.queries.js"; export async function createPartOnNode(siteContext, newId) { await siteContext.cn("PaLNode").insert({ language: newId.language, nodeId: newId.nodeId, ready: true, updatedAt: siteContext.cn.raw("current_timestamp"), }); await invalidateDocumentInCache(siteContext, { parentOf: newId }); return newId; } const ParentNodeAT = type({ typeName: "string", documentNodeId: "number|null", "+": "reject", }).pipe((r) => ({ typeName: r.typeName, documentNodeId: isDef(r.documentNodeId) ? String(r.documentNodeId) : undefined, })); const OrderNumAT = type({ orderNum: "number|null", "+": "reject", }).pipe((r) => ({ orderNum: r.orderNum ?? undefined, })); export async function createNodeWithPart(siteContext, { parentLNodeId, listName, typeName, }) { const { language, nodeId: parentNodeId } = parentLNodeId; const { cn, siteSchema } = siteContext; const row = await cn("PaNode as n") .select(["n.typeName as typeName", "p.documentNodeId as documentNodeId"]) .leftJoin("PaPartNode as p", "p.nodeId", "n.id") .where("n.id", parentNodeId) .first(); if (!row) throw new ApiError(`cannot find part-node '${parentNodeId}'`, 404); const { typeName: parentTypeName, documentNodeId } = ParentNodeAT.assert(row); const resolvedDocumentNodeId = documentNodeId ?? parentNodeId; let listType; if (resolvedDocumentNodeId === parentNodeId) { const parentDocumentType = getDocumentTypeByName(siteContext.siteSchema, parentTypeName); listType = parentDocumentType.lists?.find((l) => l.listName === listName); } else { const parentPartType = getPartTypeByName(siteContext.siteSchema, parentTypeName); listType = parentPartType.list; } if (!listType) { throw new ApiError(`unknown list '${listName}' in document or part type '${parentTypeName}'`, 400); } if (listType.limit !== undefined) { const count = await countPartsOfList(siteContext, { listName, parentNodeId, }); if (count >= listType.limit) throw new ApiError("maximum parts reached", 400); } if (!listType.parts.includes(typeName)) { throw new ApiError(`Invalid lNodeType typeName: '${typeName}' for listName '${listName}'`, 400); } let lastOrderNum; if (listType.sorting) { const row = await cn("PaNode as n") .select(cn.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", parentNodeId) .andWhere("p.listName", listName) .first(); lastOrderNum = row ? OrderNumAT.assert(row).orderNum : undefined; } const newId = await cn.transaction(async (tx) => { const node = await createNode(siteSchema, tx, { parentId: parentNodeId, typeName }); const { id: nodeId } = node; await createPartNode(tx, { nodeId, listName, documentNodeId: resolvedDocumentNodeId, isOrdered: !!listType.sorting, lastOrderNum, }); await tx("PaLNode").insert({ language, nodeId, ready: true, updatedAt: tx.raw("current_timestamp"), }); await autoCreatePartFieldParts(siteSchema, { parentNodeId: nodeId, typeName, language, documentNodeId: resolvedDocumentNodeId, tx, }); return { language, nodeId }; }); await invalidateDocumentInCache(siteContext, { documentId: parentLNodeId }); return newId; } const PartSeedRowAT = type({ typeName: "string", publishDate: "string|number|Date|null", parentId: "number", listName: "string", documentNodeId: "number", orderNum: "number|null", ready: "number", "+": "reject", }).pipe((r) => ({ typeName: r.typeName, publishDate: parseSqliteDateTime(r.publishDate), parentId: String(r.parentId), listName: r.listName, documentNodeId: String(r.documentNodeId), orderNum: r.orderNum ?? undefined, ready: Boolean(r.ready), })); export async function loadPartSeed(siteContext, lNodeId) { const { cn } = siteContext; const row = await cn("PaNode as n") .select([ "n.typeName", "n.publishDate", "n.parentId", "p.listName", "p.documentNodeId", "o.orderNum", "l.ready", ]) .innerJoin("PaPartNode as p", "p.nodeId", "n.id") .innerJoin("PaLNode as l", { "l.nodeId": "n.id", "l.language": cn.raw("?", [lNodeId.language]), }) .leftJoin("PaOrderedNode as o", "o.nodeId", "n.id") .where("n.id", lNodeId.nodeId) .whereNotExists(function () { this.select(1).from("PaDocument as d").where("d.nodeId", "n.id"); }) .first(); if (!row) { throw new ApiError(`cannot find part '${lNodeId.language}:${lNodeId.nodeId}'`, 404); } const { typeName, publishDate, parentId, listName, documentNodeId, orderNum, ready } = PartSeedRowAT.assert(row); return { typeName, partId: encodeLNodeId(lNodeId), nodeId: lNodeId.nodeId, listName, parentNodeId: parentId, documentId: encodeLNodeId({ language: lNodeId.language, nodeId: documentNodeId }), orderNum, ready, publishDate, language: lNodeId.language, scheduled: publishDate ? publishDate.getTime() > Date.now() : false, }; } export async function deletePart(siteContext, lNodeId) { const nodeId = lNodeId.nodeId; const documentNodeId = await getDocumentNodeIdOfPart(siteContext, nodeId); const { shouldDeleteNode } = await siteContext.cn.transaction(async (tx) => { await tx("PaFieldVarchar") .where("nodeId", nodeId) .where("language", lNodeId.language) .delete(); await tx("PaFieldText").where("nodeId", nodeId).where("language", lNodeId.language).delete(); await tx("PaLNode").where("nodeId", nodeId).where("language", lNodeId.language).delete(); const shouldDeleteNode = await isOrphanNode(tx, nodeId); if (shouldDeleteNode) { await shiftPartNodeOrdersAfterDeletion(tx, nodeId); await tx("PaPartNode").where("nodeId", nodeId).delete(); await tx("PaNode").where("id", nodeId).delete(); } return { shouldDeleteNode }; }); await invalidateDocumentInCache(siteContext, { documentId: { nodeId: documentNodeId, language: lNodeId.language, }, }); if (shouldDeleteNode) { const deletedIds = await siteContext.mediaStorage.deleteMedia({ handle: getHandleOfFeaturedImage(lNodeId.nodeId), }); await siteContext.imageCache.invalidate({ mediaIds: deletedIds }); } } const PartRowAT = type({ nodeId: "number", publishDate: "string|number|Date|null", typeName: "string", parentId: "number", listName: "string", orderNum: "number|null", ready: "number|null", "+": "reject", }).pipe((r) => ({ nodeId: String(r.nodeId), publishDate: parseSqliteDateTime(r.publishDate), typeName: r.typeName, parentId: String(r.parentId), listName: r.listName, orderNum: r.orderNum ?? undefined, ready: isDef(r.ready) ? Boolean(r.ready) : undefined, })); export async function readPartSeedsOf({ cn }, { documentNodeId, language, }) { const parts = await cn("PaNode as n") .select([ "n.id as nodeId", "n.publishDate", "n.typeName", "n.parentId", "p.listName", "o.orderNum", "l.ready", ]) .innerJoin("PaPartNode as p", "p.nodeId", "n.id") .leftJoin("PaLNode as l", { "l.nodeId": "n.id", "l.language": cn.raw("?", [language]), }) .leftJoin("PaOrderedNode as o", "o.nodeId", "n.id") .where("p.documentNodeId", documentNodeId); return parts.map((item) => { const { nodeId, publishDate, typeName, parentId, listName, orderNum, ready } = PartRowAT.assert(item); if (ready === undefined) { const seed = { typeName, nodeId, listName, parentNodeId: parentId, documentId: encodeLNodeId({ language, nodeId: documentNodeId }), orderNum, publishDate, }; return seed; } const seed = { typeName, partId: encodeLNodeId({ language, nodeId }), nodeId, listName, parentNodeId: parentId, documentId: encodeLNodeId({ language, nodeId: documentNodeId }), orderNum, publishDate, language, scheduled: publishDate ? publishDate.getTime() > Date.now() : false, ready, }; return seed; }); } const CountPartsAT = type({ cnt: "number", "+": "reject", }); export async function countPartsOfList({ cn }, { listName, parentNodeId, }) { const row = await cn("PaNode as n") .count("* as cnt") .innerJoin("PaPartNode as p", "p.nodeId", "n.id") .where("n.parentId", parentNodeId) .andWhere("p.listName", listName) .first(); return row ? CountPartsAT.assert(row).cnt : 0; } const DocumentNodeIdAT = type({ documentNodeId: "number", "+": "reject", }).pipe((r) => ({ documentNodeId: String(r.documentNodeId), })); export async function getDocumentNodeIdOfPart(siteContext, nodeId) { const row = await siteContext .cn("PaPartNode as p") .select("p.documentNodeId") .where("p.nodeId", nodeId) .first(); if (!row) throw new ApiError(`cannot find part-node '${nodeId}'`, 404); return DocumentNodeIdAT.assert(row).documentNodeId; } //# sourceMappingURL=part.queries.js.map