diff --git a/app/core/exceptions.py b/app/core/exceptions.py index 89a8d9d..abf8b26 100644 --- a/app/core/exceptions.py +++ b/app/core/exceptions.py @@ -304,11 +304,10 @@ def add_exception_handlers(app: FastAPI): @app.exception_handler(status.HTTP_500_INTERNAL_SERVER_ERROR) def internal_server_error_handler(request, exception): + # 에러 메시지 로깅 (한글 포함 가능) + logger.error(f"Internal Server Error: {exception}") return JSONResponse( content={"detail": "Something went wrong..."}, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - headers={ - "X-Error": f"{exception}", - } ) \ No newline at end of file diff --git a/app/social/api/routers/v1/upload.py b/app/social/api/routers/v1/upload.py index 79cf7bd..f9a3889 100644 --- a/app/social/api/routers/v1/upload.py +++ b/app/social/api/routers/v1/upload.py @@ -112,36 +112,45 @@ async def upload_to_social( ) raise SocialAccountNotFoundError() - # 3. 기존 업로드 확인 (동일 video + account 조합) - existing_result = await session.execute( + # 3. 진행 중인 업로드 확인 (pending 또는 uploading 상태만) + in_progress_result = await session.execute( select(SocialUpload).where( SocialUpload.video_id == body.video_id, SocialUpload.social_account_id == account.id, - SocialUpload.status.in_( - [UploadStatus.PENDING.value, UploadStatus.UPLOADING.value] - ), + SocialUpload.status.in_([UploadStatus.PENDING.value, UploadStatus.UPLOADING.value]), ) ) - existing_upload = existing_result.scalar_one_or_none() + in_progress_upload = in_progress_result.scalar_one_or_none() - if existing_upload: + if in_progress_upload: logger.info( - f"[UPLOAD_API] 진행 중인 업로드 존재 - upload_id: {existing_upload.id}" + f"[UPLOAD_API] 진행 중인 업로드 존재 - upload_id: {in_progress_upload.id}" ) return SocialUploadResponse( success=True, - upload_id=existing_upload.id, + upload_id=in_progress_upload.id, platform=account.platform, - status=existing_upload.status, + status=in_progress_upload.status, message="이미 업로드가 진행 중입니다.", ) - # 4. 새 업로드 레코드 생성 + # 4. 업로드 순번 계산 (동일 video + account 조합에서 최대 순번 + 1) + max_seq_result = await session.execute( + select(func.coalesce(func.max(SocialUpload.upload_seq), 0)).where( + SocialUpload.video_id == body.video_id, + SocialUpload.social_account_id == account.id, + ) + ) + max_seq = max_seq_result.scalar() or 0 + next_seq = max_seq + 1 + + # 5. 새 업로드 레코드 생성 (항상 새로 생성하여 이력 보존) social_upload = SocialUpload( user_uuid=current_user.user_uuid, video_id=body.video_id, social_account_id=account.id, - platform=account.platform, # 계정의 플랫폼 정보 사용 + upload_seq=next_seq, + platform=account.platform, status=UploadStatus.PENDING.value, upload_progress=0, title=body.title, @@ -161,10 +170,11 @@ async def upload_to_social( logger.info( f"[UPLOAD_API] 업로드 레코드 생성 - " - f"upload_id: {social_upload.id}, video_id: {body.video_id}, platform: {account.platform}" + f"upload_id: {social_upload.id}, video_id: {body.video_id}, " + f"account_id: {account.id}, upload_seq: {next_seq}, platform: {account.platform}" ) - # 5. 백그라운드 태스크 등록 + # 6. 백그라운드 태스크 등록 background_tasks.add_task(process_social_upload, social_upload.id) return SocialUploadResponse( @@ -211,6 +221,8 @@ async def get_upload_status( return SocialUploadStatusResponse( upload_id=upload.id, video_id=upload.video_id, + social_account_id=upload.social_account_id, + upload_seq=upload.upload_seq, platform=upload.platform, status=UploadStatus(upload.status), upload_progress=upload.upload_progress, @@ -282,6 +294,8 @@ async def get_upload_history( SocialUploadHistoryItem( upload_id=upload.id, video_id=upload.video_id, + social_account_id=upload.social_account_id, + upload_seq=upload.upload_seq, platform=upload.platform, status=upload.status, title=upload.title, diff --git a/app/social/models.py b/app/social/models.py index 81bd2e6..a5e7569 100644 --- a/app/social/models.py +++ b/app/social/models.py @@ -29,6 +29,7 @@ class SocialUpload(Base): user_uuid: 사용자 UUID (User.user_uuid 참조) video_id: Video 외래키 social_account_id: SocialAccount 외래키 + upload_seq: 업로드 순번 (동일 영상+채널 조합 내 순번, 관리자 추적용) platform: 플랫폼 구분 (youtube, instagram, facebook, tiktok) status: 업로드 상태 (pending, uploading, processing, completed, failed) upload_progress: 업로드 진행률 (0-100) @@ -58,12 +59,10 @@ class SocialUpload(Base): Index("idx_social_upload_platform", "platform"), Index("idx_social_upload_status", "status"), Index("idx_social_upload_created_at", "created_at"), - Index( - "uq_social_upload_video_platform", - "video_id", - "social_account_id", - unique=True, - ), + # 동일 영상+채널 조합 조회용 인덱스 (유니크 아님 - 여러 번 업로드 가능) + Index("idx_social_upload_video_account", "video_id", "social_account_id"), + # 순번 조회용 인덱스 + Index("idx_social_upload_seq", "video_id", "social_account_id", "upload_seq"), { "mysql_engine": "InnoDB", "mysql_charset": "utf8mb4", @@ -106,6 +105,16 @@ class SocialUpload(Base): comment="SocialAccount 외래키", ) + # ========================================================================== + # 업로드 순번 (관리자 추적용) + # ========================================================================== + upload_seq: Mapped[int] = mapped_column( + Integer, + nullable=False, + default=1, + comment="업로드 순번 (동일 영상+채널 조합 내 순번, 1부터 시작)", + ) + # ========================================================================== # 플랫폼 정보 # ========================================================================== @@ -238,8 +247,10 @@ class SocialUpload(Base): return ( f"" ) diff --git a/app/social/schemas.py b/app/social/schemas.py index 87e28a5..2a3a1f6 100644 --- a/app/social/schemas.py +++ b/app/social/schemas.py @@ -193,6 +193,8 @@ class SocialUploadStatusResponse(BaseModel): upload_id: int = Field(..., description="업로드 작업 ID") video_id: int = Field(..., description="영상 ID") + social_account_id: int = Field(..., description="소셜 계정 ID") + upload_seq: int = Field(..., description="업로드 순번 (동일 영상+채널 조합 내 순번)") platform: str = Field(..., description="플랫폼명") status: UploadStatus = Field(..., description="업로드 상태") upload_progress: int = Field(..., description="업로드 진행률 (0-100)") @@ -210,6 +212,8 @@ class SocialUploadStatusResponse(BaseModel): "example": { "upload_id": 456, "video_id": 123, + "social_account_id": 1, + "upload_seq": 2, "platform": "youtube", "status": "completed", "upload_progress": 100, @@ -230,6 +234,8 @@ class SocialUploadHistoryItem(BaseModel): upload_id: int = Field(..., description="업로드 작업 ID") video_id: int = Field(..., description="영상 ID") + social_account_id: int = Field(..., description="소셜 계정 ID") + upload_seq: int = Field(..., description="업로드 순번 (동일 영상+채널 조합 내 순번)") platform: str = Field(..., description="플랫폼명") status: str = Field(..., description="업로드 상태") title: str = Field(..., description="영상 제목") diff --git a/docs/database-schema/migration_social_upload_multiple.sql b/docs/database-schema/migration_social_upload_multiple.sql new file mode 100644 index 0000000..d77c067 --- /dev/null +++ b/docs/database-schema/migration_social_upload_multiple.sql @@ -0,0 +1,35 @@ +-- =================================================================== +-- social_upload 테이블 수정 마이그레이션 +-- 동일 영상 + 동일 채널 조합으로 여러 번 업로드 가능하도록 변경 +-- 관리자 추적을 위한 upload_seq 컬럼 추가 +-- 생성일: 2026-02-02 +-- =================================================================== + +-- 1. 기존 유니크 인덱스 제거 +DROP INDEX uq_social_upload_video_platform ON social_upload; + +-- 2. 업로드 순번 컬럼 추가 (관리자 추적용) +-- upload_seq: 동일 video_id + social_account_id 조합 내에서의 업로드 순번 +ALTER TABLE social_upload +ADD COLUMN upload_seq INT NOT NULL DEFAULT 1 COMMENT '업로드 순번 (동일 영상+채널 조합 내 순번)' AFTER social_account_id; + +-- 3. 추적을 위한 복합 인덱스 추가 (유니크 아님) +CREATE INDEX idx_social_upload_video_account ON social_upload(video_id, social_account_id); + +-- 4. 순번 조회를 위한 인덱스 추가 +CREATE INDEX idx_social_upload_seq ON social_upload(video_id, social_account_id, upload_seq); + +-- =================================================================== +-- 확인 쿼리 (실행 후 검증용) +-- =================================================================== +-- 테이블 구조 확인 +-- DESCRIBE social_upload; + +-- 인덱스 확인 +-- SHOW INDEX FROM social_upload; + +-- 특정 영상의 업로드 이력 조회 예시 +-- SELECT id, video_id, social_account_id, upload_seq, title, status, platform_url, created_at +-- FROM social_upload +-- WHERE video_id = 17 +-- ORDER BY upload_seq DESC;