import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui"; import { apiFetch } from "@govoplan/core-webui"; export type PermissionItem = { scope: string; label: string; description: string; category: string; level: "tenant" | "system"; system_template_id?: string | null; system_required?: boolean; }; export type AdminOverview = { active_tenant_id: string; active_tenant_name: string; tenant_count?: number | null; system_account_count?: number | null; system_group_template_count?: number | null; system_role_template_count?: number | null; user_count: number; active_user_count: number; group_count: number; role_count: number; active_api_key_count: number; capabilities: string[]; }; export type TenantAdminItem = { id: string; slug: string; name: string; description?: string | null; default_locale: string; settings: Record; allow_custom_groups?: boolean | null; allow_custom_roles?: boolean | null; allow_api_keys?: boolean | null; effective_governance: Record; is_active: boolean; counts: Record; created_at: string; updated_at: string; }; export type TenantOwnerCandidate = { account_id: string; email: string; display_name?: string | null; }; export type RoleSummary = { id: string; slug: string; name: string; description?: string | null; permissions: string[]; effective_permission_count: number; is_builtin: boolean; is_assignable: boolean; user_assignments: number; group_assignments: number; level: "tenant" | "system"; system_template_id?: string | null; system_required?: boolean; }; export type GroupSummary = { id: string; slug: string; name: string; description?: string | null; is_active: boolean; member_count: number; member_ids: string[]; roles: RoleSummary[]; created_at: string; updated_at: string; system_template_id?: string | null; system_required?: boolean; }; export type UserAdminItem = { id: string; account_id: string; tenant_id: string; email: string; display_name?: string | null; is_active: boolean; account_is_active: boolean; password_reset_required: boolean; last_login_at?: string | null; groups: GroupSummary[]; roles: RoleSummary[]; effective_scopes: string[]; is_owner: boolean; is_last_active_owner: boolean; created_at: string; updated_at: string; }; export type SystemAccountItem = { account_id: string; email: string; display_name?: string | null; is_active: boolean; memberships: Array<{ tenant_id: string; tenant_name: string; user_id: string; is_active: boolean; role_ids: string[]; group_ids: string[]; is_owner: boolean; is_last_active_owner: boolean }>; roles: RoleSummary[]; last_login_at?: string | null; }; export type SystemMembershipDraft = { tenant_id: string; is_active: boolean; role_ids: string[]; group_ids: string[]; is_owner?: boolean; is_last_active_owner?: boolean; }; export type PrivacyRetentionPolicyFieldKey = | "store_raw_campaign_json" | "raw_campaign_json_retention_days" | "generated_eml_retention_days" | "stored_report_detail_retention_days" | "mock_mailbox_retention_days" | "audit_detail_retention_days" | "audit_detail_level"; export type PrivacyRetentionLimitPermissions = Record; export type PrivacyRetentionLimitPermissionPatch = Partial; export type PrivacyRetentionPolicy = { store_raw_campaign_json: boolean; raw_campaign_json_retention_days?: number | null; generated_eml_retention_days?: number | null; stored_report_detail_retention_days?: number | null; mock_mailbox_retention_days?: number | null; audit_detail_retention_days?: number | null; audit_detail_level: "full" | "redacted" | "minimal"; allow_lower_level_limits: PrivacyRetentionLimitPermissions; }; export type PrivacyRetentionPolicyPatch = Partial> & { allow_lower_level_limits?: PrivacyRetentionLimitPermissionPatch; }; export type PrivacyRetentionPolicyScope = "system" | "tenant" | "user" | "group" | "campaign"; export type PolicySourceStep = { scope_type: string; scope_id?: string | null; label: string; applied_fields?: string[]; policy?: PrivacyRetentionPolicyPatch | PrivacyRetentionPolicy | null; }; export type PrivacyRetentionPolicyScopeResponse = { scope_type: PrivacyRetentionPolicyScope; scope_id?: string | null; policy: PrivacyRetentionPolicyPatch; effective_policy: PrivacyRetentionPolicy; parent_policy?: PrivacyRetentionPolicy | null; effective_policy_sources?: PolicySourceStep[]; parent_policy_sources?: PolicySourceStep[]; }; export type SystemSettingsItem = { default_locale: string; allow_tenant_custom_groups: boolean; allow_tenant_custom_roles: boolean; allow_tenant_api_keys: boolean; privacy_retention_policy: PrivacyRetentionPolicy; available_languages?: LanguagePackage[]; enabled_language_codes?: string[]; settings: Record; }; 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; }; export type TenantSettingsDeltaSections = Partial<{ identity: Pick; locale: Pick; languages: Pick; settings: Pick["settings"]; }>; export type RetentionRunResponse = { result: { dry_run: boolean; policy: PrivacyRetentionPolicy; cutoffs: Record; effective_policy_scope?: string; counts: Record>; }; }; export type GovernanceAssignment = { tenant_id: string; mode: "available" | "required"; }; export type GovernanceTemplateItem = { id: string; kind: "group" | "role"; slug: string; name: string; description?: string | null; permissions: string[]; effective_permission_count: number; is_active: boolean; assignments: GovernanceAssignment[]; created_at: string; updated_at: string; }; export type ApiKeyAdminItem = { id: string; user_id: string; user_email: string; name: string; prefix: string; scopes: string[]; expires_at?: string | null; last_used_at?: string | null; revoked_at?: string | null; created_at: string; }; export type ExternalFunctionRoleMappingItem = { id: string; tenant_id: string; source_module: string; function_id: string; role_id: string; settings: Record; created_at: string; updated_at: string; }; export type AuditAdminItem = { id: string; scope: "tenant" | "system"; tenant_id?: string | null; actor_email?: string | null; action: string; object_type?: string | null; object_id?: string | null; details: Record; 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 ExternalFunctionRoleMappingListDeltaResponse = { mappings: ExternalFunctionRoleMappingItem[] } & 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 { return apiFetch(settings, "/api/v1/admin/overview"); } export async function fetchPermissionCatalog(settings: ApiSettings): Promise { const response = await apiFetch<{ permissions: PermissionItem[] }>(settings, "/api/v1/admin/permissions"); return response.permissions; } export async function fetchTenants(settings: ApiSettings): Promise { const response = await apiFetch<{ tenants: TenantAdminItem[] }>(settings, "/api/v1/admin/tenants"); return response.tenants; } export function fetchTenantsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/tenants/delta${suffix}`); } export async function fetchTenantOwnerCandidates(settings: ApiSettings): Promise { const response = await apiFetch<{ accounts: TenantOwnerCandidate[] }>(settings, "/api/v1/admin/tenants/owner-candidates"); return response.accounts; } export function createTenant(settings: ApiSettings, payload: { slug: string; name: string; owner_account_id?: string | null; description?: string | null; default_locale?: string; settings?: Record; allow_custom_groups?: boolean | null; allow_custom_roles?: boolean | null; allow_api_keys?: boolean | null; }): Promise { return apiFetch(settings, "/api/v1/admin/tenants", { method: "POST", body: JSON.stringify(payload) }); } export function updateTenant(settings: ApiSettings, tenantId: string, payload: Partial<{ name: string; description: string | null; default_locale: string; settings: Record; allow_custom_groups?: boolean | null; allow_custom_roles?: boolean | null; allow_api_keys?: boolean | null; effective_governance: Record; is_active: boolean; }>): Promise { return apiFetch(settings, `/api/v1/admin/tenants/${tenantId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function fetchTenantSettings(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/admin/tenant/settings"); } export function fetchTenantSettingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { 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 { return apiFetch(settings, "/api/v1/admin/tenant/settings", { method: "PATCH", body: JSON.stringify(payload) }); } export async function fetchUsers(settings: ApiSettings): Promise { const response = await apiFetch<{ users: UserAdminItem[] }>(settings, "/api/v1/admin/users"); return response.users; } export function fetchUsersDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { 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; password?: string | null; password_reset_required?: boolean; is_active?: boolean; group_ids?: string[]; role_ids?: string[]; }): Promise<{ user: UserAdminItem; account_created: boolean; temporary_password?: string | null }> { return apiFetch(settings, "/api/v1/admin/users", { method: "POST", body: JSON.stringify(payload) }); } export function updateUser(settings: ApiSettings, userId: string, payload: Partial<{ display_name: string | null; is_active: boolean; group_ids: string[]; role_ids: string[]; }>): Promise { return apiFetch(settings, `/api/v1/admin/users/${userId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export async function fetchGroups(settings: ApiSettings): Promise { const response = await apiFetch<{ groups: GroupSummary[] }>(settings, "/api/v1/admin/groups"); return response.groups; } export function fetchGroupsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/groups/delta${suffix}`); } export function createGroup(settings: ApiSettings, payload: { slug: string; name: string; description?: string | null; is_active?: boolean; member_ids?: string[]; role_ids?: string[]; }): Promise { return apiFetch(settings, "/api/v1/admin/groups", { method: "POST", body: JSON.stringify(payload) }); } export function updateGroup(settings: ApiSettings, groupId: string, payload: Partial<{ name: string; description: string | null; is_active: boolean; member_ids: string[]; role_ids: string[]; }>): Promise { return apiFetch(settings, `/api/v1/admin/groups/${groupId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export async function fetchRoles(settings: ApiSettings): Promise { const response = await apiFetch<{ roles: RoleSummary[] }>(settings, "/api/v1/admin/roles"); return response.roles; } export function fetchRolesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/roles/delta${suffix}`); } export function createRole(settings: ApiSettings, payload: { slug: string; name: string; description?: string | null; permissions: string[]; }): Promise { return apiFetch(settings, "/api/v1/admin/roles", { method: "POST", body: JSON.stringify(payload) }); } export function updateRole(settings: ApiSettings, roleId: string, payload: { name: string; description?: string | null; permissions: string[]; is_assignable: boolean; }): Promise { return apiFetch(settings, `/api/v1/admin/roles/${roleId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteRole(settings: ApiSettings, roleId: string): Promise { return apiFetch(settings, `/api/v1/admin/roles/${roleId}`, { method: "DELETE" }); } export function fetchExternalFunctionRoleMappingsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/delta${suffix}`); } export function createExternalFunctionRoleMapping(settings: ApiSettings, payload: { source_module: string; function_id: string; role_id: string; settings?: Record; }): Promise { return apiFetch(settings, "/api/v1/admin/external-function-role-mappings", { method: "POST", body: JSON.stringify(payload) }); } export function updateExternalFunctionRoleMapping(settings: ApiSettings, mappingId: string, payload: { role_id?: string; settings?: Record; }): Promise { return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/${mappingId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteExternalFunctionRoleMapping(settings: ApiSettings, mappingId: string): Promise { return apiFetch(settings, `/api/v1/admin/external-function-role-mappings/${mappingId}`, { method: "DELETE" }); } export async function fetchSystemRoles(settings: ApiSettings): Promise { const response = await apiFetch<{ roles: RoleSummary[] }>(settings, "/api/v1/admin/system/roles"); return response.roles; } export function fetchSystemRolesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/system/roles/delta${suffix}`); } export function createSystemRole(settings: ApiSettings, payload: { slug: string; name: string; description?: string | null; permissions: string[]; }): Promise { return apiFetch(settings, "/api/v1/admin/system/roles", { method: "POST", body: JSON.stringify(payload) }); } export function updateSystemRole(settings: ApiSettings, roleId: string, payload: { name: string; description?: string | null; permissions: string[]; is_assignable: boolean; }): Promise { return apiFetch(settings, `/api/v1/admin/system/roles/${roleId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteSystemRole(settings: ApiSettings, roleId: string): Promise { return apiFetch(settings, `/api/v1/admin/system/roles/${roleId}`, { method: "DELETE" }); } export async function fetchSystemAccounts(settings: ApiSettings): Promise<{ accounts: SystemAccountItem[]; roles: RoleSummary[] }> { return apiFetch(settings, "/api/v1/admin/system/accounts"); } export function fetchSystemAccountsDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { 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; role_ids?: string[]; }): Promise { return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function updateSystemAccountRoles(settings: ApiSettings, accountId: string, roleIds: string[]): Promise { return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}/roles`, { method: "PUT", body: JSON.stringify({ role_ids: roleIds }) }); } export async function fetchApiKeys(settings: ApiSettings, includeRevoked = false): Promise { const params = new URLSearchParams(); if (includeRevoked) params.set("include_revoked", "true"); const suffix = params.toString() ? `?${params.toString()}` : ""; const response = await apiFetch<{ api_keys: ApiKeyAdminItem[] }>(settings, `/api/v1/admin/api-keys${suffix}`); return response.api_keys; } export function fetchApiKeysDelta(settings: ApiSettings, includeRevoked = false, options: { since?: string | null; limit?: number } = {}): Promise { 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; scopes: string[]; expires_at?: string | null; }): Promise { return apiFetch(settings, "/api/v1/admin/api-keys", { method: "POST", body: JSON.stringify(payload) }); } export function revokeApiKey(settings: ApiSettings, keyId: string): Promise { return apiFetch(settings, `/api/v1/admin/api-keys/${keyId}/revoke`, { method: "POST" }); } export type AuditQueryOptions = { tenantId?: string | null; allTenants?: boolean; scope?: "tenant" | "system"; limit?: number; offset?: number; page?: number; pageSize?: number; cursor?: string | null; sortBy?: "time" | "actor" | "action" | "object" | "tenant"; sortDirection?: "asc" | "desc"; filters?: Partial>; }; 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"); if (options.scope) params.set("scope", options.scope); if (options.limit) params.set("limit", String(options.limit)); 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 ?? {})) { if (value?.trim()) params.set(`filter_${column}`, value); } const suffix = params.toString() ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/admin/audit${suffix}`); } export async function fetchAdminAuditDelta(settings: ApiSettings, options: AuditQueryOptions & { since?: string | null } = {}): Promise { 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; display_name?: string | null; password?: string | null; password_reset_required?: boolean; is_active?: boolean; role_ids?: string[]; memberships?: SystemMembershipDraft[]; }): Promise<{ account: SystemAccountItem; temporary_password?: string | null }> { return apiFetch(settings, "/api/v1/admin/system/accounts", { method: "POST", body: JSON.stringify(payload) }); } export function updateSystemMemberships(settings: ApiSettings, accountId: string, memberships: SystemMembershipDraft[]): Promise { return apiFetch(settings, `/api/v1/admin/system/accounts/${accountId}/memberships`, { method: "PUT", body: JSON.stringify({ memberships }) }); } export function fetchSystemSettings(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/admin/system/settings"); } export type SystemSettingsUpdatePayload = { default_locale: string; allow_tenant_custom_groups: boolean; 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 { return apiFetch(settings, "/api/v1/admin/system/settings", { method: "PATCH", body: JSON.stringify(payload) }); } export function getPrivacyRetentionPolicy(settings: ApiSettings, scope: PrivacyRetentionPolicyScope, scopeId?: string | null): Promise { const params = new URLSearchParams(); if (scopeId) params.set("scope_id", scopeId); const suffix = params.toString() ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/admin/privacy-retention/policies/${encodeURIComponent(scope)}${suffix}`); } export function updatePrivacyRetentionPolicy(settings: ApiSettings, scope: PrivacyRetentionPolicyScope, policy: PrivacyRetentionPolicyPatch, scopeId?: string | null): Promise { const params = new URLSearchParams(); if (scopeId) params.set("scope_id", scopeId); const suffix = params.toString() ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/admin/privacy-retention/policies/${encodeURIComponent(scope)}${suffix}`, { method: "PUT", body: JSON.stringify({ policy }) }); } export function runRetentionPolicy(settings: ApiSettings, dryRun = true): Promise { return apiFetch(settings, "/api/v1/admin/system/retention/run", { method: "POST", body: JSON.stringify({ dry_run: dryRun }) }); } export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "group" | "role"): Promise { const suffix = kind ? `?kind=${encodeURIComponent(kind)}` : ""; const response = await apiFetch<{ templates: GovernanceTemplateItem[] }>(settings, `/api/v1/admin/system/governance-templates${suffix}`); return response.templates; } export function fetchGovernanceTemplatesDelta(settings: ApiSettings, options: { since?: string | null; limit?: number } = {}): Promise { const suffix = deltaSuffix(options); return apiFetch(settings, `/api/v1/admin/system/governance-templates/delta${suffix}`); } export function createGovernanceTemplate(settings: ApiSettings, payload: Omit): Promise { return apiFetch(settings, "/api/v1/admin/system/governance-templates", { method: "POST", body: JSON.stringify(payload) }); } export function updateGovernanceTemplate(settings: ApiSettings, templateId: string, payload: Omit): Promise { return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string): Promise { return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}`, { method: "DELETE" }); }