UMBRA/backend/app/schemas/shared_calendar.py
Kyle Pope e4b45763b4 Phase 1: Schema and models for shared calendars
Migrations 047-051:
- 047: Add is_shared to calendars
- 048: Create calendar_members table (permissions, status, constraints)
- 049: Create event_locks table (5min TTL, permanent owner locks)
- 050: Expand notification CHECK (calendar_invite types)
- 051: Add updated_by to calendar_events + updated_at index

New models: CalendarMember, EventLock
Updated models: Calendar (is_shared, members), CalendarEvent (updated_by),
  Notification (3 new types)
New schemas: shared_calendar.py (invite, respond, member, lock, sync)
Updated schemas: calendar.py (is_shared, sharing response fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 03:22:44 +08:00

80 lines
2.1 KiB
Python

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