feat: add access module boundary migrations

This commit is contained in:
2026-07-10 12:51:16 +02:00
parent 37828fe340
commit 04681f1d75
41 changed files with 7229 additions and 738 deletions

View File

@@ -1,4 +1,4 @@
import type { ApiSettings } from "@govoplan/core-webui";
import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui";
import { apiFetch } from "@govoplan/core-webui";
export type PermissionItem = {
@@ -171,17 +171,35 @@ export type SystemSettingsItem = {
allow_tenant_custom_roles: boolean;
allow_tenant_api_keys: boolean;
privacy_retention_policy: PrivacyRetentionPolicy;
available_languages?: LanguagePackage[];
enabled_language_codes?: string[];
settings: Record<string, unknown>;
};
export type LanguagePackage = {
code: string;
label: string;
native_label?: string | null;
};
export type TenantSettingsItem = {
id: string;
slug: string;
name: string;
default_locale: string;
available_languages: LanguagePackage[];
system_enabled_language_codes: string[];
enabled_language_codes: string[];
settings: Record<string, unknown>;
};
export type TenantSettingsDeltaSections = Partial<{
identity: Pick<TenantSettingsItem, "id" | "slug" | "name">;
locale: Pick<TenantSettingsItem, "default_locale">;
languages: Pick<TenantSettingsItem, "available_languages" | "system_enabled_language_codes" | "enabled_language_codes">;
settings: Pick<TenantSettingsItem, "settings">["settings"];
}>;
export type RetentionRunResponse = {
result: {
dry_run: boolean;
@@ -236,6 +254,42 @@ export type AuditAdminItem = {
created_at: string;
};
type DeltaResponseFields = {
deleted: DeltaDeletedItem[];
watermark?: string | null;
has_more: boolean;
full: boolean;
};
export type UserListDeltaResponse = { users: UserAdminItem[] } & DeltaResponseFields;
export type GroupListDeltaResponse = { groups: GroupSummary[] } & DeltaResponseFields;
export type RoleListDeltaResponse = { roles: RoleSummary[] } & DeltaResponseFields;
export type SystemAccountListDeltaResponse = { accounts: SystemAccountItem[]; roles: RoleSummary[] } & DeltaResponseFields;
export type ApiKeyListDeltaResponse = { api_keys: ApiKeyAdminItem[] } & DeltaResponseFields;
export type TenantListDeltaResponse = { tenants: TenantAdminItem[] } & DeltaResponseFields;
export type GovernanceTemplateListDeltaResponse = { templates: GovernanceTemplateItem[] } & DeltaResponseFields;
export type TenantSettingsDeltaResponse = {
item?: TenantSettingsItem | null;
sections: TenantSettingsDeltaSections;
changed_sections: string[];
} & DeltaResponseFields;
export type AuditAdminDeltaResponse = {
items: AuditAdminItem[];
total: number;
page: number;
page_size: number;
pages: number;
cursor?: string | null;
next_cursor?: string | null;
} & DeltaResponseFields;
function deltaSuffix(options: { since?: string | null; limit?: number } = {}): string {
const params = new URLSearchParams();
if (options.since) params.set("since", options.since);
if (options.limit) params.set("limit", String(options.limit));
return params.toString() ? `?${params.toString()}` : "";
}
export function fetchAdminOverview(settings: ApiSettings): Promise<AdminOverview> {
@@ -252,6 +306,11 @@ export async function fetchTenants(settings: ApiSettings): Promise<TenantAdminIt
return response.tenants;
}
export function fetchTenantsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<TenantListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/tenants/delta${suffix}`);
}
export async function fetchTenantOwnerCandidates(settings: ApiSettings): Promise<TenantOwnerCandidate[]> {
const response = await apiFetch<{ accounts: TenantOwnerCandidate[] }>(settings, "/api/v1/admin/tenants/owner-candidates");
return response.accounts;
@@ -289,7 +348,12 @@ export function fetchTenantSettings(settings: ApiSettings): Promise<TenantSettin
return apiFetch(settings, "/api/v1/admin/tenant/settings");
}
export function updateTenantSettings(settings: ApiSettings, payload: { default_locale: string }): Promise<TenantSettingsItem> {
export function fetchTenantSettingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<TenantSettingsDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/tenant/settings/delta${suffix}`);
}
export function updateTenantSettings(settings: ApiSettings, payload: { default_locale: string; enabled_language_codes?: string[] | null }): Promise<TenantSettingsItem> {
return apiFetch(settings, "/api/v1/admin/tenant/settings", { method: "PATCH", body: JSON.stringify(payload) });
}
@@ -298,6 +362,11 @@ export async function fetchUsers(settings: ApiSettings): Promise<UserAdminItem[]
return response.users;
}
export function fetchUsersDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<UserListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/users/delta${suffix}`);
}
export function createUser(settings: ApiSettings, payload: {
email: string;
display_name?: string | null;
@@ -324,6 +393,11 @@ export async function fetchGroups(settings: ApiSettings): Promise<GroupSummary[]
return response.groups;
}
export function fetchGroupsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<GroupListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/groups/delta${suffix}`);
}
export function createGroup(settings: ApiSettings, payload: {
slug: string;
name: string;
@@ -350,6 +424,11 @@ export async function fetchRoles(settings: ApiSettings): Promise<RoleSummary[]>
return response.roles;
}
export function fetchRolesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<RoleListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/roles/delta${suffix}`);
}
export function createRole(settings: ApiSettings, payload: {
slug: string;
name: string;
@@ -377,6 +456,11 @@ export async function fetchSystemRoles(settings: ApiSettings): Promise<RoleSumma
return response.roles;
}
export function fetchSystemRolesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<RoleListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/system/roles/delta${suffix}`);
}
export function createSystemRole(settings: ApiSettings, payload: {
slug: string;
name: string;
@@ -403,6 +487,11 @@ export async function fetchSystemAccounts(settings: ApiSettings): Promise<{ acco
return apiFetch(settings, "/api/v1/admin/system/accounts");
}
export function fetchSystemAccountsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<SystemAccountListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/system/accounts/delta${suffix}`);
}
export function updateSystemAccount(settings: ApiSettings, accountId: string, payload: {
display_name?: string | null;
is_active?: boolean;
@@ -429,6 +518,15 @@ export async function fetchApiKeys(settings: ApiSettings, includeRevoked = false
return response.api_keys;
}
export function fetchApiKeysDelta(settings: ApiSettings, includeRevoked = false, options: { since?: string | null; limit?: number } = {}): Promise<ApiKeyListDeltaResponse> {
const params = new URLSearchParams();
if (includeRevoked) params.set("include_revoked", "true");
if (options.since) params.set("since", options.since);
if (options.limit) params.set("limit", String(options.limit));
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiFetch(settings, `/api/v1/admin/api-keys/delta${suffix}`);
}
export function createApiKey(settings: ApiSettings, payload: {
name: string;
user_id?: string | null;
@@ -450,12 +548,13 @@ export type AuditQueryOptions = {
offset?: number;
page?: number;
pageSize?: number;
cursor?: string | null;
sortBy?: "time" | "actor" | "action" | "object" | "tenant";
sortDirection?: "asc" | "desc";
filters?: Partial<Record<"time" | "actor" | "action" | "object" | "tenant", string>>;
};
export async function fetchAdminAudit(settings: ApiSettings, options: AuditQueryOptions = {}): Promise<{ items: AuditAdminItem[]; total: number; page: number; page_size: number; pages: number }> {
export async function fetchAdminAudit(settings: ApiSettings, options: AuditQueryOptions = {}): Promise<{ items: AuditAdminItem[]; total: number; page: number; page_size: number; pages: number; cursor?: string | null; next_cursor?: string | null }> {
const params = new URLSearchParams();
if (options.tenantId) params.set("tenant_id", options.tenantId);
if (options.allTenants) params.set("all_tenants", "true");
@@ -464,6 +563,7 @@ export async function fetchAdminAudit(settings: ApiSettings, options: AuditQuery
if (options.offset) params.set("offset", String(options.offset));
if (options.page) params.set("page", String(options.page));
if (options.pageSize) params.set("page_size", String(options.pageSize));
if (options.cursor) params.set("cursor", options.cursor);
if (options.sortBy) params.set("sort_by", options.sortBy);
if (options.sortDirection) params.set("sort_direction", options.sortDirection);
for (const [column, value] of Object.entries(options.filters ?? {})) {
@@ -473,6 +573,24 @@ export async function fetchAdminAudit(settings: ApiSettings, options: AuditQuery
return apiFetch(settings, `/api/v1/admin/audit${suffix}`);
}
export async function fetchAdminAuditDelta(settings: ApiSettings, options: AuditQueryOptions & { since?: string | null } = {}): Promise<AuditAdminDeltaResponse> {
const params = new URLSearchParams();
if (options.tenantId) params.set("tenant_id", options.tenantId);
if (options.allTenants) params.set("all_tenants", "true");
if (options.scope) params.set("scope", options.scope);
if (options.limit) params.set("limit", String(options.limit));
if (options.pageSize) params.set("page_size", String(options.pageSize));
if (options.cursor) params.set("cursor", options.cursor);
if (options.sortBy) params.set("sort_by", options.sortBy);
if (options.sortDirection) params.set("sort_direction", options.sortDirection);
if (options.since) params.set("since", options.since);
for (const [column, value] of Object.entries(options.filters ?? {})) {
if (value?.trim()) params.set(`filter_${column}`, value);
}
const suffix = params.toString() ? `?${params.toString()}` : "";
return apiFetch(settings, `/api/v1/admin/audit/delta${suffix}`);
}
export function createSystemAccount(settings: ApiSettings, payload: {
email: string;
@@ -502,6 +620,8 @@ export type SystemSettingsUpdatePayload = {
allow_tenant_custom_roles: boolean;
allow_tenant_api_keys: boolean;
privacy_retention_policy?: PrivacyRetentionPolicy | null;
available_languages?: LanguagePackage[] | null;
enabled_language_codes?: string[] | null;
};
export function updateSystemSettings(settings: ApiSettings, payload: SystemSettingsUpdatePayload): Promise<SystemSettingsItem> {
@@ -532,6 +652,11 @@ export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "gr
return response.templates;
}
export function fetchGovernanceTemplatesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise<GovernanceTemplateListDeltaResponse> {
const suffix = deltaSuffix(options);
return apiFetch(settings, `/api/v1/admin/system/governance-templates/delta${suffix}`);
}
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count">): Promise<GovernanceTemplateItem> {
return apiFetch(settings, "/api/v1/admin/system/governance-templates", { method: "POST", body: JSON.stringify(payload) });
}