feat: revamp to dynamic database-first architecture and cleanup phase docs
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:
asepharyana
2026-08-21 11:35:42 +07:00
parent a937e8f51b
commit a9b67385c3
37 changed files with 720 additions and 2515 deletions
+47 -29
View File
@@ -9,20 +9,21 @@ interface DocFormProps {
slug?: string;
secret: string;
existingFolders?: Record<string, string[]>;
existingSections?: string[];
initial?: {
title?: string;
body?: string;
section?: "docs" | "writeups" | "research" | "notes";
type?: "documentation" | "writeup" | "research" | "note";
status?: "published" | "draft";
section?: string;
type?: string;
status?: "published" | "draft" | string;
tags?: string[];
author?: string;
extraFields?: Record<string, unknown>;
};
}
const SECTION_OPTIONS = ["docs", "writeups", "research", "notes"] as const;
const TYPE_OPTIONS = ["documentation", "writeup", "research", "note"] as const;
const DEFAULT_SECTION_LIST = ["docs", "writeups", "research", "notes", "guides", "tutorials", "ctf", "api", "projects"];
const DEFAULT_TYPE_LIST = ["documentation", "writeup", "research", "note", "guide", "tutorial", "spec"];
const PRESET_FIELDS = [
{ key: "difficulty", value: "medium" },
@@ -37,6 +38,7 @@ export default function DocForm({
slug,
secret,
existingFolders,
existingSections,
initial,
}: DocFormProps) {
const router = useRouter();
@@ -56,6 +58,10 @@ export default function DocForm({
const textareaRef = useRef<HTMLTextAreaElement>(null);
const sectionOptions = Array.from(
new Set([...(existingSections ?? []), ...DEFAULT_SECTION_LIST, section].filter(Boolean)),
);
// Extract existing custom fields
const [customFields, setCustomFields] = useState<Array<{ key: string; value: string }>>(() => {
if (initial?.extraFields) {
@@ -198,7 +204,7 @@ export default function DocForm({
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="e.g. DEF CON Quals 2024 — pwn-100 Writeup"
placeholder="e.g. Database Architecture Overview"
className={`${baseInputCls} text-base`}
required
/>
@@ -210,34 +216,46 @@ export default function DocForm({
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
Section
</label>
<select
value={section}
onChange={(e) => setSection(e.target.value as typeof section)}
className={baseInputCls}
disabled={isEdit}
>
{SECTION_OPTIONS.map((s) => (
<option key={s} value={s}>
{s.toUpperCase()}
</option>
))}
</select>
<div className="relative">
<input
type="text"
list="section-list"
value={section}
onChange={(e) => setSection(e.target.value)}
placeholder="e.g. docs, writeups, ctf, guides"
className={baseInputCls}
required
/>
<datalist id="section-list">
{sectionOptions.map((s) => (
<option key={s} value={s}>
{s.toUpperCase()}
</option>
))}
</datalist>
</div>
</div>
<div>
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
Type
</label>
<select
value={type}
onChange={(e) => setType(e.target.value as typeof type)}
className={baseInputCls}
>
{TYPE_OPTIONS.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
</select>
<div className="relative">
<input
type="text"
list="type-list"
value={type}
onChange={(e) => setType(e.target.value)}
placeholder="e.g. documentation, writeup, note"
className={baseInputCls}
/>
<datalist id="type-list">
{DEFAULT_TYPE_LIST.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
</datalist>
</div>
</div>
<div>
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">