feat(mindspace): add WeChat MP config and one-click draft push on publish.
Let users bind their own official account credentials in M 配置 and push MindSpace pages to the WeChat draft box during or after publish, reusing the existing draft conversion pipeline. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
function assertRouter(api) {
|
||||
if (
|
||||
!api ||
|
||||
typeof api.get !== 'function' ||
|
||||
typeof api.post !== 'function' ||
|
||||
typeof api.put !== 'function' ||
|
||||
typeof api.delete !== 'function'
|
||||
) {
|
||||
throw new Error(
|
||||
'attachPortalMindSpaceWechatRoutes requires an Express-compatible router',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function attachPortalMindSpaceWechatRoutes(
|
||||
api,
|
||||
{
|
||||
getWechatMpConfig = () => null,
|
||||
getWechatPageDraft = () => null,
|
||||
sendData,
|
||||
handleMindSpaceError,
|
||||
} = {},
|
||||
) {
|
||||
assertRouter(api);
|
||||
if (typeof sendData !== 'function' || typeof handleMindSpaceError !== 'function') {
|
||||
throw new Error(
|
||||
'attachPortalMindSpaceWechatRoutes requires response dependencies',
|
||||
);
|
||||
}
|
||||
|
||||
api.get('/mindspace/v1/wechat-mp/config', async (req, res) => {
|
||||
const service = getWechatMpConfig();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号配置未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(res, req, await service.getConfig(req.currentUser.id));
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
api.put('/mindspace/v1/wechat-mp/config', async (req, res) => {
|
||||
const service = getWechatMpConfig();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号配置未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(
|
||||
res,
|
||||
req,
|
||||
await service.upsertConfig(req.currentUser.id, {
|
||||
appId: req.body?.app_id ?? req.body?.appId,
|
||||
appSecret: req.body?.app_secret ?? req.body?.appSecret,
|
||||
accountType: req.body?.account_type ?? req.body?.accountType,
|
||||
label: req.body?.label,
|
||||
author: req.body?.author,
|
||||
verify: req.body?.verify !== false,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
api.post('/mindspace/v1/wechat-mp/config/verify', async (req, res) => {
|
||||
const service = getWechatMpConfig();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号配置未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(res, req, await service.verifyConfig(req.currentUser.id));
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
api.delete('/mindspace/v1/wechat-mp/config', async (req, res) => {
|
||||
const service = getWechatMpConfig();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号配置未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(res, req, await service.deleteConfig(req.currentUser.id));
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
api.post('/mindspace/v1/pages/:pageId/wechat-draft', async (req, res) => {
|
||||
const service = getWechatPageDraft();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号草稿推送未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(
|
||||
res,
|
||||
req,
|
||||
await service.pushPageDraft(req.currentUser.id, req.params.pageId, {
|
||||
triggeredBy: req.currentUser.id,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
api.get('/mindspace/v1/pages/:pageId/wechat-draft/runs', async (req, res) => {
|
||||
const service = getWechatPageDraft();
|
||||
if (!service) {
|
||||
return res.status(503).json({ message: '公众号草稿推送未启用' });
|
||||
}
|
||||
try {
|
||||
return sendData(
|
||||
res,
|
||||
req,
|
||||
await service.listRuns(req.currentUser.id, {
|
||||
pageId: req.params.pageId,
|
||||
limit: req.query?.limit,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
return handleMindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { attachPortalMindSpaceWechatRoutes } from './portal-mindspace-wechat-routes.mjs';
|
||||
|
||||
function createRouterRecorder() {
|
||||
const routes = new Map();
|
||||
return {
|
||||
routes,
|
||||
get(path, handler) {
|
||||
routes.set(`GET ${path}`, handler);
|
||||
},
|
||||
post(path, handler) {
|
||||
routes.set(`POST ${path}`, handler);
|
||||
},
|
||||
put(path, handler) {
|
||||
routes.set(`PUT ${path}`, handler);
|
||||
},
|
||||
delete(path, handler) {
|
||||
routes.set(`DELETE ${path}`, handler);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createResponseRecorder() {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: undefined,
|
||||
status(code) {
|
||||
this.statusCode = code;
|
||||
return this;
|
||||
},
|
||||
json(body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createRequest(overrides = {}) {
|
||||
return {
|
||||
body: {},
|
||||
params: { pageId: 'page-1' },
|
||||
query: {},
|
||||
currentUser: { id: 'user-1' },
|
||||
requestId: 'request-1',
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test('wechat config routes delegate to services', async () => {
|
||||
const calls = [];
|
||||
const api = createRouterRecorder();
|
||||
attachPortalMindSpaceWechatRoutes(api, {
|
||||
getWechatMpConfig: () => ({
|
||||
async getConfig(userId) {
|
||||
calls.push(['getConfig', userId]);
|
||||
return { configured: true, appId: 'wx-test' };
|
||||
},
|
||||
async upsertConfig(userId, payload) {
|
||||
calls.push(['upsertConfig', userId, payload]);
|
||||
return { configured: true, appId: payload.appId };
|
||||
},
|
||||
async verifyConfig(userId) {
|
||||
calls.push(['verifyConfig', userId]);
|
||||
return { configured: true, verifiedAt: 1 };
|
||||
},
|
||||
async deleteConfig(userId) {
|
||||
calls.push(['deleteConfig', userId]);
|
||||
return { ok: true };
|
||||
},
|
||||
}),
|
||||
getWechatPageDraft: () => ({
|
||||
async pushPageDraft(userId, pageId) {
|
||||
calls.push(['pushPageDraft', userId, pageId]);
|
||||
return { ok: true, draftMediaId: 'draft-1' };
|
||||
},
|
||||
async listRuns(userId, options) {
|
||||
calls.push(['listRuns', userId, options]);
|
||||
return [];
|
||||
},
|
||||
}),
|
||||
sendData(res, req, data) {
|
||||
return res.status(200).json({ data });
|
||||
},
|
||||
handleMindSpaceError(res, req, error) {
|
||||
return res.status(400).json({ message: error.message });
|
||||
},
|
||||
});
|
||||
|
||||
const getRes = createResponseRecorder();
|
||||
await api.routes.get('GET /mindspace/v1/wechat-mp/config')(createRequest(), getRes);
|
||||
assert.deepEqual(calls[0], ['getConfig', 'user-1']);
|
||||
|
||||
const putRes = createResponseRecorder();
|
||||
await api.routes.get('PUT /mindspace/v1/wechat-mp/config')(
|
||||
createRequest({ body: { app_id: 'wx-new', app_secret: 'secret' } }),
|
||||
putRes,
|
||||
);
|
||||
assert.equal(calls.at(-1)[0], 'upsertConfig');
|
||||
|
||||
const pushRes = createResponseRecorder();
|
||||
await api.routes.get('POST /mindspace/v1/pages/:pageId/wechat-draft')(createRequest(), pushRes);
|
||||
assert.deepEqual(calls.at(-1), ['pushPageDraft', 'user-1', 'page-1']);
|
||||
});
|
||||
Reference in New Issue
Block a user