- add grocery category
- add Dockerfile
This commit is contained in:
28
backend/Dockerfile
Normal file
28
backend/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install Python dependencies
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create a non-root user
|
||||
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Command to run the application
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -31,13 +31,16 @@ def build_shopping_event_response(event: models.ShoppingEvent, db: Session) -> s
|
||||
text("""
|
||||
SELECT p.id, p.name, p.organic, p.weight, p.weight_unit,
|
||||
sep.amount, sep.price,
|
||||
g.id as grocery_id, g.name as grocery_name, g.category as grocery_category,
|
||||
g.id as grocery_id, g.name as grocery_name,
|
||||
g.created_at as grocery_created_at, g.updated_at as grocery_updated_at,
|
||||
gc.id as category_id, gc.name as category_name,
|
||||
gc.created_at as category_created_at, gc.updated_at as category_updated_at,
|
||||
b.id as brand_id, b.name as brand_name,
|
||||
b.created_at as brand_created_at, b.updated_at as brand_updated_at
|
||||
FROM products p
|
||||
JOIN shopping_event_products sep ON p.id = sep.product_id
|
||||
JOIN groceries g ON p.grocery_id = g.id
|
||||
JOIN grocery_categories gc ON g.category_id = gc.id
|
||||
LEFT JOIN brands b ON p.brand_id = b.id
|
||||
WHERE sep.shopping_event_id = :event_id
|
||||
"""),
|
||||
@@ -47,12 +50,20 @@ def build_shopping_event_response(event: models.ShoppingEvent, db: Session) -> s
|
||||
# Convert to ProductWithEventData objects
|
||||
products_with_data = []
|
||||
for row in product_data:
|
||||
category = schemas.GroceryCategory(
|
||||
id=row.category_id,
|
||||
name=row.category_name,
|
||||
created_at=row.category_created_at,
|
||||
updated_at=row.category_updated_at
|
||||
)
|
||||
|
||||
grocery = schemas.Grocery(
|
||||
id=row.grocery_id,
|
||||
name=row.grocery_name,
|
||||
category=row.grocery_category,
|
||||
category_id=row.category_id,
|
||||
created_at=row.grocery_created_at,
|
||||
updated_at=row.grocery_updated_at
|
||||
updated_at=row.grocery_updated_at,
|
||||
category=category
|
||||
)
|
||||
|
||||
brand = None
|
||||
@@ -261,9 +272,67 @@ def delete_brand(brand_id: int, db: Session = Depends(get_db)):
|
||||
db.commit()
|
||||
return {"message": "Brand deleted successfully"}
|
||||
|
||||
# Grocery Category endpoints
|
||||
@app.post("/grocery-categories/", response_model=schemas.GroceryCategory)
|
||||
def create_grocery_category(category: schemas.GroceryCategoryCreate, db: Session = Depends(get_db)):
|
||||
db_category = models.GroceryCategory(**category.dict())
|
||||
db.add(db_category)
|
||||
db.commit()
|
||||
db.refresh(db_category)
|
||||
return db_category
|
||||
|
||||
@app.get("/grocery-categories/", response_model=List[schemas.GroceryCategory])
|
||||
def read_grocery_categories(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
|
||||
categories = db.query(models.GroceryCategory).offset(skip).limit(limit).all()
|
||||
return categories
|
||||
|
||||
@app.get("/grocery-categories/{category_id}", response_model=schemas.GroceryCategory)
|
||||
def read_grocery_category(category_id: int, db: Session = Depends(get_db)):
|
||||
category = db.query(models.GroceryCategory).filter(models.GroceryCategory.id == category_id).first()
|
||||
if category is None:
|
||||
raise HTTPException(status_code=404, detail="Grocery category not found")
|
||||
return category
|
||||
|
||||
@app.put("/grocery-categories/{category_id}", response_model=schemas.GroceryCategory)
|
||||
def update_grocery_category(category_id: int, category_update: schemas.GroceryCategoryUpdate, db: Session = Depends(get_db)):
|
||||
category = db.query(models.GroceryCategory).filter(models.GroceryCategory.id == category_id).first()
|
||||
if category is None:
|
||||
raise HTTPException(status_code=404, detail="Grocery category not found")
|
||||
|
||||
update_data = category_update.dict(exclude_unset=True)
|
||||
for field, value in update_data.items():
|
||||
setattr(category, field, value)
|
||||
|
||||
db.commit()
|
||||
db.refresh(category)
|
||||
return category
|
||||
|
||||
@app.delete("/grocery-categories/{category_id}")
|
||||
def delete_grocery_category(category_id: int, db: Session = Depends(get_db)):
|
||||
category = db.query(models.GroceryCategory).filter(models.GroceryCategory.id == category_id).first()
|
||||
if category is None:
|
||||
raise HTTPException(status_code=404, detail="Grocery category not found")
|
||||
|
||||
# Check if any groceries reference this category
|
||||
groceries_with_category = db.query(models.Grocery).filter(models.Grocery.category_id == category_id).first()
|
||||
if groceries_with_category:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cannot delete category: groceries are still associated with this category"
|
||||
)
|
||||
|
||||
db.delete(category)
|
||||
db.commit()
|
||||
return {"message": "Grocery category deleted successfully"}
|
||||
|
||||
# Grocery endpoints
|
||||
@app.post("/groceries/", response_model=schemas.Grocery)
|
||||
def create_grocery(grocery: schemas.GroceryCreate, db: Session = Depends(get_db)):
|
||||
# Validate category exists
|
||||
category = db.query(models.GroceryCategory).filter(models.GroceryCategory.id == grocery.category_id).first()
|
||||
if category is None:
|
||||
raise HTTPException(status_code=404, detail="Grocery category not found")
|
||||
|
||||
db_grocery = models.Grocery(**grocery.dict())
|
||||
db.add(db_grocery)
|
||||
db.commit()
|
||||
@@ -289,6 +358,13 @@ def update_grocery(grocery_id: int, grocery_update: schemas.GroceryUpdate, db: S
|
||||
raise HTTPException(status_code=404, detail="Grocery not found")
|
||||
|
||||
update_data = grocery_update.dict(exclude_unset=True)
|
||||
|
||||
# Validate category exists if category_id is being updated
|
||||
if 'category_id' in update_data:
|
||||
category = db.query(models.GroceryCategory).filter(models.GroceryCategory.id == update_data['category_id']).first()
|
||||
if category is None:
|
||||
raise HTTPException(status_code=404, detail="Grocery category not found")
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(grocery, field, value)
|
||||
|
||||
|
||||
@@ -28,16 +28,28 @@ class Brand(Base):
|
||||
# Relationships
|
||||
products = relationship("Product", back_populates="brand")
|
||||
|
||||
class GroceryCategory(Base):
|
||||
__tablename__ = "grocery_categories"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String, nullable=False, index=True)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Relationships
|
||||
groceries = relationship("Grocery", back_populates="category")
|
||||
|
||||
class Grocery(Base):
|
||||
__tablename__ = "groceries"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
name = Column(String, nullable=False, index=True)
|
||||
category = Column(String, nullable=False)
|
||||
category_id = Column(Integer, ForeignKey("grocery_categories.id"), nullable=False)
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
|
||||
|
||||
# Relationships
|
||||
category = relationship("GroceryCategory", back_populates="groceries")
|
||||
products = relationship("Product", back_populates="grocery")
|
||||
|
||||
class Product(Base):
|
||||
|
||||
@@ -20,22 +20,41 @@ class Brand(BrandBase):
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Grocery Category schemas
|
||||
class GroceryCategoryBase(BaseModel):
|
||||
name: str
|
||||
|
||||
class GroceryCategoryCreate(GroceryCategoryBase):
|
||||
pass
|
||||
|
||||
class GroceryCategoryUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
|
||||
class GroceryCategory(GroceryCategoryBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
# Grocery schemas
|
||||
class GroceryBase(BaseModel):
|
||||
name: str
|
||||
category: str
|
||||
category_id: int
|
||||
|
||||
class GroceryCreate(GroceryBase):
|
||||
pass
|
||||
|
||||
class GroceryUpdate(BaseModel):
|
||||
name: Optional[str] = None
|
||||
category: Optional[str] = None
|
||||
category_id: Optional[int] = None
|
||||
|
||||
class Grocery(GroceryBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
category: GroceryCategory
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
Reference in New Issue
Block a user