"use server"; import { updateTag } from "next/cache"; import * as z from "zod"; import { GLOSSARY_CACHE_TAG, pageCacheTag } from "@/lib/cache/tags"; import { createGlossaryAlias, deleteGlossaryAlias, } from "@/lib/glossary-repository"; import { requireAdmin } from "@/lib/auth/server"; import { publicGuideSections } from "@/lib/guides/public-sections"; import { publicationIssues } from "@/lib/guides/publication"; import { getAdminGuideContentById } from "@/lib/guides/queries"; import { createExtraSection, createGuide, deleteExtraSection, GuideVersionConflictError, permanentlyDeleteGuide, reorderExtraSections, restoreGuide, saveArtifacts, saveConstellations, saveExtraSection, saveTeams, setGuideVisibility, saveWeapons, trashGuide, updateGuideBasics, } from "@/lib/guides/mutations"; import { cancelCatalogSyncJob, getCatalogSyncJob, startCatalogSyncJob, } from "@/lib/catalog/sync"; import { getAdminTeamImportGuide, listAdminTeamImportGuides, } from "@/lib/guides/queries"; type GuideActionResult = | { status: "saved"; version?: number; location?: string } | { status: "conflict"; currentVersion: number; message: string } | { status: "error"; message: string }; type CreateGuideActionResult = Exclude; type SaveIntent = { overwrite?: boolean }; function overwriteDetails(admin: Awaited>, intent?: SaveIntent) { return intent?.overwrite ? { operation: "overwrite" as const, actorName: (admin.user.name?.trim() || admin.user.email).slice(0, 100) } : undefined; } async function runGuideAction(action: () => Promise<{ version?: number }>): Promise { try { const result = await action(); return { status: "saved", version: result.version }; } catch (cause) { if (cause instanceof GuideVersionConflictError) { return { status: "conflict", currentVersion: cause.currentVersion, message: cause.message }; } if (cause instanceof z.ZodError) { return { status: "error", message: cause.issues.map((issue) => issue.message).join(" • ") }; } return { status: "error", message: cause instanceof Error ? cause.message : "บันทึกไม่สำเร็จ" }; } } export async function createStructuredGuide(input: unknown): Promise { await requireAdmin(); try { const guide = await createGuide(input); return { status: "saved", version: guide.version, location: `/admin/${guide.characterKey}` }; } catch (cause) { return { status: "error", message: cause instanceof Error ? cause.message : "สร้าง Guide ไม่สำเร็จ" }; } } export async function saveWeaponSection(guideId: string, input: unknown, intent?: SaveIntent) { const admin = await requireAdmin(); return runGuideAction(() => saveWeapons(guideId, input, overwriteDetails(admin, intent))); } export async function saveGuideOverview(guideId: string, input: unknown, intent?: SaveIntent) { const admin = await requireAdmin(); return runGuideAction(() => updateGuideBasics(guideId, input, overwriteDetails(admin, intent))); } export async function saveArtifactSection(guideId: string, input: unknown, intent?: SaveIntent) { const admin = await requireAdmin(); return runGuideAction(() => saveArtifacts(guideId, input, overwriteDetails(admin, intent))); } export async function saveConstellationSection(guideId: string, input: unknown, intent?: SaveIntent) { const admin = await requireAdmin(); return runGuideAction(() => saveConstellations(guideId, input, overwriteDetails(admin, intent))); } export async function saveTeamSection(guideId: string, input: unknown, intent?: SaveIntent) { const admin = await requireAdmin(); return runGuideAction(() => saveTeams(guideId, input, overwriteDetails(admin, intent))); } export async function listTeamImportGuides(currentGuideId: string) { await requireAdmin(); const parsed = z.string().uuid().safeParse(currentGuideId); if (!parsed.success) return { status: "error" as const, message: "Guide ID ไม่ถูกต้อง" }; try { return { status: "success" as const, guides: await listAdminTeamImportGuides(parsed.data) }; } catch { return { status: "error" as const, message: "โหลดรายการ Guide ไม่สำเร็จ" }; } } export async function loadTeamImportGuide(guideId: string) { await requireAdmin(); const parsed = z.string().uuid().safeParse(guideId); if (!parsed.success) return { status: "error" as const, message: "Guide ID ไม่ถูกต้อง" }; try { const guide = await getAdminTeamImportGuide(parsed.data); return guide ? { status: "success" as const, guide } : { status: "error" as const, message: "ไม่พบ Guide นี้" }; } catch { return { status: "error" as const, message: "โหลด Team จาก Guide ไม่สำเร็จ" }; } } export async function createGuideExtra(guideId: string, input: unknown) { await requireAdmin(); try { const section = await createExtraSection(z.string().uuid().parse(guideId), input); updateTag(pageCacheTag(guideId)); return { status: "saved" as const, version: section.version, section: { id: section.id, slug: section.slug, title: section.title }, }; } catch (cause) { return { status: "error" as const, message: cause instanceof z.ZodError ? cause.issues.map((issue) => issue.message).join(" • ") : cause instanceof Error ? cause.message : "สร้าง Extra section ไม่สำเร็จ", }; } } export async function saveGuideExtra( guideId: string, sectionId: string, input: unknown, intent?: SaveIntent, ) { const admin = await requireAdmin(); const parsedGuideId = z.string().uuid().parse(guideId); const parsedSectionId = z.string().uuid().parse(sectionId); const result = await runGuideAction(() => saveExtraSection(parsedGuideId, parsedSectionId, input, overwriteDetails(admin, intent)), ); if (result.status === "saved") updateTag(pageCacheTag(guideId)); return result; } export async function deleteGuideExtra(guideId: string, sectionId: string) { await requireAdmin(); const result = await runGuideAction(() => deleteExtraSection( z.string().uuid().parse(guideId), z.string().uuid().parse(sectionId), ), ); if (result.status === "saved") updateTag(pageCacheTag(guideId)); return result; } export async function reorderGuideExtras(guideId: string, input: unknown) { await requireAdmin(); const result = await runGuideAction(() => reorderExtraSections(z.string().uuid().parse(guideId), input), ); if (result.status === "saved") updateTag(pageCacheTag(guideId)); return result; } export async function moveGuideToTrash(guideId: string) { await requireAdmin(); return runGuideAction(() => trashGuide(guideId)); } export async function restoreStructuredGuide(guideId: string) { await requireAdmin(); return runGuideAction(() => restoreGuide(guideId)); } export async function setStructuredGuideVisibility( guideId: string, input: unknown, ) { await requireAdmin(); const parsed = z.strictObject({ isPublic: z.boolean(), expectedVersion: z.number().int().positive(), }).safeParse(input); if (!parsed.success) return { status: "error" as const, message: "ข้อมูลการเผยแพร่ไม่ถูกต้อง" }; if (!parsed.data.isPublic) { return runGuideAction(() => setGuideVisibility(guideId, parsed.data)); } const guide = await getAdminGuideContentById(guideId); const issues = publicationIssues({ exists: Boolean(guide), overview: guide?.overview ?? "", coverReady: guide?.coverStatus === "ready", renderableSectionCount: guide ? publicGuideSections(guide).length : 0, version: guide?.version ?? 0, expectedVersion: parsed.data.expectedVersion, }); if (issues.length) { return { status: "error" as const, message: issues.join(" • "), issues }; } return runGuideAction(() => setGuideVisibility(guideId, parsed.data)); } export async function deleteStructuredGuide(guideId: string) { await requireAdmin(); return runGuideAction(() => permanentlyDeleteGuide(guideId)); } export async function startCatalogSync() { await requireAdmin(); try { return { status: "started" as const, job: await startCatalogSyncJob() }; } catch (cause) { return { status: "error" as const, message: cause instanceof Error ? cause.message : "เริ่ม Sync ไม่สำเร็จ" }; } } export async function getCatalogSyncProgress(jobId: string) { await requireAdmin(); return getCatalogSyncJob(jobId); } export async function cancelCatalogSync(jobId: string) { await requireAdmin(); return cancelCatalogSyncJob(jobId); } export async function addGlossaryAlias(input: unknown) { await requireAdmin(); try { const alias = await createGlossaryAlias(input); updateTag(GLOSSARY_CACHE_TAG); return { status: "saved" as const, alias }; } catch (cause) { const message = cause instanceof Error && cause.message.includes("unique") ? "Shortcut นี้ถูกใช้งานแล้ว" : cause instanceof z.ZodError ? cause.issues.map((issue) => issue.message).join(" • ") : cause instanceof Error ? cause.message : "เพิ่ม Shortcut ไม่สำเร็จ"; return { status: "error" as const, message }; } } export async function removeGlossaryAlias(id: string) { await requireAdmin(); try { await deleteGlossaryAlias(z.string().uuid().parse(id)); updateTag(GLOSSARY_CACHE_TAG); return { status: "saved" as const }; } catch (cause) { return { status: "error" as const, message: cause instanceof Error ? cause.message : "ลบ Shortcut ไม่สำเร็จ", }; } }