1ca06e58e7
Memind CI / Test, build, and release guards (push) Successful in 8m42s
Wait for HTML materialization before releasing delivery contracts, pass verified MindSpace URLs through proactive WeChat sends, resend on reconcile when pages land late, and report deferred customer-service delivery as unsent. Co-authored-by: Cursor <cursoragent@cursor.com>
110 lines
4.3 KiB
JavaScript
110 lines
4.3 KiB
JavaScript
import crypto from 'node:crypto';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
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 markPageDeliveryContractFailed({
|
|
pool,
|
|
userId,
|
|
relativePath,
|
|
failureReason = 'delivery_material_missing',
|
|
} = {}) {
|
|
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 = 'failed', failure_reason = ?, updated_at = ?
|
|
WHERE user_id = ? AND workspace_relative_path = ? AND status = 'preparing'`,
|
|
[String(failureReason ?? 'delivery_material_missing'), now, userId, workspaceRelativePath],
|
|
);
|
|
return Number(result?.affectedRows ?? 0) > 0;
|
|
}
|
|
|
|
export function scheduledTaskPublicHtmlExists(publishDir, relativePath) {
|
|
const workspaceRelativePath = normalizeDeliveryRelativePath(relativePath);
|
|
if (!publishDir || !workspaceRelativePath) return false;
|
|
return fs.existsSync(path.join(publishDir, workspaceRelativePath));
|
|
}
|
|
|
|
export async function releaseMaterializedPageDeliveryContracts({
|
|
pool,
|
|
userId,
|
|
relativePaths = [],
|
|
allowPgRequired = false,
|
|
publishDir = null,
|
|
} = {}) {
|
|
if (!pool || !userId) return [];
|
|
const released = [];
|
|
for (const rawPath of relativePaths) {
|
|
const workspaceRelativePath = normalizeDeliveryRelativePath(rawPath);
|
|
if (!workspaceRelativePath) continue;
|
|
if (publishDir && !scheduledTaskPublicHtmlExists(publishDir, 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;
|
|
}
|