"""User routes for SocialPhoto.""" import sqlite3 from typing import List from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from app.auth import get_current_user_id from app.database import get_db, row_to_dict from app.models import PostResponse, UserResponse, UserStats router = APIRouter(prefix="/users", tags=["Users"]) security = HTTPBearer() def _get_user_with_stats(conn: sqlite3.Connection, user_id: int) -> dict: """Get user data with stats.""" cursor = conn.cursor() # Get user cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,)) row = cursor.fetchone() if not row: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) user = row_to_dict(row) # Get posts count cursor.execute("SELECT COUNT(*) as count FROM posts WHERE user_id = ?", (user_id,)) posts_count = cursor.fetchone()["count"] # Get followers count cursor.execute( "SELECT COUNT(*) as count FROM follows WHERE following_id = ?", (user_id,), ) followers_count = cursor.fetchone()["count"] # Get following count cursor.execute( "SELECT COUNT(*) as count FROM follows WHERE follower_id = ?", (user_id,), ) following_count = cursor.fetchone()["count"] user["posts_count"] = posts_count user["followers_count"] = followers_count user["following_count"] = following_count return user @router.get("/{user_id}", response_model=UserResponse) async def get_user( user_id: int, conn: sqlite3.Connection = Depends(get_db), ) -> UserResponse: """Get user profile.""" user = _get_user_with_stats(conn, user_id) return UserResponse(**user) @router.get("/{user_id}/posts", response_model=List[PostResponse]) async def get_user_posts( user_id: int, conn: sqlite3.Connection = Depends(get_db), ) -> List[PostResponse]: """Get all posts by a user.""" cursor = conn.cursor() # Check user exists cursor.execute("SELECT id FROM users WHERE id = ?", (user_id,)) if not cursor.fetchone(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) # Get posts with counts cursor.execute( """ SELECT p.id, p.user_id, u.username, p.image_path, p.caption, p.created_at, (SELECT COUNT(*) FROM likes WHERE post_id = p.id) as likes_count, (SELECT COUNT(*) FROM dislikes WHERE post_id = p.id) as dislikes_count, (SELECT COUNT(*) FROM comments WHERE post_id = p.id) as comments_count FROM posts p JOIN users u ON p.user_id = u.id WHERE p.user_id = ? ORDER BY p.created_at DESC """, (user_id,), ) posts = [] for row in cursor.fetchall(): post = row_to_dict(row) posts.append( PostResponse( id=post["id"], user_id=post["user_id"], username=post["username"], image_url=f"/uploads/{post['image_path'].split('/')[-1]}", caption=post["caption"], likes_count=post["likes_count"], dislikes_count=post["dislikes_count"], comments_count=post["comments_count"], created_at=post["created_at"], ) ) return posts @router.get("/{user_id}/stats", response_model=UserStats) async def get_user_stats( user_id: int, conn: sqlite3.Connection = Depends(get_db), ) -> UserStats: """Get user statistics.""" user = _get_user_with_stats(conn, user_id) return UserStats( posts_count=user["posts_count"], followers_count=user["followers_count"], following_count=user["following_count"], ) @router.post("/{user_id}/follow", status_code=status.HTTP_201_CREATED) async def follow_user( user_id: int, credentials: HTTPAuthorizationCredentials = Depends(security), conn: sqlite3.Connection = Depends(get_db), ) -> dict: """Follow a user.""" current_user_id = await get_current_user_id(credentials) cursor = conn.cursor() # Check user exists cursor.execute("SELECT id FROM users WHERE id = ?", (user_id,)) if not cursor.fetchone(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) # Cannot follow yourself if current_user_id == user_id: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="You cannot follow yourself", ) # Check if already following cursor.execute( "SELECT id FROM follows WHERE follower_id = ? AND following_id = ?", (current_user_id, user_id), ) if cursor.fetchone(): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="You already follow this user", ) # Create follow cursor.execute( "INSERT INTO follows (follower_id, following_id) VALUES (?, ?)", (current_user_id, user_id), ) conn.commit() return {"message": "Successfully followed user"} @router.delete("/{user_id}/follow", status_code=status.HTTP_200_OK) async def unfollow_user( user_id: int, credentials: HTTPAuthorizationCredentials = Depends(security), conn: sqlite3.Connection = Depends(get_db), ) -> dict: """Unfollow a user.""" current_user_id = await get_current_user_id(credentials) cursor = conn.cursor() # Check if following cursor.execute( "SELECT id FROM follows WHERE follower_id = ? AND following_id = ?", (current_user_id, user_id), ) if not cursor.fetchone(): raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="You are not following this user", ) # Delete follow cursor.execute( "DELETE FROM follows WHERE follower_id = ? AND following_id = ?", (current_user_id, user_id), ) conn.commit() return {"message": "Successfully unfollowed user"}