""" BreakPilot Jitsi API Ermoeglicht das Versenden von Jitsi-Meeting-Einladungen per Email. """ import os import uuid from datetime import datetime from typing import Optional, List from pydantic import BaseModel, Field from fastapi import APIRouter, HTTPException router = APIRouter(prefix="/api/jitsi", tags=["Jitsi"]) # Standard Jitsi Server (kann konfiguriert werden) JITSI_SERVER = os.getenv("JITSI_SERVER", "https://meet.jit.si") # ========================================== # PYDANTIC MODELS # ========================================== class JitsiInvitation(BaseModel): """Model fuer Jitsi-Meeting-Einladung.""" to_email: str = Field(..., description="Email-Adresse des Teilnehmers") to_name: str = Field(..., description="Name des Teilnehmers") organizer_name: str = Field(default="BreakPilot Lehrer", description="Name des Organisators") meeting_title: str = Field(..., description="Titel des Meetings") meeting_date: str = Field(..., description="Datum z.B. '20. Dezember 2024'") meeting_time: str = Field(..., description="Uhrzeit z.B. '14:00 Uhr'") room_name: Optional[str] = Field(None, description="Raumname (wird generiert wenn leer)") additional_info: Optional[str] = Field(None, description="Zusaetzliche Informationen") class JitsiInvitationResponse(BaseModel): """Antwort auf eine Jitsi-Einladung.""" success: bool jitsi_url: str room_name: str email_sent: bool email_error: Optional[str] = None class JitsiBulkInvitation(BaseModel): """Model fuer mehrere Jitsi-Einladungen.""" recipients: List[dict] = Field(..., description="Liste von {email, name} Objekten") organizer_name: str = Field(default="BreakPilot Lehrer") meeting_title: str meeting_date: str meeting_time: str room_name: Optional[str] = None additional_info: Optional[str] = None class JitsiBulkResponse(BaseModel): """Antwort auf Bulk-Einladungen.""" jitsi_url: str room_name: str sent: int failed: int errors: List[str] # ========================================== # HELPER FUNCTIONS # ========================================== def generate_room_name() -> str: """Generiert einen sicheren Raumnamen.""" # UUID-basiert fuer Sicherheit unique_id = uuid.uuid4().hex[:12] return f"BreakPilot-{unique_id}" def build_jitsi_url(room_name: str) -> str: """Erstellt die vollstaendige Jitsi-URL.""" return f"{JITSI_SERVER}/{room_name}" # ========================================== # API ENDPOINTS # ========================================== @router.post("/invite", response_model=JitsiInvitationResponse) async def send_jitsi_invitation(invitation: JitsiInvitation): """ Sendet eine Jitsi-Meeting-Einladung per Email. Der Empfaenger kann dem Meeting ueber den Browser beitreten, ohne Matrix oder andere Software installieren zu muessen. """ # Raumname generieren oder verwenden room_name = invitation.room_name or generate_room_name() jitsi_url = build_jitsi_url(room_name) email_sent = False email_error = None try: from email_service import email_service result = email_service.send_jitsi_invitation( to_email=invitation.to_email, to_name=invitation.to_name, organizer_name=invitation.organizer_name, meeting_title=invitation.meeting_title, meeting_date=invitation.meeting_date, meeting_time=invitation.meeting_time, jitsi_url=jitsi_url, additional_info=invitation.additional_info ) email_sent = result.success if not result.success: email_error = result.error except Exception as e: email_error = str(e) return JitsiInvitationResponse( success=email_sent, jitsi_url=jitsi_url, room_name=room_name, email_sent=email_sent, email_error=email_error ) @router.post("/invite/bulk", response_model=JitsiBulkResponse) async def send_bulk_jitsi_invitations(bulk: JitsiBulkInvitation): """ Sendet Jitsi-Einladungen an mehrere Empfaenger. Alle Empfaenger erhalten eine Einladung zum selben Meeting. """ # Gemeinsamer Raumname fuer alle room_name = bulk.room_name or generate_room_name() jitsi_url = build_jitsi_url(room_name) sent = 0 failed = 0 errors = [] try: from email_service import email_service for recipient in bulk.recipients: if not recipient.get("email"): errors.append(f"Fehlende Email fuer {recipient.get('name', 'Unbekannt')}") failed += 1 continue result = email_service.send_jitsi_invitation( to_email=recipient["email"], to_name=recipient.get("name", ""), organizer_name=bulk.organizer_name, meeting_title=bulk.meeting_title, meeting_date=bulk.meeting_date, meeting_time=bulk.meeting_time, jitsi_url=jitsi_url, additional_info=bulk.additional_info ) if result.success: sent += 1 else: failed += 1 errors.append(f"{recipient.get('email')}: {result.error}") except Exception as e: errors.append(f"Allgemeiner Fehler: {str(e)}") return JitsiBulkResponse( jitsi_url=jitsi_url, room_name=room_name, sent=sent, failed=failed, errors=errors[:20] # Max 20 Fehler zurueckgeben ) @router.get("/room") async def generate_meeting_room(): """ Generiert einen neuen Meeting-Raum. Gibt die URL zurueck ohne Einladungen zu senden. """ room_name = generate_room_name() jitsi_url = build_jitsi_url(room_name) return { "room_name": room_name, "jitsi_url": jitsi_url, "server": JITSI_SERVER, "created_at": datetime.utcnow().isoformat() }