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>
This commit is contained in:
@@ -34,6 +34,10 @@ import type {
|
||||
MindSpacePublication,
|
||||
MindSpacePublicationStats,
|
||||
MindSpacePublishCheck,
|
||||
PageDataAccessPolicy,
|
||||
PageDataDatasetSummary,
|
||||
PageDataLogEntry,
|
||||
PageDataOpsOverview,
|
||||
PlazaCategory,
|
||||
PlazaPostBrief,
|
||||
MindSpaceRedactedCopyResult,
|
||||
@@ -1458,6 +1462,165 @@ export async function updatePublicationStatus(
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function listOwnerPageDataPolicies(): Promise<
|
||||
Array<{
|
||||
pageId: string;
|
||||
ownerUserId: string;
|
||||
accessMode: string;
|
||||
datasetCount: number;
|
||||
scopeHash: string;
|
||||
updatedAt: number;
|
||||
}>
|
||||
> {
|
||||
const result = await apiFetch<{
|
||||
data: {
|
||||
policies: Array<{
|
||||
pageId: string;
|
||||
ownerUserId: string;
|
||||
accessMode: string;
|
||||
datasetCount: number;
|
||||
scopeHash: string;
|
||||
updatedAt: number;
|
||||
}>;
|
||||
};
|
||||
}>('/page-data/policies');
|
||||
return result.data.policies;
|
||||
}
|
||||
|
||||
export function buildPageDataExportUrl(dataset: string, format: 'json' | 'csv' = 'json') {
|
||||
const params = new URLSearchParams({ format });
|
||||
return `/api/page-data/${encodeURIComponent(dataset)}/export?${params.toString()}`;
|
||||
}
|
||||
|
||||
export async function listPageDataDatasets(): Promise<PageDataDatasetSummary[]> {
|
||||
const result = await apiFetch<{ data: { datasets: PageDataDatasetSummary[] } }>('/page-data');
|
||||
return result.data.datasets;
|
||||
}
|
||||
|
||||
export async function getPageDataPolicy(pageId: string): Promise<PageDataAccessPolicy | null> {
|
||||
try {
|
||||
const result = await apiFetch<{ data: { policy: PageDataAccessPolicy } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}`,
|
||||
);
|
||||
return result.data.policy;
|
||||
} catch (error) {
|
||||
if (error instanceof ApiError && error.status === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function applyPageDataPublishPolicy(
|
||||
pageId: string,
|
||||
input: {
|
||||
datasetName: string;
|
||||
capabilities?: {
|
||||
read?: boolean;
|
||||
insert?: boolean;
|
||||
update?: boolean;
|
||||
softDelete?: boolean;
|
||||
};
|
||||
},
|
||||
): Promise<PageDataAccessPolicy> {
|
||||
const result = await apiFetch<{ data: { policy: PageDataAccessPolicy } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}/apply-publish`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
datasetName: input.datasetName,
|
||||
capabilities: input.capabilities ?? {},
|
||||
}),
|
||||
},
|
||||
);
|
||||
return result.data.policy;
|
||||
}
|
||||
|
||||
export async function savePageDataPolicy(
|
||||
pageId: string,
|
||||
policy: Partial<PageDataAccessPolicy>,
|
||||
): Promise<PageDataAccessPolicy> {
|
||||
const result = await apiFetch<{ data: { policy: PageDataAccessPolicy } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(policy),
|
||||
},
|
||||
);
|
||||
return result.data.policy;
|
||||
}
|
||||
|
||||
export async function getPageDataOpsOverview(pageId: string): Promise<PageDataOpsOverview> {
|
||||
const result = await apiFetch<{ data: PageDataOpsOverview }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}/ops`,
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function listPageDataLogs(
|
||||
pageId: string,
|
||||
input: { limit?: number; offset?: number } = {},
|
||||
): Promise<{ pageId: string; logs: PageDataLogEntry[]; count: number }> {
|
||||
const params = new URLSearchParams();
|
||||
if (input.limit != null) params.set('limit', String(input.limit));
|
||||
if (input.offset != null) params.set('offset', String(input.offset));
|
||||
const query = params.toString();
|
||||
const result = await apiFetch<{ data: { pageId: string; logs: PageDataLogEntry[]; count: number } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}/logs${query ? `?${query}` : ''}`,
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function revokePageDataTokens(
|
||||
pageId: string,
|
||||
input: { revokeAll?: boolean; token?: string },
|
||||
): Promise<{ pageId: string; revokedCount: number; revokeAll: boolean }> {
|
||||
const result = await apiFetch<{ data: { pageId: string; revokedCount: number; revokeAll: boolean } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}/tokens/revoke`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
revokeAll: Boolean(input.revokeAll),
|
||||
token: input.token,
|
||||
}),
|
||||
},
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function resetPageDataPassword(
|
||||
pageId: string,
|
||||
password: string,
|
||||
): Promise<{ pageId: string; passwordReset: boolean; revokedSessions: number }> {
|
||||
const result = await apiFetch<{
|
||||
data: { pageId: string; passwordReset: boolean; revokedSessions: number };
|
||||
}>(`/page-data/policies/${encodeURIComponent(pageId)}/password/reset`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function closePageDataDataset(
|
||||
pageId: string,
|
||||
dataset: string,
|
||||
): Promise<{ pageId: string; dataset: string; closed: boolean }> {
|
||||
const result = await apiFetch<{ data: { pageId: string; dataset: string; closed: boolean } }>(
|
||||
`/page-data/policies/${encodeURIComponent(pageId)}/datasets/${encodeURIComponent(dataset)}/close`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function restorePageDataRow(
|
||||
dataset: string,
|
||||
rowId: number | string,
|
||||
): Promise<{ restored: boolean; row: Record<string, unknown> }> {
|
||||
const result = await apiFetch<{ data: { restored: boolean; row: Record<string, unknown> } }>(
|
||||
`/page-data/${encodeURIComponent(dataset)}/rows/${encodeURIComponent(String(rowId))}/restore`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function redactMindSpacePage(
|
||||
pageId: string,
|
||||
input: {
|
||||
|
||||
Reference in New Issue
Block a user