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
@@ -1,3 +1,5 @@
import { listAchievementPagesForUser } from '../mindspace-page-achievement-list.mjs';
function assertRouter(api) {
if (
!api ||
@@ -72,6 +74,7 @@ export function attachPortalMindSpacePageCoreRoutes(
getMindSpacePageLiveEdit = () => null,
getMindSpacePageEditSession = () => null,
getUserAuth = () => null,
getAuthPool = () => null,
syncUserGeneratedPages,
ownsAgentSession,
ensureMindSpaceEnabled,
@@ -161,6 +164,35 @@ export function attachPortalMindSpacePageCoreRoutes(
}
});
api.get('/mindspace/v1/pages/achievements', async (req, res) => {
const pages = getMindSpacePages();
if (!pages) {
return res.status(503).json({ message: 'MindSpace 未启用' });
}
try {
schedulePageListSync(req.currentUser.id);
const result = await listAchievementPagesForUser({
pages,
userId: req.currentUser.id,
query: req.query,
pool: getAuthPool?.() ?? null,
logger,
});
return res.json({
data: result.items,
page: {
total: result.total,
limit: result.limit,
offset: result.offset,
has_more: result.hasMore,
next_cursor: null,
},
});
} catch (error) {
return handleMindSpaceError(res, req, error);
}
});
api.get('/mindspace/v1/pages/:pageId', async (req, res) => {
const pages = getMindSpacePages();
if (!pages) {
@@ -102,6 +102,7 @@ test('page core module preserves route inventory and order', () => {
assert.deepEqual([...api.routes.keys()], [
'POST /mindspace/v1/pages',
'GET /mindspace/v1/pages',
'GET /mindspace/v1/pages/achievements',
'GET /mindspace/v1/pages/:pageId',
'DELETE /mindspace/v1/pages/:pageId',
'GET /mindspace/v1/pages/:pageId/delete-preview',
@@ -522,3 +523,31 @@ test('page core routes preserve unavailable and mapped error responses', async (
assert.equal(failureRes.statusCode, 418);
assert.equal(failure.calls.routeErrors[0].error.message, 'list failed');
});
test('achievements route uses listPages and enriches stats before page detail route', async () => {
const api = createRouterRecorder();
const setup = createDependencies({
getMindSpacePages: () => ({
async listPages(userId, input) {
return {
items: [{ id: 'page-1', title: 'Demo', viewCount: 3, clickCount: null, statsSource: 'publication' }],
total: 1,
limit: input.limit ?? 20,
offset: input.offset ?? 0,
hasMore: false,
};
},
}),
getAuthPool: () => null,
});
attachPortalMindSpacePageCoreRoutes(api, setup.dependencies);
const res = createResponseRecorder();
await api.routes.get('GET /mindspace/v1/pages/achievements')(
createRequest({ query: { limit: '20', offset: '0' } }),
res,
);
assert.equal(res.statusCode, 200);
assert.equal(res.body.data[0].viewCount, 3);
assert.equal(res.body.page.total, 1);
});