diff --git a/server/reflector/video_platforms/__init__.py b/server/reflector/video_platforms/__init__.py new file mode 100644 index 00000000..ded6244c --- /dev/null +++ b/server/reflector/video_platforms/__init__.py @@ -0,0 +1,17 @@ +# Video Platform Abstraction Layer +""" +This module provides an abstraction layer for different video conferencing platforms. +It allows seamless switching between providers (Whereby, Daily.co, etc.) without +changing the core application logic. +""" + +from .base import MeetingData, VideoPlatformClient, VideoPlatformConfig +from .registry import get_platform_client, register_platform + +__all__ = [ + "VideoPlatformClient", + "VideoPlatformConfig", + "MeetingData", + "get_platform_client", + "register_platform", +] diff --git a/server/reflector/video_platforms/base.py b/server/reflector/video_platforms/base.py new file mode 100644 index 00000000..0c0470f3 --- /dev/null +++ b/server/reflector/video_platforms/base.py @@ -0,0 +1,82 @@ +from abc import ABC, abstractmethod +from datetime import datetime +from typing import Any, Dict, Optional + +from pydantic import BaseModel + +from reflector.db.rooms import Room + + +class MeetingData(BaseModel): + """Standardized meeting data returned by all platforms.""" + + meeting_id: str + room_name: str + room_url: str + host_room_url: str + platform: str + extra_data: Dict[str, Any] = {} # Platform-specific data + + +class VideoPlatformConfig(BaseModel): + """Configuration for a video platform.""" + + api_key: str + webhook_secret: str + api_url: Optional[str] = None + subdomain: Optional[str] = None + s3_bucket: Optional[str] = None + s3_region: Optional[str] = None + aws_role_arn: Optional[str] = None + aws_access_key_id: Optional[str] = None + aws_access_key_secret: Optional[str] = None + + +class VideoPlatformClient(ABC): + """Abstract base class for video platform integrations.""" + + PLATFORM_NAME: str = "" + + def __init__(self, config: VideoPlatformConfig): + self.config = config + + @abstractmethod + async def create_meeting( + self, room_name_prefix: str, end_date: datetime, room: Room + ) -> MeetingData: + """Create a new meeting room.""" + pass + + @abstractmethod + async def get_room_sessions(self, room_name: str) -> Dict[str, Any]: + """Get session information for a room.""" + pass + + @abstractmethod + async def delete_room(self, room_name: str) -> bool: + """Delete a room. Returns True if successful.""" + pass + + @abstractmethod + async def upload_logo(self, room_name: str, logo_path: str) -> bool: + """Upload a logo to the room. Returns True if successful.""" + pass + + @abstractmethod + def verify_webhook_signature( + self, body: bytes, signature: str, timestamp: Optional[str] = None + ) -> bool: + """Verify webhook signature for security.""" + pass + + def format_recording_config(self, room: Room) -> Dict[str, Any]: + """Format recording configuration for the platform. + Can be overridden by specific implementations.""" + if room.recording_type == "cloud" and self.config.s3_bucket: + return { + "type": room.recording_type, + "bucket": self.config.s3_bucket, + "region": self.config.s3_region, + "trigger": room.recording_trigger, + } + return {"type": room.recording_type} diff --git a/server/reflector/video_platforms/factory.py b/server/reflector/video_platforms/factory.py new file mode 100644 index 00000000..d76bb2ed --- /dev/null +++ b/server/reflector/video_platforms/factory.py @@ -0,0 +1,40 @@ +"""Factory for creating video platform clients based on configuration.""" + +from typing import Optional + +from reflector.settings import settings + +from .base import VideoPlatformClient, VideoPlatformConfig +from .registry import get_platform_client + + +def get_platform_config(platform: str) -> VideoPlatformConfig: + """Get configuration for a specific platform.""" + if platform == "whereby": + return VideoPlatformConfig( + api_key=settings.WHEREBY_API_KEY or "", + webhook_secret=settings.WHEREBY_WEBHOOK_SECRET or "", + api_url=settings.WHEREBY_API_URL, + aws_access_key_id=settings.AWS_WHEREBY_ACCESS_KEY_ID, + aws_access_key_secret=settings.AWS_WHEREBY_ACCESS_KEY_SECRET, + ) + elif platform == "jitsi": + return VideoPlatformConfig( + api_key="", # Jitsi uses JWT, no API key + webhook_secret=settings.JITSI_WEBHOOK_SECRET or "", + api_url=f"https://{settings.JITSI_DOMAIN}", + ) + else: + raise ValueError(f"Unknown platform: {platform}") + + +def create_platform_client(platform: str) -> VideoPlatformClient: + """Create a video platform client instance.""" + config = get_platform_config(platform) + return get_platform_client(platform, config) + + +def get_platform_for_room(room_id: Optional[str] = None) -> str: + """Determine which platform to use for a room based on feature flags.""" + # For now, default to whereby since we don't have feature flags yet + return "whereby" diff --git a/server/reflector/video_platforms/jitsi/__init__.py b/server/reflector/video_platforms/jitsi/__init__.py new file mode 100644 index 00000000..d8d377f2 --- /dev/null +++ b/server/reflector/video_platforms/jitsi/__init__.py @@ -0,0 +1,3 @@ +from .client import JitsiClient + +__all__ = ["JitsiClient"] diff --git a/server/reflector/video_platforms/jitsi/client.py b/server/reflector/video_platforms/jitsi/client.py new file mode 100644 index 00000000..496521e9 --- /dev/null +++ b/server/reflector/video_platforms/jitsi/client.py @@ -0,0 +1 @@ +# JitsiClient implementation - to be implemented in next task diff --git a/server/reflector/video_platforms/jitsi/tasks.py b/server/reflector/video_platforms/jitsi/tasks.py new file mode 100644 index 00000000..0dae11fc --- /dev/null +++ b/server/reflector/video_platforms/jitsi/tasks.py @@ -0,0 +1,3 @@ +"""Jitsi-specific worker tasks.""" + +# Placeholder for Jitsi recording tasks diff --git a/server/reflector/video_platforms/registry.py b/server/reflector/video_platforms/registry.py new file mode 100644 index 00000000..eee9c656 --- /dev/null +++ b/server/reflector/video_platforms/registry.py @@ -0,0 +1,37 @@ +from typing import Dict, Type + +from .base import VideoPlatformClient, VideoPlatformConfig + +# Registry of available video platforms +_PLATFORMS: Dict[str, Type[VideoPlatformClient]] = {} + + +def register_platform(name: str, client_class: Type[VideoPlatformClient]): + """Register a video platform implementation.""" + _PLATFORMS[name.lower()] = client_class + + +def get_platform_client( + platform: str, config: VideoPlatformConfig +) -> VideoPlatformClient: + """Get a video platform client instance.""" + platform_lower = platform.lower() + if platform_lower not in _PLATFORMS: + raise ValueError(f"Unknown video platform: {platform}") + + client_class = _PLATFORMS[platform_lower] + return client_class(config) + + +def get_available_platforms() -> list[str]: + """Get list of available platform names.""" + return list(_PLATFORMS.keys()) + + +# Auto-register built-in platforms +def _register_builtin_platforms(): + # Will be populated as we add platforms + pass + + +_register_builtin_platforms()