feat(templates): persist independent guide applications

This commit is contained in:
2026-08-28 19:41:08 +00:00 Unverified
parent 2ddbd374ae
commit 35a786156a
4 changed files with 331 additions and 0 deletions
+138
View File
@@ -0,0 +1,138 @@
import "server-only";
import { getDb } from "@/db";
import {
blocks,
characters,
dataSources,
dataSourceVersions,
outboxEvents,
pageDataSources,
pages,
} from "@/db/schema";
import { evaluateDataSource } from "@/lib/content/data-source";
import { applyTemplate, type TemplateBlueprint } from "@/lib/templates";
export interface NewTemplateCharacter {
slug: string;
nameTh: string;
nameEn?: string | null;
description?: string | null;
element?: string | null;
role?: string | null;
rarity?: number | null;
}
function collectSourceIds(value: unknown, result = new Set<string>()): Set<string> {
if (Array.isArray(value)) {
for (const item of value) collectSourceIds(item, result);
} else if (value && typeof value === "object") {
for (const [key, item] of Object.entries(value)) {
if (key === "dataSourceId" && typeof item === "string") result.add(item);
collectSourceIds(item, result);
}
}
return result;
}
export async function persistTemplateApplication(
template: TemplateBlueprint,
identity: NewTemplateCharacter,
authorId: string,
): Promise<{ characterId: string; characterSlug: string; firstPageSlug: string }> {
const characterId = crypto.randomUUID();
const applied = applyTemplate(template, characterId);
const db = getDb();
await db.transaction(async (tx) => {
await tx.insert(characters).values({
id: characterId,
slug: identity.slug,
nameTh: identity.nameTh,
nameEn: identity.nameEn?.trim() || null,
description: identity.description?.trim() || null,
element: identity.element?.trim() || null,
role: identity.role?.trim() || null,
rarity: identity.rarity ?? null,
visible: false,
});
for (const source of applied.dataSources) {
const snapshot = {
id: source.id,
name: source.name,
version: 1,
fields: source.fields,
rows: source.rows,
};
await tx.insert(dataSources).values({
id: source.id,
name: source.name,
description: source.description,
fields: source.fields,
currentVersion: 1,
});
await tx.insert(dataSourceVersions).values({
dataSourceId: source.id,
version: 1,
rows: source.rows,
evaluation: evaluateDataSource(snapshot) as unknown as Record<string, unknown>,
createdById: authorId,
});
}
for (const page of applied.pages) {
await tx.insert(pages).values({
id: page.id,
characterId,
slug: page.slug,
title: page.title,
publicNote: page.publicNote,
navOrder: page.navOrder,
visible: false,
version: 1,
});
if (page.blocks.length > 0) {
await tx.insert(blocks).values(
page.blocks.map((block) => ({
...block,
pageId: page.id,
})),
);
}
const dependencies = [
...collectSourceIds(page.blocks.map((block) => block.config)),
];
if (dependencies.length > 0) {
await tx.insert(pageDataSources).values(
dependencies.map((dataSourceId) => ({
pageId: page.id,
dataSourceId,
pinnedVersion: null,
})),
);
}
}
await tx.insert(outboxEvents).values([
{
topic: "directory",
aggregateId: characterId,
eventType: "directory.updated",
payload: { id: characterId, version: 1 },
},
{
topic: "admin",
aggregateId: characterId,
eventType: "admin.updated",
payload: { id: characterId, version: 1 },
},
]);
});
return {
characterId,
characterSlug: identity.slug,
firstPageSlug: applied.pages[0].slug,
};
}