diff --git a/BakeryBackend/__pycache__/__init__.cpython-313.pyc b/BakeryBackend/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..b246d75 Binary files /dev/null and b/BakeryBackend/__pycache__/__init__.cpython-313.pyc differ diff --git a/BakeryBackend/__pycache__/database.cpython-313.pyc b/BakeryBackend/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000..00a4be3 Binary files /dev/null and b/BakeryBackend/__pycache__/database.cpython-313.pyc differ diff --git a/BakeryBackend/__pycache__/main.cpython-313.pyc b/BakeryBackend/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..81f333a Binary files /dev/null and b/BakeryBackend/__pycache__/main.cpython-313.pyc differ diff --git a/BakeryBackend/__pycache__/models.cpython-313.pyc b/BakeryBackend/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000..3661da1 Binary files /dev/null and b/BakeryBackend/__pycache__/models.cpython-313.pyc differ diff --git a/BakeryBackend/__pycache__/schemas.cpython-313.pyc b/BakeryBackend/__pycache__/schemas.cpython-313.pyc new file mode 100644 index 0000000..851af0b Binary files /dev/null and b/BakeryBackend/__pycache__/schemas.cpython-313.pyc differ diff --git a/BakeryBackend/favorites.db b/BakeryBackend/favorites.db index 015fd2e..7bc4450 100644 Binary files a/BakeryBackend/favorites.db and b/BakeryBackend/favorites.db differ diff --git a/BakeryBackend/main.py b/BakeryBackend/main.py index cdd506e..a7718d7 100644 --- a/BakeryBackend/main.py +++ b/BakeryBackend/main.py @@ -1,11 +1,29 @@ from fastapi import FastAPI -from BakeryBackend.routers import favorites +from BakeryBackend.routers import favorites, categories, items from BakeryBackend.database import Base, engine +# Create all tables Base.metadata.create_all(bind=engine) -app = FastAPI(title="Bakery Backend with Favorites") +app = FastAPI( + title="Enhanced Bakery Backend API", + description="Complete bakery management system with categories, items, and favorites", + version="2.0.0" +) +# Include all routers +app.include_router(categories.router) +app.include_router(items.router) app.include_router(favorites.router) - +@app.get("/") +def root(): + return { + "message": "Enhanced Bakery Backend API", + "version": "2.0.0", + "endpoints": { + "categories": "/categories", + "items": "/items", + "favorites": "/favorites" + } + } \ No newline at end of file diff --git a/BakeryBackend/models.py b/BakeryBackend/models.py index 6bb267b..90b205b 100644 --- a/BakeryBackend/models.py +++ b/BakeryBackend/models.py @@ -1,10 +1,46 @@ -from sqlalchemy import Column, Integer, String +from sqlalchemy import Column, Integer, String, Float, Boolean, ForeignKey, DateTime, Text +from sqlalchemy.orm import relationship +from datetime import datetime from BakeryBackend.database import Base +class Category(Base): + __tablename__ = "categories" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String(100), unique=True, nullable=False, index=True) + description = Column(Text, nullable=True) + is_active = Column(Boolean, default=True) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + items = relationship("Item", back_populates="category") + +class Item(Base): + __tablename__ = "items" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String(200), nullable=False, index=True) + description = Column(Text, nullable=True) + price = Column(Float, nullable=False) + category_id = Column(Integer, ForeignKey("categories.id"), nullable=False) + image_url = Column(String(500), nullable=True) + is_available = Column(Boolean, default=True) + stock_quantity = Column(Integer, default=0) + ingredients = Column(Text, nullable=True) + allergens = Column(String(500), nullable=True) + preparation_time = Column(Integer, nullable=True) # in minutes + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + category = relationship("Category", back_populates="items") + favorites = relationship("Favorite", back_populates="item") + class Favorite(Base): __tablename__ = "favorites" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, index=True) - item_id = Column(Integer, index=True) - item_name = Column(String, nullable=False) + item_id = Column(Integer, ForeignKey("items.id"), nullable=False) + created_at = Column(DateTime, default=datetime.utcnow) + + item = relationship("Item", back_populates="favorites") \ No newline at end of file diff --git a/BakeryBackend/routers/__pycache__/__init__.cpython-313.pyc b/BakeryBackend/routers/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..666b48a Binary files /dev/null and b/BakeryBackend/routers/__pycache__/__init__.cpython-313.pyc differ diff --git a/BakeryBackend/routers/__pycache__/categories.cpython-313.pyc b/BakeryBackend/routers/__pycache__/categories.cpython-313.pyc new file mode 100644 index 0000000..5599204 Binary files /dev/null and b/BakeryBackend/routers/__pycache__/categories.cpython-313.pyc differ diff --git a/BakeryBackend/routers/__pycache__/favorites.cpython-313.pyc b/BakeryBackend/routers/__pycache__/favorites.cpython-313.pyc new file mode 100644 index 0000000..c7da971 Binary files /dev/null and b/BakeryBackend/routers/__pycache__/favorites.cpython-313.pyc differ diff --git a/BakeryBackend/routers/__pycache__/items.cpython-313.pyc b/BakeryBackend/routers/__pycache__/items.cpython-313.pyc new file mode 100644 index 0000000..436a3d4 Binary files /dev/null and b/BakeryBackend/routers/__pycache__/items.cpython-313.pyc differ diff --git a/BakeryBackend/routers/categories.py b/BakeryBackend/routers/categories.py new file mode 100644 index 0000000..59e1c08 --- /dev/null +++ b/BakeryBackend/routers/categories.py @@ -0,0 +1,67 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from sqlalchemy.exc import IntegrityError +from typing import List +from BakeryBackend.database import get_db +from BakeryBackend.models import Category as CategoryModel +from BakeryBackend.schemas import Category, CategoryCreate, CategoryUpdate + +router = APIRouter(prefix="/categories", tags=["Categories"]) + +@router.post("/", response_model=Category) +def create_category(category: CategoryCreate, db: Session = Depends(get_db)): + try: + db_category = CategoryModel(**category.dict()) + db.add(db_category) + db.commit() + db.refresh(db_category) + return db_category + except IntegrityError: + raise HTTPException(status_code=409, detail="Category with this name already exists") + +@router.get("/", response_model=List[Category]) +def get_categories(skip: int = 0, limit: int = 100, active_only: bool = True, db: Session = Depends(get_db)): + query = db.query(CategoryModel) + if active_only: + query = query.filter(CategoryModel.is_active == True) + return query.offset(skip).limit(limit).all() + +@router.get("/{category_id}", response_model=Category) +def get_category(category_id: int, db: Session = Depends(get_db)): + category = db.query(CategoryModel).filter(CategoryModel.id == category_id).first() + if not category: + raise HTTPException(status_code=404, detail="Category not found") + return category + +@router.put("/{category_id}", response_model=Category) +def update_category(category_id: int, category_update: CategoryUpdate, db: Session = Depends(get_db)): + category = db.query(CategoryModel).filter(CategoryModel.id == category_id).first() + if not category: + raise HTTPException(status_code=404, detail="Category not found") + + update_data = category_update.dict(exclude_unset=True) + if update_data: + for field, value in update_data.items(): + setattr(category, field, value) + + try: + db.commit() + db.refresh(category) + except IntegrityError: + raise HTTPException(status_code=409, detail="Category with this name already exists") + + return category + +@router.delete("/{category_id}") +def delete_category(category_id: int, db: Session = Depends(get_db)): + category = db.query(CategoryModel).filter(CategoryModel.id == category_id).first() + if not category: + raise HTTPException(status_code=404, detail="Category not found") + + # Check if category has items + if category.items: + raise HTTPException(status_code=409, detail="Cannot delete category with existing items") + + db.delete(category) + db.commit() + return {"message": "Category deleted successfully"} \ No newline at end of file diff --git a/BakeryBackend/routers/favorites.py b/BakeryBackend/routers/favorites.py index 71e8795..5a2b2c0 100644 --- a/BakeryBackend/routers/favorites.py +++ b/BakeryBackend/routers/favorites.py @@ -2,25 +2,29 @@ from sqlalchemy.orm import Session from typing import List from BakeryBackend.database import get_db -from BakeryBackend.models import Favorite as FavoriteModel -from BakeryBackend.schemas import Favorite +from BakeryBackend.models import Favorite as FavoriteModel, Item as ItemModel +from BakeryBackend.schemas import Favorite, FavoriteCreate router = APIRouter(prefix="/favorites", tags=["Favorites"]) - @router.post("/", response_model=Favorite) -def add_favorite(favorite: Favorite, db: Session = Depends(get_db)): - # Duplicate detection +def add_favorite(favorite: FavoriteCreate, db: Session = Depends(get_db)): + # Check if item exists + item = db.query(ItemModel).filter(ItemModel.id == favorite.item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + + # Check for duplicate existing = db.query(FavoriteModel).filter( FavoriteModel.user_id == favorite.user_id, FavoriteModel.item_id == favorite.item_id ).first() if existing: raise HTTPException(status_code=409, detail="Favorite already exists for this user and item.") + db_fav = FavoriteModel( user_id=favorite.user_id, - item_id=favorite.item_id, - item_name=favorite.item_name + item_id=favorite.item_id ) db.add(db_fav) db.commit() @@ -41,4 +45,18 @@ def delete_favorite(favorite_id: int, user_id: int, db: Session = Depends(get_db raise HTTPException(status_code=403, detail="Not authorized to delete this favorite.") db.delete(fav) db.commit() - return {"message": "Favorite deleted successfully"} \ No newline at end of file + return {"message": "Favorite deleted successfully"} + +@router.delete("/user/{user_id}/item/{item_id}") +def remove_favorite_by_item(user_id: int, item_id: int, db: Session = Depends(get_db)): + """Remove favorite by user_id and item_id (alternative to delete by favorite_id)""" + fav = db.query(FavoriteModel).filter( + FavoriteModel.user_id == user_id, + FavoriteModel.item_id == item_id + ).first() + if not fav: + raise HTTPException(status_code=404, detail="Favorite not found") + + db.delete(fav) + db.commit() + return {"message": "Favorite removed successfully"} \ No newline at end of file diff --git a/BakeryBackend/routers/items.py b/BakeryBackend/routers/items.py new file mode 100644 index 0000000..04e7aca --- /dev/null +++ b/BakeryBackend/routers/items.py @@ -0,0 +1,108 @@ +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.orm import Session +from typing import List, Optional +from BakeryBackend.database import get_db +from BakeryBackend.models import Item as ItemModel, Category as CategoryModel +from BakeryBackend.schemas import Item, ItemCreate, ItemUpdate + +router = APIRouter(prefix="/items", tags=["Items"]) + +@router.post("/", response_model=Item) +def create_item(item: ItemCreate, db: Session = Depends(get_db)): + # Check if category exists + category = db.query(CategoryModel).filter(CategoryModel.id == item.category_id).first() + if not category: + raise HTTPException(status_code=404, detail="Category not found") + + db_item = ItemModel(**item.dict()) + db.add(db_item) + db.commit() + db.refresh(db_item) + return db_item + +@router.get("/", response_model=List[Item]) +def get_items( + skip: int = 0, + limit: int = 100, + category_id: Optional[int] = None, + available_only: bool = True, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + db: Session = Depends(get_db) +): + query = db.query(ItemModel) + + if available_only: + query = query.filter(ItemModel.is_available == True) + if category_id: + query = query.filter(ItemModel.category_id == category_id) + if min_price is not None: + query = query.filter(ItemModel.price >= min_price) + if max_price is not None: + query = query.filter(ItemModel.price <= max_price) + + return query.offset(skip).limit(limit).all() + +@router.get("/{item_id}", response_model=Item) +def get_item(item_id: int, db: Session = Depends(get_db)): + item = db.query(ItemModel).filter(ItemModel.id == item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + return item + +@router.put("/{item_id}", response_model=Item) +def update_item(item_id: int, item_update: ItemUpdate, db: Session = Depends(get_db)): + item = db.query(ItemModel).filter(ItemModel.id == item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + + update_data = item_update.dict(exclude_unset=True) + + # Check if category exists when updating category_id + if 'category_id' in update_data: + category = db.query(CategoryModel).filter(CategoryModel.id == update_data['category_id']).first() + if not category: + raise HTTPException(status_code=404, detail="Category not found") + + if update_data: + for field, value in update_data.items(): + setattr(item, field, value) + db.commit() + db.refresh(item) + + return item + +@router.delete("/{item_id}") +def delete_item(item_id: int, db: Session = Depends(get_db)): + item = db.query(ItemModel).filter(ItemModel.id == item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + + db.delete(item) + db.commit() + return {"message": "Item deleted successfully"} + +@router.patch("/{item_id}/availability") +def toggle_availability(item_id: int, available: bool, db: Session = Depends(get_db)): + item = db.query(ItemModel).filter(ItemModel.id == item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + + item.is_available = available + db.commit() + db.refresh(item) + return {"message": f"Item availability updated to {available}"} + +@router.patch("/{item_id}/stock") +def update_stock(item_id: int, quantity: int, db: Session = Depends(get_db)): + if quantity < 0: + raise HTTPException(status_code=400, detail="Stock quantity cannot be negative") + + item = db.query(ItemModel).filter(ItemModel.id == item_id).first() + if not item: + raise HTTPException(status_code=404, detail="Item not found") + + item.stock_quantity = quantity + db.commit() + db.refresh(item) + return {"message": f"Stock updated to {quantity}"} \ No newline at end of file diff --git a/BakeryBackend/schemas.py b/BakeryBackend/schemas.py index d603cc9..d800b5c 100644 --- a/BakeryBackend/schemas.py +++ b/BakeryBackend/schemas.py @@ -1,8 +1,78 @@ -from pydantic import BaseModel, constr -from typing import Optional +from pydantic import BaseModel, constr, Field +from typing import Optional, List +from datetime import datetime -class Favorite(BaseModel): - id: Optional[int] = None # Optional, will be filled when returning data +# Category Schemas +class CategoryBase(BaseModel): + name: constr(min_length=1, max_length=100, strip_whitespace=True) + description: Optional[str] = None + is_active: bool = True + +class CategoryCreate(CategoryBase): + pass + +class CategoryUpdate(BaseModel): + name: Optional[constr(min_length=1, max_length=100, strip_whitespace=True)] = None + description: Optional[str] = None + is_active: Optional[bool] = None + +class Category(CategoryBase): + id: int + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True + +# Item Schemas +class ItemBase(BaseModel): + name: constr(min_length=1, max_length=200, strip_whitespace=True) + description: Optional[str] = None + price: float = Field(..., gt=0) + category_id: int + image_url: Optional[str] = None + is_available: bool = True + stock_quantity: int = Field(default=0, ge=0) + ingredients: Optional[str] = None + allergens: Optional[str] = None + preparation_time: Optional[int] = Field(None, gt=0) + +class ItemCreate(ItemBase): + pass + +class ItemUpdate(BaseModel): + name: Optional[constr(min_length=1, max_length=200, strip_whitespace=True)] = None + description: Optional[str] = None + price: Optional[float] = Field(None, gt=0) + category_id: Optional[int] = None + image_url: Optional[str] = None + is_available: Optional[bool] = None + stock_quantity: Optional[int] = Field(None, ge=0) + ingredients: Optional[str] = None + allergens: Optional[str] = None + preparation_time: Optional[int] = Field(None, gt=0) + +class Item(ItemBase): + id: int + created_at: datetime + updated_at: datetime + category: Category + + class Config: + from_attributes = True + +# Favorite Schemas +class FavoriteBase(BaseModel): user_id: int item_id: int - item_name: constr(min_length=1, strip_whitespace=True) + +class FavoriteCreate(FavoriteBase): + pass + +class Favorite(FavoriteBase): + id: int + created_at: datetime + item: Item + + class Config: + from_attributes = True \ No newline at end of file