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>
91 lines
2.8 KiB
JavaScript
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;
|
|
}
|