feat: Phase 11 — CRUD (create/update/delete) + auth + web UI
- Core: createDocument/updateDocument/deleteDocument (file + DB + revision + chunks) - Parser: stringifyFile (serialize markdown with frontmatter to disk) - API: tRPC CRUD routers (auth-gated via requireWriteAuth middleware) - API: createContext now passes expectedSecret from deps (request-scoped auth) - MCP: 3 new write tools (create_document, update_document, delete_document) - Web: /create page + ?edit=1 form, /api/auth/login (cookie-based), /api/docs REST CRUD - Web: Edit buttons on homepage + doc pages (auth-gated) - Tests: 8 new tests (5 tRPC CRUD + 3 MCP CRUD auth), 40 total all green
This commit is contained in:
1 parent
2d974b8952
commit
57f90018da
19 files changed
+1215
-23
No files matched your search
+29
-8
@@ -1,6 +1,7 @@
|
||||
import Link from "next/link";
|
||||
import { listDocuments } from "@mcpedia/core";
|
||||
import type { DocumentMeta } from "@mcpedia/core";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
// Render at request time — content is in Postgres (not available at build in
|
||||
// CI, which has no DB). Request-time rendering is instant at this corpus scale.
|
||||
@@ -15,6 +16,10 @@ export default async function Home() {
|
||||
docs: all.filter((d) => d.section === section),
|
||||
}));
|
||||
|
||||
// Auth check for edit/create buttons.
|
||||
const cookieStore = await cookies();
|
||||
const canEdit = cookieStore.get("mcpedia_admin")?.value != null;
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<section>
|
||||
@@ -23,6 +28,14 @@ export default async function Home() {
|
||||
A content-first knowledge base. Humans read the Web UI; AI agents use
|
||||
the MCP server. Both share one Core.
|
||||
</p>
|
||||
{canEdit && (
|
||||
<Link
|
||||
href="/create"
|
||||
className="inline-block mt-3 px-4 py-2 text-sm border border-zinc-300 dark:border-zinc-700 rounded hover:bg-zinc-100 dark:hover:bg-zinc-800"
|
||||
>
|
||||
+ Create Document
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{bySection.map(({ section, docs }) => (
|
||||
@@ -33,17 +46,25 @@ export default async function Home() {
|
||||
) : (
|
||||
<ul className="divide-y divide-zinc-200 dark:divide-zinc-800">
|
||||
{docs.map((d: DocumentMeta) => (
|
||||
<li key={d.slug} className="py-2">
|
||||
<li key={d.slug} className="py-2">
|
||||
<Link
|
||||
href={`/${d.slug}`}
|
||||
className="hover:underline font-medium"
|
||||
>
|
||||
{d.title}
|
||||
</Link>
|
||||
{canEdit && (
|
||||
<Link
|
||||
href={`/${d.slug}`}
|
||||
className="hover:underline font-medium"
|
||||
href={`/${d.slug}/edit`}
|
||||
className="ml-2 text-xs text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300"
|
||||
>
|
||||
{d.title}
|
||||
✎
|
||||
</Link>
|
||||
<div className="text-xs text-zinc-500 mt-0.5">
|
||||
{d.tags.map((t) => `#${t}`).join(" ")}
|
||||
</div>
|
||||
</li>
|
||||
)}
|
||||
<div className="text-xs text-zinc-500 mt-0.5">
|
||||
{d.tags.map((t) => `#${t}`).join(" ")}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
Reference in new issue
Block a user