from fastapi import APIRouter, Body, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from app.auth.dependencies import get_current_user, get_optional_user from app.models.user import User from app.schemas.profile import DescriptionUpdate from app.schemas.user import ( ChangeEmail, ChangePassword, ChangeUsername, MeResponse, SetPassword, UserRead, ) from app.utils.db import get_async_session from app.utils.hash_cfg import hash_password, verify_password router = APIRouter(prefix="/users", tags=["users"]) @router.get("/me", response_model=MeResponse) async def me( user: User | None = Depends(get_optional_user), ): return MeResponse( authenticated=user is not None, user=UserRead.model_validate(user) if user else None, ) @router.get("/{username}", response_model=UserRead) async def get_user( username: str, session: AsyncSession = Depends(get_async_session), ): user = await User.get_user_by_username(username, session=session) if not user: raise HTTPException(status_code=404, detail="User not found") return UserRead.model_validate(user) @router.patch("/description") async def update_description( payload: DescriptionUpdate, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_async_session), ): profile = user.profile if not profile: raise HTTPException(status_code=404, detail="Profile not found") profile.description = payload.description session.add(profile) await session.commit() await session.refresh(profile) return {"description": profile.description} @router.patch("/email") async def change_email( data: ChangeEmail = Body(...), user: User = Depends(get_current_user), session: AsyncSession = Depends(get_async_session), ): user.email = data.email session.add(user) await session.commit() await session.refresh(user) return {"email": user.email} @router.patch("/password") async def change_password( data: ChangePassword = Body(...), user: User = Depends(get_current_user), session: AsyncSession = Depends(get_async_session), ): if not user.password: raise HTTPException(status_code=400, detail="User has no password set") if not verify_password(data.current_password, user.password): raise HTTPException( status_code=400, detail="Invalid current password", ) if verify_password(data.new_password, user.password): raise HTTPException( status_code=400, detail="New password must be different from current password", ) hashed = hash_password(data.new_password) user.password = hashed session.add(user) await session.commit() await session.refresh(user) return { "success": True, "message": "Password updated successfully", } @router.post("/password/set") async def set_password( data: SetPassword, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_async_session), ): if user.password: raise HTTPException(status_code=400, detail="Password already set") if data.new_password != data.repeat_password: raise HTTPException(status_code=400, detail="Passwords do not match") user.password = hash_password(data.new_password) session.add(user) await session.commit() await session.refresh(user) return {"success": True} @router.patch("/change-username") async def change_username( data: ChangeUsername, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_async_session), ): if len(data.username) < 3: raise HTTPException(status_code=400, detail="Username too short") user.username = data.username await session.commit() await session.refresh(user) return { "success": True, "username": user.username, }