145 lines
4.5 KiB
Python
145 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field, model_validator
|
|
|
|
|
|
VCardPlanAction = Literal["create", "update", "ignore", "unchanged", "conflict"]
|
|
VCardCommitAction = Literal["create", "update", "ignore"]
|
|
|
|
|
|
class VCardBatchFilePayload(BaseModel):
|
|
filename: str = Field(min_length=1, max_length=500)
|
|
content_base64: str = Field(min_length=1, max_length=14_000_000)
|
|
|
|
|
|
class VCardBatchPreviewRequest(BaseModel):
|
|
files: list[VCardBatchFilePayload] = Field(min_length=1, max_length=50)
|
|
duplicate_card_policy: Literal["reject", "first", "last"] = "reject"
|
|
existing_contact_policy: Literal["update", "ignore", "reject"] = "update"
|
|
|
|
|
|
class VCardDuplicateSuggestion(BaseModel):
|
|
contact_id: str
|
|
display_name: str
|
|
reasons: list[str] = Field(default_factory=list)
|
|
|
|
|
|
class VCardBatchPlanItemResponse(BaseModel):
|
|
source_key: str
|
|
source_filename: str
|
|
card_index: int
|
|
action: VCardPlanAction
|
|
allowed_actions: list[VCardCommitAction] = Field(default_factory=list)
|
|
contact_id: str | None = None
|
|
display_name: str | None = None
|
|
changed_fields: list[str] = Field(default_factory=list)
|
|
duplicate_suggestions: list[VCardDuplicateSuggestion] = Field(default_factory=list)
|
|
message: str | None = None
|
|
|
|
|
|
class VCardBatchDiagnosticResponse(BaseModel):
|
|
severity: Literal["info", "warning", "error"]
|
|
code: str
|
|
message: str
|
|
source_filename: str | None = None
|
|
card_index: int | None = None
|
|
field: str | None = None
|
|
details: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class VCardBatchProgressResponse(BaseModel):
|
|
total: int
|
|
completed: int
|
|
created: int = 0
|
|
updated: int = 0
|
|
ignored: int = 0
|
|
failed: int = 0
|
|
|
|
|
|
class VCardBatchRunResponse(BaseModel):
|
|
id: str
|
|
address_book_id: str
|
|
status: str
|
|
input_hash: str
|
|
plan_hash: str
|
|
parser_version: str
|
|
execution_mode: Literal["bounded_sync", "persisted_batch"]
|
|
file_count: int
|
|
card_count: int
|
|
statistics: dict[str, int | str] = Field(default_factory=dict)
|
|
diagnostics: list[VCardBatchDiagnosticResponse] = Field(default_factory=list)
|
|
plan: list[VCardBatchPlanItemResponse] = Field(default_factory=list)
|
|
progress: VCardBatchProgressResponse
|
|
can_apply: bool
|
|
can_cancel: bool
|
|
commit_hash: str | None = None
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
applied_at: datetime | None = None
|
|
|
|
|
|
class VCardBatchSelection(BaseModel):
|
|
source_key: str = Field(min_length=1, max_length=1000)
|
|
action: VCardCommitAction
|
|
|
|
|
|
class VCardBatchCommitRequest(BaseModel):
|
|
expected_plan_hash: str = Field(min_length=64, max_length=64)
|
|
selections: list[VCardBatchSelection] = Field(
|
|
default_factory=list, max_length=10_000
|
|
)
|
|
|
|
|
|
class VCardBatchCancelRequest(BaseModel):
|
|
expected_plan_hash: str = Field(min_length=64, max_length=64)
|
|
reason: str = Field(min_length=3, max_length=2000)
|
|
|
|
|
|
class VCardExportRequest(BaseModel):
|
|
scope: Literal["address_book", "address_list", "contacts"] = "address_book"
|
|
address_list_id: str | None = Field(default=None, max_length=36)
|
|
contact_ids: list[str] = Field(default_factory=list, max_length=10_000)
|
|
version: Literal["3.0", "4.0"] = "4.0"
|
|
|
|
@model_validator(mode="after")
|
|
def validate_scope(self) -> "VCardExportRequest":
|
|
if self.scope == "address_list" and not self.address_list_id:
|
|
raise ValueError("Address-list export requires address_list_id.")
|
|
if self.scope == "contacts" and not self.contact_ids:
|
|
raise ValueError(
|
|
"Selected-contact export requires at least one contact id."
|
|
)
|
|
if self.scope != "address_list" and self.address_list_id:
|
|
raise ValueError("address_list_id is only valid for address-list export.")
|
|
if self.scope != "contacts" and self.contact_ids:
|
|
raise ValueError("contact_ids are only valid for selected-contact export.")
|
|
if len(set(self.contact_ids)) != len(self.contact_ids):
|
|
raise ValueError("Selected contact ids must be unique.")
|
|
return self
|
|
|
|
|
|
class VCardExportResponse(BaseModel):
|
|
filename: str
|
|
media_type: str = "text/vcard"
|
|
scope: str
|
|
version: str
|
|
ordering: str
|
|
contact_count: int
|
|
content_hash: str
|
|
content: str
|
|
|
|
|
|
__all__ = [
|
|
"VCardBatchCancelRequest",
|
|
"VCardBatchCommitRequest",
|
|
"VCardBatchFilePayload",
|
|
"VCardBatchPreviewRequest",
|
|
"VCardBatchRunResponse",
|
|
"VCardBatchSelection",
|
|
"VCardExportRequest",
|
|
"VCardExportResponse",
|
|
]
|