448 lines
13 KiB
JavaScript
448 lines
13 KiB
JavaScript
import { suggestCoverMetaWithAi } from '../mindspace-cover-ai.mjs';
|
|
import { pageInternals } from '../mindspace-pages.mjs';
|
|
import { upsertPageDataPolicyIndex } from '../page-data-policy-index.mjs';
|
|
import { syncPageDataPolicyAccessMode } from '../page-data-publish-sync.mjs';
|
|
|
|
function assertRouter(api) {
|
|
if (
|
|
!api ||
|
|
typeof api.get !== 'function' ||
|
|
typeof api.post !== 'function'
|
|
) {
|
|
throw new Error(
|
|
'attachPortalMindSpacePagePublishRoutes requires an Express-compatible router',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function attachPortalMindSpacePagePublishRoutes(
|
|
api,
|
|
{
|
|
env = process.env,
|
|
getMindSpacePages = () => null,
|
|
getMindSpacePublications = () => null,
|
|
getMindSpaceAudit = () => null,
|
|
getAuthPool = () => null,
|
|
sendData,
|
|
handleMindSpaceError,
|
|
previewContentSecurityPolicy =
|
|
pageInternals.previewContentSecurityPolicy,
|
|
suggestCoverMetaWithAiFn = suggestCoverMetaWithAi,
|
|
syncPageDataPolicyAccessModeFn =
|
|
syncPageDataPolicyAccessMode,
|
|
upsertPageDataPolicyIndexFn = upsertPageDataPolicyIndex,
|
|
} = {},
|
|
) {
|
|
assertRouter(api);
|
|
if (
|
|
typeof sendData !== 'function' ||
|
|
typeof handleMindSpaceError !== 'function'
|
|
) {
|
|
throw new Error(
|
|
'attachPortalMindSpacePagePublishRoutes requires response dependencies',
|
|
);
|
|
}
|
|
|
|
api.get(
|
|
'/mindspace/v1/pages/:pageId/thumbnail',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const svg = await pages.renderThumbnail(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
);
|
|
res.set('Content-Type', 'image/svg+xml; charset=utf-8');
|
|
res.set('Cache-Control', 'private, max-age=300');
|
|
res.setHeader('X-Request-Id', req.requestId);
|
|
return res.send(svg);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/thumbnail/upload',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const result = await pages.uploadThumbnail(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
imageBase64: req.body?.image_base64,
|
|
mimeType: req.body?.mime_type,
|
|
title: req.body?.title,
|
|
summary: req.body?.summary,
|
|
html: req.body?.html,
|
|
},
|
|
);
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/thumbnail/regenerate',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const useAi = Boolean(req.body?.use_ai);
|
|
const result = await pages.regenerateThumbnail(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
html: req.body?.html,
|
|
title: req.body?.title,
|
|
summary: req.body?.summary,
|
|
useAi,
|
|
instruction: req.body?.instruction,
|
|
},
|
|
{
|
|
suggestCoverMeta: useAi
|
|
? (input) => {
|
|
const pool = getAuthPool();
|
|
if (!pool) {
|
|
throw Object.assign(
|
|
new Error('LLM 服务未就绪'),
|
|
{ code: 'llm_not_configured' },
|
|
);
|
|
}
|
|
return suggestCoverMetaWithAiFn(pool, {
|
|
...input,
|
|
encryptionKey:
|
|
env.H5_SETTINGS_ENCRYPTION_KEY ??
|
|
env.TKMIND_SERVER__SECRET_KEY,
|
|
});
|
|
}
|
|
: null,
|
|
},
|
|
);
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/rewrite-download-links',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const content = String(req.body?.content ?? '').trim();
|
|
if (!content) {
|
|
throw Object.assign(
|
|
new Error('缺少页面内容'),
|
|
{ code: 'invalid_page_input' },
|
|
);
|
|
}
|
|
const html = await pages.rewriteHtmlDownloadLinksForPage(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
content,
|
|
);
|
|
return sendData(res, req, { html });
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.get(
|
|
'/mindspace/v1/pages/:pageId/preview',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const { html, contentFormat } = await pages.renderPreview(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
);
|
|
res.set('Content-Type', 'text/html; charset=utf-8');
|
|
res.set(
|
|
'Content-Security-Policy',
|
|
previewContentSecurityPolicy(contentFormat),
|
|
);
|
|
res.set('Cache-Control', 'private, no-store');
|
|
res.set('X-Content-Type-Options', 'nosniff');
|
|
return res.send(html);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/preview-draft',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const { html, contentFormat } =
|
|
await pages.renderDraftPreview(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
title: req.body?.title,
|
|
summary: req.body?.summary,
|
|
content: req.body?.content,
|
|
templateId: req.body?.template_id,
|
|
},
|
|
);
|
|
res.set('Content-Type', 'text/html; charset=utf-8');
|
|
res.set(
|
|
'Content-Security-Policy',
|
|
previewContentSecurityPolicy(contentFormat),
|
|
);
|
|
res.set('Cache-Control', 'private, no-store');
|
|
res.set('X-Content-Type-Options', 'nosniff');
|
|
return res.send(html);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/publish-check',
|
|
async (req, res) => {
|
|
const publications = getMindSpacePublications();
|
|
if (!publications) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const result = await publications.check(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
pageVersionId: req.body?.page_version_id,
|
|
accessMode: req.body?.access_mode,
|
|
urlSlug: req.body?.url_slug,
|
|
password: req.body?.password,
|
|
expiresAt: req.body?.expires_at,
|
|
},
|
|
);
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
async function transformPage(req, res, {
|
|
operation,
|
|
auditAction,
|
|
}) {
|
|
const pages = getMindSpacePages();
|
|
if (!pages) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const result = await operation(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
pageVersionId: req.body?.page_version_id,
|
|
expectedVersion: req.body?.expected_version,
|
|
title: req.body?.title,
|
|
summary: req.body?.summary,
|
|
content: req.body?.content,
|
|
},
|
|
);
|
|
await getMindSpaceAudit()?.write({
|
|
userId: req.currentUser.id,
|
|
action: auditAction,
|
|
objectType: 'page',
|
|
objectId: result.page.id,
|
|
ip: req.ip,
|
|
riskLevel: result.originalScan.riskLevel,
|
|
});
|
|
return sendData(res, req, result);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
}
|
|
|
|
api.post('/mindspace/v1/pages/:pageId/redact', (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
return transformPage(req, res, {
|
|
operation: pages?.redactPage.bind(pages),
|
|
auditAction: 'page.redact',
|
|
});
|
|
});
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/publish-fix',
|
|
(req, res) => {
|
|
const pages = getMindSpacePages();
|
|
return transformPage(req, res, {
|
|
operation:
|
|
pages?.localizePrivateResources.bind(pages),
|
|
auditAction: 'page.publish_fix',
|
|
});
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/redacted-copy',
|
|
(req, res) => {
|
|
const pages = getMindSpacePages();
|
|
return transformPage(req, res, {
|
|
operation: pages?.redactPage.bind(pages),
|
|
auditAction: 'page.redact',
|
|
});
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/:pageId/publish',
|
|
async (req, res) => {
|
|
const publications = getMindSpacePublications();
|
|
if (!publications) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const publication = await publications.publish(
|
|
req.currentUser.id,
|
|
req.params.pageId,
|
|
{
|
|
pageVersionId: req.body?.page_version_id,
|
|
accessMode: req.body?.access_mode,
|
|
urlSlug: req.body?.url_slug,
|
|
password: req.body?.password,
|
|
expiresAt: req.body?.expires_at,
|
|
acknowledgedFindingIds:
|
|
req.body?.acknowledged_finding_ids,
|
|
},
|
|
);
|
|
const workspaceRoot =
|
|
req.currentUser.workspaceRoot ?? null;
|
|
if (workspaceRoot) {
|
|
const syncedPolicy =
|
|
syncPageDataPolicyAccessModeFn(
|
|
workspaceRoot,
|
|
req.params.pageId,
|
|
publication.accessMode,
|
|
req.currentUser.id,
|
|
);
|
|
if (syncedPolicy) {
|
|
await upsertPageDataPolicyIndexFn(
|
|
getAuthPool(),
|
|
syncedPolicy,
|
|
).catch(() => null);
|
|
}
|
|
}
|
|
await getMindSpaceAudit()?.write({
|
|
userId: req.currentUser.id,
|
|
action: 'page.publish',
|
|
objectType: 'publication',
|
|
objectId: publication.id,
|
|
ip: req.ip,
|
|
});
|
|
return sendData(res, req, publication, 201);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/publications/:publicationId/update-status',
|
|
async (req, res) => {
|
|
const publications = getMindSpacePublications();
|
|
if (!publications) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const publication =
|
|
await publications.updatePublicationStatus(
|
|
req.currentUser.id,
|
|
req.params.publicationId,
|
|
{
|
|
accessMode: req.body?.access_mode,
|
|
expiresAt: req.body?.expires_at,
|
|
},
|
|
);
|
|
await getMindSpaceAudit()?.write({
|
|
userId: req.currentUser.id,
|
|
action: 'publication.status_updated',
|
|
objectType: 'publication',
|
|
objectId: req.params.publicationId,
|
|
ip: req.ip,
|
|
});
|
|
return sendData(res, req, publication);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/publications/:publicationId/offline',
|
|
async (req, res) => {
|
|
const publications = getMindSpacePublications();
|
|
if (!publications) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const publication = await publications.offline(
|
|
req.currentUser.id,
|
|
req.params.publicationId,
|
|
);
|
|
await getMindSpaceAudit()?.write({
|
|
userId: req.currentUser.id,
|
|
action: 'page.offline',
|
|
objectType: 'publication',
|
|
objectId: req.params.publicationId,
|
|
ip: req.ip,
|
|
});
|
|
return sendData(res, req, publication);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.get(
|
|
'/mindspace/v1/publications/:publicationId/stats',
|
|
async (req, res) => {
|
|
const publications = getMindSpacePublications();
|
|
if (!publications) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
return sendData(
|
|
res,
|
|
req,
|
|
await publications.getStats(
|
|
req.currentUser.id,
|
|
req.params.publicationId,
|
|
),
|
|
);
|
|
} catch (error) {
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
}
|