fix: critical bug fixes - routes UUID, image paths, favorites loading, bottom nav debounce
This commit is contained in:
@ -6,6 +6,8 @@ from app.models.business import Business
|
||||
from app.models.user import User, UserRole
|
||||
from app.api.deps import get_current_user
|
||||
|
||||
from app.services.image_handler import save_image, delete_image
|
||||
|
||||
router = APIRouter(prefix="/api/businesses", tags=["businesses"])
|
||||
|
||||
@router.get("", response_model=List[Business])
|
||||
@ -41,17 +43,7 @@ async def create_business(
|
||||
|
||||
image_url = None
|
||||
if image:
|
||||
import os
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
UPLOAD_DIR = "uploads/businesses"
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
image_url = f"/uploads/businesses/{filename}"
|
||||
image_url = save_image(image, "businesses")
|
||||
|
||||
db_business = Business(
|
||||
name=name,
|
||||
@ -110,17 +102,9 @@ async def update_business(
|
||||
db_business.longitude = longitude
|
||||
|
||||
if image:
|
||||
import os
|
||||
import shutil
|
||||
from uuid import uuid4
|
||||
UPLOAD_DIR = "uploads/businesses"
|
||||
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
db_business.image_url = f"/uploads/businesses/{filename}"
|
||||
if db_business.image_url:
|
||||
delete_image(db_business.image_url)
|
||||
db_business.image_url = save_image(image, "businesses")
|
||||
|
||||
session.add(db_business)
|
||||
session.commit()
|
||||
@ -153,6 +137,9 @@ async def delete_business(
|
||||
if not db_business:
|
||||
raise HTTPException(status_code=404, detail="Business not found")
|
||||
|
||||
if db_business.image_url:
|
||||
delete_image(db_business.image_url)
|
||||
|
||||
session.delete(db_business)
|
||||
session.commit()
|
||||
return {"status": "success", "message": "Business deleted"}
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Form, File, UploadFile
|
||||
from sqlmodel import Session, select
|
||||
from sqlalchemy.orm import joinedload
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
from uuid import UUID
|
||||
from datetime import datetime
|
||||
from app.core.database import get_session
|
||||
from app.models.coupon import Coupon, CouponCreate, CouponUpdate
|
||||
from app.models.coupon import Coupon
|
||||
from app.models.user import User, UserRole
|
||||
from app.api.deps import get_current_user
|
||||
from app.services.image_handler import save_image, delete_image
|
||||
|
||||
router = APIRouter(prefix="/api/coupons", tags=["coupons"])
|
||||
|
||||
@ -27,7 +30,19 @@ async def list_coupons(
|
||||
async def create_coupon(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
coupon_in: CouponCreate,
|
||||
title: str = Form(...),
|
||||
business_id: Optional[UUID] = Form(None),
|
||||
description: Optional[str] = Form(None),
|
||||
business_name: Optional[str] = Form(None),
|
||||
business_address: Optional[str] = Form(None),
|
||||
business_phone: Optional[str] = Form(None),
|
||||
discount_percentage: Optional[int] = Form(None),
|
||||
discount_amount: Optional[float] = Form(None),
|
||||
category: Optional[str] = Form(None),
|
||||
valid_from: Optional[datetime] = Form(None),
|
||||
valid_until: Optional[datetime] = Form(None),
|
||||
is_active: bool = Form(True),
|
||||
image: Optional[UploadFile] = File(None),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Create a new coupon (Promoters and Admins only)."""
|
||||
@ -37,7 +52,25 @@ async def create_coupon(
|
||||
detail="Only promoters and admins can create coupons"
|
||||
)
|
||||
|
||||
db_coupon = Coupon.from_orm(coupon_in)
|
||||
image_url = None
|
||||
if image:
|
||||
image_url = save_image(image, "coupons")
|
||||
|
||||
db_coupon = Coupon(
|
||||
title=title,
|
||||
business_id=business_id,
|
||||
description=description,
|
||||
business_name=business_name,
|
||||
business_address=business_address,
|
||||
business_phone=business_phone,
|
||||
discount_percentage=discount_percentage,
|
||||
discount_amount=discount_amount,
|
||||
category=category,
|
||||
valid_from=valid_from,
|
||||
valid_until=valid_until,
|
||||
is_active=is_active,
|
||||
image_url=image_url
|
||||
)
|
||||
session.add(db_coupon)
|
||||
session.commit()
|
||||
session.refresh(db_coupon)
|
||||
@ -47,8 +80,13 @@ async def create_coupon(
|
||||
async def update_coupon(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
coupon_id: str,
|
||||
coupon_in: CouponUpdate,
|
||||
coupon_id: UUID,
|
||||
title: Optional[str] = Form(None),
|
||||
description: Optional[str] = Form(None),
|
||||
discount_percentage: Optional[int] = Form(None),
|
||||
valid_until: Optional[datetime] = Form(None),
|
||||
is_active: Optional[bool] = Form(None),
|
||||
image: Optional[UploadFile] = File(None),
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Update a coupon (Promoters and Admins only)."""
|
||||
@ -62,9 +100,21 @@ async def update_coupon(
|
||||
if not db_coupon:
|
||||
raise HTTPException(status_code=404, detail="Coupon not found")
|
||||
|
||||
coupon_data = coupon_in.dict(exclude_unset=True)
|
||||
for key, value in coupon_data.items():
|
||||
setattr(db_coupon, key, value)
|
||||
if title is not None:
|
||||
db_coupon.title = title
|
||||
if description is not None:
|
||||
db_coupon.description = description
|
||||
if discount_percentage is not None:
|
||||
db_coupon.discount_percentage = discount_percentage
|
||||
if valid_until is not None:
|
||||
db_coupon.valid_until = valid_until
|
||||
if is_active is not None:
|
||||
db_coupon.is_active = is_active
|
||||
|
||||
if image:
|
||||
if db_coupon.image_url:
|
||||
delete_image(db_coupon.image_url)
|
||||
db_coupon.image_url = save_image(image, "coupons")
|
||||
|
||||
session.add(db_coupon)
|
||||
session.commit()
|
||||
@ -75,7 +125,7 @@ async def update_coupon(
|
||||
async def delete_coupon(
|
||||
*,
|
||||
session: Session = Depends(get_session),
|
||||
coupon_id: str,
|
||||
coupon_id: UUID,
|
||||
current_user: User = Depends(get_current_user)
|
||||
):
|
||||
"""Delete a coupon (Promoters and Admins only)."""
|
||||
@ -89,6 +139,9 @@ async def delete_coupon(
|
||||
if not db_coupon:
|
||||
raise HTTPException(status_code=404, detail="Coupon not found")
|
||||
|
||||
if db_coupon.image_url:
|
||||
delete_image(db_coupon.image_url)
|
||||
|
||||
session.delete(db_coupon)
|
||||
session.commit()
|
||||
return {"status": "success", "message": "Coupon deleted"}
|
||||
|
||||
@ -46,16 +46,24 @@ 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
|
||||
from uuid import UUID
|
||||
|
||||
try:
|
||||
route_id_uuid = UUID(route_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid route ID format")
|
||||
|
||||
statement = select(RouteStop, BusStop).join(
|
||||
BusStop, RouteStop.stop_id == BusStop.id
|
||||
).where(RouteStop.route_id == route_id).order_by(RouteStop.stop_order)
|
||||
).where(RouteStop.route_id == route_id_uuid).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()
|
||||
# Convert UUIDs to strings for JSON compatibility
|
||||
stop_data['id'] = str(stop_data['id'])
|
||||
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
|
||||
@ -124,21 +132,28 @@ async def add_stop_to_route(
|
||||
_: bool = Depends(get_current_admin)
|
||||
):
|
||||
"""Add a stop to a route with cascading order adjustment."""
|
||||
from uuid import UUID
|
||||
try:
|
||||
route_id_uuid = UUID(route_id)
|
||||
stop_id_uuid = UUID(stop_data.stop_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid UUID format")
|
||||
|
||||
# 1. Check if route exists
|
||||
route = session.get(Route, route_id)
|
||||
route = session.get(Route, route_id_uuid)
|
||||
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()
|
||||
max_order = session.exec(select(func.max(RouteStop.stop_order)).where(RouteStop.route_id == route_id_uuid)).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)
|
||||
select(RouteStop).where(RouteStop.route_id == route_id_uuid, RouteStop.stop_order >= stop_data.stop_order)
|
||||
).all()
|
||||
for stop in existing_stops:
|
||||
stop.stop_order += 1
|
||||
@ -146,8 +161,8 @@ async def add_stop_to_route(
|
||||
|
||||
# 3. Create new RouteStop
|
||||
new_stop = RouteStop(
|
||||
route_id=route_id,
|
||||
stop_id=stop_data.stop_id,
|
||||
route_id=route_id_uuid,
|
||||
stop_id=stop_id_uuid,
|
||||
stop_order=stop_data.stop_order,
|
||||
travel_time_minutes=stop_data.travel_time_minutes,
|
||||
stop_delay_minutes=stop_data.stop_delay_minutes or 0,
|
||||
@ -168,12 +183,14 @@ async def update_route_stop_order(
|
||||
_: 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.
|
||||
|
||||
from uuid import UUID
|
||||
try:
|
||||
route_id_uuid = UUID(route_id)
|
||||
stop_id_uuid = UUID(stop_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid UUID format")
|
||||
route_stop = session.exec(
|
||||
select(RouteStop).where(RouteStop.route_id == route_id, RouteStop.stop_id == stop_id)
|
||||
select(RouteStop).where(RouteStop.route_id == route_id_uuid, RouteStop.stop_id == stop_id_uuid)
|
||||
).first()
|
||||
|
||||
if not route_stop:
|
||||
@ -199,7 +216,7 @@ async def update_route_stop_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.route_id == route_id_uuid,
|
||||
RouteStop.stop_order > old_order,
|
||||
RouteStop.stop_order <= new_order
|
||||
)
|
||||
@ -211,7 +228,7 @@ async def update_route_stop_order(
|
||||
# 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.route_id == route_id_uuid,
|
||||
RouteStop.stop_order >= new_order,
|
||||
RouteStop.stop_order < old_order
|
||||
)
|
||||
@ -227,3 +244,45 @@ async def update_route_stop_order(
|
||||
session.refresh(route_stop)
|
||||
return route_stop
|
||||
|
||||
|
||||
@router.delete("/{route_id}/stops/{stop_id}")
|
||||
async def remove_stop_from_route(
|
||||
route_id: str,
|
||||
stop_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
_: bool = Depends(get_current_admin)
|
||||
):
|
||||
"""Remove a stop from a route."""
|
||||
from uuid import UUID
|
||||
try:
|
||||
route_id_uuid = UUID(route_id)
|
||||
stop_id_uuid = UUID(stop_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=400, detail="Invalid UUID format")
|
||||
|
||||
route_stop = session.exec(
|
||||
select(RouteStop).where(
|
||||
RouteStop.route_id == route_id_uuid,
|
||||
RouteStop.stop_id == stop_id_uuid
|
||||
)
|
||||
).first()
|
||||
|
||||
if not route_stop:
|
||||
raise HTTPException(status_code=404, detail="Stop not found on this route")
|
||||
|
||||
# Re-order remaining stops
|
||||
removed_order = route_stop.stop_order
|
||||
remaining_stops = session.exec(
|
||||
select(RouteStop).where(
|
||||
RouteStop.route_id == route_id_uuid,
|
||||
RouteStop.stop_order > removed_order
|
||||
)
|
||||
).all()
|
||||
for s in remaining_stops:
|
||||
s.stop_order -= 1
|
||||
session.add(s)
|
||||
|
||||
session.delete(route_stop)
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@ -8,9 +8,9 @@ from app.core.database import get_session
|
||||
from app.models.shuttle import Shuttle
|
||||
from app.api.deps import get_current_admin
|
||||
|
||||
router = APIRouter(prefix="/api/shuttles", tags=["shuttles"])
|
||||
from app.services.image_handler import save_image, delete_image
|
||||
|
||||
UPLOAD_DIR = "uploads"
|
||||
router = APIRouter(prefix="/api/shuttles", tags=["shuttles"])
|
||||
|
||||
@router.get("", response_model=List[Shuttle])
|
||||
async def get_shuttles(
|
||||
@ -63,12 +63,7 @@ async def create_shuttle(
|
||||
"""Create a new shuttle trip (Admin only)."""
|
||||
image_url = None
|
||||
if image:
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, "vehicles", filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
image_url = f"/uploads/vehicles/{filename}"
|
||||
image_url = save_image(image, "vehicles")
|
||||
|
||||
shuttle = Shuttle(
|
||||
route_name=route_name,
|
||||
@ -137,12 +132,10 @@ async def update_shuttle(
|
||||
db_shuttle.is_active = is_active
|
||||
|
||||
if image:
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, "vehicles", filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
db_shuttle.image_url = f"/uploads/vehicles/{filename}"
|
||||
# Delete old image if exists
|
||||
if db_shuttle.image_url:
|
||||
delete_image(db_shuttle.image_url)
|
||||
db_shuttle.image_url = save_image(image, "vehicles")
|
||||
|
||||
session.add(db_shuttle)
|
||||
session.commit()
|
||||
@ -159,6 +152,11 @@ async def delete_shuttle(
|
||||
db_shuttle = session.get(Shuttle, shuttle_id)
|
||||
if not db_shuttle:
|
||||
raise HTTPException(status_code=404, detail="Shuttle not found")
|
||||
|
||||
# Delete image from storage
|
||||
if db_shuttle.image_url:
|
||||
delete_image(db_shuttle.image_url)
|
||||
|
||||
session.delete(db_shuttle)
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
@ -8,11 +8,10 @@ from app.core.database import get_session
|
||||
from app.models.taxi import Taxi
|
||||
from app.api.deps import get_current_admin
|
||||
|
||||
from app.services.image_handler import save_image, delete_image
|
||||
|
||||
router = APIRouter(prefix="/api/taxis", tags=["taxis"])
|
||||
|
||||
UPLOAD_DIR = "uploads"
|
||||
|
||||
|
||||
@router.get("", response_model=List[Taxi])
|
||||
async def get_taxis(
|
||||
corregimiento: Optional[str] = Query(None),
|
||||
@ -62,12 +61,7 @@ async def create_taxi(
|
||||
"""Create a new taxi entry (Admin only)."""
|
||||
image_url = None
|
||||
if image:
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, "profiles", filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
image_url = f"/uploads/profiles/{filename}"
|
||||
image_url = save_image(image, "profiles")
|
||||
|
||||
taxi = Taxi(
|
||||
owner_name=owner_name,
|
||||
@ -121,12 +115,9 @@ async def update_taxi(
|
||||
|
||||
# Handle image upload
|
||||
if image:
|
||||
ext = os.path.splitext(image.filename)[1]
|
||||
filename = f"{uuid4()}{ext}"
|
||||
path = os.path.join(UPLOAD_DIR, "profiles", filename)
|
||||
with open(path, "wb") as buffer:
|
||||
shutil.copyfileobj(image.file, buffer)
|
||||
db_taxi.image_url = f"/uploads/profiles/{filename}"
|
||||
if db_taxi.image_url:
|
||||
delete_image(db_taxi.image_url)
|
||||
db_taxi.image_url = save_image(image, "profiles")
|
||||
|
||||
session.add(db_taxi)
|
||||
session.commit()
|
||||
@ -144,6 +135,10 @@ async def delete_taxi(
|
||||
db_taxi = session.get(Taxi, taxi_id)
|
||||
if not db_taxi:
|
||||
raise HTTPException(status_code=404, detail="Taxi not found")
|
||||
|
||||
if db_taxi.image_url:
|
||||
delete_image(db_taxi.image_url)
|
||||
|
||||
session.delete(db_taxi)
|
||||
session.commit()
|
||||
return {"ok": True}
|
||||
|
||||
Reference in New Issue
Block a user