@paroicms/server
Version:
The ParoiCMS server
265 lines • 11.1 kB
JavaScript
import { getDocumentTypeByName, getNodeTypeByName, isRootOfRoutingCluster, isRootOfSubRoutingCluster, } from "@paroicms/internal-anywhere-lib";
import { ApiError } from "@paroicms/public-server-lib";
import { type } from "arktype";
import { getHandleOfFeaturedImage } from "../../common/media-handles.helpers.js";
import { invalidateDocumentInCache, invalidateDocumentRelationCache, invalidateMultipleDocumentsInCache, } from "../../common/text-cache.js";
import { createNode } from "../../connector/db-init/init-node-queries.js";
import { loadRoutingCluster } from "../../connector/db-init/load-routing-cluster.queries.js";
import { autoCreateCluster } from "../../connector/db-init/populate-routing-documents.js";
import { reloadHomeRoutingCluster } from "../../site-context/refresh-site-context.js";
import { countNodeChildrenOf, getLanguagesOfNode, getTypeNameOf } from "../node/node.queries.js";
import { createRoutingDocumentsInLanguageForCluster } from "../routing-cluster/cluster-management.js";
import { getParentDocumentIdsOf, getParentLNodeIdOf, getParentNodeIdOf, } from "./load-documents.queries.js";
export class CreateDocumentValues {
slug;
title;
}
export async function updateDocument(siteContext, documentId, values) {
const { cn } = siteContext;
await cn("PaDocument")
.where({
nodeId: documentId.nodeId,
language: documentId.language,
})
.update(values);
await invalidateDocumentInCache(siteContext, {
documentId,
perimeter: "slug" in values ? "slug" : undefined,
});
}
export async function createDocumentOnNode(siteContext, { language, nodeId, values, forceReady, }) {
const { cn, siteSchema } = siteContext;
const typeName = await getTypeNameOf({ cn: siteContext.cn }, nodeId);
const documentType = getDocumentTypeByName(siteSchema, typeName);
const autoPublish = documentType.documentKind === "regular" && documentType.autoPublish;
const ready = forceReady ?? autoPublish;
if (typeName !== "home") {
const parentNodeId = await getParentNodeIdOf(siteContext, nodeId);
if (parentNodeId === undefined)
throw new Error(`missing parent for "${nodeId}"`);
const acceptedLanguages = await getLanguagesOfNode(siteContext, parentNodeId);
if (!acceptedLanguages.includes(language)) {
throw new ApiError(`language "${language}" is not available on parent`, 400);
}
}
const rootOfCluster = isRootOfRoutingCluster(documentType)
? await loadRoutingCluster(siteContext, {
kind: typeName === "home" ? "home" : "sub",
nodeId,
typeName,
})
: undefined;
const documentId = await cn.transaction(async (tx) => {
const result = await createDocumentWithManager({
language,
nodeId,
cn: tx,
values,
ready,
});
if (rootOfCluster) {
await createRoutingDocumentsInLanguageForCluster(siteContext, tx, rootOfCluster, language);
}
return result;
});
if (ready) {
const parentId = await getParentLNodeIdOf(siteContext, documentId);
if (parentId) {
await invalidateDocumentRelationCache(siteContext, {
documentId: parentId,
relation: "children",
});
}
}
if (rootOfCluster && documentType.typeName === "home") {
await reloadHomeRoutingCluster(siteContext.fqdn);
}
return documentId;
}
export async function createNodeWithDocument(siteContext, { parentId, values, forceReady, }) {
const { siteSchema } = siteContext;
const { language, nodeId: parentNodeId } = parentId;
const documentType = getDocumentTypeByName(siteSchema, values.typeName);
const autoPublish = documentType.documentKind === "regular" && documentType.autoPublish;
const ready = forceReady ?? autoPublish;
const parentTypeName = await getTypeNameOf(siteContext, parentNodeId);
const parentType = getNodeTypeByName(siteContext.siteSchema, parentTypeName);
if (parentType.kind === "document" && parentType.childLimit !== undefined) {
const count = await countNodeChildrenOf(siteContext, parentNodeId);
if (count >= parentType.childLimit)
throw new ApiError("maximum children reached", 400);
}
if (parentType.kind !== "site") {
const acceptedLanguages = await getLanguagesOfNode(siteContext, parentNodeId);
if (!acceptedLanguages.includes(language)) {
throw new ApiError(`language "${language}" is not available on parent`, 400);
}
}
const documentId = await siteContext.cn.transaction(async (tx) => {
const { id: nodeId } = await createNode(siteContext.siteSchema, tx, {
parentId: parentNodeId,
typeName: values.typeName,
});
const newId = await createDocumentWithManager({
language,
nodeId,
values,
cn: tx,
ready,
});
return newId;
});
if (ready) {
await invalidateDocumentRelationCache(siteContext, {
documentId: parentId,
relation: "children",
});
}
if (isRootOfSubRoutingCluster(documentType)) {
await autoCreateCluster(siteContext, {
nodeId: documentId.nodeId,
documentType,
});
}
return documentId;
}
const DeleteDocumentNodeRowAT = type({
id: "number",
"+": "reject",
}).pipe((r) => ({
id: String(r.id),
}));
export async function deleteNodeAndDocuments(siteContext, nodeId) {
const { cn, mediaStorage } = siteContext;
const parentDocumentIds = await getParentDocumentIdsOf(siteContext, { nodeId });
const { partNodeIds } = await cn.transaction(async (tx) => {
const nodeRows = await tx("PaNode as l")
.select("l.id as id")
.whereRaw("l.parentId = ?", [nodeId])
.whereNotExists(function () {
this.select(1).from("PaDocument as d").where("d.nodeId", "l.id");
});
const partNodeIds = nodeRows.map((r) => DeleteDocumentNodeRowAT.assert(r).id);
await fullDeleteParts(partNodeIds, tx);
await tx("PaFieldVarchar").where("nodeId", nodeId).delete();
await tx("PaFieldText").where("nodeId", nodeId).delete();
await tx("PaDocument").where("nodeId", nodeId).delete();
await tx("PaLNode").where("nodeId", nodeId).delete();
await tx("PaNode").where("id", nodeId).delete();
return { partNodeIds };
});
await mediaStorage.deleteMedias({
handles: [...partNodeIds.map(getHandleOfFeaturedImage), getHandleOfFeaturedImage(nodeId)],
});
await invalidateMultipleDocumentsInCache(siteContext, {
nodeId,
parentIds: parentDocumentIds,
fully: true,
});
}
const DeleteDocumentCountRowAT = type({
cnt: "number",
"+": "reject",
});
export async function deleteDocument(siteContext, documentId) {
const { cn, mediaStorage } = siteContext;
const { nodeId, language } = documentId;
const parentId = await getParentLNodeIdOf(siteContext, documentId);
const countRow = await cn("PaLNode as s")
.count("s.nodeId as cnt")
.where("s.nodeId", nodeId)
.andWhereNot("language", documentId.language)
.first();
const count = countRow ? DeleteDocumentCountRowAT.assert(countRow).cnt : 0;
const shouldDeleteNode = count === 0;
const { partNodeIds } = await cn.transaction(async (tx) => {
const nodeRows = await tx("PaNode as l")
.select("l.id as id")
.whereRaw("l.parentId = ?", [nodeId])
.whereNotExists(function () {
this.select(1).from("PaDocument as d").where("d.nodeId", "l.id");
});
const partNodeIds = nodeRows.map((r) => DeleteDocumentNodeRowAT.assert(r).id);
await deleteParts({
language,
nodeIdList: partNodeIds,
cn: tx,
});
await tx("PaFieldVarchar")
.where("nodeId", documentId.nodeId)
.andWhere("language", documentId.language)
.delete();
await tx("PaFieldText")
.where("nodeId", documentId.nodeId)
.andWhere("language", documentId.language)
.delete();
await tx("PaDocument")
.where("nodeId", documentId.nodeId)
.andWhere("language", documentId.language)
.delete();
await tx("PaLNode")
.where("nodeId", documentId.nodeId)
.andWhere("language", documentId.language)
.delete();
if (shouldDeleteNode) {
await tx("PaFieldVarchar").where("nodeId", nodeId).delete();
await tx("PaFieldText").where("nodeId", nodeId).delete();
await tx("PaFieldLabeling").where("nodeId", nodeId).orWhere("termId", nodeId).delete();
await tx("PaPartNode").where("documentNodeId", nodeId).delete();
await tx("PaNode").where("id", nodeId).delete();
}
return { partNodeIds };
});
await invalidateDocumentInCache(siteContext, { documentId, fully: true, parentId });
if (shouldDeleteNode) {
const handles = partNodeIds.map(getHandleOfFeaturedImage);
handles.push(getHandleOfFeaturedImage(nodeId));
const deletedIds = await mediaStorage.deleteMedias({ handles });
await siteContext.imageCache.invalidate({ mediaIds: deletedIds });
}
}
const DeletePartCountRowAT = type({
cnt: "number",
"+": "reject",
});
async function deleteParts({ nodeIdList, cn, language, }) {
if (nodeIdList.length === 0)
return;
await cn("PaLNode").whereIn("nodeId", nodeIdList).andWhere("language", language).delete();
for (const nodeId of nodeIdList) {
const countRow = await cn("PaLNode as nl")
.count("nl.nodeId as cnt")
.where("nl.nodeId", nodeId)
.first();
if (!countRow)
continue;
if (DeletePartCountRowAT.assert(countRow).cnt === 0) {
await cn("PaFieldVarchar").where("nodeId", nodeId).delete();
await cn("PaFieldText").where("nodeId", nodeId).delete();
await cn("PaNode").where("id", nodeId).delete();
}
}
}
async function fullDeleteParts(nodeIdList, cn) {
if (nodeIdList.length === 0)
return;
await cn("PaFieldText").whereIn("nodeId", nodeIdList).delete();
await cn("PaFieldVarchar").whereIn("nodeId", nodeIdList).delete();
await cn("PaLNode").whereIn("nodeId", nodeIdList).delete();
await cn("PaNode").whereIn("id", nodeIdList).delete();
}
async function createDocumentWithManager({ language, nodeId, values, ready, cn, }) {
await cn("PaLNode").insert({
language,
nodeId,
ready: !!ready,
});
await cn("PaDocument").insert({
language,
nodeId,
slug: values?.slug,
title: values?.title,
});
return { language, nodeId };
}
//# sourceMappingURL=save-documents.queries.js.map