"""BAN YARO — Direktnachrichten""" import logging from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel from database import db from auth import get_current_user router = APIRouter() logger = logging.getLogger(__name__) def _conv_key(a: int, b: int): """Normalisiert Konversations-User-IDs: user_a < user_b.""" return (min(a, b), max(a, b)) @router.get("/conversations") async def list_conversations(user=Depends(get_current_user)): uid = user["id"] with db() as conn: rows = conn.execute(""" SELECT c.id, c.last_msg_at, CASE WHEN c.user_a_id=? THEN c.user_b_id ELSE c.user_a_id END AS partner_id, CASE WHEN c.user_a_id=? THEN ub.name ELSE ua.name END AS partner_name, (SELECT text FROM direct_messages WHERE conversation_id=c.id AND is_deleted=0 ORDER BY created_at DESC LIMIT 1) AS last_text, (SELECT COUNT(*) FROM direct_messages WHERE conversation_id=c.id AND sender_id != ? AND is_deleted=0 AND created_at > COALESCE( CASE WHEN c.user_a_id=? THEN c.a_read_at ELSE c.b_read_at END, '1970-01-01' ) ) AS unread_count FROM conversations c JOIN users ua ON ua.id=c.user_a_id JOIN users ub ON ub.id=c.user_b_id WHERE c.user_a_id=? OR c.user_b_id=? ORDER BY COALESCE(c.last_msg_at, c.created_at) DESC """, (uid, uid, uid, uid, uid, uid)).fetchall() return [dict(r) for r in rows] class StartConvModel(BaseModel): partner_id: int @router.post("/conversations", status_code=201) async def start_conversation(data: StartConvModel, user=Depends(get_current_user)): uid = user["id"] if uid == data.partner_id: raise HTTPException(400, "Du kannst dir selbst keine Nachrichten schicken.") a, b = _conv_key(uid, data.partner_id) with db() as conn: f = conn.execute(""" SELECT 1 FROM friendships WHERE ((requester_id=? AND addressee_id=?) OR (requester_id=? AND addressee_id=?)) AND status='accepted' """, (uid, data.partner_id, data.partner_id, uid)).fetchone() if not f: raise HTTPException(403, "Ihr seid noch keine Freunde.") existing = conn.execute( "SELECT id FROM conversations WHERE user_a_id=? AND user_b_id=?", (a, b) ).fetchone() if existing: return {"conversation_id": existing["id"]} cur = conn.execute( "INSERT INTO conversations (user_a_id, user_b_id) VALUES (?,?)", (a, b) ) return {"conversation_id": cur.lastrowid} @router.get("/conversations/{conv_id}") async def get_messages(conv_id: int, offset: int = 0, limit: int = 50, user=Depends(get_current_user)): uid = user["id"] with db() as conn: conv = conn.execute( "SELECT * FROM conversations WHERE id=? AND (user_a_id=? OR user_b_id=?)", (conv_id, uid, uid) ).fetchone() if not conv: raise HTTPException(404, "Konversation nicht gefunden.") partner_id = conv["user_b_id"] if conv["user_a_id"] == uid else conv["user_a_id"] partner = conn.execute("SELECT name FROM users WHERE id=?", (partner_id,)).fetchone() msgs = conn.execute(""" SELECT m.id, m.sender_id, m.text, m.is_deleted, m.created_at, u.name AS sender_name FROM direct_messages m JOIN users u ON u.id=m.sender_id WHERE m.conversation_id=? ORDER BY m.created_at ASC LIMIT ? OFFSET ? """, (conv_id, limit, offset)).fetchall() return { "conversation_id": conv_id, "partner_id": partner_id, "partner_name": partner["name"] if partner else "Unbekannt", "messages": [dict(m) for m in msgs], } class SendMsgModel(BaseModel): text: str @router.post("/conversations/{conv_id}/messages", status_code=201) async def send_message(conv_id: int, data: SendMsgModel, user=Depends(get_current_user)): uid = user["id"] text = data.text.strip() if not text: raise HTTPException(400, "Nachricht darf nicht leer sein.") if len(text) > 2000: raise HTTPException(400, "Nachricht zu lang (max. 2000 Zeichen).") with db() as conn: conv = conn.execute( "SELECT * FROM conversations WHERE id=? AND (user_a_id=? OR user_b_id=?)", (conv_id, uid, uid) ).fetchone() if not conv: raise HTTPException(404, "Konversation nicht gefunden.") partner_id = conv["user_b_id"] if conv["user_a_id"] == uid else conv["user_a_id"] cur = conn.execute(""" INSERT INTO direct_messages (conversation_id, sender_id, text) VALUES (?,?,?) """, (conv_id, uid, text)) msg_id = cur.lastrowid conn.execute( "UPDATE conversations SET last_msg_at=datetime('now') WHERE id=?", (conv_id,) ) try: from routes.push import send_push_to_user preview = text[:100] + ("…" if len(text) > 100 else "") send_push_to_user(partner_id, { "title": f"Nachricht von {user['name']}", "body": preview, "type": "chat_message", "tag": f"chat-{conv_id}", "data": {"page": "chat", "conversation_id": conv_id}, }) except Exception: pass return {"id": msg_id, "ok": True} @router.post("/conversations/{conv_id}/read") async def mark_read(conv_id: int, user=Depends(get_current_user)): uid = user["id"] with db() as conn: conv = conn.execute( "SELECT * FROM conversations WHERE id=? AND (user_a_id=? OR user_b_id=?)", (conv_id, uid, uid) ).fetchone() if not conv: raise HTTPException(404) field = "a_read_at" if conv["user_a_id"] == uid else "b_read_at" conn.execute( f"UPDATE conversations SET {field}=datetime('now') WHERE id=?", (conv_id,) ) return {"ok": True} @router.delete("/messages/{msg_id}") async def delete_message(msg_id: int, user=Depends(get_current_user)): uid = user["id"] with db() as conn: msg = conn.execute( "SELECT id FROM direct_messages WHERE id=? AND sender_id=?", (msg_id, uid) ).fetchone() if not msg: raise HTTPException(404, "Nachricht nicht gefunden.") conn.execute( "UPDATE direct_messages SET is_deleted=1, text='[gelöscht]' WHERE id=?", (msg_id,) ) return {"ok": True}