166 lines
3.3 KiB
Python
166 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
ChangeAction = Literal["upsert", "delete"]
|
|
ChangeDomain = Literal["global", "report"]
|
|
EnterpriseRole = Literal["admin", "zootech", "viewer"]
|
|
ConflictResolution = Literal["keep_orchestrator", "keep_hub"]
|
|
|
|
|
|
class ChangeEventIn(BaseModel):
|
|
event_id: str
|
|
seq: int
|
|
domain: ChangeDomain = "global"
|
|
table: str = Field(alias="table")
|
|
record_id: str
|
|
action: ChangeAction
|
|
version: int = 1
|
|
content_hash: str = ""
|
|
payload: dict[str, Any] = Field(default_factory=dict)
|
|
emitted_at: datetime
|
|
origin_site_id: str | None = None
|
|
|
|
model_config = {"populate_by_name": True}
|
|
|
|
|
|
class ChangeEventOut(BaseModel):
|
|
event_id: str
|
|
seq: int
|
|
domain: ChangeDomain
|
|
table: str
|
|
record_id: str
|
|
action: ChangeAction
|
|
version: int
|
|
content_hash: str
|
|
payload: dict[str, Any]
|
|
emitted_at: datetime
|
|
origin_site_id: str | None = None
|
|
|
|
|
|
class PushChangesRequest(BaseModel):
|
|
events: list[ChangeEventIn]
|
|
|
|
|
|
class PushChangesResponse(BaseModel):
|
|
applied_event_ids: list[str]
|
|
held_event_ids: list[str]
|
|
conflicts: list[str]
|
|
|
|
|
|
class PullChangesRequest(BaseModel):
|
|
cursor: int = 0
|
|
limit: int = 100
|
|
|
|
|
|
class PullChangesResponse(BaseModel):
|
|
events: list[ChangeEventOut]
|
|
next_cursor: int
|
|
|
|
|
|
class AckChangesRequest(BaseModel):
|
|
event_ids: list[str]
|
|
direction: Literal["inbound", "outbound"] = "inbound"
|
|
|
|
|
|
class AckChangesResponse(BaseModel):
|
|
last_acked_seq: int
|
|
|
|
|
|
class SyncCapabilitiesResponse(BaseModel):
|
|
protocol_version: str = "1.0"
|
|
domains: list[str] = Field(default_factory=lambda: ["global", "report"])
|
|
|
|
|
|
class HeartbeatRequest(BaseModel):
|
|
wesp_version: str | None = None
|
|
lag_seconds: int | None = None
|
|
|
|
|
|
class HeartbeatResponse(BaseModel):
|
|
status: str = "ok"
|
|
server_time: datetime
|
|
|
|
|
|
class PairStartRequest(BaseModel):
|
|
enterprise_id: str
|
|
farm_name: str
|
|
|
|
|
|
class PairStartResponse(BaseModel):
|
|
session_id: str
|
|
code: str
|
|
expires_at: datetime
|
|
|
|
|
|
class PairConfirmRequest(BaseModel):
|
|
code: str
|
|
hub_site_id: str
|
|
hub_name: str | None = None
|
|
hub_url: str | None = None
|
|
|
|
|
|
class PairConfirmResponse(BaseModel):
|
|
farm_hub_id: str
|
|
hub_site_id: str
|
|
api_key: str
|
|
enterprise_id: str
|
|
|
|
|
|
class ConflictSummary(BaseModel):
|
|
id: str
|
|
table_name: str
|
|
record_id: str
|
|
farm_hub_id: str | None
|
|
status: str
|
|
created_at: datetime
|
|
|
|
|
|
class ConflictDetail(ConflictSummary):
|
|
orchestrator_snapshot: dict[str, Any]
|
|
hub_snapshot: dict[str, Any]
|
|
held_event_ids: list[str]
|
|
|
|
|
|
class ResolveConflictRequest(BaseModel):
|
|
resolution: ConflictResolution
|
|
|
|
|
|
class EnterpriseOut(BaseModel):
|
|
id: str
|
|
name: str
|
|
slug: str
|
|
status: str
|
|
|
|
|
|
class FarmHubOut(BaseModel):
|
|
id: str
|
|
enterprise_id: str
|
|
name: str
|
|
hub_site_id: str
|
|
url: str | None
|
|
status: str
|
|
last_seen: datetime | None
|
|
wesp_version: str | None
|
|
|
|
|
|
@dataclass
|
|
class HubAuthContext:
|
|
farm_hub_id: str
|
|
hub_site_id: str
|
|
enterprise_id: str
|
|
farm_hub: Any
|
|
|
|
|
|
@dataclass
|
|
class TenantContext:
|
|
enterprise_id: str
|
|
enterprise_role: EnterpriseRole | None
|
|
user_id: str | None
|
|
farm_hub_ids: list[str] | None
|