feat(core): dynamic custom frontmatter fields for CTF writeup metadata
CI / typecheck + build (turbo) (push) Canceled after 0s

- Add extra_fields JSONB column to documents table (migration 0003)
- CreateDocInput/UpdateDocInput: extraFields?: Record<string, string>
- parseFile: extracts non-standard frontmatter keys as extraFields
- stringifyFile: writes extraFields dynamically to YAML frontmatter
- toMeta: spreads extraFields from DB row into DocumentMeta
- DocForm: '+ Add field' UI for content creators to add any metadata
- API routes: splitPayload() separates standard vs custom fields
- Doc page: CustomFieldBadges dynamically renders any custom field
- Add db:migrate / db:push scripts + deploy workflow migration step
- Add CTF writeup template (content/writeups/ctf/template/)
- Add sample DEF CON writeup with event/challenge/category/difficulty/points
- Add @tailwindcss/typography for table rendering (prose classes)
- Add remark-gfm for GitHub Flavored Markdown table parsing
This commit is contained in:
asepharyana
2026-08-20 23:18:57 +07:00
parent 8906ed55a0
commit c760c3c087
16 changed files with 580 additions and 25 deletions
+10
View File
@@ -128,6 +128,7 @@ export interface CreateDocInput {
status?: DocStatus;
author?: string;
tags?: string[];
extraFields?: Record<string, string>;
}
export interface UpdateDocInput {
@@ -137,6 +138,7 @@ export interface UpdateDocInput {
status?: DocStatus;
tags?: string[];
author?: string;
extraFields?: Record<string, string>;
}
/** Validate that a slug is safe (no path traversal, only [a-z0-9/_-]). */
@@ -186,6 +188,7 @@ export async function createDocument(input: CreateDocInput): Promise<DocumentMet
path: relPath,
createdAt: nowIso,
updatedAt: nowIso,
extraFields: input.extraFields ?? {},
};
// Write file to disk first (source of truth).
@@ -204,6 +207,7 @@ export async function createDocument(input: CreateDocInput): Promise<DocumentMet
tags: meta.tags,
path: meta.path,
body: input.body,
extraFields: input.extraFields ?? {},
createdAt: new Date(meta.createdAt),
updatedAt: new Date(meta.updatedAt),
});
@@ -241,6 +245,11 @@ export async function updateDocument(
tags: input.tags ?? doc.tags,
author: input.author ?? doc.author,
updatedAt,
// Merge: new extraFields override old ones; merge with existing
extraFields:
input.extraFields !== undefined
? { ...doc.extraFields, ...input.extraFields }
: doc.extraFields,
};
const body = input.body ?? doc.body;
@@ -259,6 +268,7 @@ export async function updateDocument(
status: updated.status,
author: updated.author,
tags: updated.tags,
extraFields: input.extraFields ?? doc.extraFields ?? {},
body,
updatedAt: new Date(updatedAt),
})
+2
View File
@@ -48,6 +48,7 @@ export async function indexContentFile(
tags: meta.tags,
path: meta.path,
body,
extraFields: meta.extraFields ?? {},
createdAt: new Date(meta.createdAt || nowIso),
updatedAt: new Date(nowIso),
})
@@ -62,6 +63,7 @@ export async function indexContentFile(
tags: meta.tags,
path: meta.path,
body,
extraFields: meta.extraFields ?? {},
updatedAt: new Date(nowIso),
},
});