Major updates: LinkedIn auto-posting, timezone fixes, and Docker improvements

Features:
- Add LinkedIn OAuth integration and auto-posting functionality
- Add scheduler service for automated post publishing
- Add metadata field to generated_posts for LinkedIn URLs
- Add privacy policy page for LinkedIn API compliance
- Add company management features and employee accounts
- Add license key system for company registrations

Fixes:
- Fix timezone issues (use UTC consistently across app)
- Fix datetime serialization errors in database operations
- Fix scheduling timezone conversion (local time to UTC)
- Fix import errors (get_database -> db)

Infrastructure:
- Update Docker setup to use port 8001 (avoid conflicts)
- Add SSL support with nginx-proxy and Let's Encrypt
- Add LinkedIn setup documentation
- Add migration scripts for schema updates

Services:
- Add linkedin_service.py for LinkedIn API integration
- Add scheduler_service.py for background job processing
- Add storage_service.py for Supabase Storage
- Add email_service.py improvements
- Add encryption utilities for token storage

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 11:30:20 +01:00
parent b50594dbfa
commit f14515e9cf
94 changed files with 21601 additions and 5111 deletions

View File

@@ -1,12 +1,13 @@
"""Base agent class."""
import asyncio
from abc import ABC, abstractmethod
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional, Tuple
from openai import OpenAI
import httpx
from loguru import logger
from src.config import settings
from src.config import settings, estimate_cost
from src.database.client import db
class BaseAgent(ABC):
@@ -21,13 +22,59 @@ class BaseAgent(ABC):
"""
self.name = name
self.openai_client = OpenAI(api_key=settings.openai_api_key)
self._usage_logs: List[Dict[str, Any]] = []
self._user_id: Optional[str] = None
self._company_id: Optional[str] = None
self._operation: str = "unknown"
logger.info(f"Initialized {name} agent")
def set_tracking_context(
self,
operation: str,
user_id: Optional[str] = None,
company_id: Optional[str] = None
):
"""Set context for usage tracking."""
self._operation = operation
self._user_id = user_id
self._company_id = company_id
self._usage_logs = []
@abstractmethod
async def process(self, *args, **kwargs) -> Any:
"""Process the agent's task."""
pass
async def _log_usage(self, provider: str, model: str, prompt_tokens: int, completion_tokens: int, total_tokens: int):
"""Log API usage to database."""
cost = estimate_cost(model, prompt_tokens, completion_tokens)
usage = {
"provider": provider,
"model": model,
"operation": self._operation,
"prompt_tokens": prompt_tokens,
"completion_tokens": completion_tokens,
"total_tokens": total_tokens,
"estimated_cost_usd": cost
}
self._usage_logs.append(usage)
try:
from uuid import UUID
await db.log_api_usage(
provider=provider,
model=model,
operation=self._operation,
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=total_tokens,
estimated_cost_usd=cost,
user_id=UUID(self._user_id) if self._user_id else None,
company_id=UUID(self._company_id) if self._company_id else None
)
except Exception as e:
logger.warning(f"Failed to log usage to DB: {e}")
async def call_openai(
self,
system_prompt: str,
@@ -74,6 +121,16 @@ class BaseAgent(ABC):
result = response.choices[0].message.content
logger.debug(f"[{self.name}] Received response (length: {len(result)})")
# Track usage
if response.usage:
await self._log_usage(
provider="openai",
model=model,
prompt_tokens=response.usage.prompt_tokens,
completion_tokens=response.usage.completion_tokens,
total_tokens=response.usage.total_tokens
)
return result
async def call_perplexity(
@@ -117,4 +174,15 @@ class BaseAgent(ABC):
content = result["choices"][0]["message"]["content"]
logger.debug(f"[{self.name}] Received Perplexity response (length: {len(content)})")
# Track usage
usage = result.get("usage", {})
if usage:
await self._log_usage(
provider="perplexity",
model=model,
prompt_tokens=usage.get("prompt_tokens", 0),
completion_tokens=usage.get("completion_tokens", 0),
total_tokens=usage.get("prompt_tokens", 0) + usage.get("completion_tokens", 0)
)
return content