98 lines
3.0 KiB
TypeScript
98 lines
3.0 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { revalidatePath } from 'next/cache';
|
|
import { setSection, resetSection } from '@/lib/db/store';
|
|
import {
|
|
POSTS_KEY,
|
|
loadPost,
|
|
loadPostOverrides,
|
|
isKnownSlug,
|
|
} from '@/lib/content/posts-store';
|
|
import type { PostContent } from '@/lib/content/posts';
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
const ACCENTS = ['electric', 'violet', 'magenta', 'emerald', 'cyan'];
|
|
|
|
/** Minimal structural validation for an incoming PostContent payload. */
|
|
function validPost(data: unknown): data is {
|
|
date: string;
|
|
accent: string;
|
|
en: { lead: string; blocks: unknown[] };
|
|
fa: { lead: string; blocks: unknown[] };
|
|
} {
|
|
if (!data || typeof data !== 'object') return false;
|
|
const d = data as Record<string, unknown>;
|
|
if (typeof d.date !== 'string') return false;
|
|
if (typeof d.accent !== 'string' || !ACCENTS.includes(d.accent)) return false;
|
|
for (const loc of ['en', 'fa'] as const) {
|
|
const art = d[loc] as Record<string, unknown> | undefined;
|
|
if (!art || typeof art !== 'object') return false;
|
|
if (typeof art.lead !== 'string') return false;
|
|
if (!Array.isArray(art.blocks)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// GET ?slug=rag-eval-framework -> live (merged) article + override flag
|
|
export async function GET(req: Request) {
|
|
const slug = new URL(req.url).searchParams.get('slug') ?? '';
|
|
if (!isKnownSlug(slug)) {
|
|
return NextResponse.json({ error: 'unknown post' }, { status: 400 });
|
|
}
|
|
return NextResponse.json({
|
|
slug,
|
|
post: loadPost(slug),
|
|
overridden: slug in loadPostOverrides(),
|
|
});
|
|
}
|
|
|
|
// POST { slug, data: PostContent } -> save the article override
|
|
export async function POST(req: Request) {
|
|
let body: { slug?: string; data?: unknown };
|
|
try {
|
|
body = await req.json();
|
|
} catch {
|
|
return NextResponse.json({ error: 'bad json' }, { status: 400 });
|
|
}
|
|
|
|
const slug = body.slug ?? '';
|
|
if (!isKnownSlug(slug)) {
|
|
return NextResponse.json({ error: 'unknown post' }, { status: 400 });
|
|
}
|
|
if (!validPost(body.data)) {
|
|
return NextResponse.json(
|
|
{ error: 'expected a { date, accent, en:{lead,blocks}, fa:{lead,blocks} } payload' },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
|
|
const overrides = loadPostOverrides();
|
|
// validPost has confirmed the shape (incl. accent ∈ ACCENTS) above.
|
|
overrides[slug] = body.data as unknown as PostContent;
|
|
setSection(POSTS_KEY, overrides);
|
|
|
|
revalidatePath(`/blog/${slug}`);
|
|
revalidatePath('/', 'layout');
|
|
return NextResponse.json({ ok: true });
|
|
}
|
|
|
|
// DELETE ?slug=… -> revert one article to its in-code default
|
|
export async function DELETE(req: Request) {
|
|
const slug = new URL(req.url).searchParams.get('slug') ?? '';
|
|
if (!isKnownSlug(slug)) {
|
|
return NextResponse.json({ error: 'unknown post' }, { status: 400 });
|
|
}
|
|
|
|
const overrides = loadPostOverrides();
|
|
delete overrides[slug];
|
|
if (Object.keys(overrides).length === 0) {
|
|
resetSection(POSTS_KEY);
|
|
} else {
|
|
setSection(POSTS_KEY, overrides);
|
|
}
|
|
|
|
revalidatePath(`/blog/${slug}`);
|
|
revalidatePath('/', 'layout');
|
|
return NextResponse.json({ ok: true });
|
|
}
|