import random import string from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from google.oauth2 import id_token from google.auth.transport import requests as google_requests from sqlalchemy.orm import Session, selectinload from app.config import settings from app.database import get_db from app.models.user import User from app.models.user_profile import UserProfile from app.schemas.auth import ForgotPasswordRequest, ResetPasswordRequest from app.schemas.user import ( GoogleLoginRequest, LoginRequest, LoginResponse, UserCreate, UserResponse, ) from app.security import create_access_token, hash_password, verify_password from app.services.auth_service import forgot_password, reset_password router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/login", response_model=LoginResponse) def login(data: LoginRequest, db: Session = Depends(get_db)): user = db.query(User).options(selectinload(User.profile)).filter( (User.email == data.login) | (User.username == data.login), User.deleted_at.is_(None), ).first() if not user or not verify_password(data.password, user.password): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail={"message": "Kredensial autentikasi tidak ada atau tidak valid."}, ) if user.email_verified_at is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail={"message": "Email belum diverifikasi. Silakan verifikasi email terlebih dahulu."}, ) access_token = create_access_token({"sub": user.id}) return LoginResponse( access_token=access_token, user=user, ) @router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED) def register(data: UserCreate, db: Session = Depends(get_db)): existing = db.query(User).filter( (User.email == data.email) | (User.username == data.username) ).first() if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail={"message": "Email atau username sudah terdaftar."}, ) user = User( email=data.email, username=data.username, password=hash_password(data.password), ) db.add(user) db.flush() try: profile = UserProfile( user_id=user.id, full_name=data.full_name, phone=data.phone, timezone=data.timezone, ) db.add(profile) db.commit() except: db.rollback() raise db.refresh(user) return user @router.post("/google-login", response_model=LoginResponse) def google_login(data: GoogleLoginRequest, db: Session = Depends(get_db)): try: idinfo = id_token.verify_oauth2_token( data.credential, google_requests.Request(), settings.GOOGLE_CLIENT_ID, ) except Exception: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail={"message": "Kredensial Google tidak valid atau telah kedaluwarsa."}, ) email = idinfo.get("email") name = idinfo.get("name", "") picture = idinfo.get("picture", "") if not email: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail={"message": "Tidak dapat mengambil email dari akun Google."}, ) user = db.query(User).options(selectinload(User.profile)).filter( User.email == email, User.deleted_at.is_(None), ).first() if not user: base_username = email.split("@")[0] username = base_username suffix = 1 while db.query(User).filter(User.username == username).first(): suffix += 1 username = f"{base_username}{suffix}" random_password = "".join(random.choices(string.ascii_letters + string.digits, k=32)) user = User( email=email, username=username, password=hash_password(random_password), email_verified_at=datetime.now(timezone.utc), status="ACTIVE", ) db.add(user) db.flush() profile = UserProfile( user_id=user.id, full_name=name, timezone="Asia/Jakarta", ) db.add(profile) db.commit() db.refresh(user) else: if user.email_verified_at is None: user.email_verified_at = datetime.now(timezone.utc) if not user.profile: profile = UserProfile( user_id=user.id, full_name=name, timezone="Asia/Jakarta", ) db.add(profile) elif user.profile.full_name != name: user.profile.full_name = name db.commit() access_token = create_access_token({"sub": user.id}) return LoginResponse( access_token=access_token, user=user, ) @router.post("/forgot-password") def forgot_password_endpoint(data: ForgotPasswordRequest, db: Session = Depends(get_db)): return forgot_password(db, data.email) @router.post("/reset-password") def reset_password_endpoint(data: ResetPasswordRequest, db: Session = Depends(get_db)): return reset_password(db, data.token, data.password)