wahnsinn vibe
This commit is contained in:
140
backend/apps/cart/__init__.py
Normal file
140
backend/apps/cart/__init__.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import json
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from core.db import get_db
|
||||
from core.redis_client import redis_client
|
||||
from core.security import get_current_user_id
|
||||
|
||||
from .models import Cart, CartItem
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class AddItemIn(BaseModel):
|
||||
product_id: int
|
||||
qty: int = 1
|
||||
|
||||
|
||||
class UpdateItemIn(BaseModel):
|
||||
qty: int
|
||||
|
||||
|
||||
class CartItemOut(BaseModel):
|
||||
product_id: int
|
||||
qty: int
|
||||
name: dict = {}
|
||||
price: float = 0.0
|
||||
image_url: str = ""
|
||||
line_total: float = 0.0
|
||||
|
||||
|
||||
class CartOut(BaseModel):
|
||||
items: list[CartItemOut]
|
||||
subtotal: float
|
||||
|
||||
|
||||
def _get_or_create_cart(db: Session, user_id: int) -> Cart:
|
||||
cart = db.query(Cart).filter_by(user_id=user_id).first()
|
||||
if not cart:
|
||||
cart = Cart(user_id=user_id)
|
||||
db.add(cart)
|
||||
db.commit()
|
||||
db.refresh(cart)
|
||||
return cart
|
||||
|
||||
|
||||
def _cart_to_out(cart: Cart) -> CartOut:
|
||||
items: list[CartItemOut] = []
|
||||
subtotal = 0.0
|
||||
for it in cart.items:
|
||||
raw = redis_client.get(f"product:{it.product_id}")
|
||||
if not raw:
|
||||
continue
|
||||
p = json.loads(raw)
|
||||
line = round(float(p["price"]) * it.qty, 2)
|
||||
subtotal += line
|
||||
items.append(
|
||||
CartItemOut(
|
||||
product_id=it.product_id,
|
||||
qty=it.qty,
|
||||
name=p.get("name", {}),
|
||||
price=float(p["price"]),
|
||||
image_url=p.get("image_url", ""),
|
||||
line_total=line,
|
||||
)
|
||||
)
|
||||
return CartOut(items=items, subtotal=round(subtotal, 2))
|
||||
|
||||
|
||||
@router.get("", response_model=CartOut)
|
||||
def get_cart(user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db)):
|
||||
return _cart_to_out(_get_or_create_cart(db, user_id))
|
||||
|
||||
|
||||
@router.post("/items", response_model=CartOut)
|
||||
def add_item(
|
||||
body: AddItemIn,
|
||||
user_id: int = Depends(get_current_user_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if body.qty < 1:
|
||||
raise HTTPException(400, "qty must be >= 1")
|
||||
if not redis_client.get(f"product:{body.product_id}"):
|
||||
raise HTTPException(404, "Product not found or inactive")
|
||||
cart = _get_or_create_cart(db, user_id)
|
||||
existing = db.query(CartItem).filter_by(cart_id=cart.id, product_id=body.product_id).first()
|
||||
if existing:
|
||||
existing.qty += body.qty
|
||||
else:
|
||||
db.add(CartItem(cart_id=cart.id, product_id=body.product_id, qty=body.qty))
|
||||
db.commit()
|
||||
db.refresh(cart)
|
||||
return _cart_to_out(cart)
|
||||
|
||||
|
||||
@router.put("/items/{product_id}", response_model=CartOut)
|
||||
def update_item(
|
||||
product_id: int,
|
||||
body: UpdateItemIn,
|
||||
user_id: int = Depends(get_current_user_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
cart = _get_or_create_cart(db, user_id)
|
||||
item = db.query(CartItem).filter_by(cart_id=cart.id, product_id=product_id).first()
|
||||
if not item:
|
||||
raise HTTPException(404, "Not in cart")
|
||||
if body.qty < 1:
|
||||
db.delete(item)
|
||||
else:
|
||||
item.qty = body.qty
|
||||
db.commit()
|
||||
db.refresh(cart)
|
||||
return _cart_to_out(cart)
|
||||
|
||||
|
||||
@router.delete("/items/{product_id}", response_model=CartOut)
|
||||
def remove_item(
|
||||
product_id: int,
|
||||
user_id: int = Depends(get_current_user_id),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
cart = _get_or_create_cart(db, user_id)
|
||||
item = db.query(CartItem).filter_by(cart_id=cart.id, product_id=product_id).first()
|
||||
if item:
|
||||
db.delete(item)
|
||||
db.commit()
|
||||
db.refresh(cart)
|
||||
return _cart_to_out(cart)
|
||||
|
||||
|
||||
@router.delete("", response_model=CartOut)
|
||||
def clear_cart(user_id: int = Depends(get_current_user_id), db: Session = Depends(get_db)):
|
||||
cart = _get_or_create_cart(db, user_id)
|
||||
for it in list(cart.items):
|
||||
db.delete(it)
|
||||
db.commit()
|
||||
db.refresh(cart)
|
||||
return _cart_to_out(cart)
|
||||
6
backend/apps/cart/manifest.yaml
Normal file
6
backend/apps/cart/manifest.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
name: cart
|
||||
version: 0.1.0
|
||||
depends_on: [core, auth, catalog]
|
||||
conflicts_with: []
|
||||
required: true
|
||||
provides: [CartService]
|
||||
30
backend/apps/cart/models.py
Normal file
30
backend/apps/cart/models.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from core.db import Base
|
||||
|
||||
|
||||
class Cart(Base):
|
||||
__tablename__ = "carts"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), unique=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
items: Mapped[list["CartItem"]] = relationship(
|
||||
"CartItem", back_populates="cart", cascade="all, delete-orphan", lazy="joined"
|
||||
)
|
||||
|
||||
|
||||
class CartItem(Base):
|
||||
__tablename__ = "cart_items"
|
||||
__table_args__ = (UniqueConstraint("cart_id", "product_id", name="uq_cart_product"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
cart_id: Mapped[int] = mapped_column(ForeignKey("carts.id", ondelete="CASCADE"))
|
||||
product_id: Mapped[int] = mapped_column(ForeignKey("products.id", ondelete="CASCADE"))
|
||||
qty: Mapped[int] = mapped_column(Integer, default=1)
|
||||
|
||||
cart: Mapped["Cart"] = relationship("Cart", back_populates="items")
|
||||
Reference in New Issue
Block a user