From 2d11a362ea8195f4988ede197882601503718dd0 Mon Sep 17 00:00:00 2001 From: Felix Zett Date: Thu, 14 Aug 2025 16:55:00 +0200 Subject: [PATCH] fix imports (w/o backend.) and fix Items route (had wrong content) --- backend/crud.py | 2 +- backend/main.py | 6 +- backend/models.py | 2 +- backend/routes/items.py | 140 +++++++---------------- backend/routes/tags.py | 6 +- backend/routes/trip_items.py | 6 +- backend/routes/trips.py | 213 +++++++++++++++++++---------------- 7 files changed, 169 insertions(+), 206 deletions(-) diff --git a/backend/crud.py b/backend/crud.py index d36a19a..21ddd4c 100644 --- a/backend/crud.py +++ b/backend/crud.py @@ -4,7 +4,7 @@ from uuid import UUID as UUID_t from datetime import date import re, ast, operator from sqlalchemy.orm import Session, joinedload -from backend import models +import models ALLOWED_NAMES = {"days", "nights"} ALLOWED_NODES = ( diff --git a/backend/main.py b/backend/main.py index cb2916a..65478c7 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,7 +1,9 @@ +from uuid import UUID from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware -from backend.database import Base, engine -from backend.routes import items, tags, trips, trip_items +from database import Base, engine +from routes import items, tags, trips, trip_items, dev_seed + # Create tables (for MVP without Alembic) Base.metadata.create_all(bind=engine) diff --git a/backend/models.py b/backend/models.py index b4bcf30..a38d8e6 100644 --- a/backend/models.py +++ b/backend/models.py @@ -4,7 +4,7 @@ from sqlalchemy import ( ) from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import relationship -from .database import Base +from database import Base class User(Base): diff --git a/backend/routes/items.py b/backend/routes/items.py index 8be1016..d794944 100644 --- a/backend/routes/items.py +++ b/backend/routes/items.py @@ -1,37 +1,31 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session, joinedload from uuid import UUID -from backend.database import get_db -from backend import models -from backend.schemas import TripCreate, TripOut, TripUpdate, TripRegenerationResult -from backend.crud import generate_trip_items +from database import get_db +import models +from schemas import ItemCreate, ItemOut -router = APIRouter(prefix="/trips", tags=["trips"]) +router = APIRouter(prefix="/items", tags=["items"]) -@router.get("/", response_model=list[TripOut]) -def list_trips(db: Session = Depends(get_db)): - trips = ( - db.query(models.Trip) - .options( - joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag), - joinedload(models.Trip.marked_tags).joinedload(models.TripTagMarked.tag), - ) +@router.get("/", response_model=list[ItemOut]) +def list_items(db: Session = Depends(get_db)): + items = ( + db.query(models.Item) + .options(joinedload(models.Item.tags).joinedload(models.ItemTag.tag)) .all() ) + # pydantic can handle relationships if orm_mode return [ - TripOut( - id=t.id, - name=t.name, - start_date=t.start_date, - end_date=t.end_date, - selected_tags=[st.tag for st in t.selected_tags], - marked_tags=[mt.tag for mt in t.marked_tags], - ) - for t in trips + models.Item( + id=it.id, + user_id=it.user_id, + name=it.name, + tags=it.tags, + ) for it in items ] -@router.post("/", response_model=TripOut) -def create_trip(payload: TripCreate, db: Session = Depends(get_db)): +@router.post("/", response_model=ItemOut) +def create_item(payload: ItemCreate, db: Session = Depends(get_db)): user = db.query(models.User).first() if not user: from uuid import uuid4 @@ -39,85 +33,31 @@ def create_trip(payload: TripCreate, db: Session = Depends(get_db)): db.add(user) db.flush() - trip = models.Trip(user_id=user.id, name=payload.name, start_date=payload.start_date, end_date=payload.end_date) - db.add(trip) - db.flush() + item = models.Item(user_id=user.id, name=payload.name) - # attach selected & marked - if payload.selected_tag_ids: - for tid in payload.selected_tag_ids: - db.add(models.TripTagSelected(trip_id=trip.id, tag_id=tid)) - if payload.marked_tag_ids: - for tid in payload.marked_tag_ids: - db.add(models.TripTagMarked(trip_id=trip.id, tag_id=tid)) - - db.flush() - - # generate items per rules - created_ids, _ = generate_trip_items( - db, - trip=trip, - selected_tag_ids=payload.selected_tag_ids, - marked_tag_ids=payload.marked_tag_ids, - ) + if payload.tag_ids: + links = [] + tags = db.query(models.Tag).filter(models.Tag.id.in_(payload.tag_ids), models.Tag.user_id == user.id).all() + for t in tags: + links.append(models.ItemTag(item=item, tag=t)) + item.tags = links + db.add(item) db.commit() + db.refresh(item) - # reload with relationships - trip = ( - db.query(models.Trip) - .options( - joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag), - joinedload(models.Trip.marked_tags).joinedload(models.TripTagMarked.tag), - ) - .get(trip.id) - ) - return TripOut( - id=trip.id, - name=trip.name, - start_date=trip.start_date, - end_date=trip.end_date, - selected_tags=[st.tag for st in trip.selected_tags], - marked_tags=[mt.tag for mt in trip.marked_tags], + # eager load for response + item = ( + db.query(models.Item) + .options(joinedload(models.Item.tags).joinedload(models.ItemTag.tag)) + .get(item.id) ) + return item -@router.put("/{trip_id}/reconfigure", response_model=TripRegenerationResult) -def reconfigure_trip(trip_id: UUID, payload: TripUpdate, db: Session = Depends(get_db)): - trip = db.get(models.Trip, trip_id) - if not trip: - raise HTTPException(status_code=404, detail="Trip not found") - - # update base fields - if payload.name is not None: - trip.name = payload.name - if payload.start_date is not None: - trip.start_date = payload.start_date - if payload.end_date is not None: - trip.end_date = payload.end_date - db.flush() - - # update selected/marked join tables if provided - if payload.selected_tag_ids is not None: - # replace all - db.query(models.TripTagSelected).filter_by(trip_id=trip.id).delete() - for tid in payload.selected_tag_ids: - db.add(models.TripTagSelected(trip_id=trip.id, tag_id=tid)) - if payload.marked_tag_ids is not None: - db.query(models.TripTagMarked).filter_by(trip_id=trip.id).delete() - for tid in payload.marked_tag_ids: - db.add(models.TripTagMarked(trip_id=trip.id, tag_id=tid)) - db.flush() - - # read back lists - sel_ids = [row.tag_id for row in db.query(models.TripTagSelected).filter_by(trip_id=trip.id).all()] - mrk_ids = [row.tag_id for row in db.query(models.TripTagMarked).filter_by(trip_id=trip.id).all()] - - created_ids, deleted_checked = generate_trip_items( - db, trip=trip, selected_tag_ids=sel_ids, marked_tag_ids=mrk_ids - ) - db.commit() - return { - "trip_id": trip.id, - "deleted_checked_trip_item_ids": deleted_checked, - "created_trip_item_ids": created_ids, - } \ No newline at end of file +@router.delete("/{item_id}", status_code=204) +def delete_item(item_id: UUID, db: Session = Depends(get_db)): + item = db.get(models.Item, item_id) + if not item: + raise HTTPException(status_code=404, detail="Item not found") + db.delete(item) + db.commit() \ No newline at end of file diff --git a/backend/routes/tags.py b/backend/routes/tags.py index 2d0bd65..df516c3 100644 --- a/backend/routes/tags.py +++ b/backend/routes/tags.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from uuid import UUID -from backend.database import get_db -from backend import models -from backend.schemas import TagCreate, TagOut +from database import get_db +import models +from schemas import TagCreate, TagOut router = APIRouter(prefix="/tags", tags=["tags"]) diff --git a/backend/routes/trip_items.py b/backend/routes/trip_items.py index 81beb6a..458fff2 100644 --- a/backend/routes/trip_items.py +++ b/backend/routes/trip_items.py @@ -1,9 +1,9 @@ from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session, joinedload from uuid import UUID -from backend.database import get_db -from backend import models -from backend.schemas import TripItemOut +from database import get_db +import models +from schemas import TripItemOut router = APIRouter(prefix="/trip-items", tags=["trip-items"]) diff --git a/backend/routes/trips.py b/backend/routes/trips.py index 63b14e7..c6a85e8 100644 --- a/backend/routes/trips.py +++ b/backend/routes/trips.py @@ -1,102 +1,123 @@ -import re -from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends -from sqlalchemy.ext.asyncio import AsyncSession -from schemas import TripCreate, TripItemOut, TripOut -from models import Item, Trip, TripItem +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session, joinedload +from uuid import UUID from database import get_db -from sqlalchemy import select -from sqlalchemy.orm import selectinload -from config import FIXED_USER_ID +import models +from schemas import TripCreate, TripOut, TripUpdate, TripRegenerationResult +from crud import generate_trip_items -router = APIRouter(prefix="/trips", tags=["Trips"]) - -@router.post("/", response_model=TripOut) -async def create_trip(trip: TripCreate, db: AsyncSession = Depends(get_db)): - user_id = FIXED_USER_ID - - db_trip = Trip( - id=uuid4(), - name=trip.name, - user_id=user_id, - start_date=trip.start_date, - end_date=trip.end_date, - selected_tags=trip.selected_tags, - marked_tags=trip.marked_tags - ) - - await db.commit() # damit ID vorhanden ist - db.add(db_trip) - - # Tage berechnen - days = (trip.end_date - trip.start_date).days + 1 - nights = days - 1 - - # relevante Items - result = await db.execute(select(Item).where(Item.user_id == user_id).options(selectinload(Item.tags))) - all_items = result.scalars().all() - - trip_items = [] - - for item in all_items: - item_tag_names = [tag.name for tag in item.tags] - - if not set(item_tag_names).issubset(set(trip.selected_tags)): - continue - - item_text = replace_placeholders(item.name, days, nights) - - # markierte Tags matchen? - matching_marked = set(item_tag_names) & set(trip.marked_tags) - - if matching_marked: - for tag in matching_marked: - trip_items.append(TripItem( - id=uuid4(), - trip_id=db_trip.id, - item_id=item.id, - tag=tag, - name=item.name, - calculated_label=item_text, - checked=False - )) - else: - trip_items.append(TripItem( - id=uuid4(), - trip_id=db_trip.id, - item_id=item.id, - tag=None, - name=item.name, - calculated_label=item_text, - checked=False - )) - - db.add_all(trip_items) - await db.commit() - # return {"status": "trip created", "trip_id": str(db_trip.id)} - return db_trip - - -@router.get("/{trip_id}/items", response_model=list[TripItemOut]) -async def get_trip_items(trip_id: UUID, db: AsyncSession = Depends(get_db)): - result = await db.execute(select(TripItem).where(TripItem.trip_id == trip_id).options(selectinload(TripItem.item))) - items = result.scalars().all() - return [TripItemOut.model_validate(item) for item in items] +router = APIRouter(prefix="/trips", tags=["trips"]) @router.get("/", response_model=list[TripOut]) -async def get_trips(db: AsyncSession = Depends(get_db)): - user_id = FIXED_USER_ID - result = await db.execute(select(Trip).where(Trip.user_id == user_id)) - trips = result.scalars().all() - return trips +def list_trips(db: Session = Depends(get_db)): + trips = ( + db.query(models.Trip) + .options( + joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag), + joinedload(models.Trip.marked_tags).joinedload(models.TripTagMarked.tag), + ) + .all() + ) + return [ + TripOut( + id=t.id, + name=t.name, + start_date=t.start_date, + end_date=t.end_date, + selected_tags=[st.tag for st in t.selected_tags], + marked_tags=[mt.tag for mt in t.marked_tags], + ) + for t in trips + ] -def replace_placeholders(text: str, days: int, nights: int) -> str: - def replacer(match): - expr = match.group(1) - expr = expr.replace("days", str(days)).replace("nights", str(nights)) - try: - return str(eval(expr)) - except: - return match.group(0) +@router.post("/", response_model=TripOut) +def create_trip(payload: TripCreate, db: Session = Depends(get_db)): + user = db.query(models.User).first() + if not user: + from uuid import uuid4 + user = models.User(id=uuid4(), name="Demo") + db.add(user) + db.flush() - return re.sub(r"{(.*?)}", replacer, text) \ No newline at end of file + trip = models.Trip(user_id=user.id, name=payload.name, start_date=payload.start_date, end_date=payload.end_date) + db.add(trip) + db.flush() + + # attach selected & marked + if payload.selected_tag_ids: + for tid in payload.selected_tag_ids: + db.add(models.TripTagSelected(trip_id=trip.id, tag_id=tid)) + if payload.marked_tag_ids: + for tid in payload.marked_tag_ids: + db.add(models.TripTagMarked(trip_id=trip.id, tag_id=tid)) + + db.flush() + + # generate items per rules + created_ids, _ = generate_trip_items( + db, + trip=trip, + selected_tag_ids=payload.selected_tag_ids, + marked_tag_ids=payload.marked_tag_ids, + ) + + db.commit() + + # reload with relationships + trip = ( + db.query(models.Trip) + .options( + joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag), + joinedload(models.Trip.marked_tags).joinedload(models.TripTagMarked.tag), + ) + .get(trip.id) + ) + return TripOut( + id=trip.id, + name=trip.name, + start_date=trip.start_date, + end_date=trip.end_date, + selected_tags=[st.tag for st in trip.selected_tags], + marked_tags=[mt.tag for mt in trip.marked_tags], + ) + +@router.put("/{trip_id}/reconfigure", response_model=TripRegenerationResult) +def reconfigure_trip(trip_id: UUID, payload: TripUpdate, db: Session = Depends(get_db)): + trip = db.get(models.Trip, trip_id) + if not trip: + raise HTTPException(status_code=404, detail="Trip not found") + + # update base fields + if payload.name is not None: + trip.name = payload.name + if payload.start_date is not None: + trip.start_date = payload.start_date + if payload.end_date is not None: + trip.end_date = payload.end_date + db.flush() + + # update selected/marked join tables if provided + if payload.selected_tag_ids is not None: + # replace all + db.query(models.TripTagSelected).filter_by(trip_id=trip.id).delete() + for tid in payload.selected_tag_ids: + db.add(models.TripTagSelected(trip_id=trip.id, tag_id=tid)) + if payload.marked_tag_ids is not None: + db.query(models.TripTagMarked).filter_by(trip_id=trip.id).delete() + for tid in payload.marked_tag_ids: + db.add(models.TripTagMarked(trip_id=trip.id, tag_id=tid)) + db.flush() + + # read back lists + sel_ids = [row.tag_id for row in db.query(models.TripTagSelected).filter_by(trip_id=trip.id).all()] + mrk_ids = [row.tag_id for row in db.query(models.TripTagMarked).filter_by(trip_id=trip.id).all()] + + created_ids, deleted_checked = generate_trip_items( + db, trip=trip, selected_tag_ids=sel_ids, marked_tag_ids=mrk_ids + ) + db.commit() + return { + "trip_id": trip.id, + "deleted_checked_trip_item_ids": deleted_checked, + "created_trip_item_ids": created_ids, + } \ No newline at end of file