234 lines
7.7 KiB
Python
234 lines
7.7 KiB
Python
import os
|
|
import shutil
|
|
from uuid import uuid4
|
|
from typing import Annotated, Optional
|
|
from fastapi import APIRouter, HTTPException, status, Depends, UploadFile, File, Form
|
|
from sqlmodel import Session, select
|
|
from app.core.database import get_session
|
|
from app.core.security import verify_password, get_password_hash, create_access_token, get_token_payload
|
|
from app.models.user import User, DriverProfile, UserRole, VehicleType
|
|
from app.api.deps import oauth2_scheme
|
|
from app.schemas.user import PassengerCreate, Token, UserResponse, LoginRequest
|
|
|
|
|
|
router = APIRouter(prefix="/api/auth", tags=["auth"])
|
|
|
|
UPLOAD_DIR = "uploads"
|
|
|
|
|
|
@router.post("/login", response_model=Token)
|
|
async def login(
|
|
data: LoginRequest,
|
|
session: Session = Depends(get_session)
|
|
):
|
|
print(f"DEBUG: Login attempt for email: {data.email}")
|
|
user = session.exec(select(User).where(User.email == data.email)).first()
|
|
if not user:
|
|
print(f"DEBUG: User not found: {data.email}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Incorrect email or password",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
if not verify_password(data.password, user.hashed_password):
|
|
print(f"DEBUG: Invalid password for: {data.email}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
detail="Incorrect email or password",
|
|
headers={"WWW-Authenticate": "Bearer"},
|
|
)
|
|
|
|
print(f"DEBUG: Successful login for: {data.email} as {user.role}")
|
|
|
|
# Token expiration can be extended if keep_session is true
|
|
import datetime
|
|
expires = datetime.timedelta(days=30) if data.keep_session else datetime.timedelta(days=1)
|
|
|
|
access_token = create_access_token(
|
|
subject=user.id,
|
|
role=user.role,
|
|
full_name=user.full_name,
|
|
expires_delta=expires
|
|
)
|
|
return {
|
|
"access_token": access_token,
|
|
"token_type": "bearer",
|
|
"role": user.role,
|
|
"full_name": user.full_name,
|
|
"profile_photo_url": user.profile_photo_url
|
|
}
|
|
|
|
|
|
@router.post("/register/passenger", response_model=UserResponse)
|
|
async def register_passenger(
|
|
data: PassengerCreate,
|
|
session: Session = Depends(get_session)
|
|
):
|
|
# Check if user exists
|
|
existing_user = session.exec(select(User).where(User.email == data.email)).first()
|
|
if existing_user:
|
|
raise HTTPException(status_code=400, detail="Email already registered")
|
|
|
|
new_user = User(
|
|
email=data.email,
|
|
full_name=data.full_name,
|
|
hashed_password=get_password_hash(data.password),
|
|
role=UserRole.PASSENGER
|
|
)
|
|
session.add(new_user)
|
|
session.commit()
|
|
session.refresh(new_user)
|
|
return new_user
|
|
|
|
|
|
@router.post("/register/driver", response_model=UserResponse)
|
|
async def register_driver(
|
|
full_name: str = Form(...),
|
|
email: str = Form(...),
|
|
phone_number: str = Form(...),
|
|
password: str = Form(...),
|
|
cedula: str = Form(...),
|
|
vehicle_type: VehicleType = Form(...),
|
|
license_plate: str = Form(...),
|
|
cooperative_name: Optional[str] = Form(None),
|
|
profile_photo: Optional[UploadFile] = File(None),
|
|
vehicle_photo: UploadFile = File(...),
|
|
shift: Optional[str] = Form(None),
|
|
payment_methods: Optional[str] = Form(None),
|
|
speaks_english: bool = Form(False),
|
|
session: Session = Depends(get_session)
|
|
):
|
|
# Check if user exists
|
|
existing_user = session.exec(select(User).where(User.email == email)).first()
|
|
if existing_user:
|
|
raise HTTPException(status_code=400, detail="Email already registered")
|
|
|
|
# Save photos
|
|
profile_photo_url = None
|
|
if profile_photo:
|
|
ext = os.path.splitext(profile_photo.filename)[1]
|
|
filename = f"{uuid4()}{ext}"
|
|
path = os.path.join(UPLOAD_DIR, "profiles", filename)
|
|
with open(path, "wb") as buffer:
|
|
shutil.copyfileobj(profile_photo.file, buffer)
|
|
profile_photo_url = f"/uploads/profiles/{filename}"
|
|
|
|
ext_v = os.path.splitext(vehicle_photo.filename)[1]
|
|
v_filename = f"{uuid4()}{ext_v}"
|
|
v_path = os.path.join(UPLOAD_DIR, "vehicles", v_filename)
|
|
with open(v_path, "wb") as buffer:
|
|
shutil.copyfileobj(vehicle_photo.file, buffer)
|
|
vehicle_photo_url = f"/uploads/vehicles/{v_filename}"
|
|
|
|
# Create User
|
|
new_user = User(
|
|
email=email,
|
|
full_name=full_name,
|
|
hashed_password=get_password_hash(password),
|
|
role=UserRole.DRIVER,
|
|
is_verified=True, # Auto verify since it's admin registered now
|
|
profile_photo_url=profile_photo_url
|
|
)
|
|
session.add(new_user)
|
|
session.commit()
|
|
session.refresh(new_user)
|
|
|
|
# Create Driver Profile
|
|
profile = DriverProfile(
|
|
user_id=new_user.id,
|
|
cedula=cedula,
|
|
vehicle_type=vehicle_type,
|
|
license_plate=license_plate,
|
|
photo_url=profile_photo_url,
|
|
vehicle_photo_url=vehicle_photo_url,
|
|
cooperative_name=cooperative_name,
|
|
shift=shift,
|
|
payment_methods=payment_methods,
|
|
speaks_english=speaks_english
|
|
)
|
|
session.add(profile)
|
|
session.commit()
|
|
|
|
return new_user
|
|
|
|
@router.get("/me")
|
|
async def get_current_user(
|
|
token: Annotated[str, Depends(oauth2_scheme)],
|
|
session: Session = Depends(get_session)
|
|
):
|
|
"""Get current logged in user details."""
|
|
payload = get_token_payload(token)
|
|
user_id = payload.get("sub")
|
|
|
|
user = session.get(User, user_id)
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
result = {
|
|
"id": user.id,
|
|
"email": user.email,
|
|
"full_name": user.full_name,
|
|
"role": user.role,
|
|
"is_verified": user.is_verified,
|
|
"profile_photo_url": user.profile_photo_url,
|
|
"driver_profile": None
|
|
}
|
|
|
|
if user.driver_profile:
|
|
dp = user.driver_profile
|
|
result["driver_profile"] = {
|
|
"cedula": dp.cedula,
|
|
"vehicle_type": dp.vehicle_type,
|
|
"license_plate": dp.license_plate,
|
|
"cooperative_name": dp.cooperative_name,
|
|
"photo_url": dp.photo_url,
|
|
"shift": dp.shift,
|
|
"payment_methods": dp.payment_methods,
|
|
"speaks_english": dp.speaks_english
|
|
}
|
|
|
|
return result
|
|
|
|
@router.patch("/me", response_model=UserResponse)
|
|
async def update_me(
|
|
full_name: Optional[str] = Form(None),
|
|
password: Optional[str] = Form(None),
|
|
profile_photo: Optional[UploadFile] = File(None),
|
|
token: Annotated[str, Depends(oauth2_scheme)] = None,
|
|
session: Session = Depends(get_session)
|
|
):
|
|
"""Update current user profile info."""
|
|
payload = get_token_payload(token)
|
|
user_id = payload.get("sub")
|
|
user = session.get(User, user_id)
|
|
if not user:
|
|
raise HTTPException(status_code=404, detail="User not found")
|
|
|
|
if full_name:
|
|
user.full_name = full_name
|
|
if password:
|
|
user.hashed_password = get_password_hash(password)
|
|
|
|
if profile_photo:
|
|
# Create directory if not exists
|
|
profile_dir = os.path.join(UPLOAD_DIR, "profiles")
|
|
os.makedirs(profile_dir, exist_ok=True)
|
|
|
|
ext = os.path.splitext(profile_photo.filename)[1]
|
|
filename = f"{uuid4()}{ext}"
|
|
path = os.path.join(profile_dir, filename)
|
|
with open(path, "wb") as buffer:
|
|
shutil.copyfileobj(profile_photo.file, buffer)
|
|
user.profile_photo_url = f"/uploads/profiles/{filename}"
|
|
|
|
# If user is driver, also update driver profile photo_url for backwards compatibility/sync
|
|
if user.driver_profile:
|
|
user.driver_profile.photo_url = user.profile_photo_url
|
|
session.add(user.driver_profile)
|
|
|
|
session.add(user)
|
|
session.commit()
|
|
session.refresh(user)
|
|
return user
|