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>
This commit is contained in:
199
backend-lehrer/jitsi_api.py
Normal file
199
backend-lehrer/jitsi_api.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user