Sprint 11: Freunde & Chat + Phosphor-Icon-Vollmigration
- 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
This commit is contained in:
parent
96bd57f0ad
commit
097295c628
44 changed files with 9980 additions and 300 deletions
148
backend/routes/friends.py
Normal file
148
backend/routes/friends.py
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
"""BAN YARO — Freundschaften"""
|
||||
import logging
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from database import db
|
||||
from auth import get_current_user
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def list_friends(user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
friends = conn.execute("""
|
||||
SELECT f.id, f.status, f.created_at,
|
||||
CASE WHEN f.requester_id=? THEN f.addressee_id ELSE f.requester_id END AS friend_id,
|
||||
u.name AS friend_name
|
||||
FROM friendships f
|
||||
JOIN users u ON u.id = CASE WHEN f.requester_id=? THEN f.addressee_id ELSE f.requester_id END
|
||||
WHERE (f.requester_id=? OR f.addressee_id=?) AND f.status='accepted'
|
||||
ORDER BY u.name
|
||||
""", (uid, uid, uid, uid)).fetchall()
|
||||
|
||||
incoming = conn.execute("""
|
||||
SELECT f.id, f.created_at, u.name AS requester_name, u.id AS requester_id
|
||||
FROM friendships f
|
||||
JOIN users u ON u.id=f.requester_id
|
||||
WHERE f.addressee_id=? AND f.status='pending'
|
||||
ORDER BY f.created_at DESC
|
||||
""", (uid,)).fetchall()
|
||||
|
||||
outgoing = conn.execute("""
|
||||
SELECT f.id, f.created_at, u.name AS addressee_name, u.id AS addressee_id
|
||||
FROM friendships f
|
||||
JOIN users u ON u.id=f.addressee_id
|
||||
WHERE f.requester_id=? AND f.status='pending'
|
||||
ORDER BY f.created_at DESC
|
||||
""", (uid,)).fetchall()
|
||||
|
||||
return {
|
||||
"friends": [dict(r) for r in friends],
|
||||
"incoming": [dict(r) for r in incoming],
|
||||
"outgoing": [dict(r) for r in outgoing],
|
||||
}
|
||||
|
||||
|
||||
@router.get("/search")
|
||||
async def search_users(q: str = "", user=Depends(get_current_user)):
|
||||
if len(q.strip()) < 2:
|
||||
return []
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT u.id, u.name
|
||||
FROM users u
|
||||
WHERE u.id != ?
|
||||
AND u.name LIKE ?
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM friendships f
|
||||
WHERE (f.requester_id=? AND f.addressee_id=u.id)
|
||||
OR (f.requester_id=u.id AND f.addressee_id=?)
|
||||
)
|
||||
LIMIT 20
|
||||
""", (uid, f"%{q.strip()}%", uid, uid)).fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
|
||||
@router.post("/request/{target_id}", status_code=201)
|
||||
async def send_request(target_id: int, user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
if uid == target_id:
|
||||
raise HTTPException(400, "Du kannst dich nicht selbst als Freund hinzufügen.")
|
||||
|
||||
with db() as conn:
|
||||
if not conn.execute("SELECT 1 FROM users WHERE id=?", (target_id,)).fetchone():
|
||||
raise HTTPException(404, "Nutzer nicht gefunden.")
|
||||
|
||||
existing = conn.execute("""
|
||||
SELECT id, status FROM friendships
|
||||
WHERE (requester_id=? AND addressee_id=?) OR (requester_id=? AND addressee_id=?)
|
||||
""", (uid, target_id, target_id, uid)).fetchone()
|
||||
|
||||
if existing:
|
||||
if existing["status"] == "accepted":
|
||||
raise HTTPException(400, "Ihr seid bereits befreundet.")
|
||||
raise HTTPException(400, "Anfrage bereits vorhanden.")
|
||||
|
||||
conn.execute(
|
||||
"INSERT INTO friendships (requester_id, addressee_id) VALUES (?,?)",
|
||||
(uid, target_id)
|
||||
)
|
||||
|
||||
try:
|
||||
from routes.push import send_push_to_user
|
||||
send_push_to_user(target_id, {
|
||||
"title": "Neue Freundschaftsanfrage",
|
||||
"body": f"{user['name']} möchte dein Freund sein.",
|
||||
"type": "friend_request",
|
||||
"data": {"page": "friends"},
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/{friendship_id}/accept")
|
||||
async def accept_request(friendship_id: int, user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
f = conn.execute(
|
||||
"SELECT * FROM friendships WHERE id=? AND addressee_id=? AND status='pending'",
|
||||
(friendship_id, uid)
|
||||
).fetchone()
|
||||
if not f:
|
||||
raise HTTPException(404, "Anfrage nicht gefunden.")
|
||||
conn.execute(
|
||||
"UPDATE friendships SET status='accepted', updated_at=datetime('now') WHERE id=?",
|
||||
(friendship_id,)
|
||||
)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.post("/{friendship_id}/decline")
|
||||
async def decline_request(friendship_id: int, user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
f = conn.execute("""
|
||||
SELECT id FROM friendships
|
||||
WHERE id=? AND (addressee_id=? OR requester_id=?) AND status='pending'
|
||||
""", (friendship_id, uid, uid)).fetchone()
|
||||
if not f:
|
||||
raise HTTPException(404, "Anfrage nicht gefunden.")
|
||||
conn.execute("DELETE FROM friendships WHERE id=?", (friendship_id,))
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@router.delete("/{friend_user_id}")
|
||||
async def remove_friend(friend_user_id: int, user=Depends(get_current_user)):
|
||||
uid = user["id"]
|
||||
with db() as conn:
|
||||
conn.execute("""
|
||||
DELETE FROM friendships
|
||||
WHERE status='accepted'
|
||||
AND ((requester_id=? AND addressee_id=?) OR (requester_id=? AND addressee_id=?))
|
||||
""", (uid, friend_user_id, friend_user_id, uid))
|
||||
return {"ok": True}
|
||||
Loading…
Add table
Add a link
Reference in a new issue