1124 lines
34 KiB
TypeScript
1124 lines
34 KiB
TypeScript
import { apiFetch, type ApiSettings } from "@govoplan/core-webui";
|
|
|
|
export type AddressBookScope = "user" | "group" | "tenant" | "system";
|
|
|
|
export type AddressBook = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
scope_type: AddressBookScope;
|
|
scope_id?: string | null;
|
|
name: string;
|
|
description?: string | null;
|
|
source_kind: string;
|
|
source_ref?: string | null;
|
|
read_only: boolean;
|
|
sync_status?: string | null;
|
|
sync_error?: string | null;
|
|
contact_count: number;
|
|
deleted_at?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type AddressList = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
address_book_id: string;
|
|
name: string;
|
|
description?: string | null;
|
|
source_kind: string;
|
|
source_ref?: string | null;
|
|
read_only: boolean;
|
|
entry_count: number;
|
|
deleted_at?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type ContactEmail = {
|
|
id?: string;
|
|
label?: string | null;
|
|
email: string;
|
|
original_email?: string;
|
|
normalized_email?: string;
|
|
provenance?: Record<string, unknown>;
|
|
quality_state?: ContactPointQualityState;
|
|
quality_reason_code?: string | null;
|
|
is_primary: boolean;
|
|
};
|
|
|
|
export type ContactPhone = {
|
|
id?: string;
|
|
label?: string | null;
|
|
phone: string;
|
|
original_phone?: string;
|
|
normalized_phone?: string;
|
|
provenance?: Record<string, unknown>;
|
|
quality_state?: ContactPointQualityState;
|
|
quality_reason_code?: string | null;
|
|
is_primary: boolean;
|
|
};
|
|
|
|
export type ContactPostalAddress = {
|
|
id?: string;
|
|
label?: string | null;
|
|
street?: string | null;
|
|
postal_code?: string | null;
|
|
locality?: string | null;
|
|
region?: string | null;
|
|
country?: string | null;
|
|
original_value?: Record<string, unknown>;
|
|
normalized_value?: Record<string, unknown>;
|
|
provenance?: Record<string, unknown>;
|
|
quality_state?: ContactPointQualityState;
|
|
quality_reason_code?: string | null;
|
|
is_primary: boolean;
|
|
};
|
|
|
|
export type ContactPointQualityState = "valid" | "invalid" | "returned" | "stale" | "undeliverable";
|
|
|
|
export type ContactFieldProvenance = {
|
|
id: string;
|
|
contact_id: string;
|
|
field_path: string;
|
|
value?: unknown;
|
|
source_kind: string;
|
|
source_ref?: string | null;
|
|
source_revision?: string | null;
|
|
precedence: number;
|
|
selected: boolean;
|
|
reason_code: string;
|
|
explanation?: string | null;
|
|
visibility: "inherit" | "private" | "restricted" | "public";
|
|
merge_record_id?: string | null;
|
|
created_by_account_id?: string | null;
|
|
metadata: Record<string, unknown>;
|
|
created_at: string;
|
|
};
|
|
|
|
export type Contact = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
address_book_id: string;
|
|
display_name: string;
|
|
given_name?: string | null;
|
|
family_name?: string | null;
|
|
organization?: string | null;
|
|
role_title?: string | null;
|
|
note?: string | null;
|
|
tags: string[];
|
|
source_kind: string;
|
|
source_ref?: string | null;
|
|
source_payload_kind?: string | null;
|
|
source_revision?: string | null;
|
|
provenance: Record<string, unknown>;
|
|
emails: ContactEmail[];
|
|
phones: ContactPhone[];
|
|
postal_addresses: ContactPostalAddress[];
|
|
field_provenance?: ContactFieldProvenance[];
|
|
deleted_at?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type ContactPointQualityDecision = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
contact_id: string;
|
|
channel: "email" | "phone" | "postal" | "internal_mail" | "portal";
|
|
contact_point_id?: string | null;
|
|
state: ContactPointQualityState;
|
|
reason_code: string;
|
|
reason?: string | null;
|
|
evidence_ref?: string | null;
|
|
effective_from: string;
|
|
effective_until?: string | null;
|
|
created_by_account_id?: string | null;
|
|
metadata: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type ContactDuplicateFeature = {
|
|
code: string;
|
|
label: string;
|
|
weight: number;
|
|
value: string;
|
|
};
|
|
|
|
export type ContactDuplicateSuggestion = {
|
|
left: Contact;
|
|
right: Contact;
|
|
score: number;
|
|
confidence: "possible" | "likely" | "strong";
|
|
features: ContactDuplicateFeature[];
|
|
};
|
|
|
|
export type ContactDuplicateSuggestionList = {
|
|
suggestions: ContactDuplicateSuggestion[];
|
|
scanned_contacts: number;
|
|
candidate_pairs: number;
|
|
truncated: boolean;
|
|
};
|
|
|
|
export type ContactMergeRecord = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
address_book_id: string;
|
|
winner_contact_id: string;
|
|
loser_contact_ids: string[];
|
|
status: string;
|
|
reason: string;
|
|
survivorship: Record<string, unknown>;
|
|
decisions: Array<Record<string, unknown>>;
|
|
before_hash: string;
|
|
after_hash: string;
|
|
created_by_account_id?: string | null;
|
|
recovered_at?: string | null;
|
|
recovered_by_account_id?: string | null;
|
|
recovery_action?: string | null;
|
|
recovery_reason?: string | null;
|
|
provenance: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type AddressQualityCorrection = {
|
|
contact_id: string;
|
|
display_name: string;
|
|
channel: "email" | "phone" | "postal" | "internal_mail" | "portal";
|
|
contact_point_id?: string | null;
|
|
state: ContactPointQualityState;
|
|
reason_code: string;
|
|
reason?: string | null;
|
|
effective_from: string;
|
|
};
|
|
|
|
export type AddressQualitySummary = {
|
|
contact_count: number;
|
|
contact_point_count: number;
|
|
quality_counts: Record<string, number>;
|
|
duplicate_suggestion_count: number;
|
|
correction_count: number;
|
|
corrections: AddressQualityCorrection[];
|
|
truncated: boolean;
|
|
};
|
|
|
|
export type AddressDistributionChannel = "email" | "postal" | "internal_mail" | "portal";
|
|
export type AddressChannelDecision =
|
|
| "allowed"
|
|
| "opted_in"
|
|
| "preferred"
|
|
| "opted_out"
|
|
| "suppressed"
|
|
| "invalid"
|
|
| "returned"
|
|
| "temporarily_unavailable";
|
|
|
|
export type ContactChannelRule = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
contact_id: string;
|
|
channel: AddressDistributionChannel;
|
|
purpose?: string | null;
|
|
contact_point_id?: string | null;
|
|
decision: AddressChannelDecision;
|
|
legal_basis?: string | null;
|
|
evidence_ref?: string | null;
|
|
reason?: string | null;
|
|
preference_rank?: number | null;
|
|
locale?: string | null;
|
|
effective_from?: string | null;
|
|
effective_until?: string | null;
|
|
metadata: Record<string, unknown>;
|
|
created_by_account_id?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type ContactChannelRulePayload = {
|
|
channel: AddressDistributionChannel;
|
|
purpose?: string | null;
|
|
contact_point_id?: string | null;
|
|
decision: AddressChannelDecision;
|
|
legal_basis?: string | null;
|
|
evidence_ref?: string | null;
|
|
reason?: string | null;
|
|
preference_rank?: number | null;
|
|
locale?: string | null;
|
|
effective_from?: string | null;
|
|
effective_until?: string | null;
|
|
metadata?: Record<string, unknown>;
|
|
};
|
|
|
|
export type AddressBookCreatePayload = {
|
|
scope_type: AddressBookScope;
|
|
group_id?: string | null;
|
|
name: string;
|
|
description?: string | null;
|
|
};
|
|
|
|
export type AddressBookUpdatePayload = {
|
|
name?: string | null;
|
|
description?: string | null;
|
|
};
|
|
|
|
export type AddressListPayload = {
|
|
name: string;
|
|
description?: string | null;
|
|
};
|
|
|
|
export type AddressListEntryPayload = {
|
|
contact_id: string;
|
|
contact_email_id?: string | null;
|
|
contact_postal_address_id?: string | null;
|
|
label?: string | null;
|
|
};
|
|
|
|
export type AddressListEntry = {
|
|
id: string;
|
|
address_list_id: string;
|
|
contact_id: string;
|
|
contact_email_id?: string | null;
|
|
contact_postal_address_id?: string | null;
|
|
target_kind: string;
|
|
label?: string | null;
|
|
order_index: number;
|
|
contact_display_name: string;
|
|
email?: string | null;
|
|
postal_address?: string | null;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type ContactPayload = {
|
|
display_name?: string | null;
|
|
given_name?: string | null;
|
|
family_name?: string | null;
|
|
organization?: string | null;
|
|
role_title?: string | null;
|
|
note?: string | null;
|
|
tags?: string[];
|
|
emails?: Array<Omit<ContactEmail, "id">>;
|
|
phones?: Array<Omit<ContactPhone, "id">>;
|
|
postal_addresses?: Array<Omit<ContactPostalAddress, "id">>;
|
|
provenance?: Record<string, unknown>;
|
|
};
|
|
|
|
export type AddressBookWriteOperation = "create_contact" | "update_contact" | "delete_contact";
|
|
|
|
export type AddressBookWriteDecision = {
|
|
address_book_id: string;
|
|
address_book_label?: string | null;
|
|
operation: string;
|
|
allowed: boolean;
|
|
reason: string;
|
|
message: string;
|
|
scope_type?: AddressBookScope | null;
|
|
scope_id?: string | null;
|
|
tenant_id?: string | null;
|
|
source_kind?: string | null;
|
|
read_only: boolean;
|
|
required_scopes: string[];
|
|
provenance: Record<string, unknown>;
|
|
};
|
|
|
|
type AddressBookListResponse = {
|
|
address_books: AddressBook[];
|
|
};
|
|
|
|
type AddressListListResponse = {
|
|
address_lists: AddressList[];
|
|
};
|
|
|
|
type AddressListEntryListResponse = {
|
|
entries: AddressListEntry[];
|
|
};
|
|
|
|
export type ContactListResponse = {
|
|
contacts: Contact[];
|
|
total: number;
|
|
offset: number;
|
|
limit: number;
|
|
has_more: boolean;
|
|
};
|
|
|
|
type AddressBookWriteTargetsResponse = {
|
|
targets: AddressBookWriteDecision[];
|
|
};
|
|
|
|
export type VCardImportResult = {
|
|
imported: number;
|
|
skipped: number;
|
|
contacts: Contact[];
|
|
issues: Array<{ index: number; message: string; severity: "warning" | "error"; field?: string | null; line?: number | null }>;
|
|
};
|
|
|
|
export type AddressSyncSource = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
address_book_id: string;
|
|
connector_type: string;
|
|
display_name: string;
|
|
external_account_ref?: string | null;
|
|
external_address_book_ref?: string | null;
|
|
sync_direction: string;
|
|
read_only: boolean;
|
|
enabled: boolean;
|
|
status: string;
|
|
sync_token?: string | null;
|
|
etag?: string | null;
|
|
remote_revision?: string | null;
|
|
last_attempted_at?: string | null;
|
|
last_success_at?: string | null;
|
|
last_error?: string | null;
|
|
last_diagnostic?: Record<string, unknown> | null;
|
|
metadata: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type AddressCardDavAddressBook = {
|
|
collection_url: string;
|
|
href: string;
|
|
display_name?: string | null;
|
|
description?: string | null;
|
|
ctag?: string | null;
|
|
sync_token?: string | null;
|
|
};
|
|
|
|
export type AddressCredentialEnvelope = {
|
|
id: string;
|
|
scope_type: string;
|
|
scope_id?: string | null;
|
|
name: string;
|
|
description?: string | null;
|
|
credential_kind: string;
|
|
public_data: Record<string, unknown>;
|
|
secret_keys: string[];
|
|
secret_configured: boolean;
|
|
allowed_modules: string[];
|
|
inherit_to_lower_scopes: boolean;
|
|
is_active: boolean;
|
|
revision: string;
|
|
};
|
|
|
|
export type AddressSyncPlanStats = {
|
|
created: number;
|
|
updated: number;
|
|
deleted: number;
|
|
conflicts: number;
|
|
unchanged: number;
|
|
errors: number;
|
|
fetched: number;
|
|
full_sync: boolean;
|
|
used_sync_token: boolean;
|
|
sync_token?: string | null;
|
|
etag?: string | null;
|
|
remote_revision?: string | null;
|
|
};
|
|
|
|
export type AddressSyncPlanItem = {
|
|
action: "create" | "update" | "delete" | "remote_create" | "remote_update" | "remote_delete" | "conflict" | "unchanged" | "error";
|
|
href?: string | null;
|
|
remote_uid?: string | null;
|
|
contact_id?: string | null;
|
|
display_name?: string | null;
|
|
etag?: string | null;
|
|
message?: string | null;
|
|
};
|
|
|
|
export type AddressSyncPlan = {
|
|
sync_source: AddressSyncSource;
|
|
stats: AddressSyncPlanStats;
|
|
items: AddressSyncPlanItem[];
|
|
};
|
|
|
|
export type AddressSyncDiagnostic = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
sync_source_id: string;
|
|
severity: string;
|
|
code: string;
|
|
message: string;
|
|
details: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type AddressSyncTombstone = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
sync_source_id: string;
|
|
address_book_id: string;
|
|
contact_id?: string | null;
|
|
remote_uid?: string | null;
|
|
resource_href?: string | null;
|
|
local_deleted_at?: string | null;
|
|
remote_deleted_at?: string | null;
|
|
synced_at?: string | null;
|
|
metadata: Record<string, unknown>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
};
|
|
|
|
export type AddressSyncConflict = {
|
|
id: string;
|
|
tenant_id?: string | null;
|
|
sync_source_id: string;
|
|
address_book_id: string;
|
|
contact_id?: string | null;
|
|
remote_uid?: string | null;
|
|
resource_href?: string | null;
|
|
field_path: string;
|
|
local_value?: Record<string, unknown> | null;
|
|
remote_value?: Record<string, unknown> | null;
|
|
local_updated_at?: string | null;
|
|
remote_updated_at?: string | null;
|
|
status: string;
|
|
resolution?: string | null;
|
|
resolved_at?: string | null;
|
|
resolved_by_account_id?: string | null;
|
|
metadata: Record<string, unknown>;
|
|
created_at: string;
|
|
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[];
|
|
};
|
|
|
|
type AddressCardDavDiscoveryResponse = {
|
|
address_books: AddressCardDavAddressBook[];
|
|
};
|
|
|
|
type AddressCredentialEnvelopeListResponse = {
|
|
credentials: AddressCredentialEnvelope[];
|
|
};
|
|
|
|
type AddressSyncDiagnosticListResponse = {
|
|
diagnostics: AddressSyncDiagnostic[];
|
|
};
|
|
|
|
type AddressSyncTombstoneListResponse = {
|
|
tombstones: AddressSyncTombstone[];
|
|
};
|
|
|
|
type AddressSyncConflictListResponse = {
|
|
conflicts: AddressSyncConflict[];
|
|
};
|
|
|
|
type AddressImportProfileListResponse = {
|
|
profiles: AddressImportProfile[];
|
|
};
|
|
|
|
type AddressLdapDiscoveryResponse = {
|
|
base_dns: string[];
|
|
};
|
|
|
|
type ContactChannelRuleListResponse = {
|
|
rules: ContactChannelRule[];
|
|
};
|
|
|
|
type ContactPointQualityDecisionListResponse = {
|
|
decisions: ContactPointQualityDecision[];
|
|
};
|
|
|
|
type ContactMergeRecordListResponse = {
|
|
merges: ContactMergeRecord[];
|
|
};
|
|
|
|
function queryString(params: Record<string, string | number | null | undefined>): string {
|
|
const search = new URLSearchParams();
|
|
for (const [key, value] of Object.entries(params)) {
|
|
if (value !== null && value !== undefined && value !== "") search.set(key, String(value));
|
|
}
|
|
const serialized = search.toString();
|
|
return serialized ? `?${serialized}` : "";
|
|
}
|
|
|
|
export async function listAddressBooks(settings: ApiSettings, options: {includeDeleted?: boolean;} = {}): Promise<AddressBook[]> {
|
|
const response = await apiFetch<AddressBookListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/address-books${queryString({ include_deleted: options.includeDeleted ? "true" : null })}`
|
|
);
|
|
return response.address_books;
|
|
}
|
|
|
|
export function createAddressBook(settings: ApiSettings, payload: AddressBookCreatePayload): Promise<AddressBook> {
|
|
return apiFetch<AddressBook>(settings, "/api/v1/addresses/address-books", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function updateAddressBook(settings: ApiSettings, bookId: string, payload: AddressBookUpdatePayload): Promise<AddressBook> {
|
|
return apiFetch<AddressBook>(settings, `/api/v1/addresses/address-books/${bookId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function deleteAddressBook(settings: ApiSettings, bookId: string): Promise<void> {
|
|
return apiFetch<void>(settings, `/api/v1/addresses/address-books/${bookId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function restoreAddressBook(settings: ApiSettings, bookId: string): Promise<AddressBook> {
|
|
return apiFetch<AddressBook>(settings, `/api/v1/addresses/address-books/${bookId}/restore`, { method: "POST" });
|
|
}
|
|
|
|
export async function listAddressLists(
|
|
settings: ApiSettings,
|
|
options: { addressBookId?: string | null; includeDeleted?: boolean } = {}
|
|
): Promise<AddressList[]> {
|
|
const response = await apiFetch<AddressListListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/address-lists${queryString({
|
|
address_book_id: options.addressBookId,
|
|
include_deleted: options.includeDeleted ? "true" : null
|
|
})}`
|
|
);
|
|
return response.address_lists;
|
|
}
|
|
|
|
export function createAddressList(settings: ApiSettings, addressBookId: string, payload: AddressListPayload): Promise<AddressList> {
|
|
return apiFetch<AddressList>(settings, `/api/v1/addresses/address-books/${addressBookId}/address-lists`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function updateAddressList(settings: ApiSettings, addressListId: string, payload: Partial<AddressListPayload>): Promise<AddressList> {
|
|
return apiFetch<AddressList>(settings, `/api/v1/addresses/address-lists/${addressListId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function deleteAddressList(settings: ApiSettings, addressListId: string): Promise<void> {
|
|
return apiFetch<void>(settings, `/api/v1/addresses/address-lists/${addressListId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function restoreAddressList(settings: ApiSettings, addressListId: string): Promise<AddressList> {
|
|
return apiFetch<AddressList>(settings, `/api/v1/addresses/address-lists/${addressListId}/restore`, { method: "POST" });
|
|
}
|
|
|
|
export async function listAddressListEntries(settings: ApiSettings, addressListId: string): Promise<AddressListEntry[]> {
|
|
const response = await apiFetch<AddressListEntryListResponse>(settings, `/api/v1/addresses/address-lists/${addressListId}/entries`);
|
|
return response.entries;
|
|
}
|
|
|
|
export function createAddressListEntry(
|
|
settings: ApiSettings,
|
|
addressListId: string,
|
|
payload: AddressListEntryPayload
|
|
): Promise<AddressListEntry> {
|
|
return apiFetch<AddressListEntry>(settings, `/api/v1/addresses/address-lists/${addressListId}/entries`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function deleteAddressListEntry(settings: ApiSettings, entryId: string): Promise<void> {
|
|
return apiFetch<void>(settings, `/api/v1/addresses/address-list-entries/${entryId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export async function listAddressBookWriteTargets(
|
|
settings: ApiSettings,
|
|
operation: AddressBookWriteOperation = "create_contact"
|
|
): Promise<AddressBookWriteDecision[]> {
|
|
const response = await apiFetch<AddressBookWriteTargetsResponse>(
|
|
settings,
|
|
`/api/v1/addresses/write-targets${queryString({ operation })}`
|
|
);
|
|
return response.targets;
|
|
}
|
|
|
|
export function getAddressBookWriteDecision(
|
|
settings: ApiSettings,
|
|
addressBookId: string,
|
|
operation: AddressBookWriteOperation = "create_contact"
|
|
): Promise<AddressBookWriteDecision> {
|
|
return apiFetch<AddressBookWriteDecision>(
|
|
settings,
|
|
`/api/v1/addresses/address-books/${addressBookId}/write-decision${queryString({ operation })}`
|
|
);
|
|
}
|
|
|
|
export async function listAddressSyncSources(
|
|
settings: ApiSettings,
|
|
options: { addressBookId?: string | null; includeDisabled?: boolean } = {}
|
|
): Promise<AddressSyncSource[]> {
|
|
const response = await apiFetch<AddressSyncSourceListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/sync-sources${queryString({
|
|
address_book_id: options.addressBookId,
|
|
include_disabled: options.includeDisabled ? "true" : null
|
|
})}`
|
|
);
|
|
return response.sync_sources;
|
|
}
|
|
|
|
export function discoverCardDavAddressBooks(
|
|
settings: ApiSettings,
|
|
payload: {
|
|
url: string;
|
|
auth_type: "none" | "basic" | "bearer";
|
|
username?: string | null;
|
|
password?: string | null;
|
|
bearer_token?: string | null;
|
|
credential_ref?: string | null;
|
|
}
|
|
): Promise<AddressCardDavAddressBook[]> {
|
|
return apiFetch<AddressCardDavDiscoveryResponse>(settings, "/api/v1/addresses/carddav/discover", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
}).then((response) => response.address_books);
|
|
}
|
|
|
|
export function createCardDavSyncSource(
|
|
settings: ApiSettings,
|
|
addressBookId: string,
|
|
payload: {
|
|
collection_url: string;
|
|
display_name?: string | null;
|
|
auth_type: "none" | "basic" | "bearer";
|
|
username?: string | null;
|
|
password?: string | null;
|
|
bearer_token?: string | null;
|
|
credential_ref?: string | null;
|
|
sync_direction: "read_only" | "import" | "export" | "two_way";
|
|
read_only?: boolean | null;
|
|
sync_token?: string | null;
|
|
etag?: string | null;
|
|
remote_revision?: string | null;
|
|
}
|
|
): Promise<AddressSyncSource> {
|
|
return apiFetch<AddressSyncSource>(settings, `/api/v1/addresses/address-books/${addressBookId}/carddav/sources`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
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
|
|
): Promise<AddressCredentialEnvelope[]> {
|
|
const response = await apiFetch<AddressCredentialEnvelopeListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/credentials${queryString({ source_id: sourceId })}`
|
|
);
|
|
return response.credentials;
|
|
}
|
|
|
|
export function updateAddressSyncSource(
|
|
settings: ApiSettings,
|
|
syncSourceId: string,
|
|
payload: Partial<{
|
|
display_name: string | null;
|
|
external_account_ref: string | null;
|
|
external_address_book_ref: string | null;
|
|
sync_direction: "read_only" | "import" | "export" | "two_way";
|
|
read_only: boolean | null;
|
|
enabled: boolean | null;
|
|
sync_token: string | null;
|
|
etag: string | null;
|
|
remote_revision: string | null;
|
|
metadata: Record<string, unknown> | null;
|
|
}>
|
|
): Promise<AddressSyncSource> {
|
|
return apiFetch<AddressSyncSource>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function deleteAddressSyncSource(settings: ApiSettings, syncSourceId: string): Promise<void> {
|
|
return apiFetch<void>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function previewAddressSyncSource(settings: ApiSettings, syncSourceId: string, options: { forceFull?: boolean } = {}): Promise<AddressSyncPlan> {
|
|
return apiFetch<AddressSyncPlan>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/preview`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ force_full: Boolean(options.forceFull) })
|
|
});
|
|
}
|
|
|
|
export function runAddressSyncSource(settings: ApiSettings, syncSourceId: string, options: { forceFull?: boolean } = {}): Promise<AddressSyncPlan> {
|
|
return apiFetch<AddressSyncPlan>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/run`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ force_full: Boolean(options.forceFull) })
|
|
});
|
|
}
|
|
|
|
export async function listAddressSyncDiagnostics(settings: ApiSettings, syncSourceId: string): Promise<AddressSyncDiagnostic[]> {
|
|
const response = await apiFetch<AddressSyncDiagnosticListResponse>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/diagnostics`);
|
|
return response.diagnostics;
|
|
}
|
|
|
|
export async function listAddressSyncTombstones(settings: ApiSettings, syncSourceId: string): Promise<AddressSyncTombstone[]> {
|
|
const response = await apiFetch<AddressSyncTombstoneListResponse>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/tombstones`);
|
|
return response.tombstones;
|
|
}
|
|
|
|
export async function listAddressSyncConflicts(settings: ApiSettings, syncSourceId: string, status = "open"): Promise<AddressSyncConflict[]> {
|
|
const response = await apiFetch<AddressSyncConflictListResponse>(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/conflicts${queryString({ status })}`);
|
|
return response.conflicts;
|
|
}
|
|
|
|
export function resolveAddressSyncConflict(
|
|
settings: ApiSettings,
|
|
conflictId: string,
|
|
resolution = "manual",
|
|
status = "resolved",
|
|
options: { mergedPayload?: Record<string, unknown> | null } = {}
|
|
): Promise<AddressSyncConflict> {
|
|
return apiFetch<AddressSyncConflict>(settings, `/api/v1/addresses/sync-conflicts/${conflictId}/resolve`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ resolution, status, merged_payload: options.mergedPayload ?? null })
|
|
});
|
|
}
|
|
|
|
export function listContactsPage(settings: ApiSettings, options: {addressBookId?: string | null;addressListId?: string | null;query?: string | null;limit?: number;offset?: number;includeDeleted?: boolean;} = {}): Promise<ContactListResponse> {
|
|
return apiFetch<ContactListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/contacts${queryString({ address_book_id: options.addressBookId, address_list_id: options.addressListId, query: options.query, limit: options.limit, offset: options.offset, include_deleted: options.includeDeleted ? "true" : null })}`
|
|
);
|
|
}
|
|
|
|
export async function listContacts(settings: ApiSettings, options: {addressBookId?: string | null;addressListId?: string | null;query?: string | null;limit?: number;offset?: number;includeDeleted?: boolean;} = {}): Promise<Contact[]> {
|
|
const response = await listContactsPage(settings, options);
|
|
return response.contacts;
|
|
}
|
|
|
|
export function createContact(settings: ApiSettings, addressBookId: string, payload: ContactPayload): Promise<Contact> {
|
|
return apiFetch<Contact>(settings, `/api/v1/addresses/address-books/${addressBookId}/contacts`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function updateContact(settings: ApiSettings, contactId: string, payload: ContactPayload): Promise<Contact> {
|
|
return apiFetch<Contact>(settings, `/api/v1/addresses/contacts/${contactId}`, {
|
|
method: "PATCH",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function deleteContact(settings: ApiSettings, contactId: string): Promise<void> {
|
|
return apiFetch<void>(settings, `/api/v1/addresses/contacts/${contactId}`, { method: "DELETE" });
|
|
}
|
|
|
|
export function restoreContact(settings: ApiSettings, contactId: string): Promise<Contact> {
|
|
return apiFetch<Contact>(settings, `/api/v1/addresses/contacts/${contactId}/restore`, { method: "POST" });
|
|
}
|
|
|
|
export function getAddressQualitySummary(settings: ApiSettings, addressBookId: string): Promise<AddressQualitySummary> {
|
|
return apiFetch<AddressQualitySummary>(settings, `/api/v1/addresses/address-books/${addressBookId}/quality-summary`);
|
|
}
|
|
|
|
export function listContactDuplicateSuggestions(
|
|
settings: ApiSettings,
|
|
addressBookId: string,
|
|
options: { contactId?: string | null; minimumScore?: number; limit?: number; scanLimit?: number } = {}
|
|
): Promise<ContactDuplicateSuggestionList> {
|
|
return apiFetch<ContactDuplicateSuggestionList>(
|
|
settings,
|
|
`/api/v1/addresses/address-books/${addressBookId}/duplicate-suggestions${queryString({
|
|
contact_id: options.contactId,
|
|
minimum_score: options.minimumScore,
|
|
limit: options.limit,
|
|
scan_limit: options.scanLimit
|
|
})}`
|
|
);
|
|
}
|
|
|
|
export async function listContactQualityDecisions(settings: ApiSettings, contactId: string): Promise<ContactPointQualityDecision[]> {
|
|
const response = await apiFetch<ContactPointQualityDecisionListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/contacts/${contactId}/quality-decisions`
|
|
);
|
|
return response.decisions;
|
|
}
|
|
|
|
export function createContactQualityDecision(
|
|
settings: ApiSettings,
|
|
contactId: string,
|
|
payload: {
|
|
channel: ContactPointQualityDecision["channel"];
|
|
contact_point_id?: string | null;
|
|
state: ContactPointQualityState;
|
|
reason_code?: string | null;
|
|
reason?: string | null;
|
|
evidence_ref?: string | null;
|
|
}
|
|
): Promise<ContactPointQualityDecision> {
|
|
return apiFetch<ContactPointQualityDecision>(settings, `/api/v1/addresses/contacts/${contactId}/quality-decisions`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function listContactProvenance(
|
|
settings: ApiSettings,
|
|
contactId: string,
|
|
options: { currentOnly?: boolean; limit?: number } = {}
|
|
): Promise<ContactFieldProvenance[]> {
|
|
return apiFetch<ContactFieldProvenance[]>(
|
|
settings,
|
|
`/api/v1/addresses/contacts/${contactId}/provenance${queryString({
|
|
current_only: options.currentOnly ? "true" : null,
|
|
limit: options.limit
|
|
})}`
|
|
);
|
|
}
|
|
|
|
export async function listContactMerges(
|
|
settings: ApiSettings,
|
|
options: { addressBookId?: string | null; contactId?: string | null; limit?: number } = {}
|
|
): Promise<ContactMergeRecord[]> {
|
|
const response = await apiFetch<ContactMergeRecordListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/contact-merges${queryString({
|
|
address_book_id: options.addressBookId,
|
|
contact_id: options.contactId,
|
|
limit: options.limit
|
|
})}`
|
|
);
|
|
return response.merges;
|
|
}
|
|
|
|
export function mergeContacts(
|
|
settings: ApiSettings,
|
|
payload: {
|
|
winner_contact_id: string;
|
|
duplicate_contact_ids: string[];
|
|
reason: string;
|
|
field_sources?: Record<string, string>;
|
|
contact_point_strategy?: "union" | "winner_only";
|
|
source_precedence?: string[];
|
|
}
|
|
): Promise<ContactMergeRecord> {
|
|
return apiFetch<ContactMergeRecord>(settings, "/api/v1/addresses/contact-merges", {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function recoverContactMerge(
|
|
settings: ApiSettings,
|
|
merge: ContactMergeRecord,
|
|
action: "undo" | "split",
|
|
reason: string
|
|
): Promise<ContactMergeRecord> {
|
|
return apiFetch<ContactMergeRecord>(settings, `/api/v1/addresses/contact-merges/${merge.id}/${action}`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ reason, expected_after_hash: merge.after_hash })
|
|
});
|
|
}
|
|
|
|
export async function listContactChannelRules(settings: ApiSettings, contactId: string): Promise<ContactChannelRule[]> {
|
|
const response = await apiFetch<ContactChannelRuleListResponse>(
|
|
settings,
|
|
`/api/v1/addresses/contacts/${contactId}/channel-rules`
|
|
);
|
|
return response.rules;
|
|
}
|
|
|
|
export function createContactChannelRule(
|
|
settings: ApiSettings,
|
|
contactId: string,
|
|
payload: ContactChannelRulePayload
|
|
): Promise<ContactChannelRule> {
|
|
return apiFetch<ContactChannelRule>(settings, `/api/v1/addresses/contacts/${contactId}/channel-rules`, {
|
|
method: "POST",
|
|
body: JSON.stringify(payload)
|
|
});
|
|
}
|
|
|
|
export function endContactChannelRule(settings: ApiSettings, ruleId: string): Promise<ContactChannelRule> {
|
|
return apiFetch<ContactChannelRule>(settings, `/api/v1/addresses/contact-channel-rules/${ruleId}`, {
|
|
method: "DELETE"
|
|
});
|
|
}
|
|
|
|
export function importAddressBookVcards(settings: ApiSettings, addressBookId: string, content: string): Promise<VCardImportResult> {
|
|
return apiFetch<VCardImportResult>(settings, `/api/v1/addresses/address-books/${addressBookId}/vcards/import`, {
|
|
method: "POST",
|
|
body: JSON.stringify({ content })
|
|
});
|
|
}
|
|
|
|
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`);
|
|
}
|
|
|
|
export function exportContactVcard(settings: ApiSettings, contactId: string): Promise<string> {
|
|
return apiFetch<string>(settings, `/api/v1/addresses/contacts/${contactId}/vcard`);
|
|
}
|