199 lines
5.5 KiB
Python
199 lines
5.5 KiB
Python
"""
|
|
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()
|