Files
breakpilot-lehrer/backend-lehrer/messenger_conversations.py
Benjamin Admin 34da9f4cda [split-required] Split 700-870 LOC files across all services
backend-lehrer (11 files):
- llm_gateway/routes/schools.py (867 → 5), recording_api.py (848 → 6)
- messenger_api.py (840 → 5), print_generator.py (824 → 5)
- unit_analytics_api.py (751 → 5), classroom/routes/context.py (726 → 4)
- llm_gateway/routes/edu_search_seeds.py (710 → 4)

klausur-service (12 files):
- ocr_labeling_api.py (845 → 4), metrics_db.py (833 → 4)
- legal_corpus_api.py (790 → 4), page_crop.py (758 → 3)
- mail/ai_service.py (747 → 4), github_crawler.py (767 → 3)
- trocr_service.py (730 → 4), full_compliance_pipeline.py (723 → 4)
- dsfa_rag_api.py (715 → 4), ocr_pipeline_auto.py (705 → 4)

website (6 pages):
- audit-checklist (867 → 8), content (806 → 6)
- screen-flow (790 → 4), scraper (789 → 5)
- zeugnisse (776 → 5), modules (745 → 4)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-25 08:01:18 +02:00

406 lines
12 KiB
Python

"""
Messenger API - Conversation, Message, Group, Template & Stats Routes.
Conversations CRUD, message send/read, groups, templates, stats.
"""
import uuid
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, HTTPException, Query
from messenger_models import (
Conversation,
Group,
GroupCreate,
Message,
MessageBase,
)
from messenger_helpers import (
DATA_DIR,
DEFAULT_TEMPLATES,
get_contacts,
get_conversations,
save_conversations,
get_messages,
save_messages,
get_groups,
save_groups,
load_json,
save_json,
)
router = APIRouter(tags=["Messenger"])
# ==========================================
# GROUPS ENDPOINTS
# ==========================================
@router.get("/groups", response_model=List[Group])
async def list_groups():
"""Listet alle Gruppen auf."""
return get_groups()
@router.post("/groups", response_model=Group)
async def create_group(group: GroupCreate):
"""Erstellt eine neue Gruppe."""
groups = get_groups()
now = datetime.utcnow().isoformat()
new_group = {
"id": str(uuid.uuid4()),
"created_at": now,
"updated_at": now,
**group.dict()
}
groups.append(new_group)
save_groups(groups)
return new_group
@router.put("/groups/{group_id}/members")
async def update_group_members(group_id: str, member_ids: List[str]):
"""Aktualisiert die Mitglieder einer Gruppe."""
groups = get_groups()
group_idx = next((i for i, g in enumerate(groups) if g["id"] == group_id), None)
if group_idx is None:
raise HTTPException(status_code=404, detail="Gruppe nicht gefunden")
groups[group_idx]["member_ids"] = member_ids
groups[group_idx]["updated_at"] = datetime.utcnow().isoformat()
save_groups(groups)
return groups[group_idx]
@router.delete("/groups/{group_id}")
async def delete_group(group_id: str):
"""Loescht eine Gruppe."""
groups = get_groups()
groups = [g for g in groups if g["id"] != group_id]
save_groups(groups)
return {"status": "deleted", "id": group_id}
# ==========================================
# CONVERSATIONS ENDPOINTS
# ==========================================
@router.get("/conversations", response_model=List[Conversation])
async def list_conversations():
"""Listet alle Konversationen auf."""
conversations = get_conversations()
messages = get_messages()
# Unread count und letzte Nachricht hinzufuegen
for conv in conversations:
conv_messages = [m for m in messages if m.get("conversation_id") == conv["id"]]
conv["unread_count"] = len([m for m in conv_messages if not m.get("read") and m.get("sender_id") != "self"])
if conv_messages:
last_msg = max(conv_messages, key=lambda m: m.get("timestamp", ""))
conv["last_message"] = last_msg.get("content", "")[:50]
conv["last_message_time"] = last_msg.get("timestamp")
# Nach letzter Nachricht sortieren
conversations.sort(key=lambda c: c.get("last_message_time") or "", reverse=True)
return conversations
@router.post("/conversations", response_model=Conversation)
async def create_conversation(contact_id: Optional[str] = None, group_id: Optional[str] = None):
"""
Erstellt eine neue Konversation.
Entweder mit einem Kontakt (1:1) oder einer Gruppe.
"""
conversations = get_conversations()
if not contact_id and not group_id:
raise HTTPException(status_code=400, detail="Entweder contact_id oder group_id erforderlich")
# Pruefen ob Konversation bereits existiert
if contact_id:
existing = next((c for c in conversations
if not c.get("is_group") and contact_id in c.get("participant_ids", [])), None)
if existing:
return existing
now = datetime.utcnow().isoformat()
if group_id:
groups = get_groups()
group = next((g for g in groups if g["id"] == group_id), None)
if not group:
raise HTTPException(status_code=404, detail="Gruppe nicht gefunden")
new_conv = {
"id": str(uuid.uuid4()),
"name": group.get("name"),
"is_group": True,
"participant_ids": group.get("member_ids", []),
"group_id": group_id,
"created_at": now,
"updated_at": now,
"last_message": None,
"last_message_time": None,
"unread_count": 0
}
else:
contacts = get_contacts()
contact = next((c for c in contacts if c["id"] == contact_id), None)
if not contact:
raise HTTPException(status_code=404, detail="Kontakt nicht gefunden")
new_conv = {
"id": str(uuid.uuid4()),
"name": contact.get("name"),
"is_group": False,
"participant_ids": [contact_id],
"group_id": None,
"created_at": now,
"updated_at": now,
"last_message": None,
"last_message_time": None,
"unread_count": 0
}
conversations.append(new_conv)
save_conversations(conversations)
return new_conv
@router.get("/conversations/{conversation_id}", response_model=Conversation)
async def get_conversation(conversation_id: str):
"""Ruft eine Konversation ab."""
conversations = get_conversations()
conv = next((c for c in conversations if c["id"] == conversation_id), None)
if not conv:
raise HTTPException(status_code=404, detail="Konversation nicht gefunden")
return conv
@router.delete("/conversations/{conversation_id}")
async def delete_conversation(conversation_id: str):
"""Loescht eine Konversation und alle zugehoerigen Nachrichten."""
conversations = get_conversations()
conversations = [c for c in conversations if c["id"] != conversation_id]
save_conversations(conversations)
messages = get_messages()
messages = [m for m in messages if m.get("conversation_id") != conversation_id]
save_messages(messages)
return {"status": "deleted", "id": conversation_id}
# ==========================================
# MESSAGES ENDPOINTS
# ==========================================
@router.get("/conversations/{conversation_id}/messages", response_model=List[Message])
async def list_messages(
conversation_id: str,
limit: int = Query(50, ge=1, le=200),
before: Optional[str] = Query(None, description="Load messages before this timestamp")
):
"""Ruft Nachrichten einer Konversation ab."""
messages = get_messages()
conv_messages = [m for m in messages if m.get("conversation_id") == conversation_id]
if before:
conv_messages = [m for m in conv_messages if m.get("timestamp", "") < before]
# Nach Zeit sortieren (neueste zuletzt)
conv_messages.sort(key=lambda m: m.get("timestamp", ""))
return conv_messages[-limit:]
@router.post("/conversations/{conversation_id}/messages", response_model=Message)
async def send_message(conversation_id: str, message: MessageBase):
"""
Sendet eine Nachricht in einer Konversation.
Wenn send_email=True und der Kontakt eine Email-Adresse hat,
wird die Nachricht auch per Email versendet.
"""
conversations = get_conversations()
conv = next((c for c in conversations if c["id"] == conversation_id), None)
if not conv:
raise HTTPException(status_code=404, detail="Konversation nicht gefunden")
now = datetime.utcnow().isoformat()
new_message = {
"id": str(uuid.uuid4()),
"conversation_id": conversation_id,
"sender_id": "self",
"timestamp": now,
"read": True,
"read_at": now,
"email_sent": False,
"email_sent_at": None,
"email_error": None,
**message.dict()
}
# Email-Versand wenn gewuenscht
if message.send_email and not conv.get("is_group"):
# Kontakt laden
participant_ids = conv.get("participant_ids", [])
if participant_ids:
contacts = get_contacts()
contact = next((c for c in contacts if c["id"] == participant_ids[0]), None)
if contact and contact.get("email"):
try:
from email_service import email_service
result = email_service.send_messenger_notification(
to_email=contact["email"],
to_name=contact.get("name", ""),
sender_name="BreakPilot Lehrer",
message_content=message.content
)
if result.success:
new_message["email_sent"] = True
new_message["email_sent_at"] = result.sent_at
else:
new_message["email_error"] = result.error
except Exception as e:
new_message["email_error"] = str(e)
messages = get_messages()
messages.append(new_message)
save_messages(messages)
# Konversation aktualisieren
conv_idx = next(i for i, c in enumerate(conversations) if c["id"] == conversation_id)
conversations[conv_idx]["last_message"] = message.content[:50]
conversations[conv_idx]["last_message_time"] = now
conversations[conv_idx]["updated_at"] = now
save_conversations(conversations)
return new_message
@router.put("/messages/{message_id}/read")
async def mark_message_read(message_id: str):
"""Markiert eine Nachricht als gelesen."""
messages = get_messages()
msg_idx = next((i for i, m in enumerate(messages) if m["id"] == message_id), None)
if msg_idx is None:
raise HTTPException(status_code=404, detail="Nachricht nicht gefunden")
messages[msg_idx]["read"] = True
messages[msg_idx]["read_at"] = datetime.utcnow().isoformat()
save_messages(messages)
return {"status": "read", "id": message_id}
@router.put("/conversations/{conversation_id}/read-all")
async def mark_all_messages_read(conversation_id: str):
"""Markiert alle Nachrichten einer Konversation als gelesen."""
messages = get_messages()
now = datetime.utcnow().isoformat()
for msg in messages:
if msg.get("conversation_id") == conversation_id and not msg.get("read"):
msg["read"] = True
msg["read_at"] = now
save_messages(messages)
return {"status": "all_read", "conversation_id": conversation_id}
# ==========================================
# TEMPLATES ENDPOINTS
# ==========================================
@router.get("/templates")
async def list_templates():
"""Listet alle Nachrichtenvorlagen auf."""
templates_file = DATA_DIR / "templates.json"
if templates_file.exists():
templates = load_json(templates_file)
else:
templates = DEFAULT_TEMPLATES
save_json(templates_file, templates)
return templates
@router.post("/templates")
async def create_template(name: str, content: str, category: str = "custom"):
"""Erstellt eine neue Vorlage."""
templates_file = DATA_DIR / "templates.json"
templates = load_json(templates_file) if templates_file.exists() else DEFAULT_TEMPLATES.copy()
new_template = {
"id": str(uuid.uuid4()),
"name": name,
"content": content,
"category": category
}
templates.append(new_template)
save_json(templates_file, templates)
return new_template
@router.delete("/templates/{template_id}")
async def delete_template(template_id: str):
"""Loescht eine Vorlage."""
templates_file = DATA_DIR / "templates.json"
templates = load_json(templates_file) if templates_file.exists() else DEFAULT_TEMPLATES.copy()
templates = [t for t in templates if t["id"] != template_id]
save_json(templates_file, templates)
return {"status": "deleted", "id": template_id}
# ==========================================
# STATS ENDPOINT
# ==========================================
@router.get("/stats")
async def get_messenger_stats():
"""Gibt Statistiken zum Messenger zurueck."""
contacts = get_contacts()
conversations = get_conversations()
messages = get_messages()
groups = get_groups()
unread_total = sum(1 for m in messages if not m.get("read") and m.get("sender_id") != "self")
return {
"total_contacts": len(contacts),
"total_groups": len(groups),
"total_conversations": len(conversations),
"total_messages": len(messages),
"unread_messages": unread_total,
"contacts_by_role": {
role: len([c for c in contacts if c.get("role") == role])
for role in set(c.get("role", "parent") for c in contacts)
}
}