From 0ba6010d341acf9a27ecb0c7c3e229fcfb532d2b Mon Sep 17 00:00:00 2001 From: Seungpyo Suh Date: Fri, 11 Oct 2024 21:57:34 +0900 Subject: [PATCH] feat: refrigerator feature --- app/database.py | 1 + app/main.py | 8 +- ...nt_models.py => ingredient_info_models.py} | 0 .../refrigerator/refrigerator_models.py | 48 ++++++++++ app/routes/recipe/cooking_step.py | 4 +- .../{ingredient.py => ingredient_info.py} | 6 +- app/routes/recipe/recipe.py | 2 +- app/routes/refrigerator/ingredient_detect.py | 2 +- app/routes/refrigerator/refrigerator.py | 94 +++++++++++++++++++ 9 files changed, 155 insertions(+), 10 deletions(-) rename app/models/recipe/{ingredient_models.py => ingredient_info_models.py} (100%) create mode 100644 app/models/refrigerator/refrigerator_models.py rename app/routes/recipe/{ingredient.py => ingredient_info.py} (93%) create mode 100644 app/routes/refrigerator/refrigerator.py diff --git a/app/database.py b/app/database.py index 0286d8f..661d766 100644 --- a/app/database.py +++ b/app/database.py @@ -7,3 +7,4 @@ recipe_collection = db.recipes cooking_step_collection = db.cooking_steps ingredient_collection = db.ingredients +refrigerator_collection = db.refrigerator diff --git a/app/main.py b/app/main.py index c4868f9..cd47247 100644 --- a/app/main.py +++ b/app/main.py @@ -3,8 +3,8 @@ from app.dependencies.auth import verify_token from app.routes import ping, root -from app.routes.recipe import recipe, ingredient, cooking_step -from app.routes.refrigerator import ingredient_detect +from app.routes.recipe import recipe, ingredient_info, cooking_step +from app.routes.refrigerator import ingredient_detect, refrigerator app = FastAPI( title="Deening API", @@ -30,6 +30,8 @@ # Protected routes app.include_router(recipe.router, dependencies=[Depends(verify_token)]) -app.include_router(ingredient.router, dependencies=[Depends(verify_token)]) +app.include_router(ingredient_info.router, dependencies=[Depends(verify_token)]) app.include_router(cooking_step.router, dependencies=[Depends(verify_token)]) app.include_router(ingredient_detect.router, dependencies=[Depends(verify_token)]) + +app.include_router(refrigerator.router, dependencies=[Depends(verify_token)]) diff --git a/app/models/recipe/ingredient_models.py b/app/models/recipe/ingredient_info_models.py similarity index 100% rename from app/models/recipe/ingredient_models.py rename to app/models/recipe/ingredient_info_models.py diff --git a/app/models/refrigerator/refrigerator_models.py b/app/models/refrigerator/refrigerator_models.py new file mode 100644 index 0000000..224ebf3 --- /dev/null +++ b/app/models/refrigerator/refrigerator_models.py @@ -0,0 +1,48 @@ +from typing import List + +from pydantic import BaseModel + + +class Ingredient(BaseModel): + id: str + name: str + amount: float + unit: str + category: str + + +class AddIngredientForm(BaseModel): + name: str + amount: float + unit: str + category: str + + +class AddIngredientRequest(BaseModel): + ingredients: List[AddIngredientForm] + + +class AddIngredientResponse(BaseModel): + message: str + + +class DeleteIngredientResponse(BaseModel): + message: str + + +class IngredientCategory(BaseModel): + category: str + ingredients: List[Ingredient] + + +class Refrigerator(BaseModel): + categories: List[IngredientCategory] + + +class GetIngredientsResponse(BaseModel): + refrigerator: Refrigerator + + +class GetIngredientsByCategoryResponse(BaseModel): + category: str + ingredients: List[Ingredient] diff --git a/app/routes/recipe/cooking_step.py b/app/routes/recipe/cooking_step.py index 7c80b07..f36d06f 100644 --- a/app/routes/recipe/cooking_step.py +++ b/app/routes/recipe/cooking_step.py @@ -12,9 +12,9 @@ router = APIRouter() -@router.post("/cooking_step", tags=["Recipe"], response_model=CookingStepResponse, +@router.post("/recipe/cooking_step", tags=["Recipe"], response_model=CookingStepResponse, responses={400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}}) -async def get_or_create_cooking_step_info(request: CookingStepRequest): +async def get_cooking_step_info(request: CookingStepRequest): """ 레시피의 특정 조리 단계에 대한 상세 정보를 반환하거나 생성합니다. """ diff --git a/app/routes/recipe/ingredient.py b/app/routes/recipe/ingredient_info.py similarity index 93% rename from app/routes/recipe/ingredient.py rename to app/routes/recipe/ingredient_info.py index 03b8fd8..acce6b8 100644 --- a/app/routes/recipe/ingredient.py +++ b/app/routes/recipe/ingredient_info.py @@ -5,15 +5,15 @@ from app.config import client as openai_client from app.database import ingredient_collection from app.models.error_models import ErrorResponse -from app.models.recipe.ingredient_models import IngredientRequest, Ingredient, IngredientResponse +from app.models.recipe.ingredient_info_models import IngredientRequest, Ingredient, IngredientResponse from app.utils.image_utils import download_and_encode_image router = APIRouter() -@router.post("/ingredient", tags=["Recipe"], response_model=IngredientResponse, +@router.post("/recipe/ingredient-info", tags=["Recipe"], response_model=IngredientResponse, responses={400: {"model": ErrorResponse}}) -async def get_or_create_ingredient(request: IngredientRequest): +async def get_ingredient_info(request: IngredientRequest): """ 식재료 이름으로 검색하여 정보를 반환하거나, 없으면 새로 생성합니다. """ diff --git a/app/routes/recipe/recipe.py b/app/routes/recipe/recipe.py index 2730fef..b28f449 100644 --- a/app/routes/recipe/recipe.py +++ b/app/routes/recipe/recipe.py @@ -12,7 +12,7 @@ @router.post("/recipe", tags=["Recipe"], response_model=RecipeResponse, responses={400: {"model": ErrorResponse}}) -async def get_or_create_recipe(request: RecipeRequest): +async def get_recipe(request: RecipeRequest): """ 주어진 음식 이름에 대한 레시피를 검색하거나 생성합니다. """ diff --git a/app/routes/refrigerator/ingredient_detect.py b/app/routes/refrigerator/ingredient_detect.py index 191333b..13088a0 100644 --- a/app/routes/refrigerator/ingredient_detect.py +++ b/app/routes/refrigerator/ingredient_detect.py @@ -11,7 +11,7 @@ router = APIRouter() -@router.post("/ingredient_detect", tags=["Refrigerator"], +@router.post("/refrigerator/ingredient_detect", tags=["Refrigerator"], response_model=IngredientDetectResponse, responses={400: {"model": ErrorResponse}, 404: {"model": NoIngredientsFoundResponse}}) async def ingredient_detect(image: UploadFile = File(...)): diff --git a/app/routes/refrigerator/refrigerator.py b/app/routes/refrigerator/refrigerator.py new file mode 100644 index 0000000..e6e1e83 --- /dev/null +++ b/app/routes/refrigerator/refrigerator.py @@ -0,0 +1,94 @@ +from itertools import groupby + +from bson import ObjectId +from fastapi import HTTPException, APIRouter + +from app.database import refrigerator_collection +from app.models.error_models import ErrorResponse +from app.models.refrigerator.refrigerator_models import GetIngredientsResponse, Ingredient, IngredientCategory, \ + Refrigerator, AddIngredientResponse, AddIngredientRequest, DeleteIngredientResponse + +router = APIRouter() + + +@router.get("/refrigerator/ingredients", tags=["Refrigerator"], response_model=GetIngredientsResponse) +async def get_ingredients(): + """ + 냉장고에 있는 모든 재료의 리스트를 카테고리별로 묶어 반환합니다. + 재료가 없을 경우 빈 배열을 반환합니다. + """ + try: + ingredients = await refrigerator_collection.find().to_list(length=None) + + # 재료가 없을 경우 빈 Refrigerator 객체 반환 + if not ingredients: + return GetIngredientsResponse(refrigerator=Refrigerator(categories=[])) + + # 카테고리별로 정렬 + sorted_ingredients = sorted(ingredients, key=lambda x: x["category"]) + + # 카테고리별로 그룹화 + grouped_ingredients = [] + for category, items in groupby(sorted_ingredients, key=lambda x: x["category"]): + category_ingredients = [Ingredient(id=str(item["_id"]), **item) for item in items] + grouped_ingredients.append(IngredientCategory(category=category, ingredients=category_ingredients)) + + refrigerator = Refrigerator(categories=grouped_ingredients) + return GetIngredientsResponse(refrigerator=refrigerator) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + +@router.put("/refrigerator/ingredients", tags=["Refrigerator"], responses={400: {"model": ErrorResponse}}, + response_model=AddIngredientResponse) +async def add_ingredients(request: AddIngredientRequest): + """ + 냉장고에 여러 재료를 추가합니다. 이미 존재하는 재료의 경우 양을 더합니다. + """ + try: + for ingredient in request.ingredients: + # 기존 재료 찾기 + existing_ingredient = await refrigerator_collection.find_one( + {"name": ingredient.name, "category": ingredient.category}) + + if existing_ingredient: + # 이미 존재하는 재료라면 양을 더함 + new_amount = existing_ingredient["amount"] + ingredient.amount + await refrigerator_collection.update_one( + {"name": ingredient.name, "category": ingredient.category}, + {"$set": {"amount": new_amount}} + ) + else: + # 새로운 재료라면 추가 + await refrigerator_collection.insert_one(ingredient.dict()) + + return {"message": "재료가 성공적으로 추가되었습니다."} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + +@router.delete("/refrigerator/ingredient/{ingredient_id}", tags=["Refrigerator"], + response_model=DeleteIngredientResponse, + responses={400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}}) +async def delete_ingredient(ingredient_id: str): + """ + 주어진 ID로 냉장고에서 재료를 삭제합니다. + """ + try: + # ObjectId로 변환 + object_id = ObjectId(ingredient_id) + except: + raise HTTPException(status_code=404, detail="유효하지 않은 재료 ID입니다.") + + try: + # 재료 삭제 + result = await refrigerator_collection.delete_one({"_id": object_id}) + + if result.deleted_count == 0: + raise HTTPException(status_code=404, detail="해당 ID의 재료를 찾을 수 없습니다.") + + return {"message": "재료가 성공적으로 삭제되었습니다."} + except HTTPException: + raise + except Exception as e: + raise HTTPException(status_code=400, detail=str(e))