Files
SIB/backend/app/api/routes/__init__.py

230 lines
8.0 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlmodel import Session, select
from typing import List, Optional
from sqlalchemy import func
from app.core.database import get_session
from app.models.route import Route
from app.models.route_stop import RouteStop
from app.schemas.route import RouteResponse, RouteCreate, RouteUpdate
from app.schemas.route_stop import RouteStopCreate, RouteStopUpdate
from app.api.deps import get_current_admin
router = APIRouter(prefix="/api/routes", tags=["routes"])
@router.get("", response_model=List[RouteResponse])
async def get_routes(
origin_city: Optional[str] = Query(None),
destination_city: Optional[str] = Query(None),
session: Session = Depends(get_session)
):
"""Get all routes with optional filtering by origin and destination city."""
statement = select(Route)
if origin_city:
statement = statement.where(Route.origin_city.contains(origin_city))
if destination_city:
statement = statement.where(Route.destination_city.contains(destination_city))
routes = session.exec(statement).all()
return routes
@router.get("/{route_id}", response_model=RouteResponse)
async def get_route(route_id: str, session: Session = Depends(get_session)):
"""Get a single route by ID."""
route = session.get(Route, route_id)
if not route:
raise HTTPException(status_code=404, detail="Route not found")
return route
@router.get("/{route_id}/stops")
async def get_route_stops(route_id: str, session: Session = Depends(get_session)):
"""Get all stops for a route."""
from app.models.route_stop import RouteStop
from app.models.bus_stop import BusStop
statement = select(RouteStop, BusStop).join(
BusStop, RouteStop.stop_id == BusStop.id
).where(RouteStop.route_id == route_id).order_by(RouteStop.stop_order)
results = session.exec(statement).all()
# Merge RouteStop data into BusStop response
stops = []
for route_stop, bus_stop in results:
stop_data = bus_stop.model_dump()
stop_data['stop_order'] = route_stop.stop_order
stop_data['travel_time_minutes'] = route_stop.travel_time_minutes
stop_data['stop_delay_minutes'] = route_stop.stop_delay_minutes
stop_data['is_pickup_point'] = route_stop.is_pickup_point
stop_data['is_dropoff_point'] = route_stop.is_dropoff_point
stops.append(stop_data)
return stops
@router.post("", response_model=RouteResponse)
async def create_route(
route: RouteCreate,
session: Session = Depends(get_session),
_: bool = Depends(get_current_admin)
):
"""Create a new route (Admin only)."""
db_route = Route.model_validate(route)
session.add(db_route)
session.commit()
session.refresh(db_route)
return db_route
@router.put("/{route_id}", response_model=RouteResponse)
async def update_route(
route_id: str,
route_update: RouteUpdate,
session: Session = Depends(get_session),
_: bool = Depends(get_current_admin)
):
"""Update a route (Admin only)."""
db_route = session.get(Route, route_id)
if not db_route:
raise HTTPException(status_code=404, detail="Route not found")
route_data = route_update.model_dump(exclude_unset=True)
for key, value in route_data.items():
setattr(db_route, key, value)
session.add(db_route)
session.commit()
session.refresh(db_route)
return db_route
@router.delete("/{route_id}")
async def delete_route(
route_id: str,
session: Session = Depends(get_session),
_: bool = Depends(get_current_admin)
):
"""Delete a route (Admin only)."""
db_route = session.get(Route, route_id)
if not db_route:
raise HTTPException(status_code=404, detail="Route not found")
session.delete(db_route)
session.commit()
return {"ok": True}
# Route Stop Management with Cascade
@router.post("/{route_id}/stops")
async def add_stop_to_route(
route_id: str,
stop_data: RouteStopCreate,
session: Session = Depends(get_session),
_: bool = Depends(get_current_admin)
):
"""Add a stop to a route with cascading order adjustment."""
# 1. Check if route exists
route = session.get(Route, route_id)
if not route:
raise HTTPException(status_code=404, detail="Route not found")
# 2. Determine stop order
if stop_data.stop_order is None:
# Append to end
max_order = session.exec(select(func.max(RouteStop.stop_order)).where(RouteStop.route_id == route_id)).one()
# handle case where max_order is None (no stops)
stop_data.stop_order = (max_order or 0) + 1
else:
# Shift existing stops equal to or greater than new order
existing_stops = session.exec(
select(RouteStop).where(RouteStop.route_id == route_id, RouteStop.stop_order >= stop_data.stop_order)
).all()
for stop in existing_stops:
stop.stop_order += 1
session.add(stop)
# 3. Create new RouteStop
new_stop = RouteStop(
route_id=route_id,
stop_id=stop_data.stop_id,
stop_order=stop_data.stop_order,
travel_time_minutes=stop_data.travel_time_minutes,
stop_delay_minutes=stop_data.stop_delay_minutes or 0,
is_pickup_point=stop_data.is_pickup_point,
is_dropoff_point=stop_data.is_dropoff_point
)
session.add(new_stop)
session.commit()
session.refresh(new_stop)
return new_stop
@router.put("/{route_id}/stops/{stop_id}")
async def update_route_stop_order(
route_id: str,
stop_id: str,
update_data: RouteStopUpdate,
session: Session = Depends(get_session),
_: bool = Depends(get_current_admin)
):
"""Update a route stop, potentially reordering others."""
# This assumes we find the connection by route_id and stop_id.
# NOTE: If a stop is on a route multiple times, this logic needs ID, but for now assuming unique stop per route.
# Actually RouteStop has its own ID but we are using stop_id in path. Let's find the RouteStop entry.
route_stop = session.exec(
select(RouteStop).where(RouteStop.route_id == route_id, RouteStop.stop_id == stop_id)
).first()
if not route_stop:
raise HTTPException(status_code=404, detail="Stop not found on this route")
old_order = route_stop.stop_order
# Update fields
if update_data.is_pickup_point is not None:
route_stop.is_pickup_point = update_data.is_pickup_point
if update_data.is_dropoff_point is not None:
route_stop.is_dropoff_point = update_data.is_dropoff_point
if update_data.travel_time_minutes is not None:
route_stop.travel_time_minutes = update_data.travel_time_minutes
if update_data.stop_delay_minutes is not None:
route_stop.stop_delay_minutes = update_data.stop_delay_minutes
# Reordering logic
if update_data.stop_order is not None and update_data.stop_order != old_order:
new_order = update_data.stop_order
if new_order > old_order:
# Moving down: shift stops between old+1 and new DOWN (-1)
stops_to_shift = session.exec(
select(RouteStop).where(
RouteStop.route_id == route_id,
RouteStop.stop_order > old_order,
RouteStop.stop_order <= new_order
)
).all()
for s in stops_to_shift:
s.stop_order -= 1
session.add(s)
else:
# Moving up: shift stops between new and old-1 UP (+1)
stops_to_shift = session.exec(
select(RouteStop).where(
RouteStop.route_id == route_id,
RouteStop.stop_order >= new_order,
RouteStop.stop_order < old_order
)
).all()
for s in stops_to_shift:
s.stop_order += 1
session.add(s)
route_stop.stop_order = new_order
session.add(route_stop)
session.commit()
session.refresh(route_stop)
return route_stop