feat(node2): wire calendar-service and core automation tools in router
This commit is contained in:
198
services/calendar-service/storage.py
Normal file
198
services/calendar-service/storage.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user