@paroicms/server
Version:
The ParoiCMS server
294 lines • 10.8 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, 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