feat(guides): add structured image-first editor
CI / Verify (push) Successful in 1m0s
CI / Build immutable images and deploy (push) Failing after 2m38s

This commit is contained in:
2026-08-29 13:30:52 +00:00 Unverified
parent b8e3b99033
commit dc5c667b4e
36 changed files with 5611 additions and 381 deletions
+94
View File
@@ -14,6 +14,19 @@ import type { DataSourceSnapshot, PageSnapshot } from "@/lib/content/types";
import { requireAdmin } from "@/lib/auth/server";
import { builtInTemplates } from "@/lib/templates";
import { persistTemplateApplication } from "@/lib/templates/persist";
import {
createGuide,
enqueueCatalogSync,
permanentlyDeleteGuide,
restoreGuide,
saveArtifacts,
saveConstellations,
saveExtraSections,
saveTeams,
saveWeapons,
trashGuide,
updateGuideBasics,
} from "@/lib/guides/mutations";
const serializedBlockSchema = z.strictObject({
id: z.string().uuid(),
@@ -188,3 +201,84 @@ export async function applyEditorTemplate(input: unknown): Promise<
};
}
}
type GuideActionResult =
| { status: "saved"; version?: number; location?: string }
| { status: "error"; message: string };
async function runGuideAction(action: () => Promise<{ version?: number }>): Promise<GuideActionResult> {
try {
const result = await action();
return { status: "saved", version: result.version };
} catch (cause) {
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<GuideActionResult> {
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) {
await requireAdmin();
return runGuideAction(() => saveWeapons(guideId, input));
}
export async function saveGuideOverview(guideId: string, input: unknown) {
await requireAdmin();
return runGuideAction(() => updateGuideBasics(guideId, input));
}
export async function saveArtifactSection(guideId: string, input: unknown) {
await requireAdmin();
return runGuideAction(() => saveArtifacts(guideId, input));
}
export async function saveConstellationSection(guideId: string, input: unknown) {
await requireAdmin();
return runGuideAction(() => saveConstellations(guideId, input));
}
export async function saveTeamSection(guideId: string, input: unknown) {
await requireAdmin();
return runGuideAction(() => saveTeams(guideId, input));
}
export async function saveExtraSectionList(guideId: string, input: unknown) {
await requireAdmin();
return runGuideAction(() => saveExtraSections(guideId, input));
}
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 deleteStructuredGuide(guideId: string) {
await requireAdmin();
return runGuideAction(() => permanentlyDeleteGuide(guideId));
}
export async function startCatalogSync() {
const admin = await requireAdmin();
try {
const job = await enqueueCatalogSync(admin.user.id);
return { status: "saved" as const, jobId: job.id };
} catch (cause) {
return { status: "error" as const, message: cause instanceof Error ? cause.message : "เริ่ม Sync ไม่สำเร็จ" };
}
}