import { isPublicWorkspaceHtmlRelativePath, } from '../mindspace-runtime-config.mjs'; import { isPlazaEmbedRequest } from '../plaza-embed.mjs'; import { rasterizeThumbnailSvgToPng, } from '../mindspace-thumbnail-png.mjs'; function escapePublicHtml(value) { return String(value ?? '') .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } export function renderPublicationPasswordGate( action, ) { return ` 受保护页面

此页面受密码保护

请输入发布者提供的访问密码。密码不会写入链接或浏览器日志。

`; } export function renderPublicHomepage(data) { const cards = data.pages .map( (page) => `\
${escapePublicHtml(page.templateId)} ${Number(page.viewCount)} 次浏览

${escapePublicHtml(page.title)}

${escapePublicHtml(page.summary || '暂无摘要')}

${new Date(page.publishedAt).toLocaleDateString('zh-CN')} 打开页面
`, ) .join(''); return ` ${escapePublicHtml(data.owner.displayName)} · MindSpace

MindSpace Public

${escapePublicHtml(data.owner.displayName)}

这里展示 ${escapePublicHtml(data.owner.displayName)} 当前公开发布且在线的 MindSpace 页面。

${Number(data.pageCount)} 个公开页面 ${Number(data.totalViews)} 次累计浏览 /u/${escapePublicHtml(data.owner.slug)}
${ data.pages.length ? `
${cards}
` : '
这个主页还没有公开页面。稍后再来看看,或者直接访问作者分享给你的专属链接。
' }
`; } export function attachPortalPublicationRoutes({ app, urlencodedBody, userAuthReady = Promise.resolve(), h5Root, getAuthPool = () => null, getMindSpacePages = () => null, getMindSpacePublications = () => null, getWorkspacePublicationDelivery = () => null, getUserAuth = () => null, sendPublishedPage, isEmbedRequest = isPlazaEmbedRequest, isPublicWorkspaceHtml = isPublicWorkspaceHtmlRelativePath, rasterizeThumbnail = rasterizeThumbnailSvgToPng, } = {}) { if ( !app || typeof urlencodedBody !== 'function' || typeof sendPublishedPage !== 'function' ) { throw new Error( 'attachPortalPublicationRoutes requires route dependencies', ); } async function resolvePublishedRoute( req, res, password = null, ) { await userAuthReady; const mindSpacePublications = getMindSpacePublications(); if (!mindSpacePublications) { return res .status(503) .send('MindSpace 未启用'); } try { const userAuth = getUserAuth(); const viewer = req.userSession && userAuth ? await userAuth.getMe(req.userToken) : null; const result = await mindSpacePublications.resolvePublic( req.params.ownerSlug, req.params.urlSlug, viewer?.id, password, { userAgent: req.get('user-agent'), referrer: req.get('referer'), }, ); const embed = isEmbedRequest(req.query); const raw = String(req.query.view ?? '') .toLowerCase() === 'raw'; if ( !password && !embed && !raw && result.publication?.accessMode === 'public' && isPublicWorkspaceHtml( result.workspaceRelativePath, ) && result.workspacePublicUrl ) { return res.redirect( 301, result.workspacePublicUrl, ); } return await sendPublishedPage( req, res, result, { embed, raw, ownerSlug: req.params.ownerSlug, }, ); } catch (error) { if ( error?.code === 'publication_password_required' ) { return res .status(password ? 403 : 200) .send( renderPublicationPasswordGate( req.originalUrl, ), ); } if ( error?.code === 'publication_login_required' ) { return res .status(401) .send( '请先登录 TKMind 后再访问此页面', ); } if ( error?.code === 'publication_not_found' ) { return res .status(404) .send('页面不存在或已下线'); } return res .status(500) .send('页面加载失败'); } } app.use(async (req, res, next) => { const thumbnailMatch = /^\/u\/([^/]+)\/pages\/([^/]+)\.thumbnail\.png$/.exec( req.path, ); if (!thumbnailMatch) return next(); const ownerSlug = thumbnailMatch[1]; const urlSlug = thumbnailMatch[2]; await userAuthReady; const authPool = getAuthPool(); const mindSpacePages = getMindSpacePages(); if (!authPool || !mindSpacePages) { return res .status(503) .send('MindSpace 未启用'); } try { const [rows] = await authPool.query( `SELECT pr.user_id, pr.page_id FROM h5_publish_records pr JOIN h5_users u ON u.id = pr.user_id WHERE COALESCE(u.slug, u.username) = ? AND pr.url_slug = ? AND pr.status = 'online' ORDER BY pr.published_at DESC LIMIT 1`, [ownerSlug, urlSlug], ); const row = rows[0]; if (!row) { return res .status(404) .send('缩略图不存在'); } const svg = await mindSpacePages.renderThumbnail( row.user_id, row.page_id, ); res.set('Content-Type', 'image/png'); res.set( 'Cache-Control', 'public, max-age=300', ); return res.send( rasterizeThumbnail(svg), ); } catch { return res .status(500) .send('缩略图加载失败'); } }); app.get( '/u/:ownerSlug/pages/:urlSlug', async (req, res) => resolvePublishedRoute(req, res), ); app.use( '/u/:ownerSlug/public', async (req, res) => { await userAuthReady; const ownerSlug = String( req.params.ownerSlug ?? '', ) .trim() .toLowerCase(); if (!ownerSlug) { return res .status(404) .json({ message: '用户不存在' }); } const deliveryService = getWorkspacePublicationDelivery(); if ( !deliveryService || typeof deliveryService .readOwnerPublicAsset !== 'function' ) { return res .status(503) .send('MindSpace 未启用'); } try { const delivery = await deliveryService .readOwnerPublicAsset({ ownerSlug, requestPath: req.path, }); if ( delivery.action === 'forbidden' ) { return res .status(403) .json({ message: '禁止访问' }); } if ( delivery.action !== 'deliver' ) { return res .status(404) .json({ message: delivery.reason === 'missing_owner' ? '用户不存在' : '文件不存在', }); } res.type?.( delivery.servedName || 'application/octet-stream', ); if (delivery.cacheControl) { res.set( 'Cache-Control', delivery.cacheControl, ); } return res.send( Buffer.from( delivery.bodyBase64 || '', 'base64', ), ); } catch { return res .status(500) .send('文件加载失败'); } }, ); app.get( '/u/:ownerSlug', async (req, res) => { await userAuthReady; const mindSpacePublications = getMindSpacePublications(); if (!mindSpacePublications) { return res .status(503) .send('MindSpace 未启用'); } try { const data = await mindSpacePublications .getPublicHomepage( req.params.ownerSlug, ); res.set( 'Content-Type', 'text/html; charset=utf-8', ); res.set( 'Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; img-src data:; font-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'", ); res.set( 'Cache-Control', 'public, max-age=60', ); return res.send( renderPublicHomepage(data), ); } catch (error) { if ( error?.code === 'publication_not_found' ) { return res .status(404) .send('主页不存在'); } return res .status(500) .send('主页加载失败'); } }, ); app.post( '/u/:ownerSlug/pages/:urlSlug', urlencodedBody, async (req, res) => resolvePublishedRoute( req, res, req.body?.password, ), ); app.get('/s/:token', async (req, res) => { await userAuthReady; const mindSpacePublications = getMindSpacePublications(); if (!mindSpacePublications) { return res .status(503) .send('MindSpace 未启用'); } try { const userAuth = getUserAuth(); const viewer = req.userSession && userAuth ? await userAuth.getMe(req.userToken) : null; return await sendPublishedPage( req, res, await mindSpacePublications .resolvePrivateLink( req.params.token, viewer?.id, { userAgent: req.get('user-agent'), referrer: req.get('referer'), }, ), { embed: isEmbedRequest(req.query), raw: String(req.query.view ?? '') .toLowerCase() === 'raw', }, ); } catch (error) { if ( error?.code === 'publication_not_found' ) { return res .status(404) .send('页面不存在或已下线'); } return res .status(500) .send('页面加载失败'); } }); }