diff --git a/app/backend/main.py b/app/backend/main.py index 08bff47984a3d59cc410fe43ca28331e39838e55..658f0cadb39adc6136f2dbdb2bd3ab70be22cf8e 100644 --- a/app/backend/main.py +++ b/app/backend/main.py @@ -4,7 +4,7 @@ import os sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from fastapi import FastAPI -from backend.routes import auth, shop, product, category, search, order, orderitem +from backend.routes import auth, shop, product, category, search, order from backend.database import init_db from core.config import settings @@ -20,7 +20,6 @@ app.include_router(shop.router, prefix="/shops", tags=["shops"]) app.include_router(product.router, prefix="/product", tags=["product"]) app.include_router(category.router, prefix="/category", tags=["category"]) app.include_router(order.router, prefix="/order", tags=["order"]) -app.include_router(orderitem.router, prefix="/orderitem", tags=["orderitem"]) @app.get("/") diff --git a/app/backend/routes/category.py b/app/backend/routes/category.py index 7fbf3f34b0d32e48f4ee2781cf46601ac3dbbc22..5aa3a5a1b47327075a824f494e3254bfc3eacdc7 100644 --- a/app/backend/routes/category.py +++ b/app/backend/routes/category.py @@ -1,17 +1,28 @@ from fastapi import APIRouter, Depends, HTTPException, Form from sqlmodel import Session -from backend.models.models import Category -from backend.schemas.category import CategoryRead +from backend.models.models import Category, User +from backend.schemas.category import CategoryRead from backend.database import get_session +from backend.routes.auth import get_current_user router = APIRouter() -@router.post("/", response_model=CategoryRead) +def verify_admin(current_user: User): + if current_user.role != "admin": + raise HTTPException( + status_code=403, detail="Unauthorized. Admin access required." + ) + + +@router.post("/create", response_model=CategoryRead) def create_category( name: str = Form(...), session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), ): + verify_admin(current_user) + category = Category(name=name) session.add(category) session.commit() @@ -19,7 +30,7 @@ def create_category( return category -@router.get("/{category_id}", response_model=CategoryRead) +@router.get("/get/{category_id}", response_model=CategoryRead) def read_category(category_id: int, session: Session = Depends(get_session)): category = session.get(Category, category_id) if not category: @@ -27,30 +38,38 @@ def read_category(category_id: int, session: Session = Depends(get_session)): return category -@router.put("/{category_id}", response_model=CategoryRead) +@router.put("/put/{category_id}", response_model=CategoryRead) def update_category( category_id: int, name: str = Form(None), session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), ): + verify_admin(current_user) category = session.get(Category, category_id) if not category: raise HTTPException(status_code=404, detail="Category not found") if name: category.name = name - + session.add(category) session.commit() session.refresh(category) return category -@router.delete("/{category_id}") -def delete_category(category_id: int, session: Session = Depends(get_session)): +@router.delete("/delete/{category_id}") +def delete_category( + category_id: int, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): + verify_admin(current_user) + category = session.get(Category, category_id) if not category: raise HTTPException(status_code=404, detail="Category not found") session.delete(category) session.commit() - return {"message": "Category deleted successfully"} \ No newline at end of file + return {"message": "Category deleted successfully"} diff --git a/app/backend/routes/order.py b/app/backend/routes/order.py index 388a0c1fd47afaede3d88c13b1ccd24cd28d8d36..e5e643f52ef9caec8a1d657f9841b6e4fbc1deee 100644 --- a/app/backend/routes/order.py +++ b/app/backend/routes/order.py @@ -1,68 +1,112 @@ from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import Session, select -from datetime import datetime -from backend.models.models import Order, OrderItem -from backend.schemas.order import OrderRead, OrderCreate, OrderUpdate, OrderList, OrderWithItems +from sqlmodel import Session, select, delete from backend.database import get_session +from backend.routes.auth import get_current_user +from backend.models.models import Order, OrderItem, User, Product +from backend.schemas.order import OrderCreate, OrderRead router = APIRouter() + @router.post("/", response_model=OrderRead) -def create_order(order: OrderCreate, session: Session = Depends(get_session)): +def create_order( + order_data: OrderCreate, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): + total_price = 0 + for item in order_data.items: + product = session.get(Product, item.product_id) + if not product or product.stock < item.quantity: + raise HTTPException( + status_code=400, detail=f"Product {item.product_id} is out of stock" + ) + total_price += item.quantity * product.price + new_order = Order( - user_id=1, # Cần cập nhật logic lấy user từ authentication - shop_id=order.shop_id, - total_price=order.total_price, - status="pending", - created_at=datetime.utcnow(), + user_id=current_user.id, shop_id=order_data.shop_id, total_price=total_price ) session.add(new_order) session.commit() session.refresh(new_order) + + for item in order_data.items: + order_item = OrderItem( + order_id=new_order.id, + product_id=item.product_id, + quantity=item.quantity, + price=product.price, + ) + session.add(order_item) + product.stock -= item.quantity + + session.commit() return new_order -@router.get("/{order_id}", response_model=OrderWithItems) -def read_order(order_id: int, session: Session = Depends(get_session)): + +# retrieve a specific order, only the owner can access +@router.get("/{order_id}", response_model=OrderRead) +def get_order( + order_id: int, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): order = session.get(Order, order_id) - if not order: + if not order or order.user_id != current_user.id: raise HTTPException(status_code=404, detail="Order not found") return order -@router.get("/user/{user_id}", response_model=OrderList) -def list_user_orders(user_id: int, session: Session = Depends(get_session)): - orders = session.exec(select(Order).where(Order.user_id == user_id)).all() - return {"orders": orders} -@router.get("/", response_model=OrderList) -def list_all_orders(session: Session = Depends(get_session)): - orders = session.exec(select(Order)).all() - return {"orders": orders} +# list all orders associated with the current user +@router.get("/list", response_model=list[OrderRead]) +def list_orders( + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): + orders = session.exec(select(Order).where(Order.user_id == current_user.id)).all() + return orders -@router.get("/{order_id}/calculate-total", response_model=float) -def calculate_total_price(order_id: int, session: Session = Depends(get_session)): - order_items = session.exec(select(OrderItem).where(OrderItem.order_id == order_id)).all() - total_price = sum(item.price * item.quantity for item in order_items) - return total_price -@router.put("/{order_id}", response_model=OrderRead) -def update_order(order_id: int, order_update: OrderUpdate, session: Session = Depends(get_session)): +# update order status +@router.put("/status/{order_id}", response_model=OrderRead) +def update_order( + order_id: int, + status: str, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): order = session.get(Order, order_id) - if not order: - raise HTTPException(status_code=404, detail="Order not found") - - for key, value in order_update.dict(exclude_unset=True).items(): - setattr(order, key, value) - - session.add(order) + if not order or order.user_id != current_user.id: + raise HTTPException(status_code=404, detail="Order not found or unauthorized") + if order.status != "pending" or status != "completed": + raise HTTPException( + status_code=400, + detail="Order can only be updated from pending to completed", + ) + order.status = status session.commit() session.refresh(order) return order + +# Delete order @router.delete("/{order_id}") -def delete_order(order_id: int, session: Session = Depends(get_session)): +def delete_order( + order_id: int, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): order = session.get(Order, order_id) - if not order: - raise HTTPException(status_code=404, detail="Order not found") + if not order or ( + order.user_id != current_user.id and current_user.role != "shop_owner" + ): + raise HTTPException(status_code=404, detail="Order not found or unauthorized") + + # Delete related order items first + session.exec(delete(OrderItem).where(OrderItem.order_id == order.id)) + + # Now delete the order session.delete(order) session.commit() + return {"message": "Order deleted successfully"} diff --git a/app/backend/routes/orderitem.py b/app/backend/routes/orderitem.py deleted file mode 100644 index e76ccacd4416e69e3c8ca9be7709c165d11d1916..0000000000000000000000000000000000000000 --- a/app/backend/routes/orderitem.py +++ /dev/null @@ -1,57 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException -from sqlmodel import Session, select -from backend.models.models import OrderItem -from backend.schemas.orderitem import OrderItemRead, OrderItemCreate, OrderItemUpdate, OrderItemList -from backend.database import get_session - -router = APIRouter() - -@router.post("/", response_model=OrderItemRead) -def create_order_item(order_item: OrderItemCreate, session: Session = Depends(get_session)): - new_order_item = OrderItem(**order_item.dict()) - session.add(new_order_item) - session.commit() - session.refresh(new_order_item) - return new_order_item - -@router.get("/{order_item_id}", response_model=OrderItemRead) -def read_order_item(order_item_id: int, session: Session = Depends(get_session)): - order_item = session.get(OrderItem, order_item_id) - if not order_item: - raise HTTPException(status_code=404, detail="Order item not found") - return order_item - -@router.get("/order/{order_id}", response_model=OrderItemList) -def list_order_items(order_id: int, session: Session = Depends(get_session)): - items = session.exec(select(OrderItem).where(OrderItem.order_id == order_id)).all() - return {"items": items} - -@router.get("/{order_item_id}/subtotal", response_model=float) -def calculate_item_subtotal(order_item_id: int, session: Session = Depends(get_session)): - order_item = session.get(OrderItem, order_item_id) - if not order_item: - raise HTTPException(status_code=404, detail="Order item not found") - return order_item.price * order_item.quantity - -@router.put("/{order_item_id}", response_model=OrderItemRead) -def update_order_item(order_item_id: int, order_item_update: OrderItemUpdate, session: Session = Depends(get_session)): - order_item = session.get(OrderItem, order_item_id) - if not order_item: - raise HTTPException(status_code=404, detail="Order item not found") - - for key, value in order_item_update.dict(exclude_unset=True).items(): - setattr(order_item, key, value) - - session.add(order_item) - session.commit() - session.refresh(order_item) - return order_item - -@router.delete("/{order_item_id}") -def delete_order_item(order_item_id: int, session: Session = Depends(get_session)): - order_item = session.get(OrderItem, order_item_id) - if not order_item: - raise HTTPException(status_code=404, detail="Order item not found") - session.delete(order_item) - session.commit() - return {"message": "Order item deleted successfully"} diff --git a/app/backend/routes/product.py b/app/backend/routes/product.py index 8c16bf78de6e50c0763fe3cb2f7cef1aaeb582b7..cadfba24acfe07e4b53d4151e7502f0492174415 100644 --- a/app/backend/routes/product.py +++ b/app/backend/routes/product.py @@ -15,7 +15,7 @@ static_dir = os.path.join("app", "static") os.makedirs(static_dir, exist_ok=True) -@router.post("/", response_model=ProductRead) +@router.post("/create", response_model=ProductRead) def create_product( name: str = Form(...), description: str = Form(None), @@ -25,6 +25,7 @@ def create_product( category_id: int = Form(None), images: list[UploadFile] = File(None), session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), ): product = Product( name=name, @@ -39,14 +40,15 @@ def create_product( session.commit() session.refresh(product) - # Get the shop to create the correct directory shop = session.get(Shop, shop_id) - if not shop: - raise HTTPException(status_code=404, detail="Shop not found") + if not shop or shop.owner_id != current_user.id: + raise HTTPException( + status_code=403, detail="Unauthorized to create product for this shop" + ) + # Directory structure: static/shop_{shop_name}/product_{product_name}/ shop_dir = os.path.join(settings.static_dir, f"shop_{shop.name}") os.makedirs(shop_dir, exist_ok=True) - product_dir = os.path.join(shop_dir, f"product_{product.name}") os.makedirs(product_dir, exist_ok=True) @@ -79,7 +81,7 @@ def read_all_products(session: Session = Depends(get_session)): return products -@router.get("/{product_id}", response_model=ProductRead) +@router.get("/get/{product_id}", response_model=ProductRead) def read_product(product_id: int, session: Session = Depends(get_session)): product = session.get(Product, product_id) if not product: @@ -87,7 +89,7 @@ def read_product(product_id: int, session: Session = Depends(get_session)): return product -@router.put("/{product_id}", response_model=ProductUpdate) +@router.put("/put/{product_id}", response_model=ProductUpdate) def update_product( product_id: int, name: str = Form(...), @@ -97,34 +99,39 @@ def update_product( category_id: int = Form(...), images: list[UploadFile] = File(...), session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), ): product = session.get(Product, product_id) - if not product: - raise HTTPException(status_code=404, detail="Product not found") - - # if name: - # product.name = name - # if description: - # product.description = description - # if price is not None: - # product.price = price - # if stock is not None: - # product.stock = stock - # if category_id is not None: - # product.category_id = category_id + if not product or product.shop.owner_id != current_user.id: + raise HTTPException( + status_code=403, detail="Unauthorized to update this product" + ) + + if name: + product.name = name + if description: + product.description = description + if price is not None: + product.price = price + if stock is not None: + product.stock = stock + if category_id is not None: + product.category_id = category_id session.add(product) session.commit() session.refresh(product) - # Get the shop to create the correct directory - shop = session.get(Shop, product.shop_id) - if not shop: - raise HTTPException(status_code=404, detail="Shop not found") + product = session.get(Product, product_id) + if not product or product.shop.owner_id != current_user.id: + raise HTTPException( + status_code=403, detail="Unauthorized to update this product" + ) + # Directory structure: static/shop_{shop_name}/product_{product_name}/ + shop = session.get(Shop, product.shop_id) shop_dir = os.path.join(settings.static_dir, f"shop_{shop.name}") os.makedirs(shop_dir, exist_ok=True) - product_dir = os.path.join(shop_dir, f"product_{product.name}") os.makedirs(product_dir, exist_ok=True) @@ -142,11 +149,18 @@ def update_product( return product -@router.delete("/{product_id}") -def delete_product(product_id: int, session: Session = Depends(get_session)): +@router.delete("/delete/{product_id}") +def delete_product( + product_id: int, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): product = session.get(Product, product_id) - if not product: - raise HTTPException(status_code=404, detail="Product not found") + if not product or product.shop.owner_id != current_user.id: + raise HTTPException( + status_code=403, detail="Unauthorized to delete this product" + ) + session.delete(product) session.commit() return {"message": "Product deleted successfully"} diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index 88088c11741124e2a0db9b3231e33e6dfe6ef644..643b8b2ef82f4cfa0d669dbf5ae004dcc5e125ee 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -11,17 +11,15 @@ import os router = APIRouter() -@router.post("/", response_model=ShopRead) +@router.post("/create", response_model=ShopRead) def create_shop( name: str = Form(...), description: str = Form(None), file: UploadFile = File(None), session: Session = Depends(get_session), current_user: User = Depends(get_current_user), - # owner_id=int, ): shop = ShopCreate(name=name, description=description, owner_id=current_user.id) - # shop = ShopCreate(name=name, description=description, owner_id=owner_id) db_shop = Shop.from_orm(shop) session.add(db_shop) @@ -50,7 +48,7 @@ def get_all_shops(session: Session = Depends(get_session)): return shops -@router.get("/{shop_id}", response_model=ShopRead) +@router.get("/get/{shop_id}", response_model=ShopRead) def read_shop(shop_id: int, session: Session = Depends(get_session)): shop = session.get(Shop, shop_id) if not shop: @@ -58,18 +56,23 @@ def read_shop(shop_id: int, session: Session = Depends(get_session)): return shop -@router.put("/{shop_id}", response_model=ShopRead) +@router.put("/put/{shop_id}", response_model=ShopRead) def update_shop( shop_id: int, name: str = Form(None), description: str = Form(None), file: UploadFile = File(None), session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), ): db_shop = session.get(Shop, shop_id) if not db_shop: raise HTTPException(status_code=404, detail="Shop not found") + # Ensure the current user is the shop owner + if db_shop.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Unauthorized to update this shop") + if name: db_shop.name = name if description: @@ -95,11 +98,20 @@ def update_shop( return db_shop -@router.delete("/{shop_id}") -def delete_shop(shop_id: int, session: Session = Depends(get_session)): +@router.delete("/delete/{shop_id}") +def delete_shop( + shop_id: int, + session: Session = Depends(get_session), + current_user: User = Depends(get_current_user), +): shop = session.get(Shop, shop_id) if not shop: raise HTTPException(status_code=404, detail="Shop not found") + + # Ensure the current user is the shop owner + if shop.owner_id != current_user.id: + raise HTTPException(status_code=403, detail="Unauthorized to delete this shop") + session.delete(shop) session.commit() return {"message": "Shop deleted successfully"} diff --git a/app/backend/schemas/order.py b/app/backend/schemas/order.py index e8b6940e2e443d5b1e2063de2ef76d033d64c1a2..37fdda72ec31777d18347c516714ac9275a14abb 100644 --- a/app/backend/schemas/order.py +++ b/app/backend/schemas/order.py @@ -1,31 +1,36 @@ from pydantic import BaseModel -from typing import Optional, List +from typing import List, Optional from datetime import datetime -from .orderitem import OrderItemRead -class OrderBase(BaseModel): + +class OrderItemCreate(BaseModel): + product_id: int + quantity: int + + +class OrderCreate(BaseModel): shop_id: int - total_price: float - status: Optional[str] = "pending" + items: List[OrderItemCreate] -class OrderCreate(OrderBase): - pass -class OrderRead(OrderBase): +class OrderItemRead(BaseModel): id: int - user_id: int - created_at: datetime + product_id: int + quantity: int + price: float class Config: - orm_mode = True + from_attributes = True -class OrderUpdate(BaseModel): - shop_id: Optional[int] = None - total_price: Optional[float] = None - status: Optional[str] = None -class OrderList(BaseModel): - orders: List[OrderRead] +class OrderRead(BaseModel): + id: int + user_id: int + shop_id: int + total_price: float + status: str + created_at: datetime + order_items: List[OrderItemRead] -class OrderWithItems(OrderRead): - order_items: List[OrderItemRead] = [] + class Config: + from_attributes = True diff --git a/app/backend/schemas/orderitem.py b/app/backend/schemas/orderitem.py deleted file mode 100644 index 591784ed298205ca8c94436d8f99c87363c5a07a..0000000000000000000000000000000000000000 --- a/app/backend/schemas/orderitem.py +++ /dev/null @@ -1,26 +0,0 @@ -from pydantic import BaseModel -from typing import Optional, List - -class OrderItemBase(BaseModel): - order_id: int - product_id: int - quantity: int - price: float - -class OrderItemCreate(OrderItemBase): - pass - -class OrderItemRead(OrderItemBase): - id: int - - class Config: - orm_mode = True - -class OrderItemUpdate(BaseModel): - order_id: Optional[int] = None - product_id: Optional[int] = None - quantity: Optional[int] = None - price: Optional[float] = None - -class OrderItemList(BaseModel): - items: List[OrderItemRead]