fix imports (w/o backend.) and fix Items route (had wrong content)

This commit is contained in:
Felix Zett 2025-08-14 16:55:00 +02:00
parent e702418221
commit 2d11a362ea
7 changed files with 169 additions and 206 deletions

View file

@ -4,7 +4,7 @@ from uuid import UUID as UUID_t
from datetime import date from datetime import date
import re, ast, operator import re, ast, operator
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from backend import models import models
ALLOWED_NAMES = {"days", "nights"} ALLOWED_NAMES = {"days", "nights"}
ALLOWED_NODES = ( ALLOWED_NODES = (

View file

@ -1,7 +1,9 @@
from uuid import UUID
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from backend.database import Base, engine from database import Base, engine
from backend.routes import items, tags, trips, trip_items from routes import items, tags, trips, trip_items, dev_seed
# Create tables (for MVP without Alembic) # Create tables (for MVP without Alembic)
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)

View file

@ -4,7 +4,7 @@ from sqlalchemy import (
) )
from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship from sqlalchemy.orm import relationship
from .database import Base from database import Base
class User(Base): class User(Base):

View file

@ -1,37 +1,31 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from uuid import UUID from uuid import UUID
from backend.database import get_db from database import get_db
from backend import models import models
from backend.schemas import TripCreate, TripOut, TripUpdate, TripRegenerationResult from schemas import ItemCreate, ItemOut
from backend.crud import generate_trip_items
router = APIRouter(prefix="/trips", tags=["trips"]) router = APIRouter(prefix="/items", tags=["items"])
@router.get("/", response_model=list[TripOut]) @router.get("/", response_model=list[ItemOut])
def list_trips(db: Session = Depends(get_db)): def list_items(db: Session = Depends(get_db)):
trips = ( items = (
db.query(models.Trip) db.query(models.Item)
.options( .options(joinedload(models.Item.tags).joinedload(models.ItemTag.tag))
joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag),
joinedload(models.Trip.marked_tags).joinedload(models.TripTagMarked.tag),
)
.all() .all()
) )
# pydantic can handle relationships if orm_mode
return [ return [
TripOut( models.Item(
id=t.id, id=it.id,
name=t.name, user_id=it.user_id,
start_date=t.start_date, name=it.name,
end_date=t.end_date, tags=it.tags,
selected_tags=[st.tag for st in t.selected_tags], ) for it in items
marked_tags=[mt.tag for mt in t.marked_tags],
)
for t in trips
] ]
@router.post("/", response_model=TripOut) @router.post("/", response_model=ItemOut)
def create_trip(payload: TripCreate, db: Session = Depends(get_db)): def create_item(payload: ItemCreate, db: Session = Depends(get_db)):
user = db.query(models.User).first() user = db.query(models.User).first()
if not user: if not user:
from uuid import uuid4 from uuid import uuid4
@ -39,85 +33,31 @@ def create_trip(payload: TripCreate, db: Session = Depends(get_db)):
db.add(user) db.add(user)
db.flush() db.flush()
trip = models.Trip(user_id=user.id, name=payload.name, start_date=payload.start_date, end_date=payload.end_date) item = models.Item(user_id=user.id, name=payload.name)
db.add(trip)
db.flush()
# attach selected & marked if payload.tag_ids:
if payload.selected_tag_ids: links = []
for tid in payload.selected_tag_ids: tags = db.query(models.Tag).filter(models.Tag.id.in_(payload.tag_ids), models.Tag.user_id == user.id).all()
db.add(models.TripTagSelected(trip_id=trip.id, tag_id=tid)) for t in tags:
if payload.marked_tag_ids: links.append(models.ItemTag(item=item, tag=t))
for tid in payload.marked_tag_ids: item.tags = links
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.add(item)
db.commit() db.commit()
db.refresh(item)
# reload with relationships # eager load for response
trip = ( item = (
db.query(models.Trip) db.query(models.Item)
.options( .options(joinedload(models.Item.tags).joinedload(models.ItemTag.tag))
joinedload(models.Trip.selected_tags).joinedload(models.TripTagSelected.tag), .get(item.id)
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],
) )
return item
@router.put("/{trip_id}/reconfigure", response_model=TripRegenerationResult) @router.delete("/{item_id}", status_code=204)
def reconfigure_trip(trip_id: UUID, payload: TripUpdate, db: Session = Depends(get_db)): def delete_item(item_id: UUID, db: Session = Depends(get_db)):
trip = db.get(models.Trip, trip_id) item = db.get(models.Item, item_id)
if not trip: if not item:
raise HTTPException(status_code=404, detail="Trip not found") raise HTTPException(status_code=404, detail="Item not found")
db.delete(item)
# 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() db.commit()
return {
"trip_id": trip.id,
"deleted_checked_trip_item_ids": deleted_checked,
"created_trip_item_ids": created_ids,
}

View file

@ -1,9 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from uuid import UUID from uuid import UUID
from backend.database import get_db from database import get_db
from backend import models import models
from backend.schemas import TagCreate, TagOut from schemas import TagCreate, TagOut
router = APIRouter(prefix="/tags", tags=["tags"]) router = APIRouter(prefix="/tags", tags=["tags"])

View file

@ -1,9 +1,9 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, joinedload from sqlalchemy.orm import Session, joinedload
from uuid import UUID from uuid import UUID
from backend.database import get_db from database import get_db
from backend import models import models
from backend.schemas import TripItemOut from schemas import TripItemOut
router = APIRouter(prefix="/trip-items", tags=["trip-items"]) router = APIRouter(prefix="/trip-items", tags=["trip-items"])

View file

@ -1,102 +1,123 @@
import re from fastapi import APIRouter, Depends, HTTPException
from uuid import UUID, uuid4 from sqlalchemy.orm import Session, joinedload
from fastapi import APIRouter, Depends from uuid import UUID
from sqlalchemy.ext.asyncio import AsyncSession
from schemas import TripCreate, TripItemOut, TripOut
from models import Item, Trip, TripItem
from database import get_db from database import get_db
from sqlalchemy import select import models
from sqlalchemy.orm import selectinload from schemas import TripCreate, TripOut, TripUpdate, TripRegenerationResult
from config import FIXED_USER_ID from crud import generate_trip_items
router = APIRouter(prefix="/trips", tags=["Trips"]) 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.get("/", response_model=list[TripOut]) @router.get("/", response_model=list[TripOut])
async def get_trips(db: AsyncSession = Depends(get_db)): def list_trips(db: Session = Depends(get_db)):
user_id = FIXED_USER_ID trips = (
result = await db.execute(select(Trip).where(Trip.user_id == user_id)) db.query(models.Trip)
trips = result.scalars().all() .options(
return trips 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: @router.post("/", response_model=TripOut)
def replacer(match): def create_trip(payload: TripCreate, db: Session = Depends(get_db)):
expr = match.group(1) user = db.query(models.User).first()
expr = expr.replace("days", str(days)).replace("nights", str(nights)) if not user:
try: from uuid import uuid4
return str(eval(expr)) user = models.User(id=uuid4(), name="Demo")
except: db.add(user)
return match.group(0) db.flush()
return re.sub(r"{(.*?)}", replacer, text) 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,
}