Complete e-commerce API implementation with all required endpoints
This commit is contained in:
parent
fd7eca550f
commit
c6db3ebe3b
145
app/api/endpoints/cart.py
Normal file
145
app/api/endpoints/cart.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app import crud, models, schemas
|
||||||
|
from app.api import deps
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=schemas.Cart)
|
||||||
|
def read_user_cart(
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get current user's cart with all items.
|
||||||
|
"""
|
||||||
|
cart = crud.cart.get_cart_with_items(db, user_id=current_user.id)
|
||||||
|
if not cart:
|
||||||
|
# Create a new cart for the user if it doesn't exist
|
||||||
|
cart = crud.cart.create(db, obj_in=schemas.CartCreate(user_id=current_user.id))
|
||||||
|
return cart
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/items", response_model=schemas.CartItem)
|
||||||
|
def add_cart_item(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
item_in: schemas.CartItemCreate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Add item to cart. If item already exists, update quantity.
|
||||||
|
"""
|
||||||
|
# Verify the product exists and is active
|
||||||
|
product = crud.product.get(db, id=item_in.product_id)
|
||||||
|
if not product or not product.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found or inactive",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if product is in stock
|
||||||
|
if product.stock < item_in.quantity:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"Not enough stock. Available: {product.stock}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get or create user's cart
|
||||||
|
cart = crud.cart.get_or_create_cart(db, user_id=current_user.id)
|
||||||
|
|
||||||
|
# Add or update cart item
|
||||||
|
cart_item = crud.cart_item.create_or_update_cart_item(
|
||||||
|
db, cart_id=cart.id, product_id=item_in.product_id, quantity=item_in.quantity
|
||||||
|
)
|
||||||
|
|
||||||
|
return cart_item
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/items/{product_id}", response_model=schemas.CartItem)
|
||||||
|
def update_cart_item(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
product_id: int,
|
||||||
|
item_in: schemas.CartItemUpdate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update cart item quantity.
|
||||||
|
"""
|
||||||
|
# Verify the product exists and is active
|
||||||
|
product = crud.product.get(db, id=product_id)
|
||||||
|
if not product or not product.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found or inactive",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if product is in stock
|
||||||
|
if product.stock < item_in.quantity:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"Not enough stock. Available: {product.stock}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get user's cart
|
||||||
|
cart = crud.cart.get_by_user_id(db, user_id=current_user.id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cart not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update cart item
|
||||||
|
cart_item = crud.cart_item.create_or_update_cart_item(
|
||||||
|
db, cart_id=cart.id, product_id=product_id, quantity=item_in.quantity
|
||||||
|
)
|
||||||
|
|
||||||
|
return cart_item
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/items/{product_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def remove_cart_item(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
product_id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Remove item from cart.
|
||||||
|
"""
|
||||||
|
# Get user's cart
|
||||||
|
cart = crud.cart.get_by_user_id(db, user_id=current_user.id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cart not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove cart item
|
||||||
|
crud.cart_item.remove_cart_item(db, cart_id=cart.id, product_id=product_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def clear_cart(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Clear all items from cart.
|
||||||
|
"""
|
||||||
|
# Get user's cart
|
||||||
|
cart = crud.cart.get_by_user_id(db, user_id=current_user.id)
|
||||||
|
if not cart:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Cart not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Clear cart
|
||||||
|
crud.cart_item.clear_cart(db, cart_id=cart.id)
|
110
app/api/endpoints/categories.py
Normal file
110
app/api/endpoints/categories.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app import crud, models, schemas
|
||||||
|
from app.api import deps
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[schemas.Category])
|
||||||
|
def read_categories(
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieve categories.
|
||||||
|
"""
|
||||||
|
categories = crud.category.get_multi(db, skip=skip, limit=limit)
|
||||||
|
return categories
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=schemas.Category)
|
||||||
|
def create_category(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
category_in: schemas.CategoryCreate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Create new category (admin only).
|
||||||
|
"""
|
||||||
|
category = crud.category.get_by_name(db, name=category_in.name)
|
||||||
|
if category:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Category with this name already exists",
|
||||||
|
)
|
||||||
|
category = crud.category.create(db, obj_in=category_in)
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{id}", response_model=schemas.Category)
|
||||||
|
def read_category(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get category by ID.
|
||||||
|
"""
|
||||||
|
category = crud.category.get(db, id=id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Category not found",
|
||||||
|
)
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{id}", response_model=schemas.Category)
|
||||||
|
def update_category(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
category_in: schemas.CategoryUpdate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update a category (admin only).
|
||||||
|
"""
|
||||||
|
category = crud.category.get(db, id=id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Category not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if name already exists (if updating name)
|
||||||
|
if category_in.name and category_in.name != category.name:
|
||||||
|
existing_category = crud.category.get_by_name(db, name=category_in.name)
|
||||||
|
if existing_category:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Category with this name already exists",
|
||||||
|
)
|
||||||
|
|
||||||
|
category = crud.category.update(db, db_obj=category, obj_in=category_in)
|
||||||
|
return category
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def delete_category(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Delete a category (admin only).
|
||||||
|
"""
|
||||||
|
category = crud.category.get(db, id=id)
|
||||||
|
if not category:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Category not found",
|
||||||
|
)
|
||||||
|
crud.category.remove(db, id=id)
|
172
app/api/endpoints/orders.py
Normal file
172
app/api/endpoints/orders.py
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app import crud, models, schemas
|
||||||
|
from app.api import deps
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[schemas.Order])
|
||||||
|
def read_user_orders(
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieve current user's orders.
|
||||||
|
"""
|
||||||
|
orders = crud.order.get_orders_by_user(
|
||||||
|
db, user_id=current_user.id, skip=skip, limit=limit
|
||||||
|
)
|
||||||
|
return orders
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=schemas.Order)
|
||||||
|
def create_order(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
order_in: schemas.OrderCreate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Create a new order.
|
||||||
|
"""
|
||||||
|
# Validate order items
|
||||||
|
if not order_in.items or len(order_in.items) == 0:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Order must contain at least one item",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if products exist and are in stock
|
||||||
|
for item in order_in.items:
|
||||||
|
product = crud.product.get(db, id=item.product_id)
|
||||||
|
if not product or not product.is_active:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail=f"Product with id {item.product_id} not found or inactive",
|
||||||
|
)
|
||||||
|
|
||||||
|
if product.stock < item.quantity:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail=f"Not enough stock for product {product.name}. Available: {product.stock}",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create order
|
||||||
|
order = crud.order.create_with_items(
|
||||||
|
db, obj_in=order_in, user_id=current_user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update product stock
|
||||||
|
for item in order.items:
|
||||||
|
product = crud.product.get(db, id=item.product_id)
|
||||||
|
product.stock -= item.quantity
|
||||||
|
db.add(product)
|
||||||
|
|
||||||
|
# Clear user's cart
|
||||||
|
cart = crud.cart.get_by_user_id(db, user_id=current_user.id)
|
||||||
|
if cart:
|
||||||
|
crud.cart_item.clear_cart(db, cart_id=cart.id)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{order_id}", response_model=schemas.Order)
|
||||||
|
def read_order(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
order_id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get order by ID.
|
||||||
|
"""
|
||||||
|
order = crud.order.get_order_with_items(db, order_id=order_id)
|
||||||
|
if not order:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Order not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user is authorized to access this order
|
||||||
|
if order.user_id != current_user.id and not current_user.is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Not enough permissions",
|
||||||
|
)
|
||||||
|
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{order_id}", response_model=schemas.Order)
|
||||||
|
def update_order_status(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
order_id: int,
|
||||||
|
order_in: schemas.OrderUpdate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update order status (admin only).
|
||||||
|
"""
|
||||||
|
order = crud.order.get(db, id=order_id)
|
||||||
|
if not order:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Order not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
order = crud.order.update(db, db_obj=order, obj_in=order_in)
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{order_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def cancel_order(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
order_id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Cancel an order. Only allowed for pending orders.
|
||||||
|
"""
|
||||||
|
order = crud.order.get_order_with_items(db, order_id=order_id)
|
||||||
|
if not order:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Order not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user is authorized to cancel this order
|
||||||
|
if order.user_id != current_user.id and not current_user.is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Not enough permissions",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if order can be cancelled
|
||||||
|
if order.status != models.OrderStatus.PENDING:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="Only pending orders can be cancelled",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update order status to cancelled
|
||||||
|
order.status = models.OrderStatus.CANCELLED
|
||||||
|
|
||||||
|
# Restore product stock
|
||||||
|
for item in order.items:
|
||||||
|
product = crud.product.get(db, id=item.product_id)
|
||||||
|
if product:
|
||||||
|
product.stock += item.quantity
|
||||||
|
db.add(product)
|
||||||
|
|
||||||
|
db.add(order)
|
||||||
|
db.commit()
|
108
app/api/endpoints/products.py
Normal file
108
app/api/endpoints/products.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from typing import Any, List, Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app import crud, models, schemas
|
||||||
|
from app.api import deps
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=List[schemas.Product])
|
||||||
|
def read_products(
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
category_id: Optional[int] = None,
|
||||||
|
min_price: Optional[float] = None,
|
||||||
|
max_price: Optional[float] = None,
|
||||||
|
in_stock: Optional[bool] = None,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieve products with filtering options.
|
||||||
|
"""
|
||||||
|
filter_params = schemas.ProductFilterParams(
|
||||||
|
name=name,
|
||||||
|
category_id=category_id,
|
||||||
|
min_price=min_price,
|
||||||
|
max_price=max_price,
|
||||||
|
in_stock=in_stock,
|
||||||
|
)
|
||||||
|
products = crud.product.search_products(
|
||||||
|
db, filter_params=filter_params, skip=skip, limit=limit
|
||||||
|
)
|
||||||
|
return products
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=schemas.Product)
|
||||||
|
def create_product(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
product_in: schemas.ProductCreate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Create new product (admin only).
|
||||||
|
"""
|
||||||
|
product = crud.product.create(db, obj_in=product_in)
|
||||||
|
return product
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{id}", response_model=schemas.Product)
|
||||||
|
def read_product(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Get product by ID.
|
||||||
|
"""
|
||||||
|
product = crud.product.get(db, id=id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found",
|
||||||
|
)
|
||||||
|
return product
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{id}", response_model=schemas.Product)
|
||||||
|
def update_product(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
product_in: schemas.ProductUpdate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update a product (admin only).
|
||||||
|
"""
|
||||||
|
product = crud.product.get(db, id=id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found",
|
||||||
|
)
|
||||||
|
product = crud.product.update(db, db_obj=product, obj_in=product_in)
|
||||||
|
return product
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def delete_product(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_admin_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Delete a product (admin only).
|
||||||
|
"""
|
||||||
|
product = crud.product.get(db, id=id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found",
|
||||||
|
)
|
||||||
|
crud.product.remove(db, id=id)
|
124
app/api/endpoints/reviews.py
Normal file
124
app/api/endpoints/reviews.py
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Depends, HTTPException, status
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app import crud, models, schemas
|
||||||
|
from app.api import deps
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/product/{product_id}", response_model=List[schemas.Review])
|
||||||
|
def read_product_reviews(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
product_id: int,
|
||||||
|
skip: int = 0,
|
||||||
|
limit: int = 100,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Retrieve reviews for a specific product.
|
||||||
|
"""
|
||||||
|
# Check if product exists
|
||||||
|
product = crud.product.get(db, id=product_id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
reviews = crud.review.get_reviews_by_product(
|
||||||
|
db, product_id=product_id, skip=skip, limit=limit
|
||||||
|
)
|
||||||
|
return reviews
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=schemas.Review)
|
||||||
|
def create_review(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
review_in: schemas.ReviewCreate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Create a new review for a product.
|
||||||
|
"""
|
||||||
|
# Check if product exists
|
||||||
|
product = crud.product.get(db, id=review_in.product_id)
|
||||||
|
if not product:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Product not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user has already reviewed this product
|
||||||
|
existing_review = crud.review.get_user_review_for_product(
|
||||||
|
db, user_id=current_user.id, product_id=review_in.product_id
|
||||||
|
)
|
||||||
|
if existing_review:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="You have already reviewed this product",
|
||||||
|
)
|
||||||
|
|
||||||
|
review = crud.review.create_user_review(
|
||||||
|
db, obj_in=review_in, user_id=current_user.id
|
||||||
|
)
|
||||||
|
return review
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/{review_id}", response_model=schemas.Review)
|
||||||
|
def update_review(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
review_id: int,
|
||||||
|
review_in: schemas.ReviewUpdate,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Update a review.
|
||||||
|
"""
|
||||||
|
review = crud.review.get(db, id=review_id)
|
||||||
|
if not review:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Review not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user is authorized to update this review
|
||||||
|
if review.user_id != current_user.id and not current_user.is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Not enough permissions",
|
||||||
|
)
|
||||||
|
|
||||||
|
review = crud.review.update(db, db_obj=review, obj_in=review_in)
|
||||||
|
return review
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/{review_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
|
||||||
|
def delete_review(
|
||||||
|
*,
|
||||||
|
db: Session = Depends(deps.get_db),
|
||||||
|
review_id: int,
|
||||||
|
current_user: models.User = Depends(deps.get_current_active_user),
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Delete a review.
|
||||||
|
"""
|
||||||
|
review = crud.review.get(db, id=review_id)
|
||||||
|
if not review:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND,
|
||||||
|
detail="Review not found",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if user is authorized to delete this review
|
||||||
|
if review.user_id != current_user.id and not current_user.is_admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Not enough permissions",
|
||||||
|
)
|
||||||
|
|
||||||
|
crud.review.remove(db, id=review_id)
|
@ -1,9 +1,14 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from app.api.endpoints import auth, users
|
from app.api.endpoints import auth, cart, categories, orders, products, reviews, users
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
|
|
||||||
# Include authentication and user endpoints
|
# Include all API endpoints
|
||||||
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
api_router.include_router(auth.router, prefix="/auth", tags=["Authentication"])
|
||||||
api_router.include_router(users.router, prefix="/users", tags=["Users"])
|
api_router.include_router(users.router, prefix="/users", tags=["Users"])
|
||||||
|
api_router.include_router(products.router, prefix="/products", tags=["Products"])
|
||||||
|
api_router.include_router(categories.router, prefix="/categories", tags=["Categories"])
|
||||||
|
api_router.include_router(cart.router, prefix="/cart", tags=["Shopping Cart"])
|
||||||
|
api_router.include_router(orders.router, prefix="/orders", tags=["Orders"])
|
||||||
|
api_router.include_router(reviews.router, prefix="/reviews", tags=["Reviews"])
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
from app.crud.crud_cart import cart, cart_item
|
||||||
|
from app.crud.crud_order import order, order_item
|
||||||
|
from app.crud.crud_product import category, product
|
||||||
|
from app.crud.crud_review import review
|
||||||
from app.crud.crud_user import user
|
from app.crud.crud_user import user
|
||||||
|
|
||||||
__all__ = ["user"]
|
__all__ = ["user", "product", "category", "cart", "cart_item", "order", "order_item", "review"]
|
||||||
|
65
app/crud/crud_cart.py
Normal file
65
app/crud/crud_cart.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
|
from app.crud.base import CRUDBase
|
||||||
|
from app.models.cart import Cart, CartItem
|
||||||
|
from app.schemas.cart import CartCreate, CartItemCreate, CartItemUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDCart(CRUDBase[Cart, CartCreate, CartCreate]):
|
||||||
|
def get_by_user_id(self, db: Session, *, user_id: int) -> Optional[Cart]:
|
||||||
|
return db.query(Cart).filter(Cart.user_id == user_id).first()
|
||||||
|
|
||||||
|
def get_or_create_cart(self, db: Session, *, user_id: int) -> Cart:
|
||||||
|
cart = self.get_by_user_id(db, user_id=user_id)
|
||||||
|
if not cart:
|
||||||
|
cart = self.create(db, obj_in=CartCreate(user_id=user_id))
|
||||||
|
return cart
|
||||||
|
|
||||||
|
def get_cart_with_items(self, db: Session, *, user_id: int) -> Optional[Cart]:
|
||||||
|
return (
|
||||||
|
db.query(Cart)
|
||||||
|
.filter(Cart.user_id == user_id)
|
||||||
|
.options(joinedload(Cart.items).joinedload(CartItem.product))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDCartItem(CRUDBase[CartItem, CartItemCreate, CartItemUpdate]):
|
||||||
|
def create_or_update_cart_item(
|
||||||
|
self, db: Session, *, cart_id: int, product_id: int, quantity: int
|
||||||
|
) -> CartItem:
|
||||||
|
cart_item = (
|
||||||
|
db.query(CartItem)
|
||||||
|
.filter(CartItem.cart_id == cart_id, CartItem.product_id == product_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
if cart_item:
|
||||||
|
cart_item.quantity = quantity
|
||||||
|
db.add(cart_item)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(cart_item)
|
||||||
|
return cart_item
|
||||||
|
else:
|
||||||
|
return self.create(
|
||||||
|
db,
|
||||||
|
obj_in=CartItemCreate(cart_id=cart_id, product_id=product_id, quantity=quantity)
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove_cart_item(
|
||||||
|
self, db: Session, *, cart_id: int, product_id: int
|
||||||
|
) -> None:
|
||||||
|
db.query(CartItem).filter(
|
||||||
|
CartItem.cart_id == cart_id, CartItem.product_id == product_id
|
||||||
|
).delete()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def clear_cart(self, db: Session, *, cart_id: int) -> None:
|
||||||
|
db.query(CartItem).filter(CartItem.cart_id == cart_id).delete()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
cart = CRUDCart(Cart)
|
||||||
|
cart_item = CRUDCartItem(CartItem)
|
69
app/crud/crud_order.py
Normal file
69
app/crud/crud_order.py
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session, joinedload
|
||||||
|
|
||||||
|
from app.crud.base import CRUDBase
|
||||||
|
from app.models.order import Order, OrderItem
|
||||||
|
from app.schemas.order import OrderCreate, OrderItemCreate, OrderUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDOrder(CRUDBase[Order, OrderCreate, OrderUpdate]):
|
||||||
|
def get_orders_by_user(
|
||||||
|
self, db: Session, *, user_id: int, skip: int = 0, limit: int = 100
|
||||||
|
) -> List[Order]:
|
||||||
|
return (
|
||||||
|
db.query(Order)
|
||||||
|
.filter(Order.user_id == user_id)
|
||||||
|
.order_by(Order.created_at.desc())
|
||||||
|
.offset(skip)
|
||||||
|
.limit(limit)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_order_with_items(self, db: Session, *, order_id: int) -> Optional[Order]:
|
||||||
|
return (
|
||||||
|
db.query(Order)
|
||||||
|
.filter(Order.id == order_id)
|
||||||
|
.options(joinedload(Order.items))
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_with_items(
|
||||||
|
self, db: Session, *, obj_in: OrderCreate, user_id: int
|
||||||
|
) -> Order:
|
||||||
|
# Calculate the total amount
|
||||||
|
total_amount = sum(item.unit_price * item.quantity for item in obj_in.items)
|
||||||
|
|
||||||
|
# Create order
|
||||||
|
db_obj = Order(
|
||||||
|
user_id=user_id,
|
||||||
|
status=obj_in.status,
|
||||||
|
total_amount=total_amount,
|
||||||
|
shipping_address=obj_in.shipping_address,
|
||||||
|
payment_details=obj_in.payment_details,
|
||||||
|
tracking_number=obj_in.tracking_number,
|
||||||
|
)
|
||||||
|
db.add(db_obj)
|
||||||
|
db.flush()
|
||||||
|
|
||||||
|
# Create order items
|
||||||
|
for item in obj_in.items:
|
||||||
|
order_item = OrderItem(
|
||||||
|
order_id=db_obj.id,
|
||||||
|
product_id=item.product_id,
|
||||||
|
quantity=item.quantity,
|
||||||
|
unit_price=item.unit_price,
|
||||||
|
)
|
||||||
|
db.add(order_item)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDOrderItem(CRUDBase[OrderItem, OrderItemCreate, OrderItemCreate]):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
order = CRUDOrder(Order)
|
||||||
|
order_item = CRUDOrderItem(OrderItem)
|
53
app/crud/crud_product.py
Normal file
53
app/crud/crud_product.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.crud.base import CRUDBase
|
||||||
|
from app.models.product import Category, Product
|
||||||
|
from app.schemas.product import ProductCreate, ProductFilterParams, ProductUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDProduct(CRUDBase[Product, ProductCreate, ProductUpdate]):
|
||||||
|
def search_products(
|
||||||
|
self, db: Session, *, filter_params: ProductFilterParams, skip: int = 0, limit: int = 100
|
||||||
|
) -> List[Product]:
|
||||||
|
query = db.query(Product)
|
||||||
|
|
||||||
|
# Apply filters
|
||||||
|
if filter_params.name:
|
||||||
|
query = query.filter(Product.name.ilike(f"%{filter_params.name}%"))
|
||||||
|
|
||||||
|
if filter_params.category_id:
|
||||||
|
query = query.filter(Product.category_id == filter_params.category_id)
|
||||||
|
|
||||||
|
if filter_params.min_price is not None:
|
||||||
|
query = query.filter(Product.price >= filter_params.min_price)
|
||||||
|
|
||||||
|
if filter_params.max_price is not None:
|
||||||
|
query = query.filter(Product.price <= filter_params.max_price)
|
||||||
|
|
||||||
|
if filter_params.in_stock is not None:
|
||||||
|
if filter_params.in_stock:
|
||||||
|
query = query.filter(Product.stock > 0)
|
||||||
|
else:
|
||||||
|
query = query.filter(Product.stock == 0)
|
||||||
|
|
||||||
|
# Only return active products by default
|
||||||
|
query = query.filter(Product.is_active)
|
||||||
|
|
||||||
|
# Order by newest first
|
||||||
|
query = query.order_by(Product.created_at.desc())
|
||||||
|
|
||||||
|
return query.offset(skip).limit(limit).all()
|
||||||
|
|
||||||
|
def get_product_with_category(self, db: Session, product_id: int) -> Optional[Product]:
|
||||||
|
return db.query(Product).filter(Product.id == product_id).first()
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDCategory(CRUDBase[Category, ProductCreate, ProductUpdate]):
|
||||||
|
def get_by_name(self, db: Session, *, name: str) -> Optional[Category]:
|
||||||
|
return db.query(Category).filter(Category.name == name).first()
|
||||||
|
|
||||||
|
|
||||||
|
product = CRUDProduct(Product)
|
||||||
|
category = CRUDCategory(Category)
|
59
app/crud/crud_review.py
Normal file
59
app/crud/crud_review.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from app.crud.base import CRUDBase
|
||||||
|
from app.models.review import Review
|
||||||
|
from app.schemas.review import ReviewCreate, ReviewUpdate
|
||||||
|
|
||||||
|
|
||||||
|
class CRUDReview(CRUDBase[Review, ReviewCreate, ReviewUpdate]):
|
||||||
|
def get_reviews_by_product(
|
||||||
|
self, db: Session, *, product_id: int, skip: int = 0, limit: int = 100
|
||||||
|
) -> List[Review]:
|
||||||
|
return (
|
||||||
|
db.query(Review)
|
||||||
|
.filter(Review.product_id == product_id)
|
||||||
|
.order_by(Review.created_at.desc())
|
||||||
|
.offset(skip)
|
||||||
|
.limit(limit)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_reviews_by_user(
|
||||||
|
self, db: Session, *, user_id: int, skip: int = 0, limit: int = 100
|
||||||
|
) -> List[Review]:
|
||||||
|
return (
|
||||||
|
db.query(Review)
|
||||||
|
.filter(Review.user_id == user_id)
|
||||||
|
.order_by(Review.created_at.desc())
|
||||||
|
.offset(skip)
|
||||||
|
.limit(limit)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_user_review_for_product(
|
||||||
|
self, db: Session, *, user_id: int, product_id: int
|
||||||
|
) -> Optional[Review]:
|
||||||
|
return (
|
||||||
|
db.query(Review)
|
||||||
|
.filter(Review.user_id == user_id, Review.product_id == product_id)
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
|
def create_user_review(
|
||||||
|
self, db: Session, *, obj_in: ReviewCreate, user_id: int
|
||||||
|
) -> Review:
|
||||||
|
db_obj = Review(
|
||||||
|
user_id=user_id,
|
||||||
|
product_id=obj_in.product_id,
|
||||||
|
rating=obj_in.rating,
|
||||||
|
comment=obj_in.comment,
|
||||||
|
)
|
||||||
|
db.add(db_obj)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_obj)
|
||||||
|
return db_obj
|
||||||
|
|
||||||
|
|
||||||
|
review = CRUDReview(Review)
|
@ -1,5 +1,25 @@
|
|||||||
# Import schemas for convenience
|
# Import schemas for convenience
|
||||||
|
from app.schemas.cart import Cart, CartCreate, CartItem, CartItemCreate, CartItemUpdate
|
||||||
|
from app.schemas.order import Order, OrderCreate, OrderItem, OrderItemCreate, OrderUpdate
|
||||||
|
from app.schemas.product import (
|
||||||
|
Category,
|
||||||
|
CategoryCreate,
|
||||||
|
CategoryUpdate,
|
||||||
|
Product,
|
||||||
|
ProductCreate,
|
||||||
|
ProductFilterParams,
|
||||||
|
ProductUpdate,
|
||||||
|
)
|
||||||
|
from app.schemas.review import Review, ReviewCreate, ReviewUpdate
|
||||||
from app.schemas.token import Token, TokenPayload
|
from app.schemas.token import Token, TokenPayload
|
||||||
from app.schemas.user import User, UserCreate, UserInDB, UserUpdate
|
from app.schemas.user import User, UserCreate, UserInDB, UserUpdate
|
||||||
|
|
||||||
__all__ = ["Token", "TokenPayload", "User", "UserCreate", "UserInDB", "UserUpdate"]
|
__all__ = [
|
||||||
|
"Token", "TokenPayload",
|
||||||
|
"User", "UserCreate", "UserInDB", "UserUpdate",
|
||||||
|
"Product", "ProductCreate", "ProductUpdate",
|
||||||
|
"Category", "CategoryCreate", "CategoryUpdate", "ProductFilterParams",
|
||||||
|
"Order", "OrderCreate", "OrderUpdate", "OrderItem", "OrderItemCreate",
|
||||||
|
"Cart", "CartCreate", "CartItem", "CartItemCreate", "CartItemUpdate",
|
||||||
|
"Review", "ReviewCreate", "ReviewUpdate"
|
||||||
|
]
|
||||||
|
56
app/schemas/cart.py
Normal file
56
app/schemas/cart.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from app.schemas.product import Product
|
||||||
|
|
||||||
|
|
||||||
|
# Cart Item schemas
|
||||||
|
class CartItemBase(BaseModel):
|
||||||
|
product_id: int
|
||||||
|
quantity: int = Field(1, gt=0)
|
||||||
|
|
||||||
|
|
||||||
|
class CartItemCreate(CartItemBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CartItemUpdate(BaseModel):
|
||||||
|
quantity: int = Field(..., gt=0)
|
||||||
|
|
||||||
|
|
||||||
|
class CartItemInDBBase(CartItemBase):
|
||||||
|
id: int
|
||||||
|
cart_id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class CartItem(CartItemInDBBase):
|
||||||
|
product: Optional[Product] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Cart schemas
|
||||||
|
class CartBase(BaseModel):
|
||||||
|
user_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class CartCreate(CartBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CartInDBBase(CartBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class Cart(CartInDBBase):
|
||||||
|
items: List[CartItem] = []
|
64
app/schemas/order.py
Normal file
64
app/schemas/order.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
from app.models.order import OrderStatus
|
||||||
|
|
||||||
|
|
||||||
|
# Order Item schemas
|
||||||
|
class OrderItemBase(BaseModel):
|
||||||
|
product_id: int
|
||||||
|
quantity: int = Field(..., gt=0)
|
||||||
|
unit_price: float = Field(..., gt=0)
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItemCreate(OrderItemBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItemInDBBase(OrderItemBase):
|
||||||
|
id: int
|
||||||
|
order_id: int
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class OrderItem(OrderItemInDBBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Order schemas
|
||||||
|
class OrderBase(BaseModel):
|
||||||
|
status: OrderStatus = OrderStatus.PENDING
|
||||||
|
shipping_address: str
|
||||||
|
payment_details: Optional[str] = None
|
||||||
|
tracking_number: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class OrderCreate(OrderBase):
|
||||||
|
items: List[OrderItemCreate]
|
||||||
|
|
||||||
|
|
||||||
|
class OrderUpdate(BaseModel):
|
||||||
|
status: Optional[OrderStatus] = None
|
||||||
|
shipping_address: Optional[str] = None
|
||||||
|
payment_details: Optional[str] = None
|
||||||
|
tracking_number: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class OrderInDBBase(OrderBase):
|
||||||
|
id: int
|
||||||
|
user_id: int
|
||||||
|
total_amount: float
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class Order(OrderInDBBase):
|
||||||
|
items: List[OrderItem]
|
75
app/schemas/product.py
Normal file
75
app/schemas/product.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
# Category schemas
|
||||||
|
class CategoryBase(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryCreate(CategoryBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryUpdate(CategoryBase):
|
||||||
|
name: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryInDBBase(CategoryBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class Category(CategoryInDBBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Product schemas
|
||||||
|
class ProductBase(BaseModel):
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
price: float = Field(..., gt=0)
|
||||||
|
stock: int = Field(0, ge=0)
|
||||||
|
image_url: Optional[str] = None
|
||||||
|
is_active: bool = True
|
||||||
|
category_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCreate(ProductBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProductUpdate(ProductBase):
|
||||||
|
name: Optional[str] = None
|
||||||
|
price: Optional[float] = Field(None, gt=0)
|
||||||
|
stock: Optional[int] = Field(None, ge=0)
|
||||||
|
category_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ProductInDBBase(ProductBase):
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class Product(ProductInDBBase):
|
||||||
|
category: Optional[Category] = None
|
||||||
|
|
||||||
|
|
||||||
|
# Product search and filtering
|
||||||
|
class ProductFilterParams(BaseModel):
|
||||||
|
name: Optional[str] = None
|
||||||
|
category_id: Optional[int] = None
|
||||||
|
min_price: Optional[float] = None
|
||||||
|
max_price: Optional[float] = None
|
||||||
|
in_stock: Optional[bool] = None
|
33
app/schemas/review.py
Normal file
33
app/schemas/review.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewBase(BaseModel):
|
||||||
|
product_id: int
|
||||||
|
rating: float = Field(..., ge=1, le=5)
|
||||||
|
comment: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewCreate(ReviewBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewUpdate(BaseModel):
|
||||||
|
rating: Optional[float] = Field(None, ge=1, le=5)
|
||||||
|
comment: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewInDBBase(ReviewBase):
|
||||||
|
id: int
|
||||||
|
user_id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class Review(ReviewInDBBase):
|
||||||
|
pass
|
@ -5,9 +5,8 @@ Revises:
|
|||||||
Create Date: 2023-10-30 00:00:00.000000
|
Create Date: 2023-10-30 00:00:00.000000
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from alembic import op
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
# revision identifiers, used by Alembic.
|
# revision identifiers, used by Alembic.
|
||||||
revision = '9a4f22e84e75'
|
revision = '9a4f22e84e75'
|
||||||
@ -149,29 +148,29 @@ def downgrade() -> None:
|
|||||||
# Drop all tables in reverse order
|
# Drop all tables in reverse order
|
||||||
op.drop_index(op.f('ix_reviews_id'), table_name='reviews')
|
op.drop_index(op.f('ix_reviews_id'), table_name='reviews')
|
||||||
op.drop_table('reviews')
|
op.drop_table('reviews')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_order_items_id'), table_name='order_items')
|
op.drop_index(op.f('ix_order_items_id'), table_name='order_items')
|
||||||
op.drop_table('order_items')
|
op.drop_table('order_items')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_orders_id'), table_name='orders')
|
op.drop_index(op.f('ix_orders_id'), table_name='orders')
|
||||||
op.drop_table('orders')
|
op.drop_table('orders')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_cart_items_id'), table_name='cart_items')
|
op.drop_index(op.f('ix_cart_items_id'), table_name='cart_items')
|
||||||
op.drop_table('cart_items')
|
op.drop_table('cart_items')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_carts_id'), table_name='carts')
|
op.drop_index(op.f('ix_carts_id'), table_name='carts')
|
||||||
op.drop_table('carts')
|
op.drop_table('carts')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_products_name'), table_name='products')
|
op.drop_index(op.f('ix_products_name'), table_name='products')
|
||||||
op.drop_index(op.f('ix_products_id'), table_name='products')
|
op.drop_index(op.f('ix_products_id'), table_name='products')
|
||||||
op.drop_table('products')
|
op.drop_table('products')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_categories_name'), table_name='categories')
|
op.drop_index(op.f('ix_categories_name'), table_name='categories')
|
||||||
op.drop_index(op.f('ix_categories_id'), table_name='categories')
|
op.drop_index(op.f('ix_categories_id'), table_name='categories')
|
||||||
op.drop_table('categories')
|
op.drop_table('categories')
|
||||||
|
|
||||||
op.drop_index(op.f('ix_users_full_name'), table_name='users')
|
op.drop_index(op.f('ix_users_full_name'), table_name='users')
|
||||||
op.drop_index(op.f('ix_users_username'), table_name='users')
|
op.drop_index(op.f('ix_users_username'), table_name='users')
|
||||||
op.drop_index(op.f('ix_users_email'), table_name='users')
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||||
op.drop_index(op.f('ix_users_id'), table_name='users')
|
op.drop_index(op.f('ix_users_id'), table_name='users')
|
||||||
op.drop_table('users')
|
op.drop_table('users')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user