feat(node2): wire calendar-service and core automation tools in router
This commit is contained in:
12
services/calendar-service/Dockerfile
Normal file
12
services/calendar-service/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||
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()
|
||||
@@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
RUN python -m playwright install --with-deps chromium
|
||||
|
||||
# Copy application
|
||||
COPY . .
|
||||
@@ -37,4 +38,3 @@ CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -141,6 +141,11 @@ AGENT_SPECIALIZED_TOOLS = {
|
||||
'cost_analyzer_tool',
|
||||
'pieces_tool',
|
||||
'notion_tool',
|
||||
'calendar_tool',
|
||||
'agent_email_tool',
|
||||
'browser_tool',
|
||||
'safe_code_executor_tool',
|
||||
'secure_vault_tool',
|
||||
],
|
||||
|
||||
# Admin - platform operations
|
||||
@@ -154,6 +159,11 @@ AGENT_SPECIALIZED_TOOLS = {
|
||||
'cost_analyzer_tool',
|
||||
'pieces_tool',
|
||||
'notion_tool',
|
||||
'calendar_tool',
|
||||
'agent_email_tool',
|
||||
'browser_tool',
|
||||
'safe_code_executor_tool',
|
||||
'secure_vault_tool',
|
||||
],
|
||||
|
||||
# Daarion - Media Generation
|
||||
|
||||
@@ -11,6 +11,9 @@ pypdf>=5.1.0
|
||||
python-pptx>=0.6.23
|
||||
odfpy>=1.4.1
|
||||
pyarrow>=18.0.0
|
||||
cryptography>=41.0.0
|
||||
aiofiles>=23.2.1
|
||||
playwright>=1.40.0
|
||||
|
||||
# Memory Retrieval v3.0
|
||||
asyncpg>=0.29.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user