Initial commit: SIBU 2.0 MISSION
This commit is contained in:
233
backend/app/api/auth/__init__.py
Normal file
233
backend/app/api/auth/__init__.py
Normal file
@ -0,0 +1,233 @@
|
||||
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
|
||||
Reference in New Issue
Block a user