from typing import Any, Optional from fastapi import APIRouter, Depends, HTTPException, Header, Cookie, status, Response from sqlalchemy.orm import Session from app.db.session import get_db from app.models.cart import CartItem from app.schemas.cart import ( Cart as CartSchema, CartItemCreate, CartItemUpdate, CartItemWithProduct, AddToCartResponse ) from app.crud import product, cart, cart_item router = APIRouter() def get_cart_id( db: Session = Depends(get_db), user_id: Optional[int] = None, session_id: Optional[str] = Cookie(None), x_session_id: Optional[str] = Header(None) ) -> int: """ Get or create an active cart for the current user/session. This function will: 1. Try to get the active cart for the authenticated user (if logged in) 2. If no user, try to get the active cart for the current session 3. If no active cart exists, create a new one Returns the cart ID. """ # If the session_id is not in cookies, try to get it from headers current_session_id = session_id or x_session_id # Get or create an active cart active_cart = cart.get_or_create_active_cart( db=db, user_id=user_id, session_id=current_session_id ) return active_cart.id @router.get("", response_model=CartSchema) def get_current_cart( cart_id: int = Depends(get_cart_id), db: Session = Depends(get_db), ) -> Any: """ Get the current active cart with all items. """ current_cart = cart.get_cart_with_items(db=db, cart_id=cart_id) if not current_cart: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Cart not found" ) return current_cart @router.post("/items", response_model=AddToCartResponse) def add_item_to_cart( *, db: Session = Depends(get_db), item_in: CartItemCreate, cart_id: int = Depends(get_cart_id), response: Response ) -> Any: """ Add an item to the cart. If the item already exists in the cart, the quantity will be updated. """ # Check if product exists and is active item_product = product.get(db=db, id=item_in.product_id) if not item_product: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Product with ID {item_in.product_id} not found" ) if not item_product.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Product with ID {item_in.product_id} is not available" ) # Check if product is in stock if item_product.stock_quantity < item_in.quantity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Not enough stock available. Only {item_product.stock_quantity} items left." ) # Get the current cart or create a new one current_cart = cart.get(db=db, id=cart_id) # Check if the product is already in the cart existing_item = cart_item.get_by_cart_and_product( db=db, cart_id=current_cart.id, product_id=item_in.product_id ) if existing_item: # Update the quantity new_quantity = existing_item.quantity + item_in.quantity # Check stock for the new total quantity if item_product.stock_quantity < new_quantity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Not enough stock available. Only {item_product.stock_quantity} items left." ) item_update = CartItemUpdate(quantity=new_quantity) updated_item = cart_item.update(db=db, db_obj=existing_item, obj_in=item_update) # Fetch the updated item with product details result = db.query(CartItem).filter(CartItem.id == updated_item.id).first() result.product = item_product # Set a cookie with the session ID if applicable if current_cart.session_id: response.set_cookie(key="session_id", value=current_cart.session_id, httponly=True) return AddToCartResponse( cart_id=current_cart.id, message="Item quantity updated in cart", cart_item=result ) else: # Add the new item to the cart new_item = cart_item.create_with_cart( db=db, obj_in=item_in, cart_id=current_cart.id, product_price=float(item_product.price) ) # Fetch the new item with product details result = db.query(CartItem).filter(CartItem.id == new_item.id).first() result.product = item_product # Set a cookie with the session ID if applicable if current_cart.session_id: response.set_cookie(key="session_id", value=current_cart.session_id, httponly=True) return AddToCartResponse( cart_id=current_cart.id, message="Item added to cart", cart_item=result ) @router.put("/items/{item_id}", response_model=CartItemWithProduct) def update_cart_item( *, db: Session = Depends(get_db), item_id: int, item_in: CartItemUpdate, cart_id: int = Depends(get_cart_id) ) -> Any: """ Update the quantity of an item in the cart. """ # Check if the item exists in the current cart item = db.query(CartItem).filter( CartItem.id == item_id, CartItem.cart_id == cart_id ).first() if not item: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Item with ID {item_id} not found in cart" ) # Check product availability and stock item_product = product.get(db=db, id=item.product_id) if not item_product or not item_product.is_active: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Product is not available" ) if item_product.stock_quantity < item_in.quantity: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Not enough stock available. Only {item_product.stock_quantity} items left." ) # Update the item updated_item = cart_item.update(db=db, db_obj=item, obj_in=item_in) # Fetch the updated item with product details result = db.query(CartItem).filter(CartItem.id == updated_item.id).first() result.product = item_product return result @router.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def remove_cart_item( *, db: Session = Depends(get_db), item_id: int, cart_id: int = Depends(get_cart_id) ) -> None: """ Remove an item from the cart. """ # Check if the item exists in the current cart item = db.query(CartItem).filter( CartItem.id == item_id, CartItem.cart_id == cart_id ).first() if not item: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail=f"Item with ID {item_id} not found in cart" ) # Remove the item cart_item.remove(db=db, id=item_id) return None @router.delete("", status_code=status.HTTP_204_NO_CONTENT, response_model=None) def clear_cart( *, db: Session = Depends(get_db), cart_id: int = Depends(get_cart_id) ) -> None: """ Remove all items from the cart. """ # Get all items in the cart items = cart_item.get_by_cart(db=db, cart_id=cart_id) # Remove all items for item in items: cart_item.remove(db=db, id=item.id) return None