feat: revamp to dynamic database-first architecture and cleanup phase docs
CI / typecheck + build (turbo) (push) Canceled after 0s
CI / typecheck + build (turbo) (push) Canceled after 0s
- Migrate document fetching and CRUD to be PostgreSQL-authoritative - Remove static section enums and add dynamic listSections query - Support custom sections and metadata across API, MCP, and Web UI - Add /api/sections endpoint and update Header, Sidebar, and Forms - Remove obsolete phase planning docs and modernize README/AGENTS
This commit is contained in:
1 parent
a937e8f51b
commit
a9b67385c3
37 files changed
+720
-2515
No files matched your search
@@ -8,7 +8,7 @@ import DocForm from "@/components/DocForm";
|
||||
import TOC from "@/components/TOC";
|
||||
import DocActions from "@/components/DocActions";
|
||||
import { classifyPath, extractFoldersForSection } from "@mcpedia/core";
|
||||
import { SECTIONS_BY_ID } from "@mcpedia/config/sections";
|
||||
import { getSectionMeta } from "@mcpedia/config";
|
||||
import type { DocumentMeta } from "@mcpedia/core";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
@@ -19,17 +19,7 @@ interface DocPageProps {
|
||||
}
|
||||
|
||||
function getSectionInfo(section: string) {
|
||||
const info = SECTIONS_BY_ID.get(section);
|
||||
if (!info) {
|
||||
return {
|
||||
label: section.charAt(0).toUpperCase() + section.slice(1),
|
||||
icon: "📄",
|
||||
};
|
||||
}
|
||||
return {
|
||||
label: info.label,
|
||||
icon: info.icon,
|
||||
};
|
||||
return getSectionMeta(section);
|
||||
}
|
||||
|
||||
function calculateReadingTime(text: string): string {
|
||||
@@ -128,9 +118,9 @@ function FolderIndexPage({
|
||||
}) {
|
||||
const prefix = `${section}/${slug}/`;
|
||||
const folderDocPaths = allDocs
|
||||
.filter((d) => d.path.startsWith(prefix))
|
||||
.filter((d) => d.slug.startsWith(prefix))
|
||||
.map((d) => ({
|
||||
rel: d.path.slice(prefix.length).replace(/\.md$/, ""),
|
||||
rel: d.slug.slice(prefix.length),
|
||||
doc: d,
|
||||
}))
|
||||
.sort((a, b) => a.rel.localeCompare(b.rel));
|
||||
@@ -292,8 +282,8 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
||||
const fullSlug = `${section}/${slug.join("/")}`;
|
||||
|
||||
const allDocs = await listDocuments();
|
||||
const docPaths = allDocs.map((d) => d.path);
|
||||
const classification = classifyPath(docPaths, fullSlug);
|
||||
const docSlugs = allDocs.map((d) => d.slug);
|
||||
const classification = classifyPath(docSlugs, fullSlug);
|
||||
|
||||
if (classification === "folder") {
|
||||
return <FolderIndexPage section={section} slug={slug.join("/")} allDocs={allDocs} />;
|
||||
@@ -312,7 +302,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
||||
]);
|
||||
|
||||
if (edit === "1" && canEdit) {
|
||||
const existingFolders = extractFoldersForSection(docPaths, doc.section);
|
||||
const existingFolders = extractFoldersForSection(docSlugs, doc.section);
|
||||
return (
|
||||
<div>
|
||||
<Link
|
||||
@@ -328,6 +318,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
||||
mode="edit"
|
||||
slug={fullSlug}
|
||||
secret={WEBHOOK_SECRET}
|
||||
existingSections={Array.from(new Set(allDocs.map((d) => d.section)))}
|
||||
existingFolders={{
|
||||
[doc.section]: existingFolders,
|
||||
}}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { listDocuments } from "@mcpedia/core";
|
||||
import { SECTIONS } from "@mcpedia/config/sections";
|
||||
import { getSectionMeta } from "@mcpedia/config";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -24,12 +24,11 @@ interface TreeNode {
|
||||
function buildFolderTree(docs: DocMeta[], section: string): TreeNode[] {
|
||||
const root: TreeNode[] = [];
|
||||
|
||||
for (const doc of docs.filter((d) => d.section === section)) {
|
||||
const rel = doc.path.startsWith(`${section}/`)
|
||||
? doc.path.slice(section.length + 1)
|
||||
: doc.path;
|
||||
const cleanPath = rel.replace(/\.md$/, "");
|
||||
const parts = cleanPath.split("/");
|
||||
for (const doc of docs.filter((d) => (d.section || "").toLowerCase() === section.toLowerCase())) {
|
||||
const rel = doc.slug.startsWith(`${section}/`)
|
||||
? doc.slug.slice(section.length + 1)
|
||||
: doc.slug;
|
||||
const parts = rel.split("/");
|
||||
|
||||
let current = root;
|
||||
let currentPath = section;
|
||||
@@ -118,19 +117,10 @@ export default async function SectionIndexPage({
|
||||
params: Promise<{ section: string }>;
|
||||
}) {
|
||||
const { section } = await params;
|
||||
|
||||
const sectionInfo = SECTIONS.find((s) => s.id === section);
|
||||
if (!sectionInfo) {
|
||||
return (
|
||||
<div className="py-12 text-center">
|
||||
<h1 className="text-2xl font-semibold text-[var(--text-primary)]">Unknown section</h1>
|
||||
<p className="text-sm text-[var(--text-muted)] mt-2">Section "{section}" does not exist.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const sectionInfo = getSectionMeta(section);
|
||||
|
||||
const allDocs = await listDocuments();
|
||||
const sectionDocs = allDocs.filter((d) => d.section === section);
|
||||
const sectionDocs = allDocs.filter((d) => (d.section || "").toLowerCase() === section.toLowerCase());
|
||||
const tree = buildFolderTree(allDocs, section);
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user