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; }