301 lines
10 KiB
TypeScript
301 lines
10 KiB
TypeScript
"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 { auditActor } from "@/lib/audit-log";
|
|
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<GuideActionResult, { status: "conflict" }>;
|
|
type SaveIntent = { overwrite?: boolean };
|
|
|
|
function mutationContext(
|
|
admin: Awaited<ReturnType<typeof requireAdmin>>,
|
|
intent?: SaveIntent,
|
|
) {
|
|
return { actor: auditActor(admin.user), overwrite: intent?.overwrite };
|
|
}
|
|
|
|
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 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<CreateGuideActionResult> {
|
|
const admin = await requireAdmin();
|
|
try {
|
|
const guide = await createGuide(input, mutationContext(admin));
|
|
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, mutationContext(admin, intent)));
|
|
}
|
|
|
|
export async function saveGuideOverview(guideId: string, input: unknown, intent?: SaveIntent) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => updateGuideBasics(guideId, input, mutationContext(admin, intent)));
|
|
}
|
|
|
|
export async function saveArtifactSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => saveArtifacts(guideId, input, mutationContext(admin, intent)));
|
|
}
|
|
|
|
export async function saveConstellationSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => saveConstellations(guideId, input, mutationContext(admin, intent)));
|
|
}
|
|
|
|
export async function saveTeamSection(guideId: string, input: unknown, intent?: SaveIntent) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => saveTeams(guideId, input, mutationContext(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) {
|
|
const admin = await requireAdmin();
|
|
try {
|
|
const section = await createExtraSection(
|
|
z.string().uuid().parse(guideId),
|
|
input,
|
|
mutationContext(admin),
|
|
);
|
|
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, mutationContext(admin, intent)),
|
|
);
|
|
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
|
return result;
|
|
}
|
|
|
|
export async function deleteGuideExtra(guideId: string, sectionId: string) {
|
|
const admin = await requireAdmin();
|
|
const result = await runGuideAction(() =>
|
|
deleteExtraSection(
|
|
z.string().uuid().parse(guideId),
|
|
z.string().uuid().parse(sectionId),
|
|
mutationContext(admin),
|
|
),
|
|
);
|
|
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
|
return result;
|
|
}
|
|
|
|
export async function reorderGuideExtras(guideId: string, input: unknown) {
|
|
const admin = await requireAdmin();
|
|
const result = await runGuideAction(() =>
|
|
reorderExtraSections(
|
|
z.string().uuid().parse(guideId),
|
|
input,
|
|
mutationContext(admin),
|
|
),
|
|
);
|
|
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
|
return result;
|
|
}
|
|
|
|
export async function moveGuideToTrash(guideId: string) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => trashGuide(guideId, mutationContext(admin)));
|
|
}
|
|
|
|
export async function restoreStructuredGuide(guideId: string) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => restoreGuide(guideId, mutationContext(admin)));
|
|
}
|
|
|
|
export async function setStructuredGuideVisibility(
|
|
guideId: string,
|
|
input: unknown,
|
|
) {
|
|
const admin = 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, mutationContext(admin)));
|
|
}
|
|
|
|
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, mutationContext(admin)));
|
|
}
|
|
|
|
export async function deleteStructuredGuide(guideId: string) {
|
|
const admin = await requireAdmin();
|
|
return runGuideAction(() => permanentlyDeleteGuide(guideId, mutationContext(admin)));
|
|
}
|
|
|
|
export async function startCatalogSync() {
|
|
const admin = await requireAdmin();
|
|
try {
|
|
return {
|
|
status: "started" as const,
|
|
job: await startCatalogSyncJob(auditActor(admin.user)),
|
|
};
|
|
} 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) {
|
|
const admin = await requireAdmin();
|
|
return cancelCatalogSyncJob(jobId, auditActor(admin.user));
|
|
}
|
|
|
|
export async function addGlossaryAlias(input: unknown) {
|
|
const admin = await requireAdmin();
|
|
try {
|
|
const alias = await createGlossaryAlias(input, auditActor(admin.user));
|
|
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) {
|
|
const admin = await requireAdmin();
|
|
try {
|
|
await deleteGlossaryAlias(
|
|
z.string().uuid().parse(id),
|
|
auditActor(admin.user),
|
|
);
|
|
updateTag(GLOSSARY_CACHE_TAG);
|
|
return { status: "saved" as const };
|
|
} catch (cause) {
|
|
return {
|
|
status: "error" as const,
|
|
message: cause instanceof Error ? cause.message : "ลบ Shortcut ไม่สำเร็จ",
|
|
};
|
|
}
|
|
}
|