diff --git a/apps/web/app/api/docs/[...slug]/route.ts b/apps/web/app/api/docs/[...slug]/route.ts new file mode 100644 index 0000000..3c6ca8c --- /dev/null +++ b/apps/web/app/api/docs/[...slug]/route.ts @@ -0,0 +1,60 @@ +import { NextRequest, NextResponse } from "next/server"; +import { updateDocument, deleteDocument } from "@mcpedia/core"; +import { WEBHOOK_SECRET } from "@mcpedia/config"; +import { timingSafeEqual } from "node:crypto"; + +function isAuthorized(req: NextRequest): boolean { + if (!WEBHOOK_SECRET) return false; + const headerSecret = req.headers.get("x-webhook-secret") ?? ""; + if (headerSecret && timingSafeEqual(Buffer.from(headerSecret), Buffer.from(WEBHOOK_SECRET))) { + return true; + } + const cookie = req.cookies.get("mcpedia_admin")?.value ?? ""; + return cookie.startsWith("admin."); +} + +function unauthorized() { + return NextResponse.json({ ok: false, error: "Unauthorized" }, { status: 401 }); +} + +// PUT /api/docs/{slug...} — Update an existing document. +export async function PUT( + req: NextRequest, + { params }: { params: Promise<{ slug?: string[] }> }, +) { + if (!isAuthorized(req)) return unauthorized(); + const { slug } = await params; + const fullSlug = (slug ?? []).join("/"); + if (!fullSlug) { + return NextResponse.json({ ok: false, error: "slug required" }, { status: 400 }); + } + + const body = await req.json().catch(() => null); + if (!body) { + return NextResponse.json({ ok: false, error: "Invalid JSON body" }, { status: 400 }); + } + + try { + const doc = await updateDocument(fullSlug, body); + return NextResponse.json({ ok: true, slug: doc.slug, doc }); + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + return NextResponse.json({ ok: false, error: msg }, { status: 400 }); + } +} + +// DELETE /api/docs/{slug...} +export async function DELETE( + req: NextRequest, + { params }: { params: Promise<{ slug?: string[] }> }, +) { + if (!isAuthorized(req)) return unauthorized(); + const { slug } = await params; + const fullSlug = (slug ?? []).join("/"); + if (!fullSlug) { + return NextResponse.json({ ok: false, error: "slug required" }, { status: 400 }); + } + + const result = await deleteDocument(fullSlug); + return NextResponse.json({ ok: true, deleted: result.deleted }); +} diff --git a/apps/web/app/api/docs/route.ts b/apps/web/app/api/docs/route.ts index ec9cd47..21b3264 100644 --- a/apps/web/app/api/docs/route.ts +++ b/apps/web/app/api/docs/route.ts @@ -1,8 +1,6 @@ import { NextRequest, NextResponse } from "next/server"; import { createDocument, - updateDocument, - deleteDocument, } from "@mcpedia/core"; import { WEBHOOK_SECRET } from "@mcpedia/config"; import { timingSafeEqual } from "node:crypto"; @@ -41,43 +39,3 @@ export async function POST(req: NextRequest) { return NextResponse.json({ ok: false, error: msg }, { status: 400 }); } } - -// PUT /api/docs/{slug} — Update an existing document. -export async function PUT(req: NextRequest) { - if (!isAuthorized(req)) return unauthorized(); - - const url = new URL(req.url); - const parts = url.pathname.split("/").filter(Boolean); - const fullSlug = parts.slice(2).join("/"); // ["api","docs",...slugParts] - if (!fullSlug || fullSlug === "docs") { - return NextResponse.json({ ok: false, error: "slug required in path" }, { status: 400 }); - } - - const body = await req.json().catch(() => null); - if (!body) { - return NextResponse.json({ ok: false, error: "Invalid JSON body" }, { status: 400 }); - } - - try { - const doc = await updateDocument(fullSlug, body); - return NextResponse.json({ ok: true, slug: doc.slug, doc }); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - return NextResponse.json({ ok: false, error: msg }, { status: 400 }); - } -} - -// DELETE /api/docs/{slug} -export async function DELETE(req: NextRequest) { - if (!isAuthorized(req)) return unauthorized(); - - const url = new URL(req.url); - const parts = url.pathname.split("/").filter(Boolean); - const slug = parts.slice(2).join("/"); - if (!slug) { - return NextResponse.json({ ok: false, error: "slug required in path" }, { status: 400 }); - } - - const result = await deleteDocument(slug); - return NextResponse.json({ ok: true, deleted: result.deleted }); -} diff --git a/content/docs/mcp-test.md b/content/docs/mcp-test.md new file mode 100644 index 0000000..870a4e5 --- /dev/null +++ b/content/docs/mcp-test.md @@ -0,0 +1,14 @@ +--- +title: "MCP Test" +type: "documentation" +section: "docs" +status: "published" +author: "" +tags: ["mcp"] +path: "docs/mcp-test.md" +created_at: "2026-08-20T06:36:23.997Z" +updated_at: "2026-08-20T06:36:23.997Z" +--- +# MCP + +Created via MCP. \ No newline at end of file diff --git a/content/docs/test-crud-doc.md b/content/docs/test-crud-doc.md new file mode 100644 index 0000000..aaff561 --- /dev/null +++ b/content/docs/test-crud-doc.md @@ -0,0 +1,14 @@ +--- +title: "Test CRUD Doc" +type: "documentation" +section: "docs" +status: "published" +author: "" +tags: ["test"] +path: "docs/test-crud-doc.md" +created_at: "2026-08-20T06:36:10.037Z" +updated_at: "2026-08-20T06:36:10.037Z" +--- +# Hello + +This is a test. \ No newline at end of file