Add governed tabular and LDAP address sources

This commit is contained in:
2026-08-02 15:39:34 +02:00
parent e60339a5bf
commit 2c421022d4
19 changed files with 3846 additions and 78 deletions
+173
View File
@@ -484,6 +484,92 @@ export type AddressSyncConflict = {
updated_at: string;
};
export type AddressImportConfiguration = {
field_mappings: Record<string, string>;
delimiter: "," | ";" | "\t" | "|";
encoding: "utf-8" | "utf-8-sig" | "cp1252" | "latin-1";
header_row: number;
sheet_name?: string | null;
source_key_column?: string | null;
duplicate_source_key_policy: "reject" | "first" | "last";
existing_contact_policy: "update" | "ignore" | "reject";
blank_value_policy: "ignore" | "clear" | "reject";
locale?: string | null;
default_tags: string[];
max_rows: number;
};
export type AddressImportProfile = {
id: string;
profile_key: string;
version: number;
tenant_id?: string | null;
scope_type: AddressBookScope;
scope_id?: string | null;
name: string;
description?: string | null;
source_format: "csv" | "xlsx";
configuration: AddressImportConfiguration;
is_current: boolean;
created_at: string;
updated_at: string;
};
export type AddressImportEffect = {
row_number: number;
action: "create" | "update" | "conflict" | "unchanged" | "ignored";
source_key?: string | null;
contact_id?: string | null;
display_name?: string | null;
changed_fields: string[];
message?: string | null;
};
export type AddressImportDiagnostic = {
severity: "info" | "warning" | "error";
code: string;
message: string;
row_number?: number | null;
field?: string | null;
details: Record<string, unknown>;
};
export type AddressImportRun = {
id: string;
address_book_id: string;
profile_id: string;
source_filename: string;
source_format: string;
input_hash: string;
plan_hash: string;
status: string;
row_count: number;
statistics: Record<string, number>;
diagnostics: AddressImportDiagnostic[];
effects: AddressImportEffect[];
can_apply: boolean;
result_evidence: Record<string, unknown>;
created_at: string;
updated_at: string;
applied_at?: string | null;
rolled_back_at?: string | null;
};
export type AddressLdapSourcePayload = {
url: string;
credential_ref?: string | null;
bind_dn?: string | null;
start_tls: boolean;
connect_timeout?: number;
receive_timeout?: number;
display_name: string;
base_dn: string;
search_filter: string;
page_size: number;
max_entries: number;
attribute_map: Record<string, string>;
};
type AddressSyncSourceListResponse = {
sync_sources: AddressSyncSource[];
};
@@ -508,6 +594,14 @@ type AddressSyncConflictListResponse = {
conflicts: AddressSyncConflict[];
};
type AddressImportProfileListResponse = {
profiles: AddressImportProfile[];
};
type AddressLdapDiscoveryResponse = {
base_dns: string[];
};
type ContactChannelRuleListResponse = {
rules: ContactChannelRule[];
};
@@ -692,6 +786,27 @@ export function createCardDavSyncSource(
});
}
export function discoverLdapBaseDns(
settings: ApiSettings,
payload: Pick<AddressLdapSourcePayload, "url" | "credential_ref" | "bind_dn" | "start_tls" | "connect_timeout" | "receive_timeout">
): Promise<string[]> {
return apiFetch<AddressLdapDiscoveryResponse>(settings, "/api/v1/addresses/ldap/discover", {
method: "POST",
body: JSON.stringify(payload)
}).then((response) => response.base_dns);
}
export function createLdapSyncSource(
settings: ApiSettings,
addressBookId: string,
payload: AddressLdapSourcePayload
): Promise<AddressSyncSource> {
return apiFetch<AddressSyncSource>(settings, `/api/v1/addresses/address-books/${addressBookId}/ldap/sources`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export async function listAddressCredentials(
settings: ApiSettings,
sourceId?: string | null
@@ -941,6 +1056,64 @@ export function importAddressBookVcards(settings: ApiSettings, addressBookId: st
});
}
export async function listAddressImportProfiles(settings: ApiSettings): Promise<AddressImportProfile[]> {
const response = await apiFetch<AddressImportProfileListResponse>(settings, "/api/v1/addresses/import-profiles");
return response.profiles;
}
export function createAddressImportProfile(
settings: ApiSettings,
payload: {
scope_type: AddressBookScope;
scope_id?: string | null;
name: string;
description?: string | null;
source_format: "csv" | "xlsx";
configuration: AddressImportConfiguration;
}
): Promise<AddressImportProfile> {
return apiFetch<AddressImportProfile>(settings, "/api/v1/addresses/import-profiles", {
method: "POST",
body: JSON.stringify(payload)
});
}
export function updateAddressImportProfile(
settings: ApiSettings,
profileId: string,
payload: { name?: string; description?: string | null; configuration?: AddressImportConfiguration }
): Promise<AddressImportProfile> {
return apiFetch<AddressImportProfile>(settings, `/api/v1/addresses/import-profiles/${profileId}`, {
method: "PATCH",
body: JSON.stringify(payload)
});
}
export function previewAddressImport(
settings: ApiSettings,
addressBookId: string,
payload: { profile_id: string; filename: string; content_base64: string }
): Promise<AddressImportRun> {
return apiFetch<AddressImportRun>(settings, `/api/v1/addresses/address-books/${addressBookId}/imports/preview`, {
method: "POST",
body: JSON.stringify(payload)
});
}
export function applyAddressImport(settings: ApiSettings, run: AddressImportRun): Promise<AddressImportRun> {
return apiFetch<AddressImportRun>(settings, `/api/v1/addresses/imports/${run.id}/apply`, {
method: "POST",
body: JSON.stringify({ expected_plan_hash: run.plan_hash })
});
}
export function rollbackAddressImport(settings: ApiSettings, runId: string, reason: string): Promise<AddressImportRun> {
return apiFetch<AddressImportRun>(settings, `/api/v1/addresses/imports/${runId}/rollback`, {
method: "POST",
body: JSON.stringify({ reason })
});
}
export function exportAddressBookVcards(settings: ApiSettings, addressBookId: string): Promise<string> {
return apiFetch<string>(settings, `/api/v1/addresses/address-books/${addressBookId}/vcards/export`);
}