from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer from typing import Optional, Dict, Any from sqlalchemy.orm import Session from app.core.database import get_db from app.core.auth import get_password_hash, verify_password, create_access_token, check_trip_auth from app.schemas.trip import TripCreate, TripResponse, TripWithParticipants from app.schemas.auth import TripAuthRequest, TripAuthResponse from app.models.trip import Trip from app.models.participant import Participant import random import string router = APIRouter() def generate_share_code(): """Generate a unique 6-character share code""" while True: code = ''.join(random.choices(string.ascii_uppercase + string.digits, k=6)) # Check if code already exists (we'll do this in the route for simplicity) return code @router.post("") @router.post("/") async def create_trip(trip: TripCreate, db: Session = Depends(get_db)): """Create a new trip""" # Generate unique share code share_code = generate_share_code() while db.query(Trip).filter(Trip.share_code == share_code).first(): share_code = generate_share_code() # Hash password if provided password_hash = None if trip.password and trip.password.strip(): password_hash = get_password_hash(trip.password.strip()) # Create trip db_trip = Trip( name=trip.name, description=trip.description, creator_name=trip.creator_name, currency_code=trip.currency_code, share_code=share_code, password_hash=password_hash ) db.add(db_trip) db.commit() db.refresh(db_trip) # Add creator as first participant creator = Participant( trip_id=db_trip.id, name=trip.creator_name, is_creator=True, is_active=True ) db.add(creator) db.commit() # If trip has a password, create and return an access token for the creator if password_hash: # Automatically authenticate the creator access_token = create_access_token(data={"trip_id": db_trip.id}) # Return trip data with access token (as dict to include extra field) trip_response = TripResponse.from_orm(db_trip) # Use model_dump with mode='json' to properly serialize datetimes response_data = trip_response.model_dump(mode='json') response_data["access_token"] = access_token response_data["requires_auth"] = True # Use JSONResponse to return dict with extra fields from fastapi.responses import JSONResponse return JSONResponse(content=response_data) return TripResponse.from_orm(db_trip) @router.get("/{trip_id}", response_model=TripWithParticipants) async def get_trip( trip_id: int, db: Session = Depends(get_db), credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False)) ): """Get trip details with participants (requires authentication if trip has password)""" trip = check_trip_auth(trip_id, credentials, db) # Get participants participants = db.query(Participant).filter( Participant.trip_id == trip_id, Participant.is_active == True ).all() trip_with_participants = TripWithParticipants( id=trip.id, name=trip.name, description=trip.description, creator_name=trip.creator_name, currency_code=trip.currency_code, share_code=trip.share_code, is_active=trip.is_active, created_at=trip.created_at, updated_at=trip.updated_at, participants=[ {"id": p.id, "name": p.name, "is_creator": p.is_creator} for p in participants ] ) return trip_with_participants @router.get("/share/{share_code}", response_model=TripWithParticipants) async def get_trip_by_share_code(share_code: str, db: Session = Depends(get_db)): """Get trip details by share code (does not require auth, but returns if password protected)""" trip = db.query(Trip).filter(Trip.share_code == share_code, Trip.is_active == "active").first() if not trip: raise HTTPException(status_code=404, detail="Trip not found") # If trip has password, don't return full details - just indicate it's password protected # The frontend will need to authenticate separately if trip.password_hash is not None: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="This trip is password protected. Please authenticate.", headers={"WWW-Authenticate": "Bearer"}, ) # Get participants participants = db.query(Participant).filter( Participant.trip_id == trip.id, Participant.is_active == True ).all() trip_with_participants = TripWithParticipants( id=trip.id, name=trip.name, description=trip.description, creator_name=trip.creator_name, currency_code=trip.currency_code, share_code=trip.share_code, is_active=trip.is_active, created_at=trip.created_at, updated_at=trip.updated_at, participants=[ {"id": p.id, "name": p.name, "is_creator": p.is_creator} for p in participants ] ) return trip_with_participants