from fastapi import APIRouter, Depends, HTTPException, status, Query from sqlalchemy.orm import Session from typing import List, Optional from app.core.database import get_db from app.core.auth import get_current_user, requires_role from app.models.booking import Booking from app.models.user import User from app.models.product import Product from app.schemas.booking import BookingOut, BookingCreate router = APIRouter(prefix="/bookings", tags=["bookings"]) # Konstante aus deinen Policies: Nutzer dürfen nicht < 0 € fallen (Stand 2025-07-02). ALLOWED_OVERDRAFT_CENTS = 0 # falls -500 erlaubt sein soll: setze auf -500 @router.post("/", response_model=BookingOut, status_code=status.HTTP_201_CREATED) def create_booking( booking: BookingCreate, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): if current_user.role not in ("manager", "admin") and booking.user_id != current_user.id: raise HTTPException(status_code=403, detail="Forbidden") if booking.amount is None or booking.amount <= 0: raise HTTPException(status_code=400, detail="Invalid amount") # Produkt mit Row-Lock product = ( db.query(Product) .filter(Product.id == booking.product_id) .with_for_update() .first() ) if not product: raise HTTPException(status_code=404, detail="Product not found") if hasattr(product, "is_active") and not bool(product.is_active): raise HTTPException(status_code=400, detail="Product is not active") qty = int(booking.amount) # Bestand prüfen/abziehen, falls Feld vorhanden if hasattr(product, "stock"): current_stock = int(product.stock or 0) if current_stock < qty: raise HTTPException(status_code=409, detail="Not enough stock") product.stock = current_stock - qty server_total = int(product.price_cents) * qty try: # User mit Row-Lock user = ( db.query(User) .filter(User.id == booking.user_id) .with_for_update() .first() ) if not user: raise HTTPException(status_code=404, detail="User not found") new_balance = int(user.balance_cents) - server_total if new_balance < -ALLOWED_OVERDRAFT_CENTS: raise HTTPException(status_code=400, detail="Insufficient balance") user.balance_cents = new_balance db_booking = Booking( user_id=booking.user_id, product_id=booking.product_id, amount=qty, total_cents=server_total, # Client-Wert wird ignoriert ) db.add(db_booking) db.flush() db.refresh(db_booking) db.commit() return db_booking except HTTPException: db.rollback() raise except Exception: db.rollback() raise def _role_name(r) -> str: # robust gegen Enum/String/None if r is None: return "" v = getattr(r, "value", r) return str(v).strip().lower() @router.get("/", response_model=List[BookingOut]) def list_bookings( limit: int = Query(100, ge=1, le=1000), offset: int = Query(0, ge=0), user_id: Optional[int] = Query(None, ge=1), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): q = db.query(Booking) role = _role_name(getattr(current_user, "role", None)) is_manager = role in ("manager", "admin") if is_manager: # Admin/Manager: optionaler Filter auf user_id if user_id is not None: q = q.filter(Booking.user_id == user_id) else: # Normale Nutzer: strikt nur eigene Buchungen q = q.filter(Booking.user_id == current_user.id) # stabile, absteigende Reihenfolge – Zeitfeld optional order_cols = [] if hasattr(Booking, "created_at"): order_cols.append(Booking.created_at.desc()) elif hasattr(Booking, "timestamp"): order_cols.append(Booking.timestamp.desc()) elif hasattr(Booking, "created"): order_cols.append(Booking.created.desc()) order_cols.append(Booking.id.desc()) return q.order_by(*order_cols).limit(limit).offset(offset).all() @router.get("/me", response_model=List[BookingOut]) def list_my_bookings( limit: int = Query(100, ge=1, le=1000), offset: int = Query(0, ge=0), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): q = ( db.query(Booking) .filter(Booking.user_id == current_user.id) .order_by(Booking.id.desc()) .limit(limit) .offset(offset) ) return q.all() @router.get("/{booking_id}", response_model=BookingOut) def get_booking( booking_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): b = db.query(Booking).filter(Booking.id == booking_id).first() if not b: raise HTTPException(status_code=404, detail="Booking not found") if b.user_id != current_user.id and current_user.role not in ("manager", "admin"): raise HTTPException(status_code=403, detail="Forbidden") return b @router.delete("/{booking_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_booking( booking_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): requires_role("manager", "admin")(current_user) b = db.query(Booking).filter(Booking.id == booking_id).first() if not b: raise HTTPException(status_code=404, detail="Booking not found") db.delete(b) db.commit() return None