Include Loyalty/Communication/Sports Center backends and registry updates alongside production nginx and compose wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
312 lines
7.6 KiB
Python
312 lines
7.6 KiB
Python
"""Pydantic schemas for Communication APIs."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Any
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
class ORMModel(BaseModel):
|
|
model_config = ConfigDict(from_attributes=True)
|
|
|
|
|
|
class ProviderCreate(BaseModel):
|
|
name: str
|
|
provider_kind: str
|
|
channel: str
|
|
priority: int = 100
|
|
credentials: dict[str, Any] | None = None
|
|
settings: dict[str, Any] | None = None
|
|
balance: Decimal | None = None
|
|
rate_limit_per_minute: int | None = None
|
|
max_retries: int = 3
|
|
is_default: bool = False
|
|
|
|
|
|
class ProviderUpdate(BaseModel):
|
|
name: str | None = None
|
|
priority: int | None = None
|
|
credentials: dict[str, Any] | None = None
|
|
settings: dict[str, Any] | None = None
|
|
balance: Decimal | None = None
|
|
rate_limit_per_minute: int | None = None
|
|
max_retries: int | None = None
|
|
status: str | None = None
|
|
is_default: bool | None = None
|
|
|
|
|
|
class ProviderOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
name: str
|
|
provider_kind: str
|
|
channel: str
|
|
status: str
|
|
priority: int
|
|
balance: Decimal | None = None
|
|
rate_limit_per_minute: int | None = None
|
|
max_retries: int
|
|
circuit_state: str
|
|
consecutive_failures: int
|
|
last_health_at: datetime | None = None
|
|
last_error: str | None = None
|
|
is_default: bool
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class SenderCreate(BaseModel):
|
|
channel: str
|
|
value: str
|
|
label: str | None = None
|
|
provider_config_id: UUID | None = None
|
|
is_default: bool = False
|
|
is_active: bool = True
|
|
|
|
|
|
class SenderOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
channel: str
|
|
value: str
|
|
label: str | None = None
|
|
provider_config_id: UUID | None = None
|
|
is_default: bool
|
|
is_active: bool
|
|
created_at: datetime
|
|
|
|
|
|
class TemplateCreate(BaseModel):
|
|
template_key: str
|
|
name: str
|
|
channel: str
|
|
locale: str = "fa"
|
|
body: str
|
|
subject: str | None = None
|
|
variables: list[str] | None = None
|
|
|
|
|
|
class TemplateOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
template_key: str
|
|
name: str
|
|
channel: str
|
|
locale: str
|
|
version: int
|
|
status: str
|
|
body: str
|
|
subject: str | None = None
|
|
variables: list[str] | None = None
|
|
approved_by: str | None = None
|
|
approved_at: datetime | None = None
|
|
rejection_reason: str | None = None
|
|
created_at: datetime
|
|
|
|
|
|
class TemplatePreviewRequest(BaseModel):
|
|
variables: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
|
|
class TemplatePreviewResponse(BaseModel):
|
|
rendered: str
|
|
subject: str | None = None
|
|
|
|
|
|
class ContactCreate(BaseModel):
|
|
display_name: str | None = None
|
|
phone: str | None = None
|
|
email: str | None = None
|
|
push_token: str | None = None
|
|
metadata_json: dict[str, Any] | None = None
|
|
tags: list[str] | None = None
|
|
|
|
|
|
class ContactOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
display_name: str | None = None
|
|
phone: str | None = None
|
|
email: str | None = None
|
|
push_token: str | None = None
|
|
metadata_json: dict[str, Any] | None = None
|
|
tags: list[str] | None = None
|
|
created_at: datetime
|
|
|
|
|
|
class ContactSourceCreate(BaseModel):
|
|
name: str
|
|
source_type: str
|
|
base_url: str | None = None
|
|
endpoint_path: str | None = None
|
|
auth_config: dict[str, Any] | None = None
|
|
query_mapping: dict[str, Any] | None = None
|
|
field_mapping: dict[str, Any] | None = None
|
|
is_active: bool = True
|
|
|
|
|
|
class ContactSourceOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
name: str
|
|
source_type: str
|
|
base_url: str | None = None
|
|
endpoint_path: str | None = None
|
|
field_mapping: dict[str, Any] | None = None
|
|
is_active: bool
|
|
created_at: datetime
|
|
|
|
|
|
class MessageSendRequest(BaseModel):
|
|
channel: str = "sms"
|
|
to_address: str | None = None
|
|
from_address: str | None = None
|
|
body: str | None = None
|
|
subject: str | None = None
|
|
template_key: str | None = None
|
|
template_id: UUID | None = None
|
|
template_variables: dict[str, Any] | None = None
|
|
priority: str = "normal"
|
|
scheduled_at: datetime | None = None
|
|
correlation_id: str | None = None
|
|
source_module: str | None = None
|
|
metadata_json: dict[str, Any] | None = None
|
|
contact_source_id: UUID | None = None
|
|
contact_query: dict[str, Any] | None = None
|
|
batch: bool = False
|
|
process_immediately: bool = True
|
|
|
|
|
|
class MessageOut(ORMModel):
|
|
id: UUID
|
|
tenant_id: UUID
|
|
channel: str
|
|
status: str
|
|
to_address: str
|
|
from_address: str | None = None
|
|
subject: str | None = None
|
|
body: str
|
|
template_key: str | None = None
|
|
provider_config_id: UUID | None = None
|
|
provider_message_id: str | None = None
|
|
priority: str
|
|
scheduled_at: datetime | None = None
|
|
sent_at: datetime | None = None
|
|
delivered_at: datetime | None = None
|
|
failed_at: datetime | None = None
|
|
error_code: str | None = None
|
|
error_message: str | None = None
|
|
retry_count: int
|
|
correlation_id: str | None = None
|
|
source_module: str | None = None
|
|
created_at: datetime
|
|
|
|
|
|
class DeliveryEventOut(ORMModel):
|
|
id: UUID
|
|
message_id: UUID
|
|
status: str
|
|
provider_config_id: UUID | None = None
|
|
detail: str | None = None
|
|
payload: dict[str, Any] | None = None
|
|
created_at: datetime
|
|
|
|
|
|
class QueueItemOut(ORMModel):
|
|
id: UUID
|
|
message_id: UUID
|
|
status: str
|
|
priority: str
|
|
attempt: int
|
|
max_attempts: int
|
|
available_at: datetime
|
|
is_dead_letter: bool
|
|
last_error: str | None = None
|
|
batch_id: str | None = None
|
|
created_at: datetime
|
|
|
|
|
|
class OTPRequest(BaseModel):
|
|
destination: str
|
|
channel: str = "sms"
|
|
purpose: str | None = None
|
|
ttl_seconds: int | None = None
|
|
code_length: int = 6
|
|
|
|
|
|
class OTPRequestOut(BaseModel):
|
|
challenge_id: UUID
|
|
destination: str
|
|
channel: str
|
|
expires_at: datetime
|
|
message_id: UUID | None = None
|
|
# Returned only in non-production for tests/dev
|
|
debug_code: str | None = None
|
|
|
|
|
|
class OTPVerifyRequest(BaseModel):
|
|
destination: str
|
|
code: str
|
|
challenge_id: UUID | None = None
|
|
|
|
|
|
class OTPVerifyOut(BaseModel):
|
|
verified: bool
|
|
challenge_id: UUID | None = None
|
|
status: str
|
|
|
|
|
|
class WebhookIn(BaseModel):
|
|
provider_kind: str
|
|
event_type: str
|
|
external_id: str | None = None
|
|
message_id: UUID | None = None
|
|
payload: dict[str, Any] | None = None
|
|
signature: str | None = None
|
|
|
|
|
|
class WebhookOut(ORMModel):
|
|
id: UUID
|
|
provider_kind: str
|
|
event_type: str
|
|
external_id: str | None = None
|
|
message_id: UUID | None = None
|
|
signature_valid: bool
|
|
processed: bool
|
|
created_at: datetime
|
|
|
|
|
|
class CapabilitiesOut(BaseModel):
|
|
service: str
|
|
version: str
|
|
channels: list[dict[str, Any]]
|
|
features: list[str]
|
|
independent: bool = True
|
|
requires_other_modules: bool = False
|
|
|
|
|
|
class ProviderStatusOut(BaseModel):
|
|
id: UUID
|
|
name: str
|
|
provider_kind: str
|
|
channel: str
|
|
status: str
|
|
circuit_state: str
|
|
consecutive_failures: int
|
|
balance: Decimal | None = None
|
|
rate_limit_per_minute: int | None = None
|
|
last_health_at: datetime | None = None
|
|
last_error: str | None = None
|
|
healthy: bool
|
|
|
|
|
|
class MonitoringStatsOut(BaseModel):
|
|
messages_by_status: dict[str, int]
|
|
queue_by_status: dict[str, int]
|
|
providers: list[ProviderStatusOut]
|
|
rate_limit_deferrals: int = 0
|
|
checked_at: str | None = None
|