Spaces:
Sleeping
Sleeping
Upload app/api/cart.py
Browse files- app/api/cart.py +68 -18
app/api/cart.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
from typing import Any, Optional
|
| 2 |
from fastapi import APIRouter, Depends, HTTPException, status, Header
|
| 3 |
-
from sqlalchemy.orm import Session
|
| 4 |
from datetime import datetime, timezone
|
| 5 |
|
| 6 |
from app.db.base import get_db
|
|
@@ -11,6 +11,7 @@ from app.schemas.cart import (
|
|
| 11 |
CartResponse, CartItemCreate, CartItemUpdate,
|
| 12 |
CartItemResponse, CouponApply, CouponResponse
|
| 13 |
)
|
|
|
|
| 14 |
from app.core.security import get_optional_current_user
|
| 15 |
|
| 16 |
router = APIRouter(tags=["Cart"])
|
|
@@ -71,6 +72,15 @@ def get_or_create_cart(db: Session, user_id: Optional[int] = None, session_id: O
|
|
| 71 |
else:
|
| 72 |
raise HTTPException(status_code=400, detail="Either login or provide X-Cart-ID header")
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
@router.get("/cart", response_model=dict)
|
| 75 |
def get_cart(
|
| 76 |
current_user_id: Optional[int] = Depends(get_optional_current_user),
|
|
@@ -79,29 +89,69 @@ def get_cart(
|
|
| 79 |
) -> Any:
|
| 80 |
"""Get the current user's (or guest session's) shopping cart."""
|
| 81 |
cart = get_or_create_cart(db, user_id=current_user_id, session_id=x_cart_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
totals = calculate_cart_totals(cart)
|
| 83 |
|
| 84 |
-
|
|
|
|
| 85 |
for item in cart.items:
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
return {
|
| 98 |
"isSuccess": True,
|
| 99 |
-
"value":
|
| 100 |
-
id=cart.id,
|
| 101 |
-
user_id=cart.user_id,
|
| 102 |
-
items=items_response,
|
| 103 |
-
**totals
|
| 104 |
-
),
|
| 105 |
"statusCode": 200
|
| 106 |
}
|
| 107 |
|
|
|
|
| 1 |
from typing import Any, Optional
|
| 2 |
from fastapi import APIRouter, Depends, HTTPException, status, Header
|
| 3 |
+
from sqlalchemy.orm import Session, joinedload
|
| 4 |
from datetime import datetime, timezone
|
| 5 |
|
| 6 |
from app.db.base import get_db
|
|
|
|
| 11 |
CartResponse, CartItemCreate, CartItemUpdate,
|
| 12 |
CartItemResponse, CouponApply, CouponResponse
|
| 13 |
)
|
| 14 |
+
from app.schemas.product import ProductListItem
|
| 15 |
from app.core.security import get_optional_current_user
|
| 16 |
|
| 17 |
router = APIRouter(tags=["Cart"])
|
|
|
|
| 72 |
else:
|
| 73 |
raise HTTPException(status_code=400, detail="Either login or provide X-Cart-ID header")
|
| 74 |
|
| 75 |
+
|
| 76 |
+
def _get_primary_image(product) -> Optional[str]:
|
| 77 |
+
"""Extract the primary image URL from a product's images relationship."""
|
| 78 |
+
if product.images:
|
| 79 |
+
sorted_imgs = sorted(product.images, key=lambda x: x.sort_order)
|
| 80 |
+
return sorted_imgs[0].image_url if sorted_imgs else None
|
| 81 |
+
return None
|
| 82 |
+
|
| 83 |
+
|
| 84 |
@router.get("/cart", response_model=dict)
|
| 85 |
def get_cart(
|
| 86 |
current_user_id: Optional[int] = Depends(get_optional_current_user),
|
|
|
|
| 89 |
) -> Any:
|
| 90 |
"""Get the current user's (or guest session's) shopping cart."""
|
| 91 |
cart = get_or_create_cart(db, user_id=current_user_id, session_id=x_cart_id)
|
| 92 |
+
|
| 93 |
+
# Re-query the cart with all needed eager loads for response serialization
|
| 94 |
+
cart = db.query(Cart).options(
|
| 95 |
+
joinedload(Cart.items)
|
| 96 |
+
.joinedload(CartItem.product)
|
| 97 |
+
.joinedload(Product.images),
|
| 98 |
+
joinedload(Cart.items)
|
| 99 |
+
.joinedload(CartItem.product)
|
| 100 |
+
.joinedload(Product.category),
|
| 101 |
+
joinedload(Cart.coupon)
|
| 102 |
+
).filter(Cart.id == cart.id).first()
|
| 103 |
+
|
| 104 |
totals = calculate_cart_totals(cart)
|
| 105 |
|
| 106 |
+
# Build response manually as plain dicts to guarantee image_url is included
|
| 107 |
+
items_list = []
|
| 108 |
for item in cart.items:
|
| 109 |
+
primary_image = _get_primary_image(item.product)
|
| 110 |
+
|
| 111 |
+
product_dict = {
|
| 112 |
+
"id": item.product.id,
|
| 113 |
+
"name_ar": item.product.name_ar,
|
| 114 |
+
"name_en": item.product.name_en,
|
| 115 |
+
"price": item.product.price,
|
| 116 |
+
"compare_price": item.product.compare_price,
|
| 117 |
+
"stock": item.product.stock,
|
| 118 |
+
"category_id": item.product.category_id,
|
| 119 |
+
"category": None,
|
| 120 |
+
"rating": item.product.rating,
|
| 121 |
+
"rating_count": item.product.rating_count,
|
| 122 |
+
"is_featured": item.product.is_featured,
|
| 123 |
+
"image_url": primary_image,
|
| 124 |
+
"created_at": item.product.created_at.isoformat() if item.product.created_at else None,
|
| 125 |
+
}
|
| 126 |
+
if item.product.category:
|
| 127 |
+
product_dict["category"] = {
|
| 128 |
+
"id": item.product.category.id,
|
| 129 |
+
"name_ar": item.product.category.name_ar,
|
| 130 |
+
"name_en": item.product.category.name_en,
|
| 131 |
+
"icon": item.product.category.icon,
|
| 132 |
+
"sort_order": item.product.category.sort_order,
|
| 133 |
+
}
|
| 134 |
|
| 135 |
+
items_list.append({
|
| 136 |
+
"id": item.id,
|
| 137 |
+
"cart_id": item.cart_id,
|
| 138 |
+
"product_id": item.product_id,
|
| 139 |
+
"quantity": item.quantity,
|
| 140 |
+
"unit_price": item.unit_price,
|
| 141 |
+
"total_price": round(item.quantity * item.unit_price, 2),
|
| 142 |
+
"product": product_dict,
|
| 143 |
+
})
|
| 144 |
+
|
| 145 |
+
response_value = {
|
| 146 |
+
"id": cart.id,
|
| 147 |
+
"user_id": cart.user_id,
|
| 148 |
+
"items": items_list,
|
| 149 |
+
**totals
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
return {
|
| 153 |
"isSuccess": True,
|
| 154 |
+
"value": response_value,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
"statusCode": 200
|
| 156 |
}
|
| 157 |
|