333 lines
9.3 KiB
JavaScript
333 lines
9.3 KiB
JavaScript
import crypto from 'node:crypto';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { inlinePrivateAssetsInHtml } from '../mindspace-pages.mjs';
|
|
import {
|
|
getQuickPlazaFromPublicHtmlStatus,
|
|
quickPlazaFromChat,
|
|
quickPlazaFromPublicHtml,
|
|
} from '../mindspace-chat-plaza.mjs';
|
|
import { rewriteWorkspacePublicAssetReferences } from '../mindspace-publications.mjs';
|
|
import {
|
|
buildMindSpacePublicUrlForUser,
|
|
resolveMindSpaceRuntimeConfig,
|
|
resolveMindSpaceUserPublishDir,
|
|
} from '../mindspace-runtime-config.mjs';
|
|
import { PUBLIC_ZONE_DIR } from '../user-publish.mjs';
|
|
|
|
function assertRouter(api) {
|
|
if (
|
|
!api ||
|
|
typeof api.get !== 'function' ||
|
|
typeof api.post !== 'function'
|
|
) {
|
|
throw new Error(
|
|
'attachPortalMindSpaceChatShareRoutes requires an Express-compatible router',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function attachPortalMindSpaceChatShareRoutes(
|
|
api,
|
|
{
|
|
h5Root = '',
|
|
env = process.env,
|
|
getMindSpacePages = () => null,
|
|
getMindSpacePublications = () => null,
|
|
getPlazaPosts = () => null,
|
|
getAuthPool = () => null,
|
|
resolveChatSaveBundle,
|
|
resolvePlazaPostUrl,
|
|
mapPlazaError = () => 500,
|
|
sendData = (res, _req, data, status = 200) =>
|
|
res.status(status).json({ data }),
|
|
sendError = (res, _req, status, code, message) =>
|
|
res.status(status).json({ error: { code, message } }),
|
|
handlePlazaError = (_res, _req, error) => {
|
|
throw error;
|
|
},
|
|
handleMindSpaceError = (_res, _req, error) => {
|
|
throw error;
|
|
},
|
|
resolveMindSpaceRuntimeConfigFn = resolveMindSpaceRuntimeConfig,
|
|
inlinePrivateAssetsInHtmlFn = inlinePrivateAssetsInHtml,
|
|
resolveMindSpaceUserPublishDirFn = resolveMindSpaceUserPublishDir,
|
|
rewriteWorkspacePublicAssetReferencesFn =
|
|
rewriteWorkspacePublicAssetReferences,
|
|
buildMindSpacePublicUrlForUserFn =
|
|
buildMindSpacePublicUrlForUser,
|
|
quickPlazaFromChatFn = quickPlazaFromChat,
|
|
getQuickPlazaFromPublicHtmlStatusFn =
|
|
getQuickPlazaFromPublicHtmlStatus,
|
|
quickPlazaFromPublicHtmlFn = quickPlazaFromPublicHtml,
|
|
mkdir = fs.mkdir,
|
|
writeFile = fs.writeFile,
|
|
randomUUID = crypto.randomUUID,
|
|
now = Date.now,
|
|
logger = console,
|
|
} = {},
|
|
) {
|
|
assertRouter(api);
|
|
if (
|
|
typeof resolveChatSaveBundle !== 'function' ||
|
|
typeof resolvePlazaPostUrl !== 'function'
|
|
) {
|
|
throw new Error(
|
|
'attachPortalMindSpaceChatShareRoutes requires chat and Plaza resolvers',
|
|
);
|
|
}
|
|
|
|
api.post('/mindspace/v1/pages/quick-share-from-chat', async (req, res) => {
|
|
if (!getMindSpacePages()) {
|
|
return res.status(503).json({ message: 'MindSpace 未启用' });
|
|
}
|
|
try {
|
|
const bundle = await resolveChatSaveBundle(
|
|
req.currentUser,
|
|
h5Root,
|
|
req.body,
|
|
);
|
|
logger.log(
|
|
'[quick-share] bundle resolved, hasHtml:',
|
|
Boolean(bundle.resolvedHtml),
|
|
);
|
|
if (!bundle.resolvedHtml) {
|
|
throw Object.assign(new Error('无法读取链接页面内容'), {
|
|
code: 'static_page_not_found',
|
|
});
|
|
}
|
|
|
|
const { storageRoot } = resolveMindSpaceRuntimeConfigFn(
|
|
h5Root,
|
|
env,
|
|
);
|
|
const { html: localizedHtml } =
|
|
await inlinePrivateAssetsInHtmlFn(
|
|
getAuthPool(),
|
|
storageRoot,
|
|
req.currentUser.id,
|
|
bundle.resolvedHtml.content,
|
|
);
|
|
|
|
const publishDir = resolveMindSpaceUserPublishDirFn(
|
|
h5Root,
|
|
req.currentUser,
|
|
);
|
|
const sharedDir = path.join(
|
|
publishDir,
|
|
PUBLIC_ZONE_DIR,
|
|
'shared',
|
|
);
|
|
await mkdir(sharedDir, { recursive: true });
|
|
|
|
const basename = bundle.resolvedHtml.filename.replace(
|
|
/\.html$/i,
|
|
'',
|
|
);
|
|
const filename =
|
|
`${basename}-${randomUUID().slice(0, 8)}.html`;
|
|
const sharedRelativePath =
|
|
`${PUBLIC_ZONE_DIR}/shared/${filename}`;
|
|
const sharedHtml =
|
|
rewriteWorkspacePublicAssetReferencesFn(
|
|
localizedHtml,
|
|
sharedRelativePath,
|
|
);
|
|
const destPath = path.join(sharedDir, filename);
|
|
await writeFile(destPath, sharedHtml, 'utf8');
|
|
|
|
const publicUrl = buildMindSpacePublicUrlForUserFn({
|
|
h5Root,
|
|
env,
|
|
user: req.currentUser.id,
|
|
relativePath: sharedRelativePath,
|
|
});
|
|
|
|
return res.status(201).json({
|
|
data: {
|
|
publicUrl,
|
|
filename,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error('[quick-share] error:', error);
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
});
|
|
|
|
api.post('/mindspace/v1/pages/quick-plaza-from-chat', async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
const publications = getMindSpacePublications();
|
|
const plazaPosts = getPlazaPosts();
|
|
if (!pages || !publications || !plazaPosts) {
|
|
return sendError(
|
|
res,
|
|
req,
|
|
503,
|
|
'plaza_unavailable',
|
|
'Plaza 或 MindSpace 未启用',
|
|
);
|
|
}
|
|
const startedAt = now();
|
|
try {
|
|
const bundle = await resolveChatSaveBundle(
|
|
req.currentUser,
|
|
h5Root,
|
|
req.body,
|
|
);
|
|
const publishDir = resolveMindSpaceUserPublishDirFn(
|
|
h5Root,
|
|
req.currentUser,
|
|
);
|
|
const result = await quickPlazaFromChatFn({
|
|
user: req.currentUser,
|
|
h5Root,
|
|
bundle,
|
|
body: req.body ?? {},
|
|
mindSpacePages: pages,
|
|
mindSpacePublications: publications,
|
|
plazaPosts,
|
|
publishDir,
|
|
});
|
|
logger.info('[quick-plaza] ok', {
|
|
ms: now() - startedAt,
|
|
pageId: result.pageId,
|
|
publicationId: result.publicationId,
|
|
postId: result.post?.id,
|
|
});
|
|
return sendData(res, req, result, 201);
|
|
} catch (error) {
|
|
logger.error('[quick-plaza] error:', {
|
|
ms: now() - startedAt,
|
|
error,
|
|
});
|
|
if (error?.code && mapPlazaError(error) !== 500) {
|
|
return handlePlazaError(res, req, error);
|
|
}
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
});
|
|
|
|
api.get(
|
|
'/mindspace/v1/pages/quick-plaza-from-public-html/status',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
const publications = getMindSpacePublications();
|
|
const plazaPosts = getPlazaPosts();
|
|
if (!pages || !publications || !plazaPosts) {
|
|
return sendError(
|
|
res,
|
|
req,
|
|
503,
|
|
'plaza_unavailable',
|
|
'Plaza 或 MindSpace 未启用',
|
|
);
|
|
}
|
|
try {
|
|
const relativePath = String(
|
|
req.query?.relative_path ??
|
|
req.query?.relativePath ??
|
|
'',
|
|
).trim();
|
|
const result =
|
|
await getQuickPlazaFromPublicHtmlStatusFn({
|
|
user: req.currentUser,
|
|
relativePath,
|
|
mindSpacePages: pages,
|
|
mindSpacePublications: publications,
|
|
plazaPosts,
|
|
});
|
|
const postId = result.post?.id;
|
|
return sendData(res, req, {
|
|
...result,
|
|
plaza_url: postId
|
|
? resolvePlazaPostUrl(postId, req)
|
|
: null,
|
|
});
|
|
} catch (error) {
|
|
if (error?.code && mapPlazaError(error) !== 500) {
|
|
return handlePlazaError(res, req, error);
|
|
}
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
|
|
api.post(
|
|
'/mindspace/v1/pages/quick-plaza-from-public-html',
|
|
async (req, res) => {
|
|
const pages = getMindSpacePages();
|
|
const publications = getMindSpacePublications();
|
|
const plazaPosts = getPlazaPosts();
|
|
if (!pages || !publications || !plazaPosts) {
|
|
return sendError(
|
|
res,
|
|
req,
|
|
503,
|
|
'plaza_unavailable',
|
|
'Plaza 或 MindSpace 未启用',
|
|
);
|
|
}
|
|
const startedAt = now();
|
|
try {
|
|
const relativePath = String(
|
|
req.body?.relative_path ??
|
|
req.body?.relativePath ??
|
|
'',
|
|
).trim();
|
|
const publishDir = resolveMindSpaceUserPublishDirFn(
|
|
h5Root,
|
|
req.currentUser,
|
|
);
|
|
const result = await quickPlazaFromPublicHtmlFn({
|
|
user: req.currentUser,
|
|
relativePath,
|
|
mindSpacePages: pages,
|
|
mindSpacePublications: publications,
|
|
plazaPosts,
|
|
publishDir,
|
|
});
|
|
const plazaUrl = resolvePlazaPostUrl(
|
|
result.post.id,
|
|
req,
|
|
);
|
|
logger.info('[quick-plaza-public-html] ok', {
|
|
ms: now() - startedAt,
|
|
pageId: result.pageId,
|
|
publicationId: result.publicationId,
|
|
postId: result.post?.id,
|
|
relativePath,
|
|
});
|
|
return sendData(
|
|
res,
|
|
req,
|
|
{
|
|
...result,
|
|
plaza_url: plazaUrl,
|
|
},
|
|
201,
|
|
);
|
|
} catch (error) {
|
|
logger.error('[quick-plaza-public-html] error:', {
|
|
ms: now() - startedAt,
|
|
error,
|
|
});
|
|
if (error?.code === 'ALREADY_PUBLISHED') {
|
|
const postId =
|
|
error?.details?.post_id ?? error?.details?.postId;
|
|
if (postId) {
|
|
error.details = {
|
|
...(error.details ?? {}),
|
|
plaza_url: resolvePlazaPostUrl(postId, req),
|
|
};
|
|
}
|
|
}
|
|
if (error?.code && mapPlazaError(error) !== 500) {
|
|
return handlePlazaError(res, req, error);
|
|
}
|
|
return handleMindSpaceError(res, req, error);
|
|
}
|
|
},
|
|
);
|
|
}
|