From e90970adca67508cadef9cbc6c9f30a6f9babc5b Mon Sep 17 00:00:00 2001 From: nd2-trung <Nguyen5.Trung@live.uwe.ac.uk> Date: Wed, 12 Mar 2025 12:57:07 +0700 Subject: [PATCH] add CRUD category, product --- app/backend/main.py | 5 +- app/backend/routes/category.py | 56 ++++++++++++ app/backend/routes/product.py | 153 ++++++++++++++++++++++++++++++++ app/backend/routes/shop.py | 7 +- app/backend/schemas/category.py | 18 ++++ app/backend/schemas/product.py | 50 +++++++++++ requirements.txt | 4 +- 7 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 app/backend/routes/category.py create mode 100644 app/backend/routes/product.py create mode 100644 app/backend/schemas/category.py create mode 100644 app/backend/schemas/product.py diff --git a/app/backend/main.py b/app/backend/main.py index 34d7e88..f903ab4 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 +from backend.routes import auth, shop, product, category from backend.database import init_db from core.config import settings @@ -16,6 +16,9 @@ init_db() # Include API routes app.include_router(auth.router, prefix="/auth", tags=["auth"]) 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.get("/") diff --git a/app/backend/routes/category.py b/app/backend/routes/category.py new file mode 100644 index 0000000..7fbf3f3 --- /dev/null +++ b/app/backend/routes/category.py @@ -0,0 +1,56 @@ +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.database import get_session + +router = APIRouter() + + +@router.post("/", response_model=CategoryRead) +def create_category( + name: str = Form(...), + session: Session = Depends(get_session), +): + category = Category(name=name) + session.add(category) + session.commit() + session.refresh(category) + return category + + +@router.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: + raise HTTPException(status_code=404, detail="Category not found") + return category + + +@router.put("/{category_id}", response_model=CategoryRead) +def update_category( + category_id: int, + name: str = Form(None), + session: Session = Depends(get_session), +): + 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)): + 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 diff --git a/app/backend/routes/product.py b/app/backend/routes/product.py new file mode 100644 index 0000000..4ccdeb6 --- /dev/null +++ b/app/backend/routes/product.py @@ -0,0 +1,153 @@ +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form +from sqlmodel import Session +from datetime import datetime +from backend.models.models import Product, ProductImage, User +from backend.schemas.product import ProductRead, ProductImageRead +from backend.database import get_session +from backend.routes.auth import get_current_user +import shutil +import os + +router = APIRouter() + +static_dir = os.path.join("app", "static") +os.makedirs(static_dir, exist_ok=True) + + +@router.post("/", response_model=ProductRead) +def create_product( + name: str = Form(...), + description: str = Form(None), + price: float = Form(...), + stock: int = Form(...), + shop_id: int = Form(...), + category_id: int = Form(None), + images: UploadFile = File(None), # Ensuring image is correctly set + session: Session = Depends(get_session), +): + product = Product( + name=name, + description=description, + price=price, + stock=stock, + shop_id=shop_id, + category_id=category_id, + created_at=datetime.utcnow(), + ) + session.add(product) + session.commit() + session.refresh(product) + + # Handling image upload + if images and images.filename: + file_ext = os.path.splitext(images.filename)[1] # Get file extension + file_name = f"product_{product.id}{file_ext}" + file_location = os.path.join(static_dir, file_name) + + with open(file_location, "wb") as buffer: + shutil.copyfileobj(images.file, buffer) + + # Save image record in ProductImage + product_image = ProductImage(product_id=product.id, image_url=file_location) + session.add(product_image) + session.commit() + + return product + + +@router.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: + raise HTTPException(status_code=404, detail="Product not found") + return product + + +@router.put("/{product_id}", response_model=ProductRead) +def update_product( + product_id: int, + name: str = Form(None), + description: str = Form(None), + price: float = Form(None), + stock: int = Form(None), + category_id: int = Form(None), + file: UploadFile = File(None), + session: Session = Depends(get_session), +): + 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 + + session.add(product) + session.commit() + session.refresh(product) + + if file and file.filename: + file_location = os.path.join(static_dir, f"product_{product.id}_{file.filename}") + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + image = ProductImage(product_id=product.id, image_url=file_location) + session.add(image) + session.commit() + + return product + + +@router.delete("/{product_id}") +def delete_product(product_id: int, session: Session = Depends(get_session)): + product = session.get(Product, product_id) + if not product: + raise HTTPException(status_code=404, detail="Product not found") + session.delete(product) + session.commit() + return {"message": "Product deleted successfully"} + + +@router.post("/ProductImage/", response_model=ProductImageRead) +def upload_product_image( + product_id: int = Form(...), + file: UploadFile = File(...), + session: Session = Depends(get_session), +): + product = session.get(Product, product_id) + if not product: + raise HTTPException(status_code=404, detail="Product not found") + + file_location = os.path.join(static_dir, f"product_{product.id}_{file.filename}") + with open(file_location, "wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + image = ProductImage(product_id=product.id, image_url=file_location) + session.add(image) + session.commit() + return image + + +@router.get("/ProductImage/{product_id}", response_model=list[ProductImageRead]) +def get_product_images(product_id: int, session: Session = Depends(get_session)): + images = session.query(ProductImage).filter(ProductImage.product_id == product_id).all() + if not images: + raise HTTPException(status_code=404, detail="No images found for this product") + return images + + +@router.delete("/ProductImage/{image_id}") +def delete_product_image(image_id: int, session: Session = Depends(get_session)): + image = session.get(ProductImage, image_id) + if not image: + raise HTTPException(status_code=404, detail="Image not found") + session.delete(image) + session.commit() + return {"message": "Product image deleted successfully"} diff --git a/app/backend/routes/shop.py b/app/backend/routes/shop.py index 7775a1c..ffea6d9 100644 --- a/app/backend/routes/shop.py +++ b/app/backend/routes/shop.py @@ -19,9 +19,12 @@ def create_shop( description: str = Form(None), file: UploadFile = File(None), session: Session = Depends(get_session), - current_user: User = Depends(get_current_user), + # 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=current_user.id) + shop = ShopCreate(name=name, description=description, owner_id=owner_id) db_shop = Shop.from_orm(shop) if file and file.filename: diff --git a/app/backend/schemas/category.py b/app/backend/schemas/category.py new file mode 100644 index 0000000..976870f --- /dev/null +++ b/app/backend/schemas/category.py @@ -0,0 +1,18 @@ +from sqlmodel import SQLModel +from typing import Optional + + +class CategoryBase(SQLModel): + name: str + + +class CategoryCreate(CategoryBase): + pass + + +class CategoryRead(CategoryBase): + id: int + + +class CategoryUpdate(SQLModel): + name: Optional[str] = None \ No newline at end of file diff --git a/app/backend/schemas/product.py b/app/backend/schemas/product.py new file mode 100644 index 0000000..885907f --- /dev/null +++ b/app/backend/schemas/product.py @@ -0,0 +1,50 @@ +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime + + +class ProductBase(BaseModel): + shop_id: int + category_id: Optional[int] = None + name: str + description: Optional[str] = None + price: float + stock: int + + +class ProductCreate(ProductBase): + pass + + +class ProductRead(ProductBase): + id: int + created_at: datetime + images: List["ProductImageRead"] = [] + + class Config: + orm_mode = True + + +class ProductUpdate(BaseModel): + shop_id: Optional[int] = None + category_id: Optional[int] = None + name: Optional[str] = None + description: Optional[str] = None + price: Optional[float] = None + stock: Optional[int] = None + + +class ProductImageBase(BaseModel): + product_id: int + image_url: str + + +class ProductImageCreate(ProductImageBase): + pass + + +class ProductImageRead(ProductImageBase): + id: int + + class Config: + orm_mode = True diff --git a/requirements.txt b/requirements.txt index 7dfdd9c..c8b7950 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,6 +6,7 @@ cffi==1.17.1 charset-normalizer==3.4.1 click==8.1.8 colorama==0.4.6 +cryptography==44.0.2 dnspython==2.7.0 email_validator==2.2.0 fastapi==0.115.11 @@ -31,6 +32,7 @@ pydantic-extra-types==2.10.2 pydantic-settings==2.8.1 pydantic_core==2.27.2 Pygments==2.19.1 +PyJWT==2.10.1 PyMySQL==1.1.1 python-dotenv==1.0.1 python-multipart==0.0.20 @@ -50,4 +52,4 @@ ujson==5.10.0 urllib3==2.3.0 uvicorn==0.34.0 watchfiles==1.0.4 -websockets==15.0 +websockets==15.0 \ No newline at end of file -- GitLab