From 9139fb8728c1bb18e8e2078558b9e11a4d08a7c8 Mon Sep 17 00:00:00 2001 From: Corentin Date: Fri, 8 May 2026 13:01:10 +0200 Subject: [PATCH] =?UTF-8?q?fix(acadenice):=20pass=20content=20object=20dir?= =?UTF-8?q?ectly=20to=20jsonb=20param=20+=20remove=20empty=20text=20nodes?= =?UTF-8?q?=20=E2=80=94=20Patch=20028?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs in template seed/instantiate: 1. ${JSON.stringify(content)}::jsonb made Postgres store the content as a jsonb scalar string (jsonb_typeof = 'string'), not an object. The instantiate read it back as a JSON-encoded string, which ProseMirror tried to parse as a node tree and crashed with 'Unknown node type: undefined' on the outer string. Pass the object directly with ${content as unknown as string}::jsonb so postgres-js binds it as a JSONB value. 2. Built-in template seed used { type: 'paragraph', content: [{ type: 'text', text: '' }] } for empty paragraphs / list items / task items. ProseMirror schema rejects empty text nodes ('Empty text nodes are not allowed'). Replaced with content: []. Verified via curl: POST /api/acadenice/templates/{id}/instantiate now returns 201 with the new pageId/slugId. Patch 028. --- .../services/template-seed.service.ts | 24 +++++++++---------- .../templates/services/template.service.ts | 8 +++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/server/src/core/acadenice/templates/services/template-seed.service.ts b/apps/server/src/core/acadenice/templates/services/template-seed.service.ts index 7b4d9e18..52057123 100644 --- a/apps/server/src/core/acadenice/templates/services/template-seed.service.ts +++ b/apps/server/src/core/acadenice/templates/services/template-seed.service.ts @@ -35,13 +35,13 @@ const BUILT_IN_TEMPLATES: ReadonlyArray = [ { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Meeting Note' }] }, { type: 'paragraph', content: [{ type: 'text', marks: [{ type: 'bold' }], text: 'Date: ' }, { type: 'text', text: new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Attendees' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Agenda' }] }, - { type: 'orderedList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'orderedList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Notes' }] }, - { type: 'paragraph', content: [{ type: 'text', text: '' }] }, + { type: 'paragraph', content: [] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Action Items' }] }, - { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: false }, content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: false }, content: [{ type: 'paragraph', content: [] }] }] }, ], }, }, @@ -59,11 +59,11 @@ const BUILT_IN_TEMPLATES: ReadonlyArray = [ { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Scope' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'What is in scope / out of scope?' }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Stakeholders' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Timeline' }] }, { type: 'paragraph', content: [{ type: 'text', text: 'Key milestones and deadlines.' }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Risks' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, ], }, }, @@ -77,11 +77,11 @@ const BUILT_IN_TEMPLATES: ReadonlyArray = [ content: [ { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Daily Standup' }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Yesterday' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Today' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Blockers' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, ], }, }, @@ -95,11 +95,11 @@ const BUILT_IN_TEMPLATES: ReadonlyArray = [ content: [ { type: 'heading', attrs: { level: 1 }, content: [{ type: 'text', text: 'Weekly Review' }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Wins' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Challenges' }] }, - { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'bulletList', content: [{ type: 'listItem', content: [{ type: 'paragraph', content: [] }] }] }, { type: 'heading', attrs: { level: 2 }, content: [{ type: 'text', text: 'Next Week Priorities' }] }, - { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: false }, content: [{ type: 'paragraph', content: [{ type: 'text', text: '' }] }] }] }, + { type: 'taskList', content: [{ type: 'taskItem', attrs: { checked: false }, content: [{ type: 'paragraph', content: [] }] }] }, ], }, }, diff --git a/apps/server/src/core/acadenice/templates/services/template.service.ts b/apps/server/src/core/acadenice/templates/services/template.service.ts index fefd0586..3b30ea0d 100644 --- a/apps/server/src/core/acadenice/templates/services/template.service.ts +++ b/apps/server/src/core/acadenice/templates/services/template.service.ts @@ -157,7 +157,7 @@ export class TemplateService { ${dto.icon ?? null}, ${dto.coverUrl ?? null}, ${dto.category ?? null}, - ${JSON.stringify(content)}::jsonb, + ${content as unknown as string}::jsonb, ${dto.sourcePageId ?? null}, false, ${userId} @@ -227,7 +227,7 @@ export class TemplateService { } const contentParam = dto.content - ? sql`${JSON.stringify(dto.content)}::jsonb` + ? sql`${dto.content as unknown as string}::jsonb` : sql`content`; const result = await sql` @@ -331,7 +331,7 @@ export class TemplateService { ${workspaceId}, ${userId}, ${userId}, - ${JSON.stringify(content)}::jsonb, + ${content as unknown as string}::jsonb, ${textContent ?? ''}, ${ydoc}, ${nextPosition}, @@ -461,7 +461,7 @@ export class TemplateService { ${spec.description}, ${spec.icon ?? null}, ${spec.category}, - ${JSON.stringify(spec.content)}::jsonb, + ${spec.content as unknown as string}::jsonb, true, ${systemUserId} )