feat(mindspace): enforce PostgreSQL user data delivery

This commit is contained in:
john
2026-07-13 15:29:29 +08:00
parent b33c943b69
commit a6620fb719
57 changed files with 2779 additions and 164 deletions
+34 -7
View File
@@ -26,6 +26,7 @@ function mapDeliverableRows(rows) {
publicationId: row.publication_id ? String(row.publication_id) : null,
publicationStatus: row.publication_status ? String(row.publication_status) : null,
publicUrl: row.public_url ? String(row.public_url) : null,
workspaceRelativePath: row.workspace_relative_path ? String(row.workspace_relative_path) : null,
})).filter((row) => row.pageId);
const publicationCount = pages.filter((row) => row.publicationId).length;
return {
@@ -51,13 +52,16 @@ export function mergeDeliverableSummaries(...summaries) {
};
}
export async function detectSessionDeliverables(pool, userId, sessionId) {
export async function detectSessionDeliverables(pool, userId, sessionId, { sinceMs = null } = {}) {
if (!pool?.query || !userId || !sessionId) {
return { pageCount: 0, publicationCount: 0, pages: [] };
}
const sinceClause = sinceMs != null ? 'AND p.updated_at >= ?' : '';
const params = sinceMs != null ? [userId, sessionId, sinceMs] : [userId, sessionId];
const [rows] = await pool.query(
`SELECT p.id AS page_id,
p.title,
p.workspace_relative_path,
pr.id AS publication_id,
pr.status AS publication_status,
pr.public_url
@@ -68,22 +72,39 @@ export async function detectSessionDeliverables(pool, userId, sessionId) {
AND pr.status = 'online'
WHERE p.user_id = ?
AND p.source_session_id = ?
${sinceClause}
AND p.status <> 'deleted'
ORDER BY p.created_at ASC`,
[userId, sessionId],
params,
);
return mapDeliverableRows(rows);
}
export async function detectWorkspaceSyncedDeliverables(pool, userId, { sinceMs = null } = {}) {
export async function detectWorkspaceSyncedDeliverables(
pool,
userId,
{ sinceMs = null, onlyRelativePaths = null } = {},
) {
if (!pool?.query || !userId) {
return { pageCount: 0, publicationCount: 0, pages: [] };
}
const normalizedPaths = onlyRelativePaths == null
? null
: [...new Set(onlyRelativePaths.map((item) => String(item ?? '').trim()).filter(Boolean))];
if (normalizedPaths?.length === 0) {
return { pageCount: 0, publicationCount: 0, pages: [] };
}
const sinceClause = sinceMs != null ? 'AND p.updated_at >= ?' : '';
const params = sinceMs != null ? [userId, sinceMs] : [userId];
const pathClause = normalizedPaths == null
? ''
: `AND p.workspace_relative_path IN (${normalizedPaths.map(() => '?').join(', ')})`;
const params = [userId];
if (sinceMs != null) params.push(sinceMs);
if (normalizedPaths != null) params.push(...normalizedPaths);
const [rows] = await pool.query(
`SELECT p.id AS page_id,
p.title,
p.workspace_relative_path,
pr.id AS publication_id,
pr.status AS publication_status,
pr.public_url
@@ -96,6 +117,7 @@ export async function detectWorkspaceSyncedDeliverables(pool, userId, { sinceMs
WHERE p.user_id = ?
AND p.status <> 'deleted'
${sinceClause}
${pathClause}
AND (
p.workspace_relative_path LIKE 'public/%.html'
OR JSON_UNQUOTE(JSON_EXTRACT(pv.source_snapshot_json, '$.relative_path')) LIKE 'public/%.html'
@@ -116,15 +138,20 @@ export async function prepareAndDetectSessionDeliverables({
sessionId,
runStartedAtMs = null,
prepareDeliverables = null,
workspaceRelativePaths = null,
} = {}) {
let prepared = null;
if (typeof prepareDeliverables === 'function') {
await prepareDeliverables({ userId, sessionId }).catch(() => {});
prepared = await prepareDeliverables({ userId, sessionId }).catch(() => null);
}
const sinceMs = runStartedAtMs == null
? null
: Math.max(0, Number(runStartedAtMs) - WORKSPACE_DELIVERABLE_LOOKBACK_MS);
const bySession = await detectSessionDeliverables(pool, userId, sessionId);
const byWorkspace = await detectWorkspaceSyncedDeliverables(pool, userId, { sinceMs });
const bySession = await detectSessionDeliverables(pool, userId, sessionId, { sinceMs });
const byWorkspace = await detectWorkspaceSyncedDeliverables(pool, userId, {
sinceMs,
onlyRelativePaths: workspaceRelativePaths ?? prepared?.pageDataRelativePaths ?? null,
});
return mergeDeliverableSummaries(bySession, byWorkspace);
}