from fastapi import APIRouter, Depends, HTTPException, status from pydantic import BaseModel from sqlalchemy.ext.asyncio import AsyncSession from app.auth.dependencies import get_current_user from app.auth.security import hash_password, verify_password from app.db.models import User from app.db.session import get_session router = APIRouter(prefix="/users", tags=["users"]) _MIN_PASSWORD_LENGTH = 8 class MeResponse(BaseModel): id: int username: str address: str balance_sats: int created_at: str @router.get("/me", response_model=MeResponse) async def me(user: User = Depends(get_current_user)) -> MeResponse: return MeResponse( id=user.id, username=user.username, address=user.address, balance_sats=user.cached_balance_sats, created_at=user.created_at.isoformat(), ) class ChangePasswordRequest(BaseModel): current_password: str new_password: str @router.post("/me/change-password", status_code=status.HTTP_204_NO_CONTENT) async def change_password( body: ChangePasswordRequest, user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> None: """Self-service password change — requires the current password, unlike the admin-only /admin/users/{id}/reset-password (which is for a user who's actually locked out and can't provide it).""" if not verify_password(body.current_password, user.password_hash): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "current password is incorrect") if len(body.new_password) < _MIN_PASSWORD_LENGTH: raise HTTPException(status.HTTP_400_BAD_REQUEST, f"new password must be at least {_MIN_PASSWORD_LENGTH} characters") user.password_hash = hash_password(body.new_password) await session.commit()