From c6d9edbb42582eefb16aca402d25e985b950e23b Mon Sep 17 00:00:00 2001 From: bluebamus Date: Mon, 29 Dec 2025 11:01:35 +0900 Subject: [PATCH] =?UTF-8?q?=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=95=8C=20task=5Fid=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD,=20=EA=B0=80=EC=82=AC?= =?UTF-8?q?=20=EC=83=9D=EC=84=B1=EC=8B=9C=20task=5Fid=20=EB=B0=9B=EC=95=84?= =?UTF-8?q?=EC=98=A4=EB=8A=94=20=EA=B2=83=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/home/api/routers/v1/home.py | 35 ++++++++--- app/home/schemas/home_schema.py | 43 +++++++++++++- app/lyric/api/routers/v1/lyric.py | 8 ++- app/lyric/schemas/lyric.py | 97 ++++++++++++++++++------------- 4 files changed, 130 insertions(+), 53 deletions(-) diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py index d7a9ffb..ede14e1 100644 --- a/app/home/api/routers/v1/home.py +++ b/app/home/api/routers/v1/home.py @@ -179,11 +179,11 @@ IMAGES_JSON_EXAMPLE = """[ @router.post( - "/image/upload/server/{task_id}", + "/image/upload/server", include_in_schema=False, summary="이미지 업로드 (로컬 서버)", description=""" -task_id에 연결된 이미지를 로컬 서버(media 폴더)에 업로드합니다. +이미지를 로컬 서버(media 폴더)에 업로드하고 새로운 task_id를 생성합니다. ## 요청 방식 multipart/form-data 형식으로 전송합니다. @@ -258,6 +258,10 @@ print(response.json()) ## 저장 경로 - 바이너리 파일: /media/image/{날짜}/{uuid7}/{파일명} - URL 이미지: 외부 URL 그대로 Image 테이블에 저장 + +## 반환 정보 +- **task_id**: 새로 생성된 작업 고유 식별자 +- **image_urls**: Image 테이블에 저장된 현재 task_id의 이미지 URL 목록 """, response_model=ImageUploadResponse, responses={ @@ -267,7 +271,6 @@ print(response.json()) tags=["image"], ) async def upload_images( - task_id: str, images_json: Optional[str] = Form( default=None, description="외부 이미지 URL 목록 (JSON 문자열)", @@ -279,6 +282,9 @@ async def upload_images( session: AsyncSession = Depends(get_session), ) -> ImageUploadResponse: """이미지 업로드 (URL + 바이너리 파일)""" + # task_id 생성 + task_id = await generate_task_id() + # 1. 진입 검증: images_json 또는 files 중 하나는 반드시 있어야 함 has_images_json = images_json is not None and images_json.strip() != "" has_files = files is not None and len(files) > 0 @@ -405,6 +411,9 @@ async def upload_images( saved_count = len(result_images) await session.commit() + # Image 테이블에서 현재 task_id의 이미지 URL 목록 조회 + image_urls = [img.img_url for img in result_images] + return ImageUploadResponse( task_id=task_id, total_count=len(result_images), @@ -412,14 +421,15 @@ async def upload_images( file_count=len(valid_files), saved_count=saved_count, images=result_images, + image_urls=image_urls, ) @router.post( - "/image/upload/blob/{task_id}", + "/image/upload/blob", summary="이미지 업로드 (Azure Blob Storage)", description=""" -task_id에 연결된 이미지를 Azure Blob Storage에 업로드합니다. +이미지를 Azure Blob Storage에 업로드하고 새로운 task_id를 생성합니다. 바이너리 파일은 로컬 서버에 저장하지 않고 Azure Blob에 직접 업로드됩니다. ## 요청 방식 @@ -447,24 +457,25 @@ jpg, jpeg, png, webp, heic, heif ### cURL로 테스트 ```bash # 바이너리 파일만 업로드 -curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ +curl -X POST "http://localhost:8000/image/upload/blob" \\ -F "files=@/path/to/image1.jpg" \\ -F "files=@/path/to/image2.png" # URL + 바이너리 파일 동시 업로드 -curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ +curl -X POST "http://localhost:8000/image/upload/blob" \\ -F 'images_json=[{"url":"https://example.com/image.jpg"}]' \\ -F "files=@/path/to/local_image.jpg" ``` ## 반환 정보 -- **task_id**: 작업 고유 식별자 +- **task_id**: 새로 생성된 작업 고유 식별자 - **total_count**: 총 업로드된 이미지 개수 - **url_count**: URL로 등록된 이미지 개수 (Image 테이블에 외부 URL 그대로 저장) - **file_count**: 파일로 업로드된 이미지 개수 (Azure Blob Storage에 저장) - **saved_count**: Image 테이블에 저장된 row 수 - **images**: 업로드된 이미지 목록 - **source**: "url" (외부 URL) 또는 "blob" (Azure Blob Storage) +- **image_urls**: Image 테이블에 저장된 현재 task_id의 이미지 URL 목록 ## 저장 경로 - 바이너리 파일: Azure Blob Storage ({BASE_URL}/{task_id}/image/{파일명}) @@ -478,7 +489,6 @@ curl -X POST "http://localhost:8000/image/upload/blob/test-task-001" \\ tags=["image"], ) async def upload_images_blob( - task_id: str, images_json: Optional[str] = Form( default=None, description="외부 이미지 URL 목록 (JSON 문자열)", @@ -490,6 +500,9 @@ async def upload_images_blob( session: AsyncSession = Depends(get_session), ) -> ImageUploadResponse: """이미지 업로드 (URL + Azure Blob Storage)""" + # task_id 생성 + task_id = await generate_task_id() + # 1. 진입 검증 has_images_json = images_json is not None and images_json.strip() != "" has_files = files is not None and len(files) > 0 @@ -609,6 +622,9 @@ async def upload_images_blob( saved_count = len(result_images) await session.commit() + # Image 테이블에서 현재 task_id의 이미지 URL 목록 조회 + image_urls = [img.img_url for img in result_images] + return ImageUploadResponse( task_id=task_id, total_count=len(result_images), @@ -616,4 +632,5 @@ async def upload_images_blob( file_count=len(valid_files) - len(skipped_files), saved_count=saved_count, images=result_images, + image_urls=image_urls, ) diff --git a/app/home/schemas/home_schema.py b/app/home/schemas/home_schema.py index 5b34872..99bb043 100644 --- a/app/home/schemas/home_schema.py +++ b/app/home/schemas/home_schema.py @@ -211,9 +211,50 @@ class ImageUploadResultItem(BaseModel): class ImageUploadResponse(BaseModel): """이미지 업로드 응답 스키마""" - task_id: str = Field(..., description="작업 고유 식별자") + model_config = ConfigDict( + json_schema_extra={ + "example": { + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", + "total_count": 3, + "url_count": 2, + "file_count": 1, + "saved_count": 3, + "images": [ + { + "id": 1, + "img_name": "외관", + "img_url": "https://example.com/images/image_001.jpg", + "img_order": 0, + "source": "url", + }, + { + "id": 2, + "img_name": "내부", + "img_url": "https://example.com/images/image_002.jpg", + "img_order": 1, + "source": "url", + }, + { + "id": 3, + "img_name": "uploaded_image.jpg", + "img_url": "/media/image/2024-01-15/0694b716-dbff-7219-8000-d08cb5fce431/uploaded_image_002.jpg", + "img_order": 2, + "source": "file", + }, + ], + "image_urls": [ + "https://example.com/images/image_001.jpg", + "https://example.com/images/image_002.jpg", + "/media/image/2024-01-15/0694b716-dbff-7219-8000-d08cb5fce431/uploaded_image_002.jpg", + ], + } + } + ) + + task_id: str = Field(..., description="작업 고유 식별자 (새로 생성된 UUID7)") total_count: int = Field(..., description="총 업로드된 이미지 개수") url_count: int = Field(..., description="URL로 등록된 이미지 개수") file_count: int = Field(..., description="파일로 업로드된 이미지 개수") saved_count: int = Field(..., description="Image 테이블에 저장된 row 수") images: list[ImageUploadResultItem] = Field(..., description="업로드된 이미지 목록") + image_urls: list[str] = Field(..., description="Image 테이블에 저장된 현재 task_id의 이미지 URL 목록") diff --git a/app/lyric/api/routers/v1/lyric.py b/app/lyric/api/routers/v1/lyric.py index 914a2c1..8d8a9b9 100644 --- a/app/lyric/api/routers/v1/lyric.py +++ b/app/lyric/api/routers/v1/lyric.py @@ -42,7 +42,6 @@ from app.lyric.schemas.lyric import ( LyricStatusResponse, ) from app.utils.chatgpt_prompt import ChatgptService -from app.utils.common import generate_task_id from app.utils.pagination import PaginatedResponse, get_paginated router = APIRouter(prefix="/lyric", tags=["lyric"]) @@ -159,6 +158,7 @@ async def get_lyric_by_task_id( 고객 정보를 기반으로 ChatGPT를 이용하여 가사를 생성합니다. ## 요청 필드 +- **task_id**: 작업 고유 식별자 (이미지 업로드 시 생성된 task_id, 필수) - **customer_name**: 고객명/가게명 (필수) - **region**: 지역명 (필수) - **detail_region_info**: 상세 지역 정보 (선택) @@ -180,6 +180,7 @@ async def get_lyric_by_task_id( ``` POST /lyric/generate { + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", "customer_name": "스테이 머뭄", "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", @@ -220,9 +221,10 @@ async def generate_lyric( session: AsyncSession = Depends(get_session), ) -> GenerateLyricResponse: """고객 정보를 기반으로 가사를 생성합니다.""" - task_id = await generate_task_id(session=session, table_name=Project) + task_id = request_body.task_id print( - f"[generate_lyric] START - task_id: {task_id}, customer_name: {request_body.customer_name}, region: {request_body.region}" + f"[generate_lyric] START - task_id: {task_id}, " + f"customer_name: {request_body.customer_name}, region: {request_body.region}" ) try: diff --git a/app/lyric/schemas/lyric.py b/app/lyric/schemas/lyric.py index ff82516..e33d6c7 100644 --- a/app/lyric/schemas/lyric.py +++ b/app/lyric/schemas/lyric.py @@ -25,7 +25,7 @@ Lyric API Schemas from datetime import datetime from typing import Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field class GenerateLyricRequest(BaseModel): @@ -37,6 +37,7 @@ class GenerateLyricRequest(BaseModel): Example Request: { + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", "customer_name": "스테이 머뭄", "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", @@ -44,17 +45,21 @@ class GenerateLyricRequest(BaseModel): } """ - model_config = { - "json_schema_extra": { + model_config = ConfigDict( + json_schema_extra={ "example": { + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", "customer_name": "스테이 머뭄", "region": "군산", "detail_region_info": "군산 신흥동 말랭이 마을", "language": "Korean", } } - } + ) + task_id: str = Field( + ..., description="작업 고유 식별자 (이미지 업로드 시 생성된 task_id)" + ) customer_name: str = Field(..., description="고객명/가게명") region: str = Field(..., description="지역명") detail_region_info: Optional[str] = Field(None, description="상세 지역 정보") @@ -76,26 +81,20 @@ class GenerateLyricResponse(BaseModel): - ChatGPT API 오류 - ChatGPT 거부 응답 (I'm sorry, I cannot, I can't, I apologize 등) - 응답에 ERROR: 포함 - - Example Response (Success): - { - "success": true, - "task_id": "019123ab-cdef-7890-abcd-ef1234567890", - "lyric": "인스타 감성의 스테이 머뭄...", - "language": "Korean", - "error_message": null - } - - Example Response (Failure): - { - "success": false, - "task_id": "019123ab-cdef-7890-abcd-ef1234567890", - "lyric": null, - "language": "Korean", - "error_message": "I'm sorry, I can't comply with that request." - } """ + model_config = ConfigDict( + json_schema_extra={ + "example": { + "success": True, + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", + "lyric": "인스타 감성의 스테이 머뭄\n군산 신흥동 말랭이 마을에서\n여유로운 하루를 보내며\n추억을 만들어가요", + "language": "Korean", + "error_message": None, + } + } + ) + success: bool = Field(..., description="생성 성공 여부") task_id: Optional[str] = Field(None, description="작업 고유 식별자 (uuid7)") lyric: Optional[str] = Field(None, description="생성된 가사 (성공 시)") @@ -109,15 +108,18 @@ class LyricStatusResponse(BaseModel): Usage: GET /lyric/status/{task_id} Returns the current processing status of a lyric generation task. - - Example Response: - { - "task_id": "019123ab-cdef-7890-abcd-ef1234567890", - "status": "completed", - "message": "가사 생성이 완료되었습니다." - } """ + model_config = ConfigDict( + json_schema_extra={ + "example": { + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", + "status": "completed", + "message": "가사 생성이 완료되었습니다.", + } + } + ) + task_id: str = Field(..., description="작업 고유 식별자") status: str = Field(..., description="처리 상태 (processing, completed, failed)") message: str = Field(..., description="상태 메시지") @@ -129,19 +131,22 @@ class LyricDetailResponse(BaseModel): Usage: GET /lyric/{task_id} Returns the generated lyric content for a specific task. - - Example Response: - { - "id": 1, - "task_id": "019123ab-cdef-7890-abcd-ef1234567890", - "project_id": 1, - "status": "completed", - "lyric_prompt": "...", - "lyric_result": "생성된 가사...", - "created_at": "2024-01-01T12:00:00" - } """ + model_config = ConfigDict( + json_schema_extra={ + "example": { + "id": 1, + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", + "project_id": 1, + "status": "completed", + "lyric_prompt": "고객명: 스테이 머뭄, 지역: 군산...", + "lyric_result": "인스타 감성의 스테이 머뭄\n군산 신흥동 말랭이 마을에서\n여유로운 하루를 보내며\n추억을 만들어가요", + "created_at": "2024-01-15T12:00:00", + } + } + ) + id: int = Field(..., description="가사 ID") task_id: str = Field(..., description="작업 고유 식별자") project_id: int = Field(..., description="프로젝트 ID") @@ -158,6 +163,18 @@ class LyricListItem(BaseModel): Used as individual items in paginated lyric list responses. """ + model_config = ConfigDict( + json_schema_extra={ + "example": { + "id": 1, + "task_id": "0694b716-dbff-7219-8000-d08cb5fce431", + "status": "completed", + "lyric_result": "인스타 감성의 스테이 머뭄\n군산 신흥동 말랭이 마을에서...", + "created_at": "2024-01-15T12:00:00", + } + } + ) + id: int = Field(..., description="가사 ID") task_id: str = Field(..., description="작업 고유 식별자") status: str = Field(..., description="처리 상태")