Files
Benjamin Boenisch 5a31f52310 Initial commit: breakpilot-lehrer - Lehrer KI Platform
Services: Admin-Lehrer, Backend-Lehrer, Studio v2, Website,
Klausur-Service, School-Service, Voice-Service, Geo-Service,
BreakPilot Drive, Agent-Core

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:26 +01:00

200 lines
5.9 KiB
Python

"""
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()
}