from __future__ import annotations from datetime import datetime from typing import ClassVar from uuid import UUID from zoneinfo import ZoneInfo, ZoneInfoNotFoundError from pydantic import BaseModel, ConfigDict, Field, field_validator from schemas.inbox.messages import ( CalendarContent, CalendarDeleteContent, CalendarInviteContent, CalendarUpdateContent, parse_calendar_content, ) from schemas.schedule.items import ( AttachmentType, ScheduleItemMetadata, ScheduleItemMetadataAttachment, ScheduleItemSourceType, ScheduleItemStatus, ) __all__ = [ "AttachmentType", "CalendarContent", "CalendarDeleteContent", "CalendarInviteContent", "CalendarUpdateContent", "ScheduleItemMetadata", "ScheduleItemMetadataAttachment", "ScheduleItemSourceType", "ScheduleItemStatus", "parse_calendar_content", "ScheduleItemCreateRequest", "ScheduleItemUpdateRequest", "ScheduleItemResponse", "ScheduleItemListItem", "ScheduleItemListRequest", "ScheduleItemShareRequest", "ScheduleItemShareResponse", ] class ScheduleItemCreateRequest(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid") title: str = Field(min_length=1, max_length=255) description: str | None = Field(default=None, max_length=2000) start_at: datetime end_at: datetime | None = None timezone: str = Field(..., min_length=1, max_length=50) metadata: ScheduleItemMetadata | None = None @field_validator("timezone") @classmethod def validate_timezone(cls, value: str) -> str: try: ZoneInfo(value) except ZoneInfoNotFoundError as exc: raise ValueError("timezone must be a valid IANA timezone") from exc return value @field_validator("start_at", "end_at") @classmethod def validate_datetime_tzinfo(cls, value: datetime | None) -> datetime | None: if value is None: return None if value.tzinfo is None: raise ValueError("datetime must include timezone offset") return value class ScheduleItemUpdateRequest(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid") title: str | None = Field(default=None, min_length=1, max_length=255) description: str | None = Field(default=None, max_length=2000) start_at: datetime | None = None end_at: datetime | None = None timezone: str | None = Field(default=None, max_length=50) metadata: ScheduleItemMetadata | None = None status: ScheduleItemStatus | None = None @field_validator("timezone") @classmethod def validate_timezone(cls, value: str | None) -> str | None: if value is None: return None try: ZoneInfo(value) except ZoneInfoNotFoundError as exc: raise ValueError("timezone must be a valid IANA timezone") from exc return value @field_validator("start_at", "end_at") @classmethod def validate_datetime_tzinfo(cls, value: datetime | None) -> datetime | None: if value is None: return None if value.tzinfo is None: raise ValueError("datetime must include timezone offset") return value class ScheduleItemResponse(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True) id: UUID owner_id: UUID title: str description: str | None = None start_at: datetime end_at: datetime | None = None timezone: str metadata: ScheduleItemMetadata | None = None status: ScheduleItemStatus source_type: ScheduleItemSourceType created_at: datetime updated_at: datetime permission: int = 1 is_owner: bool = False class ScheduleItemListItem(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(from_attributes=True) id: UUID title: str start_at: datetime end_at: datetime | None = None timezone: str status: ScheduleItemStatus class ScheduleItemListRequest(BaseModel): start_at: datetime end_at: datetime @field_validator("start_at", "end_at") @classmethod def validate_datetime_tzinfo(cls, value: datetime) -> datetime: if value.tzinfo is None: raise ValueError("datetime must include timezone offset") return value _PERMISSION_VIEW = 1 _PERMISSION_INVITE = 2 _PERMISSION_EDIT = 4 class ScheduleItemShareRequest(BaseModel): model_config: ClassVar[ConfigDict] = ConfigDict(extra="forbid") phone: str = Field( ..., pattern=r"^\+861[3-9]\d{9}$", description="Phone of user to share with", ) permission_view: bool = Field(True, description="Grant view permission") permission_edit: bool = Field(False, description="Grant edit permission") permission_invite: bool = Field(False, description="Grant invite permission") def _permission_value(self) -> int: value = 0 if self.permission_view: value |= _PERMISSION_VIEW if self.permission_edit: value |= _PERMISSION_EDIT if self.permission_invite: value |= _PERMISSION_INVITE return value class ScheduleItemShareResponse(BaseModel): message: str