feat: release chat and mindspace UI updates
This commit is contained in:
@@ -537,6 +537,22 @@ export function createPageService(pool, options = {}) {
|
||||
return rows[0] ? pageResponse(rows[0]) : null;
|
||||
};
|
||||
|
||||
const findPageBySourceMessage = async (userId, sessionId, messageId) => {
|
||||
if (!sessionId || !messageId) return null;
|
||||
const [rows] = await pool.query(
|
||||
`SELECT p.*, c.category_code, pv.version_no, pr.access_mode AS pub_access_mode, pr.public_url AS pub_public_url
|
||||
FROM h5_page_records p
|
||||
JOIN h5_space_categories c ON c.id = p.category_id AND c.user_id = p.user_id
|
||||
LEFT JOIN h5_page_versions pv ON pv.id = p.current_version_id
|
||||
LEFT JOIN h5_publish_records pr ON pr.id = p.current_publish_id AND pr.status = 'online'
|
||||
WHERE p.user_id = ? AND p.source_session_id = ? AND p.source_message_id = ? AND p.status <> 'deleted'
|
||||
ORDER BY p.updated_at DESC
|
||||
LIMIT 1`,
|
||||
[userId, sessionId, messageId],
|
||||
);
|
||||
return rows[0] ? pageResponse(rows[0]) : null;
|
||||
};
|
||||
|
||||
const listPages = async (userId, filters = {}) => {
|
||||
const clauses = [`p.user_id = ?`, `p.status <> 'deleted'`];
|
||||
const params = [userId];
|
||||
@@ -1148,6 +1164,7 @@ export function createPageService(pool, options = {}) {
|
||||
createRedactedCopy: redactPage,
|
||||
listPages,
|
||||
findPageBySourceAsset,
|
||||
findPageBySourceMessage,
|
||||
getPage,
|
||||
getDeletePreview,
|
||||
deletePage,
|
||||
@@ -1211,6 +1228,50 @@ export const pageInternals = {
|
||||
buildPageDeleteSummary,
|
||||
};
|
||||
|
||||
export async function inlinePrivateAssetsInHtml(pool, storageRoot, userId, html) {
|
||||
const PATTERN =
|
||||
/(?:https?:\/\/[^/]+)?\/api\/mindspace\/v1\/assets\/([a-z0-9-]+)\/download(?:\?[^"'<>\\\s)]*)?/gi;
|
||||
const resolvedRoot = path.resolve(storageRoot);
|
||||
const absoluteStoragePath = (key) => {
|
||||
const resolved = path.resolve(resolvedRoot, key);
|
||||
if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${path.sep}`)) {
|
||||
throw Object.assign(new Error('路径越界'), { code: 'invalid_storage_path' });
|
||||
}
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const matches = [...String(html).matchAll(PATTERN)];
|
||||
if (matches.length === 0) return { html, count: 0 };
|
||||
|
||||
const assetIds = [...new Set(matches.map((m) => m[1]).filter(Boolean))];
|
||||
const [assets] = await pool.query(
|
||||
`SELECT a.id, a.mime_type, a.original_filename, v.storage_key
|
||||
FROM h5_assets a
|
||||
JOIN h5_asset_versions v ON v.id = a.current_version_id
|
||||
WHERE a.user_id = ? AND a.id IN (?) AND a.mime_type LIKE 'image/%'
|
||||
AND a.status <> 'deleted'`,
|
||||
[userId, assetIds],
|
||||
);
|
||||
const dataUriMap = new Map();
|
||||
for (const asset of assets) {
|
||||
try {
|
||||
const buffer = await fs.readFile(absoluteStoragePath(asset.storage_key));
|
||||
dataUriMap.set(asset.id, `data:${asset.mime_type};base64,${buffer.toString('base64')}`);
|
||||
} catch {
|
||||
// skip unreadable assets
|
||||
}
|
||||
}
|
||||
|
||||
let count = 0;
|
||||
const result = String(html).replace(PATTERN, (_match, assetId) => {
|
||||
const uri = dataUriMap.get(assetId);
|
||||
if (!uri) return '';
|
||||
count += 1;
|
||||
return uri;
|
||||
});
|
||||
return { html: result, count };
|
||||
}
|
||||
|
||||
export function buildPageDeleteSummary(preview) {
|
||||
const lines = [
|
||||
`页面「${preview.page.title}」及其 ${preview.page.versionCount} 个版本`,
|
||||
|
||||
Reference in New Issue
Block a user