243 lines
8.8 KiB
JavaScript
243 lines
8.8 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { slugFromPageTitle } from './mindspace-chat-plaza.mjs';
|
|
import { normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
|
|
import { htmlUsesPageDataApi } from './page-data-html-detect.mjs';
|
|
import { resolvePublishDir } from './user-publish.mjs';
|
|
|
|
function parseJsonColumn(value, fallback = {}) {
|
|
if (value == null || value === '') return { ...fallback };
|
|
if (typeof value === 'object' && !Buffer.isBuffer(value)) return value;
|
|
try {
|
|
return JSON.parse(String(value));
|
|
} catch {
|
|
return { ...fallback };
|
|
}
|
|
}
|
|
|
|
function isPublicWorkspaceHtmlPath(relativePath) {
|
|
const normalized = normalizeWorkspaceRelativePath(relativePath);
|
|
return Boolean(normalized?.startsWith('public/') && normalized.toLowerCase().endsWith('.html'));
|
|
}
|
|
|
|
function readWorkspaceHtmlContent(publishDir, relativePath) {
|
|
const normalized = normalizeWorkspaceRelativePath(relativePath);
|
|
if (!normalized || !publishDir) return '';
|
|
const absolutePath = path.join(publishDir, ...normalized.split('/'));
|
|
try {
|
|
return fs.readFileSync(absolutePath, 'utf8');
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function isPageDataWorkspaceHtml(publishDir, relativePath) {
|
|
const content = readWorkspaceHtmlContent(publishDir, relativePath);
|
|
return htmlUsesPageDataApi(content);
|
|
}
|
|
|
|
function filterWorkspacePagesByRelativePaths(rows, onlyRelativePaths) {
|
|
if (onlyRelativePaths == null) return rows;
|
|
const allowed = new Set(
|
|
[...onlyRelativePaths]
|
|
.map((relativePath) => normalizeWorkspaceRelativePath(relativePath))
|
|
.filter(Boolean),
|
|
);
|
|
return rows.filter((row) => {
|
|
const snapshot = parseJsonColumn(row.source_snapshot_json);
|
|
const relativePath = normalizeWorkspaceRelativePath(
|
|
row.workspace_relative_path ?? snapshot.relative_path ?? null,
|
|
);
|
|
return relativePath ? allowed.has(relativePath) : false;
|
|
});
|
|
}
|
|
|
|
export function createWorkspacePageDeliverService({
|
|
pool,
|
|
pageService,
|
|
publicationService,
|
|
pageSyncService,
|
|
pageDataEnsure = null,
|
|
h5Root = null,
|
|
storageRoot = null,
|
|
findPageByRelativePath = null,
|
|
logger = console,
|
|
} = {}) {
|
|
async function listUnpublishedAutoSyncedWorkspacePages(userId, { onlyRelativePaths = null } = {}) {
|
|
if (!pool || !userId) return [];
|
|
const [rows] = await pool.query(
|
|
`SELECT p.id AS page_id, p.title, p.current_version_id, p.workspace_relative_path,
|
|
pv.source_snapshot_json
|
|
FROM h5_page_records p
|
|
JOIN h5_page_versions pv ON pv.id = p.current_version_id
|
|
LEFT JOIN h5_publish_records pr
|
|
ON pr.page_id = p.id AND pr.user_id = p.user_id AND pr.status = 'online'
|
|
WHERE p.user_id = ?
|
|
AND p.status <> 'deleted'
|
|
AND pr.id IS NULL`,
|
|
[userId],
|
|
);
|
|
const candidates = (rows ?? []).filter((row) => {
|
|
const snapshot = parseJsonColumn(row.source_snapshot_json);
|
|
const relativePath = row.workspace_relative_path ?? snapshot.relative_path ?? null;
|
|
if (!isPublicWorkspaceHtmlPath(relativePath)) return false;
|
|
return snapshot.auto_synced === true || snapshot.content_mode === 'static_html';
|
|
});
|
|
return filterWorkspacePagesByRelativePaths(candidates, onlyRelativePaths);
|
|
}
|
|
|
|
async function listOnlineAutoSyncedWorkspacePages(userId, { onlyRelativePaths = null } = {}) {
|
|
if (!pool || !userId) return [];
|
|
const [rows] = await pool.query(
|
|
`SELECT p.id AS page_id, p.title, p.current_version_id, p.workspace_relative_path,
|
|
pv.source_snapshot_json
|
|
FROM h5_page_records p
|
|
JOIN h5_page_versions pv ON pv.id = p.current_version_id
|
|
JOIN h5_publish_records pr
|
|
ON pr.page_id = p.id AND pr.user_id = p.user_id AND pr.status = 'online'
|
|
WHERE p.user_id = ?
|
|
AND p.status <> 'deleted'`,
|
|
[userId],
|
|
);
|
|
const candidates = (rows ?? []).filter((row) => {
|
|
const snapshot = parseJsonColumn(row.source_snapshot_json);
|
|
const relativePath = row.workspace_relative_path ?? snapshot.relative_path ?? null;
|
|
if (!isPublicWorkspaceHtmlPath(relativePath)) return false;
|
|
return snapshot.auto_synced === true || snapshot.content_mode === 'static_html';
|
|
});
|
|
return filterWorkspacePagesByRelativePaths(candidates, onlyRelativePaths);
|
|
}
|
|
|
|
function resolveWorkspaceRoot(userId) {
|
|
if (!h5Root || !userId) return null;
|
|
return resolvePublishDir(h5Root, { id: userId });
|
|
}
|
|
|
|
async function ensurePageDataBindings(userId, { onlyRelativePaths = null } = {}) {
|
|
if (!pageDataEnsure?.ensurePageDataHtmlPagesBound || !pool || !userId) {
|
|
return { bound: [], skipped: [], errors: [] };
|
|
}
|
|
const workspaceRoot = resolveWorkspaceRoot(userId);
|
|
if (!workspaceRoot) {
|
|
return { bound: [], skipped: [], errors: [{ code: 'missing_workspace_root' }] };
|
|
}
|
|
return pageDataEnsure.ensurePageDataHtmlPagesBound({
|
|
pool,
|
|
h5Root,
|
|
storageRoot,
|
|
userId,
|
|
workspaceRoot,
|
|
findPageByRelativePath,
|
|
onlyRelativePaths,
|
|
logger,
|
|
});
|
|
}
|
|
|
|
async function refreshOnlineWorkspacePublications(userId, { onlyRelativePaths = null } = {}) {
|
|
if (!publicationService?.refreshOnlinePublicationHtml || !userId) {
|
|
return { refreshed: 0, skipped: 0, errors: [] };
|
|
}
|
|
const candidates = await listOnlineAutoSyncedWorkspacePages(userId, { onlyRelativePaths });
|
|
let refreshed = 0;
|
|
let skipped = 0;
|
|
const errors = [];
|
|
for (const candidate of candidates) {
|
|
try {
|
|
const result = await publicationService.refreshOnlinePublicationHtml(
|
|
userId,
|
|
candidate.page_id,
|
|
);
|
|
if (result) refreshed += 1;
|
|
else skipped += 1;
|
|
} catch (error) {
|
|
errors.push({
|
|
pageId: candidate.page_id,
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
logger?.warn?.(
|
|
`[MindSpace] refresh publication failed for page ${candidate.page_id}:`,
|
|
error instanceof Error ? error.message : error,
|
|
);
|
|
}
|
|
}
|
|
return { refreshed, skipped, errors };
|
|
}
|
|
|
|
async function ensureWorkspaceHtmlPublications(userId, { onlyRelativePaths = null } = {}) {
|
|
if (!publicationService?.publish || !pageService?.getPage || !userId) {
|
|
return { published: 0, skipped: 0, errors: [] };
|
|
}
|
|
const candidates = await listUnpublishedAutoSyncedWorkspacePages(userId, { onlyRelativePaths });
|
|
const publishDir = resolveWorkspaceRoot(userId);
|
|
let published = 0;
|
|
let skipped = 0;
|
|
const errors = [];
|
|
for (const candidate of candidates) {
|
|
try {
|
|
const snapshot = parseJsonColumn(candidate.source_snapshot_json);
|
|
const relativePath =
|
|
candidate.workspace_relative_path ?? snapshot.relative_path ?? null;
|
|
if (isPageDataWorkspaceHtml(publishDir, relativePath)) {
|
|
skipped += 1;
|
|
continue;
|
|
}
|
|
const page = await pageService.getPage(userId, candidate.page_id);
|
|
const current = await publicationService.getCurrent?.(userId, candidate.page_id);
|
|
if (current?.status === 'online') {
|
|
skipped += 1;
|
|
continue;
|
|
}
|
|
await publicationService.publish(userId, candidate.page_id, {
|
|
pageVersionId: page.currentVersionId,
|
|
accessMode: 'public',
|
|
urlSlug: slugFromPageTitle(page.title, candidate.page_id),
|
|
autoAcknowledgeFindings: true,
|
|
});
|
|
published += 1;
|
|
} catch (error) {
|
|
errors.push({
|
|
pageId: candidate.page_id,
|
|
message: error instanceof Error ? error.message : String(error),
|
|
});
|
|
logger?.warn?.(
|
|
`[MindSpace] auto publish failed for page ${candidate.page_id}:`,
|
|
error instanceof Error ? error.message : error,
|
|
);
|
|
}
|
|
}
|
|
return { published, skipped, errors };
|
|
}
|
|
|
|
async function syncAndDeliver(userId, { pageDataRelativePaths = null } = {}) {
|
|
let syncResult = { created: 0, updated: 0, skipped: 0 };
|
|
if (pageSyncService?.syncUserGeneratedPages) {
|
|
syncResult = await pageSyncService.syncUserGeneratedPages(userId, {
|
|
onlyRelativePaths: pageDataRelativePaths,
|
|
});
|
|
}
|
|
const pageDataBindResult = await ensurePageDataBindings(userId, {
|
|
onlyRelativePaths: pageDataRelativePaths,
|
|
});
|
|
const publishResult = await ensureWorkspaceHtmlPublications(userId, {
|
|
onlyRelativePaths: pageDataRelativePaths,
|
|
});
|
|
const refreshResult = await refreshOnlineWorkspacePublications(userId, {
|
|
onlyRelativePaths: pageDataRelativePaths,
|
|
});
|
|
return {
|
|
pageDataRelativePaths: pageDataRelativePaths == null ? null : [...pageDataRelativePaths],
|
|
sync: syncResult,
|
|
pageDataBind: pageDataBindResult,
|
|
publish: publishResult,
|
|
refresh: refreshResult,
|
|
};
|
|
}
|
|
|
|
return {
|
|
syncAndDeliver,
|
|
ensurePageDataBindings,
|
|
ensureWorkspaceHtmlPublications,
|
|
refreshOnlineWorkspacePublications,
|
|
};
|
|
}
|