fix(acadenice): use camelCase row keys in sync-block repo — Patch 027

The DatabaseModule registers a global CamelCasePlugin which converts
every column name (including SELECT aliases) from snake_case to
camelCase at runtime. The sync-block repo declared sql<{...}> result
types in snake_case (workspace_id, created_at, etc.) and accessed those
keys in mapRow / findUsages. At runtime kysely returned camelCase keys
so every property read was undefined, causing 'Invalid time value' on
new Date(undefined).toISOString().

Same pattern is likely present in graph.service.ts and backlinks
services — Patch 028 will sweep those.

Verified via curl: POST /api/acadenice/sync-blocks now returns 201 with
the full DTO.

Patch 027.
This commit is contained in:
Corentin JOGUET 2026-05-08 12:53:29 +02:00
parent 7fba3c0452
commit 8c3d55024b

View file

@ -24,11 +24,11 @@ export class SyncBlockRepo {
): Promise<SyncBlockResponseDto> {
const result = await sql<{
id: string;
workspace_id: string;
workspaceId: string;
content: Record<string, unknown>;
created_by: string;
created_at: Date;
updated_at: Date;
createdBy: string;
createdAt: Date;
updatedAt: Date;
}>`
INSERT INTO acadenice_sync_block (workspace_id, content, created_by)
VALUES (${workspaceId}, ${JSON.stringify(content)}::jsonb, ${createdBy})
@ -44,11 +44,11 @@ export class SyncBlockRepo {
): Promise<SyncBlockResponseDto | null> {
const result = await sql<{
id: string;
workspace_id: string;
workspaceId: string;
content: Record<string, unknown>;
created_by: string;
created_at: Date;
updated_at: Date;
createdBy: string;
createdAt: Date;
updatedAt: Date;
}>`
SELECT id, workspace_id, content, created_by, created_at, updated_at
FROM acadenice_sync_block
@ -67,11 +67,11 @@ export class SyncBlockRepo {
): Promise<SyncBlockResponseDto | null> {
const result = await sql<{
id: string;
workspace_id: string;
workspaceId: string;
content: Record<string, unknown>;
created_by: string;
created_at: Date;
updated_at: Date;
createdBy: string;
createdAt: Date;
updatedAt: Date;
}>`
UPDATE acadenice_sync_block
SET content = ${JSON.stringify(content)}::jsonb, updated_at = NOW()
@ -101,7 +101,7 @@ export class SyncBlockRepo {
id: string,
workspaceId: string,
): Promise<Buffer | null> {
const result = await sql<{ yjs_state: Buffer | null }>`
const result = await sql<{ yjsState: Buffer | null }>`
SELECT yjs_state
FROM acadenice_sync_block
WHERE id = ${id}
@ -109,7 +109,7 @@ export class SyncBlockRepo {
`.execute(this.db);
if (result.rows.length === 0) return null;
return result.rows[0].yjs_state ?? null;
return result.rows[0].yjsState ?? null;
}
async delete(id: string, workspaceId: string): Promise<boolean> {
@ -137,12 +137,15 @@ export class SyncBlockRepo {
// Pages store their content as JSONB. We search for any object in the
// content tree where type='syncBlock' and attrs->>'masterId' = blockId.
// Using jsonb_path_exists for recursive search.
// CamelCasePlugin (in DatabaseModule) converts snake_case columns to
// camelCase at runtime, including SELECT aliases. So even though the SQL
// says "page_id" the row property is "pageId".
const result = await sql<{
page_id: string;
pageId: string;
title: string | null;
slug_id: string;
space_id: string;
workspace_id: string;
slugId: string;
spaceId: string;
workspaceId: string;
}>`
SELECT DISTINCT p.id AS page_id, p.title, p.slug_id, p.space_id, s.workspace_id
FROM pages p
@ -158,11 +161,11 @@ export class SyncBlockRepo {
`.execute(this.db);
return result.rows.map((r) => ({
pageId: r.page_id,
pageId: r.pageId,
pageTitle: r.title,
slugId: r.slug_id,
spaceId: r.space_id,
workspaceId: r.workspace_id,
slugId: r.slugId,
spaceId: r.spaceId,
workspaceId: r.workspaceId,
}));
}
@ -178,19 +181,19 @@ export class SyncBlockRepo {
private mapRow(row: {
id: string;
workspace_id: string;
workspaceId: string;
content: Record<string, unknown>;
created_by: string;
created_at: Date | string;
updated_at: Date | string;
createdBy: string;
createdAt: Date | string;
updatedAt: Date | string;
}): SyncBlockResponseDto {
return {
id: row.id,
workspaceId: row.workspace_id,
workspaceId: row.workspaceId,
content: row.content,
createdBy: row.created_by,
createdAt: new Date(row.created_at).toISOString(),
updatedAt: new Date(row.updated_at).toISOString(),
createdBy: row.createdBy,
createdAt: new Date(row.createdAt).toISOString(),
updatedAt: new Date(row.updatedAt).toISOString(),
};
}
}