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()): Set { 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, 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, }; }