from typing import Optional from sqlalchemy.orm import Session from fastapi import HTTPException, status from backend.app.core.security import get_password_hash, verify_password, create_access_token from backend.app.repositories.users import UserRepository from backend.app.schemas.auth import UserRegister, UserLogin from backend.app.models.users import User class AuthService: def __init__(self, db: Session): self.db = db self.user_repo = UserRepository(db) def signup(self, data: UserRegister) -> User: """Register a new customer or worker.""" # Check if phone already exists existing_user = self.user_repo.get_by_phone(data.phone) if existing_user: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="A user with this phone number is already registered." ) # Hash password if provided hashed_password = None if data.password: hashed_password = get_password_hash(data.password) if data.role == "worker": # For workers, ensure rate and skill are provided if not data.skill or data.rate is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Workers must provide a skill and a daily rate." ) # Create worker and worker profile user = self.user_repo.create_worker( name=data.name, phone=data.phone, skill=data.skill, rate=data.rate, city=data.city ) # Set hashed password if provided if hashed_password: user.password_hash = hashed_password self.db.commit() elif data.role in ("customer", "mediator", "admin"): # Create customer/mediator/admin user = self.user_repo.create_customer( name=data.name, phone=data.phone, city=data.city ) # Override default role if not customer user.role = data.role if hashed_password: user.password_hash = hashed_password self.db.commit() else: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid registration role: {data.role}" ) return user def login(self, data: UserLogin) -> dict: """Authenticate user and return a JWT access token.""" user = self.user_repo.get_by_phone(data.phone) # Bypass for demo OTP 123456 if data.otp == "123456": if not user: # Dynamically create the user for demo convenience if they don't exist if data.role == "worker": user = self.user_repo.create_worker( name="Ramesh Kumar", phone=data.phone, skill="Mason", rate=650 ) else: user = self.user_repo.create_customer( name="Harsh", phone=data.phone ) user.role = data.role self.db.commit() else: # Update role if user log in with a different role in the demo selector user.role = data.role self.db.commit() else: # If not using demo OTP, check password/credentials raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials. Use demo OTP 123456." ) if not user.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="User account is suspended. Contact admin." ) # Create access token token = create_access_token(subject=user.phone) return { "access_token": token, "token_type": "bearer", "role": user.role } def get_current_user_by_token(self, token: str) -> User: """Decode JWT token and get the user.""" from jose import jwt, JWTError from backend.app.core.config import settings try: payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]) phone: str = payload.get("sub") if phone is None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid credentials token signature." ) except JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token. Please log in again." ) user = self.user_repo.get_by_phone(phone) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found." ) if not user.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Account is suspended." ) return user