Files
govoplan-addresses/src/govoplan_addresses/backend/schemas.py
2026-07-14 13:22:09 +02:00

511 lines
16 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel, ConfigDict, Field, SecretStr
AddressBookScope = Literal["user", "group", "tenant", "system"]
AddressSyncDirection = Literal["read_only", "import", "export", "two_way"]
AddressSyncStatus = Literal["idle", "running", "succeeded", "failed", "conflict", "disabled"]
AddressSyncDiagnosticSeverity = Literal["debug", "info", "warning", "error"]
AddressSyncConflictStatus = Literal["open", "resolved", "ignored"]
AddressSyncConflictResolution = Literal["keep_local", "use_remote", "merge", "manual", "ignored"]
AddressCardDavAuthType = Literal["none", "basic", "bearer"]
AddressSyncPlanAction = Literal["create", "update", "delete", "remote_create", "remote_update", "remote_delete", "conflict", "unchanged", "error"]
class ContactEmailPayload(BaseModel):
label: str | None = Field(default=None, max_length=80)
email: str = Field(max_length=320)
is_primary: bool = False
class ContactPhonePayload(BaseModel):
label: str | None = Field(default=None, max_length=80)
phone: str = Field(max_length=100)
is_primary: bool = False
class ContactPostalAddressPayload(BaseModel):
label: str | None = Field(default=None, max_length=80)
street: str | None = Field(default=None, max_length=500)
postal_code: str | None = Field(default=None, max_length=40)
locality: str | None = Field(default=None, max_length=255)
region: str | None = Field(default=None, max_length=255)
country: str | None = Field(default=None, max_length=255)
is_primary: bool = False
class AddressBookCreateRequest(BaseModel):
scope_type: AddressBookScope = "user"
group_id: str | None = Field(default=None, max_length=36)
name: str = Field(min_length=1, max_length=255)
description: str | None = None
class AddressBookUpdateRequest(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = None
class AddressBookResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
scope_type: AddressBookScope
scope_id: str | None = None
name: str
description: str | None = None
source_kind: str
source_ref: str | None = None
read_only: bool
sync_status: str | None = None
sync_error: str | None = None
contact_count: int = 0
deleted_at: datetime | None = None
created_at: datetime
updated_at: datetime
class AddressBookListResponse(BaseModel):
address_books: list[AddressBookResponse]
class AddressListCreateRequest(BaseModel):
name: str = Field(min_length=1, max_length=255)
description: str | None = None
class AddressListUpdateRequest(BaseModel):
name: str | None = Field(default=None, min_length=1, max_length=255)
description: str | None = None
class AddressListEntryCreateRequest(BaseModel):
contact_id: str = Field(max_length=36)
contact_email_id: str | None = Field(default=None, max_length=36)
contact_postal_address_id: str | None = Field(default=None, max_length=36)
label: str | None = Field(default=None, max_length=255)
class AddressListResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
address_book_id: str
name: str
description: str | None = None
source_kind: str
source_ref: str | None = None
read_only: bool
entry_count: int = 0
deleted_at: datetime | None = None
created_at: datetime
updated_at: datetime
class AddressListListResponse(BaseModel):
address_lists: list[AddressListResponse]
class AddressListEntryResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
address_list_id: str
contact_id: str
contact_email_id: str | None = None
contact_postal_address_id: str | None = None
target_kind: str
label: str | None = None
order_index: int
contact_display_name: str
email: str | None = None
postal_address: str | None = None
created_at: datetime
updated_at: datetime
class AddressListEntryListResponse(BaseModel):
entries: list[AddressListEntryResponse]
class ContactCreateRequest(BaseModel):
display_name: str | None = Field(default=None, max_length=255)
given_name: str | None = Field(default=None, max_length=255)
family_name: str | None = Field(default=None, max_length=255)
organization: str | None = Field(default=None, max_length=255)
role_title: str | None = Field(default=None, max_length=255)
note: str | None = None
tags: list[str] = Field(default_factory=list)
emails: list[ContactEmailPayload] = Field(default_factory=list)
phones: list[ContactPhonePayload] = Field(default_factory=list)
postal_addresses: list[ContactPostalAddressPayload] = Field(default_factory=list)
provenance: dict[str, Any] = Field(default_factory=dict)
class ContactUpdateRequest(BaseModel):
display_name: str | None = Field(default=None, max_length=255)
given_name: str | None = Field(default=None, max_length=255)
family_name: str | None = Field(default=None, max_length=255)
organization: str | None = Field(default=None, max_length=255)
role_title: str | None = Field(default=None, max_length=255)
note: str | None = None
tags: list[str] | None = None
emails: list[ContactEmailPayload] | None = None
phones: list[ContactPhonePayload] | None = None
postal_addresses: list[ContactPostalAddressPayload] | None = None
provenance: dict[str, Any] | None = None
class ContactEmailResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
label: str | None = None
email: str
is_primary: bool
class ContactPhoneResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
label: str | None = None
phone: str
is_primary: bool
class ContactPostalAddressResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
label: str | None = None
street: str | None = None
postal_code: str | None = None
locality: str | None = None
region: str | None = None
country: str | None = None
is_primary: bool
class ContactResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
address_book_id: str
display_name: str
given_name: str | None = None
family_name: str | None = None
organization: str | None = None
role_title: str | None = None
note: str | None = None
tags: list[str]
source_kind: str
source_ref: str | None = None
source_payload_kind: str | None = None
source_revision: str | None = None
provenance: dict[str, Any]
emails: list[ContactEmailResponse]
phones: list[ContactPhoneResponse]
postal_addresses: list[ContactPostalAddressResponse]
deleted_at: datetime | None = None
created_at: datetime
updated_at: datetime
class ContactListResponse(BaseModel):
contacts: list[ContactResponse]
class AddressLookupResponse(BaseModel):
contacts: list[ContactResponse]
class AddressBookWriteDecisionResponse(BaseModel):
address_book_id: str
address_book_label: str | None = None
operation: str
allowed: bool
reason: str
message: str
scope_type: AddressBookScope | None = None
scope_id: str | None = None
tenant_id: str | None = None
source_kind: str | None = None
read_only: bool = False
required_scopes: list[str] = Field(default_factory=list)
provenance: dict[str, Any] = Field(default_factory=dict)
class AddressBookWriteTargetsResponse(BaseModel):
targets: list[AddressBookWriteDecisionResponse] = Field(default_factory=list)
class AddressSyncSourceCreateRequest(BaseModel):
connector_type: str = Field(min_length=1, max_length=60)
display_name: str = Field(min_length=1, max_length=255)
external_account_ref: str | None = Field(default=None, max_length=1000)
external_address_book_ref: str | None = Field(default=None, max_length=1000)
sync_direction: AddressSyncDirection = "read_only"
read_only: bool | None = None
enabled: bool = True
sync_token: str | None = None
etag: str | None = Field(default=None, max_length=1000)
remote_revision: str | None = Field(default=None, max_length=1000)
metadata: dict[str, Any] = Field(default_factory=dict)
class AddressSyncSourceUpdateRequest(BaseModel):
display_name: str | None = Field(default=None, min_length=1, max_length=255)
external_account_ref: str | None = Field(default=None, max_length=1000)
external_address_book_ref: str | None = Field(default=None, max_length=1000)
sync_direction: AddressSyncDirection | None = None
read_only: bool | None = None
enabled: bool | None = None
sync_token: str | None = None
etag: str | None = Field(default=None, max_length=1000)
remote_revision: str | None = Field(default=None, max_length=1000)
metadata: dict[str, Any] | None = None
class AddressSyncAttemptFinishRequest(BaseModel):
status: Literal["succeeded", "failed", "conflict"]
sync_token: str | None = None
etag: str | None = Field(default=None, max_length=1000)
remote_revision: str | None = Field(default=None, max_length=1000)
error: str | None = None
diagnostic: dict[str, Any] | None = None
class AddressSyncSourceResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
address_book_id: str
connector_type: str
display_name: str
external_account_ref: str | None = None
external_address_book_ref: str | None = None
sync_direction: str
read_only: bool
enabled: bool
status: str
sync_token: str | None = None
etag: str | None = None
remote_revision: str | None = None
last_attempted_at: datetime | None = None
last_success_at: datetime | None = None
last_error: str | None = None
last_diagnostic: dict[str, Any] | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
created_at: datetime
updated_at: datetime
class AddressSyncSourceListResponse(BaseModel):
sync_sources: list[AddressSyncSourceResponse]
class AddressSyncDiagnosticCreateRequest(BaseModel):
severity: AddressSyncDiagnosticSeverity = "info"
code: str = Field(min_length=1, max_length=120)
message: str = Field(min_length=1)
details: dict[str, Any] = Field(default_factory=dict)
class AddressSyncDiagnosticResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
sync_source_id: str
severity: str
code: str
message: str
details: dict[str, Any] = Field(default_factory=dict)
created_at: datetime
updated_at: datetime
class AddressSyncDiagnosticListResponse(BaseModel):
diagnostics: list[AddressSyncDiagnosticResponse]
class AddressSyncTombstoneCreateRequest(BaseModel):
contact_id: str | None = Field(default=None, max_length=36)
remote_uid: str | None = Field(default=None, max_length=1000)
resource_href: str | None = Field(default=None, max_length=1000)
local_deleted_at: datetime | None = None
remote_deleted_at: datetime | None = None
synced_at: datetime | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
class AddressSyncTombstoneResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
sync_source_id: str
address_book_id: str
contact_id: str | None = None
remote_uid: str | None = None
resource_href: str | None = None
local_deleted_at: datetime | None = None
remote_deleted_at: datetime | None = None
synced_at: datetime | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
created_at: datetime
updated_at: datetime
class AddressSyncTombstoneListResponse(BaseModel):
tombstones: list[AddressSyncTombstoneResponse]
class AddressSyncConflictCreateRequest(BaseModel):
contact_id: str | None = Field(default=None, max_length=36)
remote_uid: str | None = Field(default=None, max_length=1000)
resource_href: str | None = Field(default=None, max_length=1000)
field_path: str = Field(min_length=1, max_length=500)
local_value: dict[str, Any] | None = None
remote_value: dict[str, Any] | None = None
local_updated_at: datetime | None = None
remote_updated_at: datetime | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
class AddressSyncConflictResolveRequest(BaseModel):
resolution: AddressSyncConflictResolution
status: Literal["resolved", "ignored"] = "resolved"
merged_payload: ContactCreateRequest | None = None
class AddressSyncConflictResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
tenant_id: str | None = None
sync_source_id: str
address_book_id: str
contact_id: str | None = None
remote_uid: str | None = None
resource_href: str | None = None
field_path: str
local_value: dict[str, Any] | None = None
remote_value: dict[str, Any] | None = None
local_updated_at: datetime | None = None
remote_updated_at: datetime | None = None
status: str
resolution: str | None = None
resolved_at: datetime | None = None
resolved_by_account_id: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
created_at: datetime
updated_at: datetime
class AddressSyncConflictListResponse(BaseModel):
conflicts: list[AddressSyncConflictResponse]
class AddressCardDavDiscoveryRequest(BaseModel):
url: str = Field(min_length=1, max_length=2000)
auth_type: AddressCardDavAuthType = "none"
username: str | None = Field(default=None, max_length=320)
password: SecretStr | None = None
bearer_token: SecretStr | None = None
credential_ref: str | None = Field(default=None, max_length=1000)
source_id: str | None = Field(default=None, max_length=36)
class AddressCardDavAddressBookResponse(BaseModel):
collection_url: str
href: str
display_name: str | None = None
description: str | None = None
ctag: str | None = None
sync_token: str | None = None
class AddressCardDavDiscoveryResponse(BaseModel):
address_books: list[AddressCardDavAddressBookResponse]
class AddressCardDavSourceCreateRequest(BaseModel):
collection_url: str = Field(min_length=1, max_length=2000)
display_name: str | None = Field(default=None, max_length=255)
auth_type: AddressCardDavAuthType = "none"
username: str | None = Field(default=None, max_length=320)
password: SecretStr | None = None
bearer_token: SecretStr | None = None
credential_ref: str | None = Field(default=None, max_length=1000)
sync_direction: AddressSyncDirection = "read_only"
read_only: bool | None = None
sync_token: str | None = None
etag: str | None = Field(default=None, max_length=1000)
remote_revision: str | None = Field(default=None, max_length=1000)
class AddressSyncRunRequest(BaseModel):
force_full: bool = False
password: SecretStr | None = None
bearer_token: SecretStr | None = None
class AddressSyncPlanStats(BaseModel):
created: int = 0
updated: int = 0
deleted: int = 0
conflicts: int = 0
unchanged: int = 0
errors: int = 0
fetched: int = 0
full_sync: bool = False
used_sync_token: bool = False
sync_token: str | None = None
etag: str | None = None
remote_revision: str | None = None
class AddressSyncPlanItemResponse(BaseModel):
action: AddressSyncPlanAction
href: str | None = None
remote_uid: str | None = None
contact_id: str | None = None
display_name: str | None = None
etag: str | None = None
message: str | None = None
class AddressSyncPlanResponse(BaseModel):
sync_source: AddressSyncSourceResponse
stats: AddressSyncPlanStats
items: list[AddressSyncPlanItemResponse]
class VCardImportRequest(BaseModel):
content: str = Field(min_length=1)
class VCardImportIssue(BaseModel):
index: int
message: str
severity: Literal["warning", "error"] = "error"
field: str | None = None
line: int | None = None
class VCardImportResponse(BaseModel):
imported: int
skipped: int
contacts: list[ContactResponse]
issues: list[VCardImportIssue] = Field(default_factory=list)