feat(guides) : add safer publishing and discovery
This commit is contained in:
@@ -84,6 +84,9 @@ export default async function StructuredEditorPage({
|
||||
</h1>
|
||||
<EditorPresence guideId={guide.id} section={sectionTitle} />
|
||||
</div>
|
||||
<Button className="ml-auto" variant="outline" render={<Link href={`/admin/${guide.characterKey}/preview/${page}`} target="_blank" />} nativeButton={false}>
|
||||
Preview
|
||||
</Button>
|
||||
</header>
|
||||
<GuideSectionNav
|
||||
guideId={guide.id}
|
||||
|
||||
@@ -51,7 +51,10 @@ export default async function GuideOverviewPage({ params }: { params: Promise<{
|
||||
<h1 className="font-heading text-3xl font-semibold">{guide.name}</h1>
|
||||
<EditorPresence guideId={guide.id} section="Overview" />
|
||||
</div>
|
||||
<GuideVisibilityControl guideId={guide.id} guideName={guide.name} isPublic={guide.isPublic} />
|
||||
<GuideVisibilityControl guideId={guide.id} guideName={guide.name} isPublic={guide.isPublic} version={guide.version} />
|
||||
<Button variant="outline" render={<Link href={`/admin/${guide.characterKey}/preview`} target="_blank" />} nativeButton={false}>
|
||||
Preview
|
||||
</Button>
|
||||
</header>
|
||||
<GuideSectionNav guideId={guide.id} characterKey={guide.characterKey} current="overview" extras={extras} />
|
||||
<GuideOverviewForm key={guide.version} guide={guide} />
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { notFound, redirect } from "next/navigation";
|
||||
import { connection } from "next/server";
|
||||
|
||||
import { StructuredGuideShell } from "@/components/public/structured-guide";
|
||||
import { getAdminSession } from "@/lib/auth/server";
|
||||
import { publicGuideSections } from "@/lib/guides/public-sections";
|
||||
import { getAdminGuideContent, getGuideEditorHeader } from "@/lib/guides/queries";
|
||||
|
||||
export const instant = false;
|
||||
|
||||
export default async function GuidePreviewPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ character: string; page?: string[] }>;
|
||||
}) {
|
||||
await connection();
|
||||
if (!(await getAdminSession())) redirect("/admin/login");
|
||||
const { character, page } = await params;
|
||||
if (page && page.length > 1) notFound();
|
||||
const header = await getGuideEditorHeader(character);
|
||||
if (!header) notFound();
|
||||
const guide = await getAdminGuideContent(header.slug);
|
||||
if (!guide) notFound();
|
||||
const section = page?.[0];
|
||||
if (section && !publicGuideSections(guide).some((item) => item.slug === section)) notFound();
|
||||
return <StructuredGuideShell guide={guide} section={section} preview />;
|
||||
}
|
||||
+36
-10
@@ -9,6 +9,10 @@ import {
|
||||
deleteGlossaryAlias,
|
||||
} from "@/lib/glossary-repository";
|
||||
import { requireAdmin } from "@/lib/auth/server";
|
||||
import { getCurrentCatalogVersion } from "@/lib/catalog/sync";
|
||||
import { publicGuideSections } from "@/lib/guides/public-sections";
|
||||
import { publicationIssues } from "@/lib/guides/publication";
|
||||
import { getAdminGuideContentById } from "@/lib/guides/queries";
|
||||
import {
|
||||
createExtraSection,
|
||||
createGuide,
|
||||
@@ -20,7 +24,6 @@ import {
|
||||
saveArtifacts,
|
||||
saveConstellations,
|
||||
saveExtraSection,
|
||||
saveExtraSections,
|
||||
saveTeams,
|
||||
setGuideVisibility,
|
||||
saveWeapons,
|
||||
@@ -126,13 +129,6 @@ export async function loadTeamImportGuide(guideId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function saveExtraSectionList(guideId: string, input: unknown) {
|
||||
await requireAdmin();
|
||||
const result = await runGuideAction(() => saveExtraSections(guideId, input));
|
||||
if (result.status === "saved") updateTag(pageCacheTag(guideId));
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function createGuideExtra(guideId: string, input: unknown) {
|
||||
await requireAdmin();
|
||||
try {
|
||||
@@ -204,10 +200,40 @@ export async function restoreStructuredGuide(guideId: string) {
|
||||
|
||||
export async function setStructuredGuideVisibility(
|
||||
guideId: string,
|
||||
isPublic: boolean,
|
||||
input: unknown,
|
||||
) {
|
||||
await requireAdmin();
|
||||
return runGuideAction(() => setGuideVisibility(guideId, isPublic));
|
||||
const parsed = z.strictObject({
|
||||
isPublic: z.boolean(),
|
||||
expectedVersion: z.number().int().positive(),
|
||||
publicationNote: z.string().trim().max(500).optional(),
|
||||
}).safeParse(input);
|
||||
if (!parsed.success) return { status: "error" as const, message: "ข้อมูลการเผยแพร่ไม่ถูกต้อง" };
|
||||
if (!parsed.data.isPublic) {
|
||||
return runGuideAction(() => setGuideVisibility(guideId, parsed.data));
|
||||
}
|
||||
|
||||
const [guide, catalogVersion] = await Promise.all([
|
||||
getAdminGuideContentById(guideId),
|
||||
getCurrentCatalogVersion(),
|
||||
]);
|
||||
const issues = publicationIssues({
|
||||
exists: Boolean(guide),
|
||||
overview: guide?.overview ?? "",
|
||||
coverReady: guide?.coverStatus === "ready",
|
||||
catalogVersion,
|
||||
renderableSectionCount: guide ? publicGuideSections(guide).length : 0,
|
||||
version: guide?.version ?? 0,
|
||||
expectedVersion: parsed.data.expectedVersion,
|
||||
});
|
||||
if (issues.length || !catalogVersion) {
|
||||
return { status: "error" as const, message: issues.join(" • "), issues };
|
||||
}
|
||||
return runGuideAction(() => setGuideVisibility(guideId, {
|
||||
...parsed.data,
|
||||
publicationNote: parsed.data.publicationNote || null,
|
||||
catalogVersion,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function deleteStructuredGuide(guideId: string) {
|
||||
|
||||
Reference in New Issue
Block a user