A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.
This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).
Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
200 lines
5.9 KiB
Python
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()
|
|
}
|