이미지 업로드 때 task_id 생성으로 변경, 가사 생성시 task_id 받아오는 것으로 변경

insta
bluebamus 2025-12-29 11:01:35 +09:00
parent f81d158f0f
commit c6d9edbb42
4 changed files with 130 additions and 53 deletions

View File

@ -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,
)

View File

@ -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 목록")

View File

@ -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:

View File

@ -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,18 +131,21 @@ class LyricDetailResponse(BaseModel):
Usage:
GET /lyric/{task_id}
Returns the generated lyric content for a specific task.
"""
Example Response:
{
model_config = ConfigDict(
json_schema_extra={
"example": {
"id": 1,
"task_id": "019123ab-cdef-7890-abcd-ef1234567890",
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
"project_id": 1,
"status": "completed",
"lyric_prompt": "...",
"lyric_result": "생성된 가사...",
"created_at": "2024-01-01T12:00:00"
"lyric_prompt": "고객명: 스테이 머뭄, 지역: 군산...",
"lyric_result": "인스타 감성의 스테이 머뭄\n군산 신흥동 말랭이 마을에서\n여유로운 하루를 보내며\n추억을 만들어가요",
"created_at": "2024-01-15T12:00:00",
}
"""
}
)
id: int = Field(..., description="가사 ID")
task_id: str = Field(..., description="작업 고유 식별자")
@ -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="처리 상태")