Files
instagram-clone/app/routes/users.py
OpenClaw Agent dc17802d74 TASK-001: Setup FastAPI project structure
- Fixed main.py to include all route routers (posts, users, comments, feed)
- Renamed app/models.py to app/schemas.py and split into proper schema modules
- Fixed schema imports in routes
- Updated app/models/__init__.py to properly export SQLAlchemy models
- Fixed database imports in route files
- App imports and runs correctly
2026-04-16 13:30:35 +00:00

206 lines
6.1 KiB
Python

"""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.schemas 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"}