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:
parent
7fba3c0452
commit
8c3d55024b
1 changed files with 33 additions and 30 deletions
|
|
@ -24,11 +24,11 @@ export class SyncBlockRepo {
|
||||||
): Promise<SyncBlockResponseDto> {
|
): Promise<SyncBlockResponseDto> {
|
||||||
const result = await sql<{
|
const result = await sql<{
|
||||||
id: string;
|
id: string;
|
||||||
workspace_id: string;
|
workspaceId: string;
|
||||||
content: Record<string, unknown>;
|
content: Record<string, unknown>;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
created_at: Date;
|
createdAt: Date;
|
||||||
updated_at: Date;
|
updatedAt: Date;
|
||||||
}>`
|
}>`
|
||||||
INSERT INTO acadenice_sync_block (workspace_id, content, created_by)
|
INSERT INTO acadenice_sync_block (workspace_id, content, created_by)
|
||||||
VALUES (${workspaceId}, ${JSON.stringify(content)}::jsonb, ${createdBy})
|
VALUES (${workspaceId}, ${JSON.stringify(content)}::jsonb, ${createdBy})
|
||||||
|
|
@ -44,11 +44,11 @@ export class SyncBlockRepo {
|
||||||
): Promise<SyncBlockResponseDto | null> {
|
): Promise<SyncBlockResponseDto | null> {
|
||||||
const result = await sql<{
|
const result = await sql<{
|
||||||
id: string;
|
id: string;
|
||||||
workspace_id: string;
|
workspaceId: string;
|
||||||
content: Record<string, unknown>;
|
content: Record<string, unknown>;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
created_at: Date;
|
createdAt: Date;
|
||||||
updated_at: Date;
|
updatedAt: Date;
|
||||||
}>`
|
}>`
|
||||||
SELECT id, workspace_id, content, created_by, created_at, updated_at
|
SELECT id, workspace_id, content, created_by, created_at, updated_at
|
||||||
FROM acadenice_sync_block
|
FROM acadenice_sync_block
|
||||||
|
|
@ -67,11 +67,11 @@ export class SyncBlockRepo {
|
||||||
): Promise<SyncBlockResponseDto | null> {
|
): Promise<SyncBlockResponseDto | null> {
|
||||||
const result = await sql<{
|
const result = await sql<{
|
||||||
id: string;
|
id: string;
|
||||||
workspace_id: string;
|
workspaceId: string;
|
||||||
content: Record<string, unknown>;
|
content: Record<string, unknown>;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
created_at: Date;
|
createdAt: Date;
|
||||||
updated_at: Date;
|
updatedAt: Date;
|
||||||
}>`
|
}>`
|
||||||
UPDATE acadenice_sync_block
|
UPDATE acadenice_sync_block
|
||||||
SET content = ${JSON.stringify(content)}::jsonb, updated_at = NOW()
|
SET content = ${JSON.stringify(content)}::jsonb, updated_at = NOW()
|
||||||
|
|
@ -101,7 +101,7 @@ export class SyncBlockRepo {
|
||||||
id: string,
|
id: string,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<Buffer | null> {
|
): Promise<Buffer | null> {
|
||||||
const result = await sql<{ yjs_state: Buffer | null }>`
|
const result = await sql<{ yjsState: Buffer | null }>`
|
||||||
SELECT yjs_state
|
SELECT yjs_state
|
||||||
FROM acadenice_sync_block
|
FROM acadenice_sync_block
|
||||||
WHERE id = ${id}
|
WHERE id = ${id}
|
||||||
|
|
@ -109,7 +109,7 @@ export class SyncBlockRepo {
|
||||||
`.execute(this.db);
|
`.execute(this.db);
|
||||||
|
|
||||||
if (result.rows.length === 0) return null;
|
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> {
|
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
|
// Pages store their content as JSONB. We search for any object in the
|
||||||
// content tree where type='syncBlock' and attrs->>'masterId' = blockId.
|
// content tree where type='syncBlock' and attrs->>'masterId' = blockId.
|
||||||
// Using jsonb_path_exists for recursive search.
|
// 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<{
|
const result = await sql<{
|
||||||
page_id: string;
|
pageId: string;
|
||||||
title: string | null;
|
title: string | null;
|
||||||
slug_id: string;
|
slugId: string;
|
||||||
space_id: string;
|
spaceId: string;
|
||||||
workspace_id: string;
|
workspaceId: string;
|
||||||
}>`
|
}>`
|
||||||
SELECT DISTINCT p.id AS page_id, p.title, p.slug_id, p.space_id, s.workspace_id
|
SELECT DISTINCT p.id AS page_id, p.title, p.slug_id, p.space_id, s.workspace_id
|
||||||
FROM pages p
|
FROM pages p
|
||||||
|
|
@ -158,11 +161,11 @@ export class SyncBlockRepo {
|
||||||
`.execute(this.db);
|
`.execute(this.db);
|
||||||
|
|
||||||
return result.rows.map((r) => ({
|
return result.rows.map((r) => ({
|
||||||
pageId: r.page_id,
|
pageId: r.pageId,
|
||||||
pageTitle: r.title,
|
pageTitle: r.title,
|
||||||
slugId: r.slug_id,
|
slugId: r.slugId,
|
||||||
spaceId: r.space_id,
|
spaceId: r.spaceId,
|
||||||
workspaceId: r.workspace_id,
|
workspaceId: r.workspaceId,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,19 +181,19 @@ export class SyncBlockRepo {
|
||||||
|
|
||||||
private mapRow(row: {
|
private mapRow(row: {
|
||||||
id: string;
|
id: string;
|
||||||
workspace_id: string;
|
workspaceId: string;
|
||||||
content: Record<string, unknown>;
|
content: Record<string, unknown>;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
created_at: Date | string;
|
createdAt: Date | string;
|
||||||
updated_at: Date | string;
|
updatedAt: Date | string;
|
||||||
}): SyncBlockResponseDto {
|
}): SyncBlockResponseDto {
|
||||||
return {
|
return {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
workspaceId: row.workspace_id,
|
workspaceId: row.workspaceId,
|
||||||
content: row.content,
|
content: row.content,
|
||||||
createdBy: row.created_by,
|
createdBy: row.createdBy,
|
||||||
createdAt: new Date(row.created_at).toISOString(),
|
createdAt: new Date(row.createdAt).toISOString(),
|
||||||
updatedAt: new Date(row.updated_at).toISOString(),
|
updatedAt: new Date(row.updatedAt).toISOString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue