6b0c633a75
Add visitor roles, row-level scope, owner ops APIs, MySQL policy index, Turnstile captcha, browser client SDK, publish-panel dataset binding, acceptance tests, and usage documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
48 lines
1.8 KiB
JavaScript
48 lines
1.8 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const DEFAULT_MAX_LOG_BYTES = 2 * 1024 * 1024;
|
|
|
|
export function resolvePageDataLogDir(workspaceRoot) {
|
|
return path.join(workspaceRoot, '.mindspace', 'page-data-logs');
|
|
}
|
|
|
|
export function resolvePageDataLogPath(workspaceRoot, pageId) {
|
|
const safePageId = String(pageId ?? '').trim();
|
|
if (!safePageId || /[\\/]/.test(safePageId)) {
|
|
throw Object.assign(new Error('pageId 无效'), { code: 'invalid_page_id' });
|
|
}
|
|
return path.join(resolvePageDataLogDir(workspaceRoot), `${safePageId}.jsonl`);
|
|
}
|
|
|
|
export function appendPageDataLog(workspaceRoot, pageId, entry, { maxBytes = DEFAULT_MAX_LOG_BYTES } = {}) {
|
|
const logPath = resolvePageDataLogPath(workspaceRoot, pageId);
|
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
const line = `${JSON.stringify({ ...entry, at: entry.at ?? new Date().toISOString() })}\n`;
|
|
fs.appendFileSync(logPath, line, 'utf8');
|
|
if (maxBytes > 0 && fs.existsSync(logPath) && fs.statSync(logPath).size > maxBytes) {
|
|
const raw = fs.readFileSync(logPath, 'utf8').trim().split('\n');
|
|
const trimmed = raw.slice(-Math.max(50, Math.floor(raw.length / 2)));
|
|
fs.writeFileSync(logPath, `${trimmed.join('\n')}\n`, 'utf8');
|
|
}
|
|
return line;
|
|
}
|
|
|
|
export function listPageDataLogs(workspaceRoot, pageId, { limit = 100, offset = 0 } = {}) {
|
|
const logPath = resolvePageDataLogPath(workspaceRoot, pageId);
|
|
if (!fs.existsSync(logPath)) return [];
|
|
const lines = fs.readFileSync(logPath, 'utf8').trim().split('\n').filter(Boolean);
|
|
const parsed = lines
|
|
.map((line) => {
|
|
try {
|
|
return JSON.parse(line);
|
|
} catch {
|
|
return null;
|
|
}
|
|
})
|
|
.filter(Boolean);
|
|
const safeOffset = Math.max(0, Number(offset ?? 0));
|
|
const safeLimit = Math.min(Math.max(1, Number(limit ?? 100)), 500);
|
|
return parsed.slice(safeOffset, safeOffset + safeLimit);
|
|
}
|