new init
This commit is contained in:
171
apps/backend/app/api/bookings.py
Normal file
171
apps/backend/app/api/bookings.py
Normal file
@@ -0,0 +1,171 @@
|
||||
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
|
Reference in New Issue
Block a user