- Freundschaften (pending/accepted), Nutzersuche, Anfragen per Push - Direktnachrichten mit Polling, iMessage-Stil, Deep-Links aus Push - Alle Seiten (map, places, diary, health, dog-profile, sitting, knigge, forum, wiki, walks) vollständig auf Phosphor-Icons migriert - Wikidata-Rassen-Scraper (~833 neue Rassen, lokal gespiegelte Fotos) - TheDogAPI lokal gespiegelt (169 Rassen + Fotos) - Quiz-Result-Cards horizontal (korrekte Bildproportionen) - SW by-v89
191 lines
6.7 KiB
Python
191 lines
6.7 KiB
Python
"""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}
|