@paroicms/server
Version:
The ParoiCMS server
285 lines • 10.5 kB
JavaScript
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 } from "@paroicms/public-server-lib";
import { type } from "arktype";
import { getHandleOfFeaturedImage } from "../../common/media-handles.helpers.js";
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 { 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,
});
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 l")
.select(["l.typeName as typeName", "p.documentNodeId as documentNodeId"])
.leftJoin("PaPartNode as p", "p.nodeId", "l.id")
.where("l.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 l")
.select(cn.raw("max(o.orderNum) as orderNum"))
.innerJoin("PaPartNode as p", "p.nodeId", "l.id")
.innerJoin("PaOrderedNode as o", "o.nodeId", "l.id")
.where("l.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,
});
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 l")
.select([
"l.typeName",
"l.publishDate",
"l.parentId",
"p.listName",
"p.documentNodeId",
"o.orderNum",
"s.ready",
])
.innerJoin("PaPartNode as p", "p.nodeId", "l.id")
.innerJoin("PaLNode as s", {
"s.nodeId": "l.id",
"s.language": cn.raw("?", [lNodeId.language]),
})
.leftJoin("PaOrderedNode as o", "o.nodeId", "l.id")
.where("l.id", lNodeId.nodeId)
.whereNotExists(function () {
this.select(1).from("PaDocument as d").where("d.nodeId", "l.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 l")
.select([
"l.id as nodeId",
"l.publishDate",
"l.typeName",
"l.parentId",
"p.listName",
"o.orderNum",
"s.ready",
])
.innerJoin("PaPartNode as p", "p.nodeId", "l.id")
.leftJoin("PaLNode as s", {
"s.nodeId": "l.id",
"s.language": cn.raw("?", [language]),
})
.leftJoin("PaOrderedNode as o", "o.nodeId", "l.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 l")
.count("* as cnt")
.innerJoin("PaPartNode as p", "p.nodeId", "l.id")
.where("l.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