Files
memind/server/portal-seo-discovery-routes.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
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>
2026-08-10 08:06:12 +08:00

85 lines
2.9 KiB
JavaScript

function assertRouter(api) {
if (!api || typeof api.get !== 'function') {
throw new Error('attachPortalSeoDiscoveryRoutes requires an Express-compatible router');
}
}
export function attachPortalSeoDiscoveryRoutes(
app,
{
getAuthPool = () => null,
getMindSpaceConfig = async () => ({ seoGeo: { enabled: false } }),
getSeoDiscoveryService = () => null,
resolveRequestOrigin = () => '',
} = {},
) {
assertRouter(app);
app.get('/robots.txt', async (req, res) => {
const config = await getMindSpaceConfig();
const seoGeo = config?.seoGeo ?? {};
if (!seoGeo.enabled || !seoGeo.seo?.robotsTxt) {
return res.type('text/plain; charset=utf-8').send('User-agent: *\nDisallow: /\n');
}
const service = getSeoDiscoveryService();
if (!service) {
return res.status(503).type('text/plain; charset=utf-8').send('SEO discovery unavailable\n');
}
const origin = resolveRequestOrigin(req);
return res
.type('text/plain; charset=utf-8')
.set('Cache-Control', 'public, max-age=300')
.send(
service.renderRobotsTxt({
origin,
sitemapEnabled: Boolean(seoGeo.seo?.sitemap),
llmsTxtEnabled: Boolean(seoGeo.geo?.llmsTxt),
}),
);
});
app.get('/sitemap.xml', async (req, res) => {
const config = await getMindSpaceConfig();
const seoGeo = config?.seoGeo ?? {};
if (!seoGeo.enabled || !seoGeo.seo?.enabled || !seoGeo.seo?.sitemap) {
return res.status(404).type('text/plain; charset=utf-8').send('Not Found\n');
}
const pool = getAuthPool();
const service = getSeoDiscoveryService();
if (!pool || !service) {
return res.status(503).type('text/plain; charset=utf-8').send('SEO discovery unavailable\n');
}
const origin = resolveRequestOrigin(req);
const entries = await service.listIndexablePublications(pool, {
limit: req.query.limit,
offset: req.query.offset,
});
return res
.type('application/xml; charset=utf-8')
.set('Cache-Control', 'public, max-age=300')
.send(service.renderSitemapXml(entries, { origin }));
});
app.get('/llms.txt', async (req, res) => {
const config = await getMindSpaceConfig();
const seoGeo = config?.seoGeo ?? {};
if (!seoGeo.enabled || !seoGeo.geo?.enabled || !seoGeo.geo?.llmsTxt) {
return res.status(404).type('text/plain; charset=utf-8').send('Not Found\n');
}
const pool = getAuthPool();
const service = getSeoDiscoveryService();
if (!pool || !service) {
return res.status(503).type('text/plain; charset=utf-8').send('GEO discovery unavailable\n');
}
const origin = resolveRequestOrigin(req);
const entries = await service.listIndexablePublications(pool, {
limit: req.query.limit,
offset: req.query.offset,
});
return res
.type('text/plain; charset=utf-8')
.set('Cache-Control', 'public, max-age=300')
.send(service.renderLlmsTxt(entries, { origin }));
});
}