Files
memind/page-data-policy-index.mjs
T
john 6b0c633a75 feat(page-data): complete Phase 4-5, ops UI, and publish integration
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>
2026-07-08 14:52:49 +08:00

91 lines
2.8 KiB
JavaScript

import crypto from 'node:crypto';
export function buildPageDataPolicyScopeHash(policy) {
const payload = {
accessMode: policy?.accessMode ?? null,
datasets: Object.keys(policy?.datasets ?? {}).sort(),
};
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 16);
}
export async function upsertPageDataPolicyIndex(pool, policy) {
if (!pool || !policy?.pageId || !policy?.ownerUserId) return null;
const datasetCount = Object.keys(policy.datasets ?? {}).length;
const now = Date.now();
await pool.query(
`INSERT INTO h5_page_data_policy_index
(page_id, owner_user_id, access_mode, dataset_count, scope_hash, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
owner_user_id = VALUES(owner_user_id),
access_mode = VALUES(access_mode),
dataset_count = VALUES(dataset_count),
scope_hash = VALUES(scope_hash),
updated_at = VALUES(updated_at)`,
[
policy.pageId,
policy.ownerUserId,
policy.accessMode,
datasetCount,
buildPageDataPolicyScopeHash(policy),
now,
],
);
return {
pageId: policy.pageId,
ownerUserId: policy.ownerUserId,
accessMode: policy.accessMode,
datasetCount,
scopeHash: buildPageDataPolicyScopeHash(policy),
updatedAt: now,
};
}
export async function getPageDataPolicyIndex(pool, pageId) {
if (!pool) return null;
const [rows] = await pool.query(
`SELECT page_id, owner_user_id, access_mode, dataset_count, scope_hash, updated_at
FROM h5_page_data_policy_index
WHERE page_id = ?
LIMIT 1`,
[pageId],
);
const row = rows[0];
if (!row) return null;
return {
pageId: row.page_id,
ownerUserId: row.owner_user_id,
accessMode: row.access_mode,
datasetCount: Number(row.dataset_count ?? 0),
scopeHash: row.scope_hash,
updatedAt: Number(row.updated_at ?? 0),
};
}
export async function listPageDataPolicyIndexByOwner(pool, ownerUserId, { limit = 50 } = {}) {
if (!pool) return [];
const safeLimit = Math.min(Math.max(1, Number(limit ?? 50)), 200);
const [rows] = await pool.query(
`SELECT page_id, owner_user_id, access_mode, dataset_count, scope_hash, updated_at
FROM h5_page_data_policy_index
WHERE owner_user_id = ?
ORDER BY updated_at DESC
LIMIT ?`,
[ownerUserId, safeLimit],
);
return rows.map((row) => ({
pageId: row.page_id,
ownerUserId: row.owner_user_id,
accessMode: row.access_mode,
datasetCount: Number(row.dataset_count ?? 0),
scopeHash: row.scope_hash,
updatedAt: Number(row.updated_at ?? 0),
}));
}
export async function deletePageDataPolicyIndex(pool, pageId) {
if (!pool || !pageId) return false;
const [result] = await pool.query(`DELETE FROM h5_page_data_policy_index WHERE page_id = ?`, [pageId]);
return Number(result?.affectedRows ?? 0) > 0;
}