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