import re from pydantic import BaseModel, ConfigDict, Field, field_validator from typing import Optional, Literal from datetime import datetime class InviteMemberRequest(BaseModel): model_config = ConfigDict(extra="forbid") connection_id: int = Field(ge=1, le=2147483647) permission: Literal["read_only", "create_modify", "full_access"] can_add_others: bool = False class RespondInviteRequest(BaseModel): model_config = ConfigDict(extra="forbid") action: Literal["accept", "reject"] class UpdateMemberRequest(BaseModel): model_config = ConfigDict(extra="forbid") permission: Optional[Literal["read_only", "create_modify", "full_access"]] = None can_add_others: Optional[bool] = None class UpdateLocalColorRequest(BaseModel): model_config = ConfigDict(extra="forbid") local_color: Optional[str] = Field(None, max_length=20) @field_validator("local_color") @classmethod def validate_color(cls, v: Optional[str]) -> Optional[str]: if v is not None and not re.match(r"^#[0-9a-fA-F]{6}$", v): raise ValueError("Color must be a hex color code (#RRGGBB)") return v class ConvertToSharedRequest(BaseModel): model_config = ConfigDict(extra="forbid") class CalendarMemberResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int calendar_id: int user_id: int umbral_name: str preferred_name: Optional[str] = None permission: str can_add_others: bool local_color: Optional[str] = None status: str invited_at: datetime accepted_at: Optional[datetime] = None class CalendarInviteResponse(BaseModel): model_config = ConfigDict(from_attributes=True) id: int calendar_id: int calendar_name: str calendar_color: str owner_umbral_name: str inviter_umbral_name: str permission: str invited_at: datetime class LockStatusResponse(BaseModel): locked: bool locked_by_name: Optional[str] = None expires_at: Optional[datetime] = None is_permanent: bool = False class SyncResponse(BaseModel): events: list[dict] member_changes: list[dict] server_time: datetime truncated: bool = False