fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config. Co-authored-by: Cursor <cursoragent@cursor.com>
126 lines
4.8 KiB
JavaScript
126 lines
4.8 KiB
JavaScript
function assertRouter(api) {
|
|
if (!api || typeof api.get !== 'function' || typeof api.post !== 'function') {
|
|
throw new Error('attachPortalTemplateCatalogRoutes requires an Express-compatible router');
|
|
}
|
|
}
|
|
|
|
export function attachPortalTemplateCatalogRoutes(
|
|
api,
|
|
{
|
|
getTemplateCatalog = () => null,
|
|
ensureMindSpaceEnabled = () => false,
|
|
sendData = (res, _req, data) => res.json({ data }),
|
|
sendError = (res, _req, status, code, message) =>
|
|
res.status(status).json({ error: { code, message } }),
|
|
} = {},
|
|
) {
|
|
assertRouter(api);
|
|
|
|
api.get('/mindspace/v1/template-catalog', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const items = await catalog.listCatalogForUser(req.currentUser.id);
|
|
return sendData(res, req, { items });
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '读取模板商城失败';
|
|
return sendError(res, req, 500, 'template_catalog_error', message);
|
|
}
|
|
});
|
|
|
|
api.get('/mindspace/v1/template-catalog/mine', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const items = await catalog.listMine(req.currentUser.id);
|
|
return sendData(res, req, { items });
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '读取已购模板失败';
|
|
return sendError(res, req, 500, 'template_catalog_error', message);
|
|
}
|
|
});
|
|
|
|
api.post('/mindspace/v1/template-catalog/:skillName/purchase', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const result = await catalog.purchaseWithBalance(req.currentUser.id, req.params.skillName);
|
|
if (!result.ok) {
|
|
if (result.code === 'INSUFFICIENT_BALANCE') {
|
|
return res.status(402).json({
|
|
error: {
|
|
code: result.code,
|
|
message: result.message,
|
|
},
|
|
details: {
|
|
balanceCents: result.balanceCents,
|
|
minRechargeCents: result.minRechargeCents,
|
|
suggestedTiers: result.suggestedTiers,
|
|
},
|
|
});
|
|
}
|
|
return sendError(res, req, 400, 'template_purchase_failed', result.message);
|
|
}
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '购买模板失败';
|
|
return sendError(res, req, 500, 'template_purchase_error', message);
|
|
}
|
|
});
|
|
|
|
api.post('/mindspace/v1/template-catalog/:skillName/favorite', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const result = await catalog.toggleTemplateFavorite(req.currentUser.id, req.params.skillName);
|
|
if (!result.ok) {
|
|
return sendError(res, req, 400, 'template_favorite_failed', result.message);
|
|
}
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '收藏操作失败';
|
|
return sendError(res, req, 500, 'template_favorite_error', message);
|
|
}
|
|
});
|
|
|
|
api.get('/mindspace/v1/template-catalog/:skillName/preview', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const result = await catalog.getTemplatePreviewHtml(req.params.skillName);
|
|
if (!result.ok) {
|
|
return sendError(res, req, 404, 'template_preview_not_found', result.message);
|
|
}
|
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
res.setHeader('Cache-Control', 'public, max-age=300');
|
|
return res.send(result.html);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '读取模板预览失败';
|
|
return sendError(res, req, 500, 'template_preview_error', message);
|
|
}
|
|
});
|
|
|
|
api.post('/mindspace/v1/template-catalog/:skillName/checkout', async (req, res) => {
|
|
const catalog = getTemplateCatalog();
|
|
if (!catalog || !ensureMindSpaceEnabled(res, req)) return;
|
|
try {
|
|
const payScene = ['native', 'h5', 'jsapi'].includes(req.body?.payScene)
|
|
? req.body.payScene
|
|
: 'native';
|
|
const result = await catalog.createWechatCheckout({
|
|
userId: req.currentUser.id,
|
|
skillName: req.params.skillName,
|
|
payScene,
|
|
clientIp: req.ip,
|
|
});
|
|
if (!result.ok) {
|
|
return sendError(res, req, 400, 'template_checkout_failed', result.message);
|
|
}
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : '创建模板支付订单失败';
|
|
return sendError(res, req, 500, 'template_checkout_error', message);
|
|
}
|
|
});
|
|
}
|