Files
memind/mindspace-delivery-contract.mjs
T
john 53b0d2c62f
Memind CI / Test, build, and release guards (push) Successful in 3m37s
fix(mindspace): release edited public pages and fix achievements dates
Finish now releases static HTML delivery contracts in a finally block so
re-edited pages are not stuck at HTTP 409, and M成果 groups pages by
Asia/Shanghai calendar dates to avoid duplicate day headings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 06:41:40 +08:00

80 lines
3.1 KiB
JavaScript

import crypto from 'node:crypto';
export function normalizeDeliveryRelativePath(value) {
const path = String(value ?? '').replace(/\\/g, '/').replace(/^\/+/, '');
if (!path.startsWith('public/') || !path.toLowerCase().endsWith('.html')) return null;
if (path.split('/').some((part) => !part || part === '.' || part === '..')) return null;
return path;
}
export async function preparePageDeliveryContract({ pool, userId, requestId, relativePath, pgRequired = false }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !requestId || !workspaceRelativePath) return null;
const now = Date.now();
const id = crypto.randomUUID();
await pool.query(
`INSERT INTO h5_page_delivery_contracts
(id, user_id, request_id, workspace_relative_path, data_mode, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'preparing', ?, ?)
ON DUPLICATE KEY UPDATE data_mode = VALUES(data_mode), status = 'preparing', failure_reason = NULL, updated_at = VALUES(updated_at)`,
[id, userId, requestId, workspaceRelativePath, pgRequired ? 'pg_required' : 'static', now, now],
);
return { id, userId, requestId, workspaceRelativePath, dataMode: pgRequired ? 'pg_required' : 'static', status: 'preparing' };
}
export async function getPageDeliveryContract({ pool, userId, relativePath }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !workspaceRelativePath) return null;
const [rows] = await pool.query(
`SELECT id, data_mode, status, failure_reason FROM h5_page_delivery_contracts
WHERE user_id = ? AND workspace_relative_path = ?
ORDER BY updated_at DESC LIMIT 1`,
[userId, workspaceRelativePath],
);
return rows?.[0] ?? null;
}
export async function markPageDeliveryContractReady({ pool, userId, relativePath }) {
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
if (!pool || !userId || !workspaceRelativePath) return false;
const now = Date.now();
const [result] = await pool.query(
`UPDATE h5_page_delivery_contracts
SET status = 'ready', ready_at = ?, failure_reason = NULL, updated_at = ?
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
[now, now, userId, workspaceRelativePath],
);
return Number(result?.affectedRows ?? 0) > 0;
}
export async function releaseMaterializedPageDeliveryContracts({
pool,
userId,
relativePaths = [],
allowPgRequired = false,
} = {}) {
if (!pool || !userId) return [];
const released = [];
for (const rawPath of relativePaths) {
const workspaceRelativePath = normalizeDeliveryRelativePath(rawPath);
if (!workspaceRelativePath) continue;
const contract = await getPageDeliveryContract({
pool,
userId,
relativePath: workspaceRelativePath,
});
if (!contract || contract.status === 'ready') continue;
if (contract.data_mode === 'pg_required' && !allowPgRequired) continue;
if (
await markPageDeliveryContractReady({
pool,
userId,
relativePath: workspaceRelativePath,
})
) {
released.push(workspaceRelativePath);
}
}
return released;
}