Skip to content

Commit

Permalink
feat: use mongodb
Browse files Browse the repository at this point in the history
  • Loading branch information
sspzoa committed Oct 3, 2024
1 parent a254197 commit 8113537
Show file tree
Hide file tree
Showing 16 changed files with 215 additions and 85 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
build-args: |
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
ACCESS_TOKEN=${{ secrets.ACCESS_TOKEN }}
MONGODB_URL=${{ secrets.MONGODB_URL }}
deploy:
needs: build
Expand All @@ -58,4 +59,5 @@ jobs:
docker run -d -p 8001:8001 --name ${{ github.repository_id }} --restart always \
-e OPENAI_API_KEYL=${{ secrets.OPENAI_API_KEY }} \
-e ACCESS_TOKEN=${{ secrets.ACCESS_TOKEN }} \
-e MONGODB_URL=${{ secrets.MONGODB_URL }} \
${{ env.DOCKER_IMAGE }}:latest
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ COPY ./app /code/app

ARG OPENAI_API_KEY
ARG ACCESS_TOKEN
ARG MONGODB_URL

ENV OPENAI_API_KEY=${OPENAI_API_KEY}
ENV ACCESS_TOKEN=${ACCESS_TOKEN}
ENV MONGODB_URL=${MONGODB_URL}

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"]
4 changes: 3 additions & 1 deletion app/config.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import os
from pathlib import Path

from dotenv import load_dotenv
from openai import OpenAI

Expand All @@ -10,4 +11,5 @@

client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN")
ACCESS_TOKEN = os.environ.get("ACCESS_TOKEN")
MONGODB_URL = os.environ.get("MONGODB_URL")
9 changes: 9 additions & 0 deletions app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from motor.motor_asyncio import AsyncIOMotorClient

from app.config import MONGODB_URL

client = AsyncIOMotorClient(MONGODB_URL)
db = client.deening
recipe_collection = db.recipes
cooking_step_collection = db.cooking_steps
ingredient_collection = db.ingredients
5 changes: 3 additions & 2 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from fastapi import FastAPI, Depends

from app.dependencies.auth import verify_token
from app.routes import ping
from app.routes.food import ingredient_detect
from app.routes.recipe import recipe, ingredient, cooking_step
from app.dependencies.auth import verify_token

