2025-07-19 17:41:46 +02:00

339 lines
11 KiB
Python

from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import select
from ..config import settings
from ..deps import SessionDep, get_current_username
from ..models.models import (Image, Place, Trip, TripCreate, TripDay,
TripDayBase, TripDayRead, TripItem,
TripItemCreate, TripItemRead, TripItemUpdate,
TripPlaceLink, TripRead, TripReadBase, TripUpdate)
from ..security import verify_exists_and_owns
from ..utils.utils import b64img_decode, remove_image, save_image_to_file
router = APIRouter(prefix="/api/trips", tags=["trips"])
@router.get("", response_model=list[TripReadBase])
def read_trips(
session: SessionDep, current_user: Annotated[str, Depends(get_current_username)]
) -> list[TripReadBase]:
trips = session.exec(select(Trip).filter(Trip.user == current_user))
return [TripReadBase.serialize(trip) for trip in trips]
@router.get("/{trip_id}", response_model=TripRead)
def read_trip(
session: SessionDep, trip_id: int, current_user: Annotated[str, Depends(get_current_username)]
) -> TripRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
return TripRead.serialize(db_trip)
@router.post("", response_model=TripRead)
def create_trip(
trip: TripCreate, session: SessionDep, current_user: Annotated[str, Depends(get_current_username)]
) -> TripRead:
new_trip = Trip(
name=trip.name,
user=current_user,
)
if trip.image:
image_bytes = b64img_decode(trip.image)
filename = save_image_to_file(image_bytes, settings.TRIP_IMAGE_SIZE)
if not filename:
raise HTTPException(status_code=400, detail="Bad request")
image = Image(filename=filename, user=current_user)
session.add(image)
session.commit()
session.refresh(image)
new_trip.image_id = image.id
if trip.place_ids:
for place_id in trip.place_ids:
db_place = session.get(Place, place_id)
verify_exists_and_owns(current_user, db_place)
session.add(TripPlaceLink(trip_id=new_trip.id, place_id=db_place.id))
session.commit()
session.add(new_trip)
session.commit()
session.refresh(new_trip)
return TripRead.serialize(new_trip)
@router.put("/{trip_id}", response_model=TripRead)
def update_trip(
session: SessionDep,
trip_id: int,
trip: TripUpdate,
current_user: Annotated[str, Depends(get_current_username)],
) -> TripRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived and (trip.archived is not False):
raise HTTPException(status_code=400, detail="Bad request")
trip_data = trip.model_dump(exclude_unset=True)
if trip_data.get("image"):
try:
image_bytes = b64img_decode(trip_data.pop("image"))
except Exception:
raise HTTPException(status_code=400, detail="Bad request")
filename = save_image_to_file(image_bytes, settings.TRIP_IMAGE_SIZE)
if not filename:
raise HTTPException(status_code=400, detail="Bad request")
image = Image(filename=filename, user=current_user)
session.add(image)
session.commit()
session.refresh(image)
if db_trip.image_id:
old_image = session.get(Image, db_trip.image_id)
try:
remove_image(old_image.filename)
session.delete(old_image)
db_trip.image_id = None
session.refresh(db_trip)
except Exception:
raise HTTPException(status_code=400, detail="Bad request")
db_trip.image_id = image.id
place_ids = trip_data.pop("place_ids", None)
if place_ids is not None: # Could be empty [], so 'in'
db_trip.places.clear()
for place_id in place_ids:
db_place = session.get(Place, place_id)
verify_exists_and_owns(current_user, db_place)
db_trip.places.append(db_place)
item_place_ids = {
item.place.id for day in db_trip.days for item in day.items if item.place is not None
}
invalid_place_ids = item_place_ids - set(place.id for place in db_trip.places)
if invalid_place_ids: # TripItem references a Place that Trip.places misses
raise HTTPException(status_code=400, detail="Bad Request")
for key, value in trip_data.items():
setattr(db_trip, key, value)
session.add(db_trip)
session.commit()
session.refresh(db_trip)
return TripRead.serialize(db_trip)
@router.delete("/{trip_id}")
def delete_trip(
session: SessionDep, trip_id: int, current_user: Annotated[str, Depends(get_current_username)]
):
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
if db_trip.image:
try:
remove_image(db_trip.image.filename)
session.delete(db_trip.image)
except Exception:
raise HTTPException(
status_code=500,
detail="Roses are red, violets are blue, if you're reading this, I'm sorry for you",
)
session.delete(db_trip)
session.commit()
return {}
@router.post("/{trip_id}/days", response_model=TripDayRead)
def create_tripday(
td: TripDayBase,
trip_id: int,
session: SessionDep,
current_user: Annotated[str, Depends(get_current_username)],
) -> TripDayRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
new_day = TripDay(label=td.label, trip_id=trip_id, user=current_user)
session.add(new_day)
session.commit()
session.refresh(new_day)
return TripDayRead.serialize(new_day)
@router.put("/{trip_id}/days/{day_id}", response_model=TripDayRead)
def update_tripday(
td: TripDayBase,
trip_id: int,
day_id: int,
session: SessionDep,
current_user: Annotated[str, Depends(get_current_username)],
) -> TripDayRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
db_day = session.get(TripDay, day_id)
verify_exists_and_owns(current_user, db_day)
if db_day.trip_id != trip_id:
raise HTTPException(status_code=400, detail="Bad request")
td_data = td.model_dump(exclude_unset=True)
for key, value in td_data.items():
setattr(db_day, key, value)
session.add(db_day)
session.commit()
session.refresh(db_day)
return TripDayRead.serialize(db_day)
@router.delete("/{trip_id}/days/{day_id}")
def delete_tripday(
session: SessionDep,
trip_id: int,
day_id: int,
current_user: Annotated[str, Depends(get_current_username)],
):
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
db_day = session.get(TripDay, day_id)
verify_exists_and_owns(current_user, db_day)
if db_day.trip_id != trip_id:
raise HTTPException(status_code=400, detail="Bad request")
session.delete(db_day)
session.commit()
return {}
@router.post("/{trip_id}/days/{day_id}/items", response_model=TripItemRead)
def create_tripitem(
item: TripItemCreate,
trip_id: int,
day_id: int,
session: SessionDep,
current_user: Annotated[str, Depends(get_current_username)],
) -> TripItemRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
db_day = session.get(TripDay, day_id)
if db_day.trip_id != trip_id:
raise HTTPException(status_code=400, detail="Bad request")
new_item = TripItem(
time=item.time,
text=item.text,
comment=item.comment,
lat=item.lat,
lng=item.lng,
day_id=day_id,
price=item.price,
status=item.status,
)
if item.place and item.place != "":
place_in_trip = any(place.id == item.place for place in db_trip.places)
if not place_in_trip:
raise HTTPException(status_code=400, detail="Bad request")
new_item.place_id = item.place
session.add(new_item)
session.commit()
session.refresh(new_item)
return TripItemRead.serialize(new_item)
@router.put("/{trip_id}/days/{day_id}/items/{item_id}", response_model=TripItemRead)
def update_tripitem(
item: TripItemUpdate,
trip_id: int,
day_id: int,
item_id: int,
session: SessionDep,
current_user: Annotated[str, Depends(get_current_username)],
) -> TripItemRead:
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
db_day = session.get(TripDay, day_id)
if db_day.trip_id != trip_id:
raise HTTPException(status_code=400, detail="Bad request")
db_item = session.get(TripItem, item_id)
if db_item.day_id != day_id:
raise HTTPException(status_code=400, detail="Bad request")
item_data = item.model_dump(exclude_unset=True)
place_id = item_data.pop("place", None)
db_item.place_id = place_id
if place_id is not None:
place_in_trip = any(p.id == place_id for p in db_trip.places)
if not place_in_trip:
raise HTTPException(status_code=400, detail="Bad request")
for key, value in item_data.items():
setattr(db_item, key, value)
session.add(db_item)
session.commit()
session.refresh(db_item)
return TripItemRead.serialize(db_item)
@router.delete("/{trip_id}/days/{day_id}/items/{item_id}")
def delete_tripitem(
session: SessionDep,
trip_id: int,
day_id: int,
item_id: int,
current_user: Annotated[str, Depends(get_current_username)],
):
db_trip = session.get(Trip, trip_id)
verify_exists_and_owns(current_user, db_trip)
if db_trip.archived:
raise HTTPException(status_code=400, detail="Bad request")
db_day = session.get(TripDay, day_id)
if db_day.trip_id != trip_id:
raise HTTPException(status_code=400, detail="Bad request")
db_item = session.get(TripItem, item_id)
if db_item.day_id != day_id:
raise HTTPException(status_code=400, detail="Bad request")
session.delete(db_item)
session.commit()
return {}