Automated Action 9e56bda916 Implement Multimodal Ticketing System with FastAPI and SQLite
This commit includes:
- Project structure and FastAPI setup
- SQLAlchemy models for users, vehicles, schedules, and tickets
- Alembic migrations
- User authentication and management
- Vehicle and schedule management
- Ticket purchase and cancellation with time restrictions
- Comprehensive API documentation
2025-06-17 11:08:42 +00:00

353 lines
10 KiB
Python

from datetime import datetime
from typing import Any, List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy import or_
from sqlalchemy.orm import Session
from app.core.auth import get_current_active_user
from app.db.session import get_db
from app.models.user import User
from app.models.vehicle import Schedule, Vehicle, VehicleType
from app.schemas.vehicle import (
Schedule as ScheduleSchema,
)
from app.schemas.vehicle import (
ScheduleCreate,
ScheduleUpdate,
VehicleCreate,
VehicleUpdate,
)
from app.schemas.vehicle import (
Vehicle as VehicleSchema,
)
router = APIRouter()
# Vehicle endpoints
@router.get("/", response_model=List[VehicleSchema])
def list_vehicles(
*,
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
vehicle_type: Optional[VehicleType] = None,
search: Optional[str] = None,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve vehicles with optional filtering.
"""
query = db.query(Vehicle).filter(Vehicle.is_active == True)
if vehicle_type:
query = query.filter(Vehicle.vehicle_type == vehicle_type)
if search:
query = query.filter(
or_(
Vehicle.vehicle_number.contains(search),
)
)
vehicles = query.offset(skip).limit(limit).all()
return vehicles
@router.post("/", response_model=VehicleSchema, status_code=status.HTTP_201_CREATED)
def create_vehicle(
*,
db: Session = Depends(get_db),
vehicle_in: VehicleCreate,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Create new vehicle.
"""
# Check if vehicle with same number already exists
db_vehicle = db.query(Vehicle).filter(Vehicle.vehicle_number == vehicle_in.vehicle_number).first()
if db_vehicle:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Vehicle with this number already exists",
)
vehicle = Vehicle(
vehicle_number=vehicle_in.vehicle_number,
vehicle_type=vehicle_in.vehicle_type,
capacity=vehicle_in.capacity,
is_active=True,
)
db.add(vehicle)
db.commit()
db.refresh(vehicle)
return vehicle
@router.get("/{vehicle_id}", response_model=VehicleSchema)
def get_vehicle(
*,
db: Session = Depends(get_db),
vehicle_id: int,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Get vehicle by ID.
"""
vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first()
if not vehicle:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Vehicle not found"
)
return vehicle
@router.put("/{vehicle_id}", response_model=VehicleSchema)
def update_vehicle(
*,
db: Session = Depends(get_db),
vehicle_id: int,
vehicle_in: VehicleUpdate,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Update a vehicle.
"""
vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first()
if not vehicle:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Vehicle not found"
)
# Check if vehicle number is being updated and if it's already taken
if vehicle_in.vehicle_number and vehicle_in.vehicle_number != vehicle.vehicle_number:
db_vehicle = db.query(Vehicle).filter(Vehicle.vehicle_number == vehicle_in.vehicle_number).first()
if db_vehicle:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Vehicle with this number already exists",
)
vehicle_data = vehicle_in.dict(exclude_unset=True)
for key, value in vehicle_data.items():
setattr(vehicle, key, value)
db.add(vehicle)
db.commit()
db.refresh(vehicle)
return vehicle
@router.delete("/{vehicle_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
def delete_vehicle(
*,
db: Session = Depends(get_db),
vehicle_id: int,
_: User = Depends(get_current_active_user),
) -> None:
"""
Delete a vehicle (soft delete).
"""
vehicle = db.query(Vehicle).filter(Vehicle.id == vehicle_id, Vehicle.is_active == True).first()
if not vehicle:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Vehicle not found"
)
# Soft delete by setting is_active to False
vehicle.is_active = False
db.add(vehicle)
db.commit()
return None
# Schedule endpoints
@router.get("/schedules", response_model=List[ScheduleSchema])
def list_schedules(
*,
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100,
vehicle_id: Optional[int] = None,
vehicle_type: Optional[VehicleType] = None,
departure_from: Optional[datetime] = None,
departure_to: Optional[datetime] = None,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Retrieve schedules with optional filtering.
"""
query = db.query(Schedule).join(Vehicle).filter(
Schedule.is_active == True,
Vehicle.is_active == True
)
if vehicle_id:
query = query.filter(Schedule.vehicle_id == vehicle_id)
if vehicle_type:
query = query.filter(Vehicle.vehicle_type == vehicle_type)
if departure_from:
query = query.filter(Schedule.departure_time >= departure_from)
if departure_to:
query = query.filter(Schedule.departure_time <= departure_to)
schedules = query.order_by(Schedule.departure_time).offset(skip).limit(limit).all()
return schedules
@router.post("/schedules", response_model=ScheduleSchema, status_code=status.HTTP_201_CREATED)
def create_schedule(
*,
db: Session = Depends(get_db),
schedule_in: ScheduleCreate,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Create new schedule.
"""
# Check if vehicle exists
vehicle = db.query(Vehicle).filter(
Vehicle.id == schedule_in.vehicle_id,
Vehicle.is_active == True
).first()
if not vehicle:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Vehicle not found",
)
# Validate that arrival time is after departure time
if schedule_in.arrival_time <= schedule_in.departure_time:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Arrival time must be after departure time",
)
# Validate that available seats doesn't exceed vehicle capacity
if schedule_in.available_seats > vehicle.capacity:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Available seats cannot exceed vehicle capacity ({vehicle.capacity})",
)
schedule = Schedule(
vehicle_id=schedule_in.vehicle_id,
departure_location=schedule_in.departure_location,
arrival_location=schedule_in.arrival_location,
departure_time=schedule_in.departure_time,
arrival_time=schedule_in.arrival_time,
available_seats=schedule_in.available_seats,
is_active=True,
)
db.add(schedule)
db.commit()
db.refresh(schedule)
return schedule
@router.get("/schedules/{schedule_id}", response_model=ScheduleSchema)
def get_schedule(
*,
db: Session = Depends(get_db),
schedule_id: int,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Get schedule by ID.
"""
schedule = db.query(Schedule).filter(
Schedule.id == schedule_id,
Schedule.is_active == True
).first()
if not schedule:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Schedule not found"
)
return schedule
@router.put("/schedules/{schedule_id}", response_model=ScheduleSchema)
def update_schedule(
*,
db: Session = Depends(get_db),
schedule_id: int,
schedule_in: ScheduleUpdate,
_: User = Depends(get_current_active_user),
) -> Any:
"""
Update a schedule.
"""
schedule = db.query(Schedule).filter(
Schedule.id == schedule_id,
Schedule.is_active == True
).first()
if not schedule:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Schedule not found"
)
# If updating available seats, check that it doesn't exceed vehicle capacity
if schedule_in.available_seats is not None:
vehicle = db.query(Vehicle).filter(Vehicle.id == schedule.vehicle_id).first()
if schedule_in.available_seats > vehicle.capacity:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Available seats cannot exceed vehicle capacity ({vehicle.capacity})",
)
# If updating times, validate that arrival is after departure
new_departure = schedule_in.departure_time or schedule.departure_time
new_arrival = schedule_in.arrival_time or schedule.arrival_time
if new_arrival <= new_departure:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Arrival time must be after departure time",
)
schedule_data = schedule_in.dict(exclude_unset=True)
for key, value in schedule_data.items():
setattr(schedule, key, value)
db.add(schedule)
db.commit()
db.refresh(schedule)
return schedule
@router.delete("/schedules/{schedule_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
def delete_schedule(
*,
db: Session = Depends(get_db),
schedule_id: int,
_: User = Depends(get_current_active_user),
) -> None:
"""
Delete a schedule (soft delete).
"""
schedule = db.query(Schedule).filter(
Schedule.id == schedule_id,
Schedule.is_active == True
).first()
if not schedule:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Schedule not found"
)
# Soft delete by setting is_active to False
schedule.is_active = False
db.add(schedule)
db.commit()
return None