handle invalid userID
Browse files- app/itineraries/router.py +45 -0
app/itineraries/router.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
"""Itineraries Router - Multi-day trip planning with persistent storage."""
|
| 2 |
|
|
|
|
| 3 |
from fastapi import APIRouter, HTTPException, Depends, Query
|
| 4 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 5 |
from sqlalchemy import text
|
|
@@ -14,6 +15,18 @@ from app.itineraries import (
|
|
| 14 |
router = APIRouter(prefix="/itineraries", tags=["Itineraries"])
|
| 15 |
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
# ==================== ITINERARY CRUD ====================
|
| 18 |
|
| 19 |
@router.post(
|
|
@@ -28,6 +41,9 @@ async def create_itinerary(
|
|
| 28 |
db: AsyncSession = Depends(get_db),
|
| 29 |
) -> ItineraryResponse:
|
| 30 |
"""Create a new itinerary."""
|
|
|
|
|
|
|
|
|
|
| 31 |
result = await db.execute(
|
| 32 |
text("""
|
| 33 |
INSERT INTO itineraries (user_id, title, start_date, end_date, total_days, total_budget, currency)
|
|
@@ -76,6 +92,9 @@ async def list_itineraries(
|
|
| 76 |
db: AsyncSession = Depends(get_db),
|
| 77 |
) -> list[ItineraryListItem]:
|
| 78 |
"""List all itineraries for a user."""
|
|
|
|
|
|
|
|
|
|
| 79 |
result = await db.execute(
|
| 80 |
text("""
|
| 81 |
SELECT i.id, i.title, i.start_date, i.end_date, i.total_days, i.created_at,
|
|
@@ -116,6 +135,10 @@ async def get_itinerary(
|
|
| 116 |
db: AsyncSession = Depends(get_db),
|
| 117 |
) -> ItineraryResponse:
|
| 118 |
"""Get itinerary by ID with all stops."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
# Get itinerary
|
| 120 |
result = await db.execute(
|
| 121 |
text("""
|
|
@@ -191,6 +214,10 @@ async def update_itinerary(
|
|
| 191 |
db: AsyncSession = Depends(get_db),
|
| 192 |
) -> ItineraryResponse:
|
| 193 |
"""Update itinerary."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 194 |
update_fields = []
|
| 195 |
params = {"id": itinerary_id, "user_id": user_id}
|
| 196 |
|
|
@@ -246,6 +273,10 @@ async def delete_itinerary(
|
|
| 246 |
db: AsyncSession = Depends(get_db),
|
| 247 |
) -> dict:
|
| 248 |
"""Delete itinerary."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
# Delete stops first (cascade)
|
| 250 |
await db.execute(
|
| 251 |
text("DELETE FROM itinerary_stops WHERE itinerary_id = :id"),
|
|
@@ -280,6 +311,10 @@ async def add_stop(
|
|
| 280 |
db: AsyncSession = Depends(get_db),
|
| 281 |
) -> StopResponse:
|
| 282 |
"""Add a stop to the itinerary."""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
# Verify itinerary exists and belongs to user
|
| 284 |
check = await db.execute(
|
| 285 |
text("SELECT id FROM itineraries WHERE id = :id AND user_id = :user_id"),
|
|
@@ -357,6 +392,11 @@ async def update_stop(
|
|
| 357 |
db: AsyncSession = Depends(get_db),
|
| 358 |
) -> StopResponse:
|
| 359 |
"""Update a stop."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
# Verify ownership
|
| 361 |
check = await db.execute(
|
| 362 |
text("""
|
|
@@ -437,6 +477,11 @@ async def delete_stop(
|
|
| 437 |
db: AsyncSession = Depends(get_db),
|
| 438 |
) -> dict:
|
| 439 |
"""Delete a stop."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
# Verify ownership and delete
|
| 441 |
result = await db.execute(
|
| 442 |
text("""
|
|
|
|
| 1 |
"""Itineraries Router - Multi-day trip planning with persistent storage."""
|
| 2 |
|
| 3 |
+
from uuid import UUID
|
| 4 |
from fastapi import APIRouter, HTTPException, Depends, Query
|
| 5 |
from sqlalchemy.ext.asyncio import AsyncSession
|
| 6 |
from sqlalchemy import text
|
|
|
|
| 15 |
router = APIRouter(prefix="/itineraries", tags=["Itineraries"])
|
| 16 |
|
| 17 |
|
| 18 |
+
def validate_uuid(value: str, field_name: str = "ID") -> str:
|
| 19 |
+
"""Validate that a string is a valid UUID format."""
|
| 20 |
+
try:
|
| 21 |
+
UUID(value)
|
| 22 |
+
return value
|
| 23 |
+
except ValueError:
|
| 24 |
+
raise HTTPException(
|
| 25 |
+
status_code=400,
|
| 26 |
+
detail=f"Invalid {field_name}: '{value}' is not a valid UUID format. Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
# ==================== ITINERARY CRUD ====================
|
| 31 |
|
| 32 |
@router.post(
|
|
|
|
| 41 |
db: AsyncSession = Depends(get_db),
|
| 42 |
) -> ItineraryResponse:
|
| 43 |
"""Create a new itinerary."""
|
| 44 |
+
# Validate user_id is a valid UUID
|
| 45 |
+
validate_uuid(user_id, "user_id")
|
| 46 |
+
|
| 47 |
result = await db.execute(
|
| 48 |
text("""
|
| 49 |
INSERT INTO itineraries (user_id, title, start_date, end_date, total_days, total_budget, currency)
|
|
|
|
| 92 |
db: AsyncSession = Depends(get_db),
|
| 93 |
) -> list[ItineraryListItem]:
|
| 94 |
"""List all itineraries for a user."""
|
| 95 |
+
# Validate user_id is a valid UUID
|
| 96 |
+
validate_uuid(user_id, "user_id")
|
| 97 |
+
|
| 98 |
result = await db.execute(
|
| 99 |
text("""
|
| 100 |
SELECT i.id, i.title, i.start_date, i.end_date, i.total_days, i.created_at,
|
|
|
|
| 135 |
db: AsyncSession = Depends(get_db),
|
| 136 |
) -> ItineraryResponse:
|
| 137 |
"""Get itinerary by ID with all stops."""
|
| 138 |
+
# Validate UUIDs
|
| 139 |
+
validate_uuid(user_id, "user_id")
|
| 140 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 141 |
+
|
| 142 |
# Get itinerary
|
| 143 |
result = await db.execute(
|
| 144 |
text("""
|
|
|
|
| 214 |
db: AsyncSession = Depends(get_db),
|
| 215 |
) -> ItineraryResponse:
|
| 216 |
"""Update itinerary."""
|
| 217 |
+
# Validate UUIDs
|
| 218 |
+
validate_uuid(user_id, "user_id")
|
| 219 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 220 |
+
|
| 221 |
update_fields = []
|
| 222 |
params = {"id": itinerary_id, "user_id": user_id}
|
| 223 |
|
|
|
|
| 273 |
db: AsyncSession = Depends(get_db),
|
| 274 |
) -> dict:
|
| 275 |
"""Delete itinerary."""
|
| 276 |
+
# Validate UUIDs
|
| 277 |
+
validate_uuid(user_id, "user_id")
|
| 278 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 279 |
+
|
| 280 |
# Delete stops first (cascade)
|
| 281 |
await db.execute(
|
| 282 |
text("DELETE FROM itinerary_stops WHERE itinerary_id = :id"),
|
|
|
|
| 311 |
db: AsyncSession = Depends(get_db),
|
| 312 |
) -> StopResponse:
|
| 313 |
"""Add a stop to the itinerary."""
|
| 314 |
+
# Validate UUIDs
|
| 315 |
+
validate_uuid(user_id, "user_id")
|
| 316 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 317 |
+
|
| 318 |
# Verify itinerary exists and belongs to user
|
| 319 |
check = await db.execute(
|
| 320 |
text("SELECT id FROM itineraries WHERE id = :id AND user_id = :user_id"),
|
|
|
|
| 392 |
db: AsyncSession = Depends(get_db),
|
| 393 |
) -> StopResponse:
|
| 394 |
"""Update a stop."""
|
| 395 |
+
# Validate UUIDs
|
| 396 |
+
validate_uuid(user_id, "user_id")
|
| 397 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 398 |
+
validate_uuid(stop_id, "stop_id")
|
| 399 |
+
|
| 400 |
# Verify ownership
|
| 401 |
check = await db.execute(
|
| 402 |
text("""
|
|
|
|
| 477 |
db: AsyncSession = Depends(get_db),
|
| 478 |
) -> dict:
|
| 479 |
"""Delete a stop."""
|
| 480 |
+
# Validate UUIDs
|
| 481 |
+
validate_uuid(user_id, "user_id")
|
| 482 |
+
validate_uuid(itinerary_id, "itinerary_id")
|
| 483 |
+
validate_uuid(stop_id, "stop_id")
|
| 484 |
+
|
| 485 |
# Verify ownership and delete
|
| 486 |
result = await db.execute(
|
| 487 |
text("""
|