diff --git a/mindspace-page-sync.mjs b/mindspace-page-sync.mjs index e35a2c5..883c04e 100644 --- a/mindspace-page-sync.mjs +++ b/mindspace-page-sync.mjs @@ -52,6 +52,16 @@ async function findPageByWorkspaceRelativePath(pool, userId, relativePath) { return { id: row.id, updatedAt: Number(row.updated_at ?? 0) }; } +function parseJsonColumn(value, fallback = {}) { + if (value == null || value === '') return { ...fallback }; + if (typeof value === 'object' && !Buffer.isBuffer(value)) return value; + try { + return JSON.parse(String(value)); + } catch { + return { ...fallback }; + } +} + async function loadIndexedWorkspacePages(pool, userId) { const [rows] = await pool.query( `SELECT p.id, p.updated_at, p.source_asset_id, pv.source_snapshot_json @@ -62,12 +72,7 @@ async function loadIndexedWorkspacePages(pool, userId) { ); const byPath = new Map(); for (const row of rows) { - let snapshot = {}; - try { - snapshot = JSON.parse(row.source_snapshot_json ?? '{}'); - } catch { - snapshot = {}; - } + const snapshot = parseJsonColumn(row.source_snapshot_json); const relativePath = normalizePublicHtmlPath(snapshot.relative_path); if (relativePath) { byPath.set(relativePath, { diff --git a/mindspace-pages.mjs b/mindspace-pages.mjs index b1fbefb..411671c 100644 --- a/mindspace-pages.mjs +++ b/mindspace-pages.mjs @@ -29,6 +29,16 @@ function asNumber(value) { return Number(value ?? 0); } +export function parseJsonColumn(value, fallback = {}) { + if (value == null || value === '') return { ...fallback }; + if (typeof value === 'object' && !Buffer.isBuffer(value)) return value; + try { + return JSON.parse(String(value)); + } catch { + return { ...fallback }; + } +} + function pageError(message, code, details) { return Object.assign(new Error(message), { code, details }); } @@ -730,7 +740,7 @@ export function createPageService(pool, options = {}) { ); let snapshot = {}; try { - snapshot = JSON.parse(rows[0]?.source_snapshot_json ?? '{}'); + snapshot = parseJsonColumn(rows[0]?.source_snapshot_json); } catch { snapshot = {}; } @@ -1084,7 +1094,7 @@ export function createPageService(pool, options = {}) { LIMIT 1`, [pageId, userId], ); - const snapshot = JSON.parse(rows[0]?.source_snapshot_json ?? '{}'); + const snapshot = parseJsonColumn(rows[0]?.source_snapshot_json); workspaceHtmlRelativePath = snapshot.relative_path ?? null; } catch { workspaceHtmlRelativePath = null; @@ -1166,7 +1176,10 @@ export function createPageService(pool, options = {}) { publishDir: workspacePublishDir, htmlRelativePath: workspaceHtmlRelativePath, html: page.content ?? '', - }).catch(() => ({ removed: [], skipped: [] })); + }).catch((error) => { + console.warn('[MindSpace] workspace purge failed:', error?.message ?? error); + return { removed: [], skipped: [] }; + }); return { ...result, workspaceFilesRemoved: purgeResult.removed ?? [] }; }; @@ -1190,7 +1203,7 @@ export function createPageService(pool, options = {}) { const content = await fs.readFile(storagePath, 'utf8'); let snapshot = {}; try { - snapshot = JSON.parse(row.source_snapshot_json ?? '{}'); + snapshot = parseJsonColumn(row.source_snapshot_json); } catch { snapshot = {}; } @@ -1375,6 +1388,7 @@ export const pageInternals = { escapeHtml, normalizePageInput, normalizeListPageFilters, + parseJsonColumn, renderContent, renderPreviewHtml, renderHtmlPreview, diff --git a/mindspace-pages.test.mjs b/mindspace-pages.test.mjs index 5404dbb..6801a80 100644 --- a/mindspace-pages.test.mjs +++ b/mindspace-pages.test.mjs @@ -2,6 +2,16 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { pageInternals } from './mindspace-pages.mjs'; +test('parseJsonColumn accepts mysql json objects and strings', () => { + const objectValue = { relative_path: 'public/demo.html', auto_synced: true }; + assert.deepEqual(pageInternals.parseJsonColumn(objectValue), objectValue); + assert.deepEqual( + pageInternals.parseJsonColumn(JSON.stringify(objectValue)), + objectValue, + ); + assert.deepEqual(pageInternals.parseJsonColumn(null, { ok: true }), { ok: true }); +}); + test('normalizePageInput trims values and constrains templates', () => { const result = pageInternals.normalizePageInput({ title: ' 页面标题 ',