from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from sqlalchemy.exc import IntegrityError from datetime import datetime from auth.database import get_db from auth.models import User from auth.schemas import ( SignupSchema, LoginSchema, TokenSchema, ) from auth.security import ( hash_password, verify_password, create_access_token, decode_token, ) router = APIRouter( prefix="/api/auth", tags=["Authentication"], ) # ========================================================= # SIGNUP # ========================================================= @router.post("/signup", status_code=201) def signup( data: SignupSchema, db: Session = Depends(get_db), ): try: # ================================================= # NORMALIZATION # ================================================= email = data.email.strip().lower() username = data.username.strip() # ================================================= # EXISTING EMAIL CHECK # ================================================= existing_email = ( db.query(User) .filter(User.email == email) .first() ) if existing_email: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Email already registered", ) # ================================================= # EXISTING USERNAME CHECK # ================================================= existing_username = ( db.query(User) .filter(User.username == username) .first() ) if existing_username: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Username already taken", ) # ================================================= # HASH PASSWORD # ================================================= password_hash = hash_password(data.password) # ================================================= # CREATE USER # ================================================= user = User( email=email, username=username, password_hash=password_hash, created_at=datetime.utcnow(), ) db.add(user) db.commit() db.refresh(user) # ================================================= # CREATE JWT # ================================================= token = create_access_token( { "sub": str(user.id), "email": user.email, } ) return { "status": "success", "message": "Account created successfully", "token": token, "user": { "id": str(user.id), "email": user.email, "username": user.username, "created_at": ( user.created_at.isoformat() if user.created_at else None ), }, } except HTTPException: raise except IntegrityError as e: db.rollback() raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="User already exists", ) except ValueError as e: db.rollback() raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: db.rollback() print("SIGNUP ERROR:", str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Signup failed: {str(e)}", ) # ========================================================= # LOGIN # ========================================================= @router.post("/login") def login( data: LoginSchema, db: Session = Depends(get_db), ): try: email = data.email.strip().lower() user = ( db.query(User) .filter(User.email == email) .first() ) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", ) if not verify_password( data.password, user.password_hash, ): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid email or password", ) token = create_access_token( { "sub": str(user.id), "email": user.email, } ) return { "status": "success", "message": "Login successful", "token": token, "user": { "id": str(user.id), "email": user.email, "username": user.username, "created_at": ( user.created_at.isoformat() if user.created_at else None ), }, } except HTTPException: raise except Exception as e: print("LOGIN ERROR:", str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Login failed: {str(e)}", ) # ========================================================= # VERIFY TOKEN # ========================================================= @router.post("/verify") def verify_token( data: TokenSchema, ): try: payload = decode_token(data.token) if not payload: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token", ) return { "valid": True, "payload": payload, } except HTTPException: raise except Exception as e: print("VERIFY ERROR:", str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Token verification failed: {str(e)}", ) # ========================================================= # REFRESH TOKEN # ========================================================= @router.post("/refresh") def refresh_token( data: TokenSchema, db: Session = Depends(get_db), ): try: payload = decode_token(data.token) user_id = payload.get("sub") if not user_id: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token payload", ) user = ( db.query(User) .filter(User.id == user_id) .first() ) if not user: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="User not found", ) new_token = create_access_token( { "sub": str(user.id), "email": user.email, } ) return { "status": "success", "token": new_token, } except HTTPException: raise except Exception as e: print("REFRESH ERROR:", str(e)) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Refresh failed: {str(e)}", ) # ========================================================= # LOGOUT # ========================================================= @router.post("/logout") def logout(): return { "status": "success", "message": "Logout successful", }