fix: preserve value types in extraFields (no string coercion)
CI / typecheck + build (turbo) (push) Canceled after 0s
CI / typecheck + build (turbo) (push) Canceled after 0s
- DocumentMeta.extraFields type -> Record<string, unknown> - API routes pass extraFields through without JSON.stringify coercion - CustomFieldBadges auto-styles by value type/content (number->purple, boolean->green/red, etc.) - DocForm help text: generic (no hardcoded field examples)
This commit is contained in:
@@ -24,15 +24,15 @@ const STANDARD_FIELDS = new Set([
|
|||||||
|
|
||||||
function splitPayload(body: Record<string, unknown>): {
|
function splitPayload(body: Record<string, unknown>): {
|
||||||
standard: Record<string, unknown>;
|
standard: Record<string, unknown>;
|
||||||
extraFields: Record<string, string>;
|
extraFields: Record<string, unknown>;
|
||||||
} {
|
} {
|
||||||
const standard: Record<string, unknown> = {};
|
const standard: Record<string, unknown> = {};
|
||||||
const extraFields: Record<string, string> = {};
|
const extraFields: Record<string, unknown> = {};
|
||||||
for (const [key, value] of Object.entries(body)) {
|
for (const [key, value] of Object.entries(body)) {
|
||||||
if (STANDARD_FIELDS.has(key)) {
|
if (STANDARD_FIELDS.has(key)) {
|
||||||
standard[key] = value;
|
standard[key] = value;
|
||||||
} else if (value !== undefined && value !== null) {
|
} else if (value !== undefined && value !== null) {
|
||||||
extraFields[key] = typeof value === "string" ? value : JSON.stringify(value);
|
extraFields[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { standard, extraFields };
|
return { standard, extraFields };
|
||||||
|
|||||||
@@ -68,14 +68,7 @@ export async function POST(req: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { standard, extraFields } = splitPayload(body);
|
const { standard, extraFields } = splitPayload(body);
|
||||||
// Coerce all extra field values to strings (form sends strings; API/MCP may send objects).
|
const doc = await createDocument({ ...standard, extraFields } as any);
|
||||||
const stringExtra: Record<string, string> = {};
|
|
||||||
for (const [k, v] of Object.entries(extraFields)) {
|
|
||||||
if (v !== undefined && v !== null) {
|
|
||||||
stringExtra[k] = typeof v === "string" ? v : JSON.stringify(v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const doc = await createDocument({ ...standard, extraFields: stringExtra } as any);
|
|
||||||
return NextResponse.json({ ok: true, slug: doc.slug, doc });
|
return NextResponse.json({ ok: true, slug: doc.slug, doc });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: "Dynamic Field Types Test"
|
||||||
|
type: "note"
|
||||||
|
section: "notes"
|
||||||
|
status: "published"
|
||||||
|
author: "asep"
|
||||||
|
tags: ["test"]
|
||||||
|
path: "notes/test/dynamic-field-types.md"
|
||||||
|
created_at: "2026-08-20T17:45:07.294Z"
|
||||||
|
updated_at: "2026-08-20T17:45:07.294Z"
|
||||||
|
priority: "5"
|
||||||
|
resolved: "true"
|
||||||
|
os: "linux"
|
||||||
|
complexity: "medium"
|
||||||
|
tags_list: "[\"alpha\",\"beta\"]"
|
||||||
|
metadata: "{\"owner\":\"team-a\",\"tier\":2}"
|
||||||
|
---
|
||||||
|
# Dynamic Field Types Test
|
||||||
|
Testing various field value types.
|
||||||
@@ -128,7 +128,7 @@ export interface CreateDocInput {
|
|||||||
status?: DocStatus;
|
status?: DocStatus;
|
||||||
author?: string;
|
author?: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
extraFields?: Record<string, string>;
|
extraFields?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateDocInput {
|
export interface UpdateDocInput {
|
||||||
@@ -138,7 +138,7 @@ export interface UpdateDocInput {
|
|||||||
status?: DocStatus;
|
status?: DocStatus;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
author?: string;
|
author?: string;
|
||||||
extraFields?: Record<string, string>;
|
extraFields?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Validate that a slug is safe (no path traversal, only [a-z0-9/_-]). */
|
/** Validate that a slug is safe (no path traversal, only [a-z0-9/_-]). */
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ export interface DocumentMeta {
|
|||||||
updatedAt: string; // ISO
|
updatedAt: string; // ISO
|
||||||
// Dynamic custom frontmatter fields (e.g. CTF: event, challenge, category,
|
// Dynamic custom frontmatter fields (e.g. CTF: event, challenge, category,
|
||||||
// difficulty, points). Content creators add arbitrary key-value pairs.
|
// difficulty, points). Content creators add arbitrary key-value pairs.
|
||||||
extraFields?: Record<string, string>;
|
// Values can be strings, numbers, booleans, arrays, or objects.
|
||||||
|
extraFields?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Document extends DocumentMeta {
|
export interface Document extends DocumentMeta {
|
||||||
|
|||||||
Reference in New Issue
Block a user