diff --git a/apps/pre-processing-service/app/api/endpoints/blog.py b/apps/pre-processing-service/app/api/endpoints/blog.py new file mode 100644 index 00000000..0507495f --- /dev/null +++ b/apps/pre-processing-service/app/api/endpoints/blog.py @@ -0,0 +1,21 @@ +# app/api/endpoints/keywords.py +from fastapi import APIRouter +from app.decorators.logging import log_api_call +from ...errors.CustomException import * +from fastapi import APIRouter + +from ...model.schemas import * +# 이 파일만의 독립적인 라우터를 생성합니다. +router = APIRouter() + +@router.get("/") +async def root(): + return {"message": "blog API"} + +@router.post("/rag/create", response_model=ResponsetBlogCreate) +async def rag_create(request: RequestBlogCreate): + return {"message": "blog API"} + +@router.post("/publish", response_model=RequestBlogPublish) +async def publish(request: ResponsetBlogPublish): + return {"message": "blog API"} \ No newline at end of file diff --git a/apps/pre-processing-service/app/api/endpoints/keywords.py b/apps/pre-processing-service/app/api/endpoints/keywords.py new file mode 100644 index 00000000..9b3fd61d --- /dev/null +++ b/apps/pre-processing-service/app/api/endpoints/keywords.py @@ -0,0 +1,65 @@ +# app/api/endpoints/keywords.py +from ...service.keyword_service import keyword_search + +from fastapi import APIRouter +from app.decorators.logging import log_api_call +from ...errors.CustomException import * +from fastapi import APIRouter +from ...errors.CustomException import * +from ...model.schemas import RequestNaverSearch, ResponseNaverSearch, RequestSadaguValidate, ResponsetSadaguValidate + +# 이 파일만의 독립적인 라우터를 생성합니다. +router = APIRouter() + +@router.get("/") +async def root(): + return {"message": "Items API"} + +@router.post("/search") +async def search(request: RequestNaverSearch): + """ + 이 엔드포인트는 아래와 같은 JSON 요청을 받습니다. + RequestBase와 RequestNaverSearch의 모든 필드를 포함해야 합니다. + { + "job_id": "job-123", + "schedule_id": "schedule-456", + "schedule_his_id": 789, + "tag": "fastapi", + "category": "tech", + "start_date": "2025-09-01T12:00:00", + "end_date": "2025-09-02T15:00:00" + } + """ + job_id = request.job_id + schedule_id = request.schedule_id + category = request.category + keywords = "밥밥밥" + return ResponseNaverSearch( + job_id=job_id, + schedule_id=schedule_id, + category=category, + keyword=keywords, + total_keyword = {1: "바밥밥", 2: "밥밥밥", 3: "바밤바"} + ) + +@router.post("/search/test",response_model=ResponsetSadaguValidate) +async def search(request: RequestSadaguValidate): + """ + 이 엔드포인트는 아래와 같은 JSON 요청을 받습니다. + RequestBase와 RequestNaverSearch의 모든 필드를 포함해야 합니다. + { + "job_id": "job-123", + "schedule_id": "schedule-456", + "schedule_his_id": 789, + "tag": "fastapi", + "category": "tech", + "start_date": "2025-09-01T12:00:00", + "end_date": "2025-09-02T15:00:00" + } + """ + response_data= keyword_search(request) + return response_data + +@router.post("/ssadagu/validate",response_model=ResponseNaverSearch) +async def ssadagu_validate(request: RequestNaverSearch): + return ResponseNaverSearch() diff --git a/apps/pre-processing-service/app/api/endpoints/processing.py b/apps/pre-processing-service/app/api/endpoints/processing.py deleted file mode 100644 index 51c8ff27..00000000 --- a/apps/pre-processing-service/app/api/endpoints/processing.py +++ /dev/null @@ -1,12 +0,0 @@ -# app/api/endpoints/embedding.py -from fastapi import APIRouter -from app.decorators.logging import log_api_call -from ...errors.CustomException import * -from fastapi import APIRouter - -# 이 파일만의 독립적인 라우터를 생성합니다. -router = APIRouter() - -@router.get("/") -async def root(): - return {"message": "사용자API"} \ No newline at end of file diff --git a/apps/pre-processing-service/app/api/endpoints/embedding.py b/apps/pre-processing-service/app/api/endpoints/product.py similarity index 57% rename from apps/pre-processing-service/app/api/endpoints/embedding.py rename to apps/pre-processing-service/app/api/endpoints/product.py index 8a8d1d6f..c8ae71db 100644 --- a/apps/pre-processing-service/app/api/endpoints/embedding.py +++ b/apps/pre-processing-service/app/api/endpoints/product.py @@ -1,12 +1,13 @@ -# app/api/endpoints/embedding.py from fastapi import APIRouter from app.decorators.logging import log_api_call from ...errors.CustomException import * from fastapi import APIRouter +from ...model.schemas import *; + # 이 파일만의 독립적인 라우터를 생성합니다. router = APIRouter() -@router.get("/") -async def root(): - return {"message": "Items API"} \ No newline at end of file +@router.post("/crawl",response_model=ResponsetSadaguCrawl) +async def crawl(request: RequestSadaguCrawl): + return ResponsetSadaguCrawl() diff --git a/apps/pre-processing-service/app/api/router.py b/apps/pre-processing-service/app/api/router.py index a1c064cc..df54c807 100644 --- a/apps/pre-processing-service/app/api/router.py +++ b/apps/pre-processing-service/app/api/router.py @@ -1,15 +1,18 @@ # app/api/router.py from fastapi import APIRouter -from .endpoints import embedding, processing,test +from .endpoints import keywords, blog,test,product from ..core.config import settings api_router = APIRouter() # embedding API URL -api_router.include_router(embedding.router, prefix="/emb", tags=["Embedding"]) +api_router.include_router(keywords.router, prefix="/keywords", tags=["keyword"]) # processing API URL -api_router.include_router(processing.router, prefix="/prc", tags=["Processing"]) +api_router.include_router(blog.router, prefix="/blog", tags=["blog"]) + +#상품 API URL +api_router.include_router(product.router, prefix="/product", tags=["product"]) #모듈 테스터를 위한 endpoint api_router.include_router(test.router, prefix="/test", tags=["Test"]) diff --git a/apps/pre-processing-service/app/errors/handlers.py b/apps/pre-processing-service/app/errors/handlers.py index db05e176..1b5caf3d 100644 --- a/apps/pre-processing-service/app/errors/handlers.py +++ b/apps/pre-processing-service/app/errors/handlers.py @@ -1,65 +1,90 @@ -# app/errors/handlers.py from fastapi import Request, status from fastapi.responses import JSONResponse +from pydantic import BaseModel from starlette.exceptions import HTTPException as StarletteHTTPException from fastapi.exceptions import RequestValidationError from .messages import ERROR_MESSAGES, get_error_message from ..errors.CustomException import CustomException +class ErrorBaseModel(BaseModel): + """ + 모든 에러 응답의 기반이 되는 Pydantic 모델. + API의 에러 응답 형식을 통일하는 역할을 합니다. + """ + status_code: int + detail: str + code: str + # CustomException 핸들러 async def custom_exception_handler(request: Request, exc: CustomException): """ CustomException을 상속받는 모든 예외를 처리합니다. """ + # 변경점: ErrorBaseModel을 사용하여 응답 본문 생성 + error_content = ErrorBaseModel( + status_code=exc.status_code, + detail=exc.detail, + code=exc.code + ) return JSONResponse( status_code=exc.status_code, - content={ - "error_code": exc.code, - "message": exc.detail, - }, + content=error_content.model_dump(), ) + # FastAPI의 HTTPException 핸들러 (예: 404 Not Found) async def http_exception_handler(request: Request, exc: StarletteHTTPException): """ FastAPI에서 기본적으로 발생하는 HTTP 관련 예외를 처리합니다. """ - if exc.status_code == status.HTTP_404_NOT_FOUND: - # 404 에러의 경우, FastAPI의 기본 "Not Found" 메시지 대신 우리가 정의한 메시지를 사용합니다. - message = ERROR_MESSAGES.get(exc.status_code, "요청하신 리소스를 찾을 수 없습니다.") - else: - # 다른 HTTP 예외들은 FastAPI가 제공하는 detail 메시지를 우선적으로 사용합니다. - message = get_error_message(exc.status_code, exc.detail) + message = get_error_message(exc.status_code, exc.detail) + # 변경점: ErrorBaseModel을 사용하여 응답 본문 생성 + error_content = ErrorBaseModel( + status_code=exc.status_code, + detail=message, + code=f"HTTP_{exc.status_code}" + ) return JSONResponse( status_code=exc.status_code, - content={ - "error_code": f"HTTP_{exc.status_code}", - "message": message - }, + content=error_content.model_dump(), ) + # Pydantic Validation Error 핸들러 (422) async def validation_exception_handler(request: Request, exc: RequestValidationError): """ Pydantic 모델 유효성 검사 실패 시 발생하는 예외를 처리합니다. """ + # 변경점: ErrorBaseModel을 기본 구조로 사용하고, 추가 정보를 더함 + base_error = ErrorBaseModel( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + detail=ERROR_MESSAGES[status.HTTP_422_UNPROCESSABLE_ENTITY], + code="VALIDATION_ERROR" + ) + + # 모델의 내용과 추가적인 'details' 필드를 결합 + response_content = base_error.model_dump() + response_content["details"] = exc.errors() + return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - content={ - "error_code": "VALIDATION_ERROR", - "message": ERROR_MESSAGES[status.HTTP_422_UNPROCESSABLE_ENTITY], - "details": exc.errors(), - }, + content=response_content, ) + # 처리되지 않은 모든 예외 핸들러 (500) async def unhandled_exception_handler(request: Request, exc: Exception): - # ... + """ + 처리되지 않은 모든 예외를 처리합니다. + """ + # 변경점: ErrorBaseModel을 사용하여 응답 본문 생성 + error_content = ErrorBaseModel( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=ERROR_MESSAGES[status.HTTP_500_INTERNAL_SERVER_ERROR], + code="INTERNAL_SERVER_ERROR" + ) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={ - "error_code": "INTERNAL_SERVER_ERROR", - "message": ERROR_MESSAGES[status.HTTP_500_INTERNAL_SERVER_ERROR], - }, + content=error_content.model_dump(), ) diff --git a/apps/pre-processing-service/app/model/schemas.py b/apps/pre-processing-service/app/model/schemas.py index e69de29b..cd953d39 100644 --- a/apps/pre-processing-service/app/model/schemas.py +++ b/apps/pre-processing-service/app/model/schemas.py @@ -0,0 +1,64 @@ +from datetime import datetime +from typing import Optional +from pydantic import BaseModel + + + +#기본 요청 +class RequestBase(BaseModel): + job_id: str + schedule_id: str + schedule_his_id: Optional[int] = None + +#기본 응답 +class ResponseBase(BaseModel): + job_id: str + schedule_id: str + status: str + + +#네이버 키워드 추출 +class RequestNaverSearch(RequestBase): + tag: str + category: str + startDate :datetime + endDate :datetime + +class ResponseNaverSearch(ResponseBase): + category: str + keyword: str + total_keyword: dict[int, str] + + +#키워드 사다구몰 검증 +class RequestSadaguValidate(RequestBase): + tag: str + category: str + +class ResponsetSadaguValidate(ResponseBase): + keyword: str + + +#사다구몰 상품 크롤링 +class RequestSadaguCrawl(RequestBase): + tag: str + category: str + +class ResponsetSadaguCrawl(ResponseBase): + pass + +#블로그 생성 +class RequestBlogCreate(RequestBase): + tag: str + category: str + +class ResponsetBlogCreate(ResponseBase): + pass + +#블로그 배포 +class RequestBlogPublish(RequestBase): + tag: str + category: str + +class ResponsetBlogPublish(ResponseBase): + pass diff --git a/apps/pre-processing-service/app/service/__init__.py b/apps/pre-processing-service/app/service/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/apps/pre-processing-service/app/service/keyword_service.py b/apps/pre-processing-service/app/service/keyword_service.py new file mode 100644 index 00000000..d26a458b --- /dev/null +++ b/apps/pre-processing-service/app/service/keyword_service.py @@ -0,0 +1,16 @@ +# Pydantic 모델을 가져오기 위해 schemas 파일 import +from ..model.schemas import RequestNaverSearch + +def keyword_search(request: RequestNaverSearch) -> dict: + """ + 네이버 검색 요청을 처리하는 비즈니스 로직입니다. + 입력받은 데이터를 기반으로 응답 데이터를 생성하여 딕셔너리로 반환합니다. + """ + + response_data = request.model_dump() + + response_data["keyword"] = "밥밥밥" + total_keyword = {1: "바밥밥", 2: "밥밥밥", 3: "바밤바"} + response_data["total_keyword"] = total_keyword + + return response_data \ No newline at end of file