177 lines
6.6 KiB
Python
177 lines
6.6 KiB
Python
from fastapi import APIRouter, HTTPException, Depends
|
|
from pydantic import BaseModel
|
|
from typing import Optional
|
|
import structlog
|
|
import time
|
|
|
|
from auth import get_db, get_current_user
|
|
|
|
logger = structlog.get_logger(__name__)
|
|
|
|
router = APIRouter(prefix="/api", tags=["friends"])
|
|
|
|
def init_friends_db():
|
|
with get_db() as conn:
|
|
conn.executescript("""
|
|
CREATE TABLE IF NOT EXISTS friendships (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
requester_id INTEGER NOT NULL,
|
|
target_id INTEGER NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(requester_id, target_id),
|
|
FOREIGN KEY (requester_id) REFERENCES users(id),
|
|
FOREIGN KEY (target_id) REFERENCES users(id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS user_status (
|
|
user_id INTEGER PRIMARY KEY,
|
|
is_online INTEGER DEFAULT 0,
|
|
current_pack TEXT DEFAULT '',
|
|
last_seen TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (user_id) REFERENCES users(id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_friendships_requester ON friendships(requester_id);
|
|
CREATE INDEX IF NOT EXISTS idx_friendships_target ON friendships(target_id);
|
|
""")
|
|
|
|
class AddFriendRequest(BaseModel):
|
|
username: str
|
|
|
|
class RemoveFriendRequest(BaseModel):
|
|
user_id: int
|
|
|
|
class AcceptFriendRequest(BaseModel):
|
|
user_id: int
|
|
|
|
class StatusUpdateRequest(BaseModel):
|
|
online: bool = True
|
|
current_pack: Optional[str] = None
|
|
|
|
@router.post("/friends/add")
|
|
async def add_friend(
|
|
req: AddFriendRequest,
|
|
current_user: dict = Depends(get_current_user)
|
|
):
|
|
with get_db() as conn:
|
|
cursor = conn.execute("SELECT id FROM users WHERE username = ?", (req.username,))
|
|
target = cursor.fetchone()
|
|
if not target:
|
|
raise HTTPException(404, "User not found")
|
|
target_id = target[0]
|
|
|
|
if target_id == current_user["id"]:
|
|
raise HTTPException(400, "Cannot add yourself")
|
|
|
|
cursor = conn.execute(
|
|
"SELECT status FROM friendships WHERE requester_id = ? AND target_id = ?",
|
|
(current_user["id"], target_id)
|
|
)
|
|
existing = cursor.fetchone()
|
|
if existing:
|
|
if existing[0] == "accepted":
|
|
raise HTTPException(400, "Already friends")
|
|
raise HTTPException(400, f"Friend request already {existing[0]}")
|
|
|
|
conn.execute(
|
|
"INSERT INTO friendships (requester_id, target_id, status) VALUES (?, ?, 'pending')",
|
|
(current_user["id"], target_id)
|
|
)
|
|
logger.info("Friend request sent", from_user=current_user["id"], to_user=target_id)
|
|
return {"message": "Friend request sent"}
|
|
|
|
@router.post("/friends/accept")
|
|
async def accept_friend(
|
|
req: AcceptFriendRequest,
|
|
current_user: dict = Depends(get_current_user)
|
|
):
|
|
with get_db() as conn:
|
|
cursor = conn.execute(
|
|
"SELECT id, requester_id FROM friendships WHERE target_id = ? AND requester_id = ? AND status = 'pending'",
|
|
(current_user["id"], req.user_id)
|
|
)
|
|
row = cursor.fetchone()
|
|
if not row:
|
|
raise HTTPException(404, "No pending friend request from this user")
|
|
conn.execute("UPDATE friendships SET status = 'accepted' WHERE id = ?", (row[0],))
|
|
logger.info("Friend request accepted", from_user=req.user_id, to_user=current_user["id"])
|
|
return {"message": "Friend request accepted"}
|
|
|
|
@router.post("/friends/remove")
|
|
async def remove_friend(
|
|
req: RemoveFriendRequest,
|
|
current_user: dict = Depends(get_current_user)
|
|
):
|
|
with get_db() as conn:
|
|
cursor = conn.execute(
|
|
"SELECT id FROM friendships WHERE (requester_id = ? AND target_id = ?) OR (requester_id = ? AND target_id = ?)",
|
|
(current_user["id"], req.user_id, req.user_id, current_user["id"])
|
|
)
|
|
row = cursor.fetchone()
|
|
if not row:
|
|
raise HTTPException(404, "Not friends")
|
|
conn.execute("DELETE FROM friendships WHERE id = ?", (row[0],))
|
|
logger.info("Friend removed", user=current_user["id"], target=req.user_id)
|
|
return {"message": "Friend removed"}
|
|
|
|
@router.get("/friends/list")
|
|
async def list_friends(current_user: dict = Depends(get_current_user)):
|
|
friends = []
|
|
with get_db() as conn:
|
|
rows = conn.execute("""
|
|
SELECT u.id, u.username, u.role,
|
|
COALESCE(us.is_online, 0) as online,
|
|
COALESCE(us.current_pack, '') as current_pack,
|
|
us.last_seen
|
|
FROM friendships f
|
|
JOIN users u ON (CASE WHEN f.requester_id = ? THEN f.target_id ELSE f.requester_id END) = u.id
|
|
LEFT JOIN user_status us ON u.id = us.user_id
|
|
WHERE (f.requester_id = ? OR f.target_id = ?) AND f.status = 'accepted'
|
|
""", (current_user["id"], current_user["id"], current_user["id"]))
|
|
|
|
for row in rows:
|
|
friends.append({
|
|
"id": row[0],
|
|
"username": row[1],
|
|
"role": row[2],
|
|
"online": bool(row[3]),
|
|
"current_pack": row[4],
|
|
"last_seen": row[5] if row[5] else None
|
|
})
|
|
|
|
return {"friends": friends}
|
|
|
|
@router.get("/friends/requests")
|
|
async def list_friend_requests(current_user: dict = Depends(get_current_user)):
|
|
requests = []
|
|
with get_db() as conn:
|
|
rows = conn.execute("""
|
|
SELECT u.id, u.username, u.role, f.created_at
|
|
FROM friendships f
|
|
JOIN users u ON f.requester_id = u.id
|
|
WHERE f.target_id = ? AND f.status = 'pending'
|
|
""", (current_user["id"],))
|
|
for row in rows:
|
|
requests.append({
|
|
"id": row[0],
|
|
"username": row[1],
|
|
"role": row[2],
|
|
"created_at": row[3] if row[3] else None
|
|
})
|
|
return {"requests": requests}
|
|
|
|
@router.post("/friends/status")
|
|
async def update_status(
|
|
req: StatusUpdateRequest,
|
|
current_user: dict = Depends(get_current_user)
|
|
):
|
|
with get_db() as conn:
|
|
conn.execute("""
|
|
INSERT INTO user_status (user_id, is_online, current_pack, last_seen)
|
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(user_id) DO UPDATE SET
|
|
is_online = excluded.is_online,
|
|
current_pack = COALESCE(excluded.current_pack, user_status.current_pack),
|
|
last_seen = CURRENT_TIMESTAMP
|
|
""", (current_user["id"], int(req.online), req.current_pack or ""))
|
|
return {"status": "ok"}
|