иним чиним чиним чиним а так же новая система друзей и бутстраппера
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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].isoformat() 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].isoformat() 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"}
|
||||
Reference in New Issue
Block a user