""" Calendar Storage - Database models and operations """ from __future__ import annotations import os import logging from datetime import datetime from typing import Optional, List from dataclasses import dataclass logger = logging.getLogger(__name__) # Simple SQLite-based storage (can be replaced with Postgres) class CalendarStorage: """In-memory/SQLite storage for calendar accounts and reminders""" def __init__(self, db_session=None): self.db = db_session self._accounts = {} self._reminders = {} self._idempotency_keys = {} self._next_id = 1 def create_account( self, workspace_id: str, user_id: str, provider: str, username: str, password: str, principal_url: str = None, default_calendar_id: str = None ): """Create calendar account""" # Check if exists for acc in self._accounts.values(): if (acc.workspace_id == workspace_id and acc.user_id == user_id and acc.provider == provider): # Update acc.username = username acc.password = password acc.principal_url = principal_url acc.default_calendar_id = default_calendar_id acc.updated_at = datetime.utcnow() return acc # Create new account = CalendarAccount( id=f"acc_{self._next_id}", workspace_id=workspace_id, user_id=user_id, provider=provider, username=username, password=password, # In production, encrypt this! principal_url=principal_url, default_calendar_id=default_calendar_id ) self._accounts[account.id] = account self._next_id += 1 logger.info(f"Created calendar account: {account.id}") return account def get_account(self, account_id: str) -> Optional[CalendarAccount]: """Get account by ID""" return self._accounts.get(account_id) def list_accounts( self, workspace_id: str, user_id: str ) -> List[CalendarAccount]: """List accounts for user""" return [ acc for acc in self._accounts.values() if acc.workspace_id == workspace_id and acc.user_id == user_id ] def count_accounts(self) -> int: """Count total accounts""" return len(self._accounts) def create_reminder( self, workspace_id: str, user_id: str, account_id: str, event_uid: str, remind_at: str, channel: str = "inapp" ) -> "CalendarReminder": """Create reminder""" reminder = CalendarReminder( id=f"rem_{self._next_id}", workspace_id=workspace_id, user_id=user_id, account_id=account_id, event_uid=event_uid, remind_at=datetime.fromisoformat(remind_at), channel=channel, status="pending" ) self._reminders[reminder.id] = reminder self._next_id += 1 logger.info(f"Created reminder: {reminder.id}") return reminder def get_pending_reminders(self) -> List["CalendarReminder"]: """Get pending reminders""" now = datetime.utcnow() return [ r for r in self._reminders.values() if r.status == "pending" and r.remind_at <= now ] def update_reminder_status( self, reminder_id: str, status: str, error: str = None ): """Update reminder status""" if reminder_id in self._reminders: self._reminders[reminder_id].status = status self._reminders[reminder_id].attempts += 1 if error: self._reminders[reminder_id].last_error = error def count_pending_reminders(self) -> int: """Count pending reminders""" return len([r for r in self._reminders.values() if r.status == "pending"]) def store_idempotency_key( self, key: str, workspace_id: str, user_id: str, event_uid: str ): """Store idempotency key""" self._idempotency_keys[key] = { "workspace_id": workspace_id, "user_id": user_id, "event_uid": event_uid } def get_by_idempotency_key(self, key: str) -> Optional[dict]: """Get event UID by idempotency key""" return self._idempotency_keys.get(key) @dataclass class CalendarAccount: """Calendar account model""" id: str workspace_id: str user_id: str provider: str username: str password: str principal_url: str = None default_calendar_id: str = None created_at: datetime = None updated_at: datetime = None def __post_init__(self): if self.created_at is None: self.created_at = datetime.utcnow() if self.updated_at is None: self.updated_at = datetime.utcnow() @dataclass class CalendarReminder: """Calendar reminder model""" id: str workspace_id: str user_id: str account_id: str event_uid: str remind_at: datetime channel: str status: str = "pending" attempts: int = 0 last_error: str = None created_at: datetime = None def __post_init__(self): if self.created_at is None: self.created_at = datetime.utcnow()