app = FastAPI(
title="Deening API",
Expand All @@ -26,4 +27,4 @@
app.include_router(recipe.router, dependencies=[Depends(verify_token)])
app.include_router(ingredient.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(ingredient_detect.router, dependencies=[Depends(verify_token)])
9 changes: 7 additions & 2 deletions app/models/food/ingredient_detect_models.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
from pydantic import BaseModel
from typing import List

from fastapi import UploadFile
from pydantic import BaseModel


class IngredientDetectRequest(BaseModel):
image: UploadFile


class DetectedIngredient(BaseModel):
ingredient_name: str


class IngredientDetectResponse(BaseModel):
ingredients: List[DetectedIngredient]


class NoIngredientsFoundResponse(BaseModel):
message: str = "No ingredients found in the image"
message: str = "No ingredients found in the image"
3 changes: 2 additions & 1 deletion app/models/ping_models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydantic import BaseModel


class PingResponse(BaseModel):
message: str
message: str
9 changes: 7 additions & 2 deletions app/models/recipe/cooking_step_models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from pydantic import BaseModel
from typing import List

from pydantic import BaseModel


class CookingStepRequest(BaseModel):
recipe_id: str
step_number: int


class CookingStep(BaseModel):
recipe_id: str
step_number: int
Expand All @@ -14,6 +17,8 @@ class CookingStep(BaseModel):
ingredients_used: List[str]
tips: str


class CookingStepResponse(BaseModel):
id: str
cooking_step: CookingStep
image_url: str
image_url: str
10 changes: 8 additions & 2 deletions app/models/recipe/ingredient_models.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from pydantic import BaseModel
from typing import List

from pydantic import BaseModel


class IngredientRequest(BaseModel):
ingredient_name: str


class NutritionalInfo(BaseModel):
calories: int
protein: float
Expand All @@ -13,6 +16,7 @@ class NutritionalInfo(BaseModel):
vitamins: str
minerals: str


class Ingredient(BaseModel):
name: str
description: str
Expand All @@ -21,6 +25,8 @@ class Ingredient(BaseModel):
storage_tips: str
culinary_uses: List[str]


class IngredientResponse(BaseModel):
id: str
ingredient: Ingredient
image_url: str
image_url: str
8 changes: 7 additions & 1 deletion app/models/recipe/recipe_models.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
from pydantic import BaseModel
from typing import List


class Ingredient(BaseModel):
name: str
amount: float
unit: str


class Instruction(BaseModel):
step: int
description: str


class Nutrition(BaseModel):
calories: int
protein: str
carbohydrates: str
fat: str


class Recipe(BaseModel):
name: str
description: str
Expand All @@ -30,10 +34,12 @@ class Recipe(BaseModel):
tags: List[str]
source: str


class RecipeRequest(BaseModel):
food_name: str


class RecipeResponse(BaseModel):
id: str
recipe: Recipe
image_url: str
image_url: str
20 changes: 13 additions & 7 deletions app/routes/food/ingredient_detect.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
from fastapi import APIRouter, File, UploadFile, HTTPException
from pydantic import BaseModel
import json
import base64
import json
import logging

from fastapi import APIRouter, File, UploadFile, HTTPException
from pydantic import BaseModel

from app.config import client
from app.models.food.ingredient_detect_models import IngredientDetectResponse, DetectedIngredient, IngredientDetectRequest, NoIngredientsFoundResponse
from app.models.food.ingredient_detect_models import IngredientDetectResponse, DetectedIngredient, \
NoIngredientsFoundResponse

router = APIRouter()


class ErrorResponse(BaseModel):
error: str

@router.post("/ingredient_detect", tags=["Food"], response_model=IngredientDetectResponse, responses={400: {"model": ErrorResponse}, 404: {"model": NoIngredientsFoundResponse}})

@router.post("/ingredient_detect", tags=["Food"], response_model=IngredientDetectResponse,
responses={400: {"model": ErrorResponse}, 404: {"model": NoIngredientsFoundResponse}})
async def ingredient_detect(image: UploadFile = File(...)):
"""
업로드된 이미지 파일에서 식재료를 탐색해 반환합니다.
Expand Down Expand Up @@ -68,7 +73,8 @@ async def ingredient_detect(image: UploadFile = File(...)):
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="생성된 식재료 정보를 JSON으로 파싱할 수 없습니다.")

detected_ingredients = [DetectedIngredient(**ingredient) for ingredient in ingredient_detect_json["ingredients"]]
detected_ingredients = [DetectedIngredient(**ingredient) for ingredient in
ingredient_detect_json["ingredients"]]

if not detected_ingredients:
return NoIngredientsFoundResponse(status_code=404)
Expand All @@ -79,4 +85,4 @@ async def ingredient_detect(image: UploadFile = File(...)):
raise http_ex
except Exception as e:
logging.error(f"Unexpected error: {str(e)}")
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
raise HTTPException(status_code=500, detail=f"An unexpected error occurred: {str(e)}")
4 changes: 3 additions & 1 deletion app/routes/ping.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from fastapi import APIRouter

from app.models.ping_models import PingResponse

router = APIRouter()


@router.get("/ping", response_model=PingResponse)
async def ping():
"""
Health check endpoint
"""
return {"message": "pong"}
return {"message": "pong"}
86 changes: 63 additions & 23 deletions app/routes/recipe/cooking_step.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,47 @@
import json

from bson import ObjectId
from fastapi import APIRouter, HTTPException
from app.models.recipe.cooking_step_models import CookingStepRequest, CookingStep, CookingStepResponse
from pydantic import BaseModel
from app.config import client
import json

from app.routes.recipe.recipe import recipe_store
from app.config import client as openai_client
from app.database import recipe_collection, cooking_step_collection
from app.models.recipe.cooking_step_models import CookingStepRequest, CookingStep, CookingStepResponse

router = APIRouter()


class ErrorResponse(BaseModel):
error: str

@router.post("/cooking_step", tags=["Recipe"], response_model=CookingStepResponse, responses={400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}})
async def get_cooking_step_info(request: CookingStepRequest):

@router.post("/cooking_step", tags=["Recipe"], response_model=CookingStepResponse,
responses={400: {"model": ErrorResponse}, 404: {"model": ErrorResponse}})
async def get_or_create_cooking_step_info(request: CookingStepRequest):
"""
레시피의 특정 조리 단계에 대한 상세 정보를 생성합니다.
레시피의 특정 조리 단계에 대한 상세 정보를 반환하거나 생성합니다.
"""
if request.recipe_id not in recipe_store:
raise HTTPException(status_code=404, detail="레시피를 찾을 수 없습니다.")

stored_data = recipe_store[request.recipe_id]
recipe = stored_data["recipe"]

try:
# 조리 과정 정보 생성 프롬프트
# 기존 조리 단계 정보 검색
existing_step = await cooking_step_collection.find_one({
"recipe_id": request.recipe_id,
"step_number": request.step_number
})

if existing_step:
# 기존 정보가 있으면 그대로 반환
cooking_step = CookingStep(**existing_step)
return CookingStepResponse(
id=str(existing_step['_id']),
cooking_step=cooking_step,
image_url=existing_step.get('image_url')
)

# 기존 정보가 없으면 새로 생성
recipe = await recipe_collection.find_one({"_id": ObjectId(request.recipe_id)})
if not recipe:
raise HTTPException(status_code=404, detail="레시피를 찾을 수 없습니다.")

cooking_step_prompt = f"""제공된 레시피에 대한 자세한 조리 과정 정보를 JSON 형식으로 생성해주세요. 다음 구조를 따라주세요:
{{
Expand All @@ -43,8 +61,7 @@ async def get_cooking_step_info(request: CookingStepRequest):
주의: 반드시 다른 텍스트 없이 유효한 JSON 형식으로만 응답해주세요.
"""

# 조리 과정 정보 생성
cooking_step_response = client.chat.completions.create(
cooking_step_response = openai_client.chat.completions.create(
model="chatgpt-4o-latest",
messages=[
{"role": "system", "content": "당신은 전문 요리사입니다. 주어진 레시피의 특정 조리 단계에 대한 상세한 정보를 JSON 형식으로 제공합니다."},
Expand All @@ -55,8 +72,7 @@ async def get_cooking_step_info(request: CookingStepRequest):
cooking_step_json = json.loads(cooking_step_response.choices[0].message.content)
cooking_step = CookingStep(**cooking_step_json)

# 이미지 생성 프롬프트
image_prompt = f"""A high-quality, detailed photo demonstrating the cooking step for {recipe.name}, step number {cooking_step.step_number}:
image_prompt = f"""A high-quality, detailed photo demonstrating the cooking step for {recipe['name']}, step number {cooking_step.step_number}:
- Description: {cooking_step.description}
- Tools used: {', '.join(cooking_step.tools_needed)}
Expand All @@ -67,20 +83,44 @@ async def get_cooking_step_info(request: CookingStepRequest):
The image should be from a slightly elevated angle to give a clear view of the cooking surface and the chef's hands (if applicable).
"""

# DALL-E를 사용한 이미지 생성
image_response = client.images.generate(
image_response = openai_client.images.generate(
model="dall-e-3",
prompt=image_prompt,
size="1024x1024",
quality="standard",
n=1,
)

# 이미지 URL 가져오기
image_url = image_response.data[0].url

return CookingStepResponse(cooking_step=cooking_step, image_url=image_url)
cooking_step_dict = cooking_step.dict()
cooking_step_dict['image_url'] = image_url
result = await cooking_step_collection.insert_one(cooking_step_dict)
cooking_step_id = str(result.inserted_id)

print(cooking_step_response)
print(image_response)

return CookingStepResponse(id=cooking_step_id, cooking_step=cooking_step, image_url=image_url)
except json.JSONDecodeError:
raise HTTPException(status_code=400, detail="생성된 조리 과정 정보를 JSON으로 파싱할 수 없습니다.")
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
raise HTTPException(status_code=400, detail=str(e))


@router.get("/cooking_step/{cooking_step_id}", tags=["Recipe"], response_model=CookingStepResponse,
responses={404: {"model": ErrorResponse}})
async def get_cooking_step_by_id(cooking_step_id: str):
"""
주어진 ID에 대한 조리 단계 정보를 반환합니다.
"""
try:
cooking_step_data = await cooking_step_collection.find_one({"_id": ObjectId(cooking_step_id)})
if not cooking_step_data:
raise HTTPException(status_code=404, detail="조리 단계 정보를 찾을 수 없습니다.")

image_url = cooking_step_data.pop('image_url', None)
cooking_step = CookingStep(**cooking_step_data)
return CookingStepResponse(id=str(cooking_step_data['_id']), cooking_step=cooking_step, image_url=image_url)
except Exception as e:
raise HTTPException(status_code=400, detail=str(e))
Loading

0 comments on commit 8113537

Please sign in to comment.