feat(mindspace): add achievements showcase with paginated management
Memind CI / Test, build, and release guards (push) Successful in 3m28s

Ship the M成果 page with list/delete UX, fix deletePage for incomplete page records, route /space/achievements, and add M成果 under the WeChat M空间 menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-08-12 15:21:42 +08:00
parent e9cecf8733
commit 421e204711
21 changed files with 1405 additions and 29 deletions
+106
View File
@@ -88,6 +88,45 @@ test('normalizeListPageFilters clamps limit and offset', () => {
});
});
test('pageResponse includes publication metrics when publish row is present', () => {
const response = pageInternals.pageResponse(
{
id: 'page-1',
user_id: 'user-1',
category_id: 'cat-1',
category_code: 'public',
source_session_id: null,
source_message_id: null,
source_asset_id: null,
title: '成果页面',
summary: '摘要',
page_type: 'html',
template_id: 'static-html',
status: 'published',
visibility: 'public',
current_version_id: 'ver-1',
version_no: 3,
pub_access_mode: 'public',
pub_public_url: 'https://m.tkmind.cn/u/john/pages/demo',
pub_id: 'pub-1',
pub_status: 'online',
pub_view_count: 128,
pub_published_at: 1_700_000_000_000,
created_at: 1_699_000_000_000,
updated_at: 1_700_100_000_000,
},
{ includeContent: false },
);
assert.equal(response.publicationId, 'pub-1');
assert.equal(response.publicationStatus, 'online');
assert.equal(response.viewCount, 128);
assert.equal(response.clickCount, null);
assert.equal(response.statsSource, 'publication');
assert.equal(response.publishedAt, 1_700_000_000_000);
assert.equal(response.publicationUrl, 'https://m.tkmind.cn/u/john/pages/demo');
});
test('preview rendering escapes active HTML and emits a restrictive CSP', () => {
const html = pageInternals.renderPreviewHtml({
title: '<script>alert(1)</script>',
@@ -295,3 +334,70 @@ test('getPage falls back to workspace html when storage asset is missing', async
assert.equal(page.title, 'Page Demo');
assert.match(page.content, /<h1>Body<\/h1>/);
});
test('deletePage succeeds when page content asset chain is incomplete', async () => {
const userId = 'user-1';
const pageId = 'page-broken';
let deletedPage = false;
const conn = {
async beginTransaction() {},
async commit() {},
async rollback() {},
async release() {},
query: async (sql) => {
const normalized = String(sql);
if (normalized.includes('FOR UPDATE') && normalized.includes('h5_page_records')) {
return [[{ id: pageId, space_id: 'space-1', title: 'test', source_asset_id: null }]];
}
if (normalized.includes('FROM h5_page_versions pv')) {
return [[]];
}
if (normalized.includes('FROM h5_publish_records') && normalized.includes("status = 'online'")) {
return [[]];
}
if (normalized.includes('FROM h5_agent_jobs')) {
return [[{ job_count: 0 }]];
}
if (normalized.includes("SET status = 'deleted'") && normalized.includes('h5_page_records')) {
deletedPage = true;
return [{ affectedRows: 1 }];
}
if (normalized.includes('UPDATE h5_agent_jobs')) {
return [{ affectedRows: 0 }];
}
return [[]];
},
};
const pool = {
getConnection: async () => conn,
query: async (sql) => {
const normalized = String(sql);
if (
normalized.includes('FROM h5_page_records p') &&
normalized.includes('page_type')
) {
return [[{
id: pageId,
title: 'test',
page_type: 'markdown',
space_id: 'space-1',
source_asset_id: null,
}]];
}
if (normalized.includes('JOIN h5_asset_versions')) {
return [[]];
}
if (normalized.includes('source_snapshot_json')) {
return [[{ source_snapshot_json: null }]];
}
return [[]];
},
};
const pageService = createPageService(pool, { storageRoot: '/tmp/unused', h5Root: null });
const result = await pageService.deletePage(userId, pageId);
assert.equal(result.deleted, true);
assert.equal(result.pageId, pageId);
assert.equal(result.title, 'test');
assert.equal(deletedPage, true);
});