From 247a9f3322a246adf55d89a238d22dfc6cbfd960 Mon Sep 17 00:00:00 2001 From: Dohyun Lim Date: Wed, 28 Jan 2026 19:41:18 +0900 Subject: [PATCH] merge with my-archive --- app/archive/api/routers/v1/archive.py | 3 ++ app/home/api/routers/v1/home.py | 1 + app/lyric/api/routers/v1/lyric.py | 22 ++++++++------ app/song/api/routers/v1/song.py | 8 +++-- app/user/api/routers/v1/auth.py | 12 ++++++++ app/user/schemas/user_schema.py | 2 +- app/video/api/routers/v1/video.py | 20 ++++++++----- config.py | 2 +- access_plan.md => docs/plan/access_plan.md | 0 token_plan.md => docs/plan/token_plan.md | 0 main.py | 34 ++++++++++++---------- 11 files changed, 67 insertions(+), 37 deletions(-) rename access_plan.md => docs/plan/access_plan.md (100%) rename token_plan.md => docs/plan/token_plan.md (100%) diff --git a/app/archive/api/routers/v1/archive.py b/app/archive/api/routers/v1/archive.py index 4e639d7..678ffbd 100644 --- a/app/archive/api/routers/v1/archive.py +++ b/app/archive/api/routers/v1/archive.py @@ -51,6 +51,7 @@ GET /archive/videos/?page=1&page_size=10 ``` ## 참고 +- **본인이 소유한 프로젝트의 영상만 반환됩니다.** - status가 'completed'인 영상만 반환됩니다. - 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다. - created_at 기준 내림차순 정렬됩니다. @@ -58,6 +59,7 @@ GET /archive/videos/?page=1&page_size=10 response_model=PaginatedResponse[VideoListItem], responses={ 200: {"description": "영상 목록 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 500: {"description": "조회 실패"}, }, ) @@ -243,6 +245,7 @@ task_id에 해당하는 프로젝트와 관련된 모든 데이터를 소프트 """, responses={ 200: {"description": "삭제 요청 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 403: {"description": "삭제 권한 없음"}, 404: {"description": "프로젝트를 찾을 수 없음"}, 500: {"description": "삭제 실패"}, diff --git a/app/home/api/routers/v1/home.py b/app/home/api/routers/v1/home.py index 6e66d1a..9ee7867 100644 --- a/app/home/api/routers/v1/home.py +++ b/app/home/api/routers/v1/home.py @@ -716,6 +716,7 @@ curl -X POST "http://localhost:8000/image/upload/blob" \\ responses={ 200: {"description": "이미지 업로드 성공"}, 400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, }, tags=["Image-Blob"], openapi_extra={ diff --git a/app/lyric/api/routers/v1/lyric.py b/app/lyric/api/routers/v1/lyric.py index 886bb33..1effee9 100644 --- a/app/lyric/api/routers/v1/lyric.py +++ b/app/lyric/api/routers/v1/lyric.py @@ -8,11 +8,11 @@ Lyric API Router - POST /lyric/generate: 가사 생성 - GET /lyric/status/{task_id}: 가사 생성 상태 조회 - GET /lyric/{task_id}: 가사 상세 조회 - - GET /lyrics: 가사 목록 조회 (페이지네이션) + - GET /lyric/list: 가사 목록 조회 (페이지네이션) 사용 예시: from app.lyric.api.routers.v1.lyric import router - app.include_router(router, prefix="/api/v1") + app.include_router(router) 다른 서비스에서 재사용: # 이 파일의 헬퍼 함수들을 import하여 사용 가능 @@ -197,7 +197,7 @@ async def get_lyric_by_task_id( ## 사용 예시 (cURL) ```bash -curl -X POST "http://localhost:8000/api/v1/lyric/generate" \\ +curl -X POST "http://localhost:8000/lyric/generate" \\ -H "Authorization: Bearer {access_token}" \\ -H "Content-Type: application/json" \\ -d '{ @@ -223,6 +223,7 @@ curl -X POST "http://localhost:8000/api/v1/lyric/generate" \\ response_model=GenerateLyricResponse, responses={ 200: {"description": "가사 생성 요청 접수 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 500: {"description": "서버 내부 오류"}, }, ) @@ -392,13 +393,14 @@ task_id로 가사 생성 작업의 현재 상태를 조회합니다. ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/lyric/status/019123ab-cdef-7890-abcd-ef1234567890" \\ +curl -X GET "http://localhost:8000/lyric/status/019123ab-cdef-7890-abcd-ef1234567890" \\ -H "Authorization: Bearer {access_token}" ``` """, response_model=LyricStatusResponse, responses={ 200: {"description": "상태 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 404: {"description": "해당 task_id를 찾을 수 없음"}, }, ) @@ -412,7 +414,7 @@ async def get_lyric_status( @router.get( - "s/", + "/list", summary="가사 목록 조회 (페이지네이션)", description=""" 생성 완료된 가사를 페이지네이션으로 조회합니다. @@ -436,15 +438,15 @@ async def get_lyric_status( ## 사용 예시 (cURL) ```bash # 기본 조회 (1페이지, 20개) -curl -X GET "http://localhost:8000/api/v1/lyrics/" \\ +curl -X GET "http://localhost:8000/lyric/list" \\ -H "Authorization: Bearer {access_token}" # 2페이지 조회 -curl -X GET "http://localhost:8000/api/v1/lyrics/?page=2" \\ +curl -X GET "http://localhost:8000/lyric/list?page=2" \\ -H "Authorization: Bearer {access_token}" # 50개씩 조회 -curl -X GET "http://localhost:8000/api/v1/lyrics/?page=1&page_size=50" \\ +curl -X GET "http://localhost:8000/lyric/list?page=1&page_size=50" \\ -H "Authorization: Bearer {access_token}" ``` @@ -455,6 +457,7 @@ curl -X GET "http://localhost:8000/api/v1/lyrics/?page=1&page_size=50" \\ response_model=PaginatedResponse[LyricListItem], responses={ 200: {"description": "가사 목록 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, }, ) async def list_lyrics( @@ -496,13 +499,14 @@ task_id로 생성된 가사의 상세 정보를 조회합니다. ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/lyric/019123ab-cdef-7890-abcd-ef1234567890" \\ +curl -X GET "http://localhost:8000/lyric/019123ab-cdef-7890-abcd-ef1234567890" \\ -H "Authorization: Bearer {access_token}" ``` """, response_model=LyricDetailResponse, responses={ 200: {"description": "가사 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 404: {"description": "해당 task_id를 찾을 수 없음"}, }, ) diff --git a/app/song/api/routers/v1/song.py b/app/song/api/routers/v1/song.py index 3dd4e08..3f2a07b 100644 --- a/app/song/api/routers/v1/song.py +++ b/app/song/api/routers/v1/song.py @@ -9,7 +9,7 @@ Song API Router 사용 예시: from app.song.api.routers.v1.song import router - app.include_router(router, prefix="/api/v1") + app.include_router(router) """ from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException @@ -61,7 +61,7 @@ Suno API를 통해 노래 생성을 요청합니다. ## 사용 예시 (cURL) ```bash -curl -X POST "http://localhost:8000/api/v1/song/generate/019123ab-cdef-7890-abcd-ef1234567890" \\ +curl -X POST "http://localhost:8000/song/generate/019123ab-cdef-7890-abcd-ef1234567890" \\ -H "Authorization: Bearer {access_token}" \\ -H "Content-Type: application/json" \\ -d '{ @@ -79,6 +79,7 @@ curl -X POST "http://localhost:8000/api/v1/song/generate/019123ab-cdef-7890-abcd response_model=GenerateSongResponse, responses={ 200: {"description": "노래 생성 요청 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 404: {"description": "Project 또는 Lyric을 찾을 수 없음"}, 500: {"description": "노래 생성 요청 실패"}, }, @@ -334,7 +335,7 @@ SUCCESS 상태인 경우 백그라운드에서 MP3 파일을 다운로드하고 ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/song/status/{song_id}" \\ +curl -X GET "http://localhost:8000/song/status/{song_id}" \\ -H "Authorization: Bearer {access_token}" ``` @@ -354,6 +355,7 @@ curl -X GET "http://localhost:8000/api/v1/song/status/{song_id}" \\ response_model=PollingSongResponse, responses={ 200: {"description": "상태 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, }, ) async def get_song_status( diff --git a/app/user/api/routers/v1/auth.py b/app/user/api/routers/v1/auth.py index 76e61e9..2c730f9 100644 --- a/app/user/api/routers/v1/auth.py +++ b/app/user/api/routers/v1/auth.py @@ -238,6 +238,10 @@ async def refresh_token( status_code=status.HTTP_204_NO_CONTENT, summary="로그아웃", description="현재 세션의 리프레시 토큰을 폐기합니다.", + responses={ + 204: {"description": "로그아웃 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, + }, ) async def logout( body: RefreshTokenRequest, @@ -263,6 +267,10 @@ async def logout( status_code=status.HTTP_204_NO_CONTENT, summary="모든 기기에서 로그아웃", description="사용자의 모든 리프레시 토큰을 폐기합니다.", + responses={ + 204: {"description": "모든 기기에서 로그아웃 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, + }, ) async def logout_all( current_user: User = Depends(get_current_user), @@ -286,6 +294,10 @@ async def logout_all( response_model=UserResponse, summary="내 정보 조회", description="현재 로그인한 사용자의 정보를 반환합니다.", + responses={ + 200: {"description": "조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, + }, ) async def get_me( current_user: User = Depends(get_current_user), diff --git a/app/user/schemas/user_schema.py b/app/user/schemas/user_schema.py index 918f4c5..e0c6337 100644 --- a/app/user/schemas/user_schema.py +++ b/app/user/schemas/user_schema.py @@ -21,7 +21,7 @@ class KakaoLoginResponse(BaseModel): model_config = { "json_schema_extra": { "example": { - "auth_url": "https://kauth.kakao.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8000/api/v1/user/auth/kakao/callback&response_type=code" + "auth_url": "https://kauth.kakao.com/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=http://localhost:8000/user/auth/kakao/callback&response_type=code" } } } diff --git a/app/video/api/routers/v1/video.py b/app/video/api/routers/v1/video.py index b4d39b5..9f268b7 100644 --- a/app/video/api/routers/v1/video.py +++ b/app/video/api/routers/v1/video.py @@ -7,11 +7,11 @@ Video API Router - POST /video/generate/{task_id}: 영상 생성 요청 (task_id로 Project/Lyric/Song 연결) - GET /video/status/{creatomate_render_id}: Creatomate API 영상 생성 상태 조회 - GET /video/download/{task_id}: 영상 다운로드 상태 조회 (DB polling) - - GET /videos/: 완료된 영상 목록 조회 (페이지네이션) + - GET /video/list: 완료된 영상 목록 조회 (페이지네이션) 사용 예시: from app.video.api.routers.v1.video import router - app.include_router(router, prefix="/api/v1") + app.include_router(router) """ import json @@ -76,11 +76,11 @@ Creatomate API를 통해 영상 생성을 요청합니다. ## 사용 예시 (cURL) ```bash # 세로형 영상 생성 (기본값) -curl -X GET "http://localhost:8000/api/v1/video/generate/0694b716-dbff-7219-8000-d08cb5fce431" \\ +curl -X GET "http://localhost:8000/video/generate/0694b716-dbff-7219-8000-d08cb5fce431" \\ -H "Authorization: Bearer {access_token}" # 가로형 영상 생성 -curl -X GET "http://localhost:8000/api/v1/video/generate/0694b716-dbff-7219-8000-d08cb5fce431?orientation=horizontal" \\ +curl -X GET "http://localhost:8000/video/generate/0694b716-dbff-7219-8000-d08cb5fce431?orientation=horizontal" \\ -H "Authorization: Bearer {access_token}" ``` @@ -96,6 +96,7 @@ curl -X GET "http://localhost:8000/api/v1/video/generate/0694b716-dbff-7219-8000 responses={ 200: {"description": "영상 생성 요청 성공"}, 400: {"description": "Song의 음악 URL, 가사(song_prompt) 또는 이미지가 없음"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 404: {"description": "Project, Lyric, Song 또는 Image를 찾을 수 없음"}, 500: {"description": "영상 생성 요청 실패"}, }, @@ -480,7 +481,7 @@ succeeded 상태인 경우 백그라운드에서 MP4 파일을 다운로드하 ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/video/status/{creatomate_render_id}" \\ +curl -X GET "http://localhost:8000/video/status/{creatomate_render_id}" \\ -H "Authorization: Bearer {access_token}" ``` @@ -498,6 +499,7 @@ curl -X GET "http://localhost:8000/api/v1/video/status/{creatomate_render_id}" \ response_model=PollingVideoResponse, responses={ 200: {"description": "상태 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 500: {"description": "상태 조회 실패"}, }, ) @@ -633,7 +635,7 @@ completed인 경우 Project 정보와 영상 URL을 반환합니다. ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/video/download/019123ab-cdef-7890-abcd-ef1234567890" \\ +curl -X GET "http://localhost:8000/video/download/019123ab-cdef-7890-abcd-ef1234567890" \\ -H "Authorization: Bearer {access_token}" ``` @@ -644,6 +646,7 @@ curl -X GET "http://localhost:8000/api/v1/video/download/019123ab-cdef-7890-abcd response_model=DownloadVideoResponse, responses={ 200: {"description": "조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 404: {"description": "Video를 찾을 수 없음"}, 500: {"description": "조회 실패"}, }, @@ -730,7 +733,7 @@ async def download_video( @router.get( - "s/", + "/list", summary="생성된 영상 목록 조회", description=""" 완료된 영상 목록을 페이지네이션하여 조회합니다. @@ -753,7 +756,7 @@ async def download_video( ## 사용 예시 (cURL) ```bash -curl -X GET "http://localhost:8000/api/v1/videos/?page=1&page_size=10" \\ +curl -X GET "http://localhost:8000/video/list?page=1&page_size=10" \\ -H "Authorization: Bearer {access_token}" ``` @@ -765,6 +768,7 @@ curl -X GET "http://localhost:8000/api/v1/videos/?page=1&page_size=10" \\ response_model=PaginatedResponse[VideoListItem], responses={ 200: {"description": "영상 목록 조회 성공"}, + 401: {"description": "인증 실패 (토큰 없음/만료)"}, 500: {"description": "조회 실패"}, }, ) diff --git a/config.py b/config.py index 6feb831..40489c5 100644 --- a/config.py +++ b/config.py @@ -216,7 +216,7 @@ class KakaoSettings(BaseSettings): KAKAO_CLIENT_ID: str = Field(default="", description="카카오 REST API 키") KAKAO_CLIENT_SECRET: str = Field(default="", description="카카오 Client Secret (선택)") KAKAO_REDIRECT_URI: str = Field( - default="http://localhost:8000/api/v1/user/auth/kakao/callback", + default="http://localhost:8000/user/auth/kakao/callback", description="카카오 로그인 후 리다이렉트 URI", ) diff --git a/access_plan.md b/docs/plan/access_plan.md similarity index 100% rename from access_plan.md rename to docs/plan/access_plan.md diff --git a/token_plan.md b/docs/plan/token_plan.md similarity index 100% rename from token_plan.md rename to docs/plan/token_plan.md diff --git a/main.py b/main.py index 4977749..516cada 100644 --- a/main.py +++ b/main.py @@ -27,10 +27,10 @@ tags_metadata = [ ## 인증 흐름 -1. `GET /api/v1/user/auth/kakao/login` - 카카오 로그인 URL 획득 +1. `GET /user/auth/kakao/login` - 카카오 로그인 URL 획득 2. 사용자를 auth_url로 리다이렉트 → 카카오 로그인 3. 카카오에서 인가 코드(code) 발급 -4. `POST /api/v1/user/auth/kakao/callback` - 인가 코드로 JWT 토큰 발급 +4. `POST /user/auth/kakao/callback` - 인가 코드로 JWT 토큰 발급 5. 이후 API 호출 시 `Authorization: Bearer {access_token}` 헤더 사용 ## 토큰 관리 @@ -73,9 +73,10 @@ tags_metadata = [ ## 가사 생성 흐름 -1. `POST /api/v1/lyric/generate` - 가사 생성 요청 (백그라운드 처리) -2. `GET /api/v1/lyric/status/{task_id}` - 생성 상태 확인 -3. `GET /api/v1/lyric/{task_id}` - 생성된 가사 조회 +1. `POST /lyric/generate` - 가사 생성 요청 (백그라운드 처리) +2. `GET /lyric/status/{task_id}` - 생성 상태 확인 +3. `GET /lyric/{task_id}` - 생성된 가사 조회 +4. `GET /lyric/list` - 가사 목록 조회 (페이지네이션) """, }, { @@ -86,8 +87,8 @@ tags_metadata = [ ## 노래 생성 흐름 -1. `POST /api/v1/song/generate/{task_id}` - 노래 생성 요청 -2. `GET /api/v1/song/status/{song_id}` - Suno API 상태 확인 +1. `POST /song/generate/{task_id}` - 노래 생성 요청 +2. `GET /song/status/{song_id}` - Suno API 상태 확인 """, }, { @@ -98,9 +99,10 @@ tags_metadata = [ ## 영상 생성 흐름 -1. `GET /api/v1/video/generate/{task_id}` - 영상 생성 요청 -2. `GET /api/v1/video/status/{creatomate_render_id}` - Creatomate 상태 확인 -3. `GET /api/v1/video/download/{task_id}` - 영상 다운로드 URL 조회 +1. `GET /video/generate/{task_id}` - 영상 생성 요청 +2. `GET /video/status/{creatomate_render_id}` - Creatomate 상태 확인 +3. `GET /video/download/{task_id}` - 영상 다운로드 URL 조회 +4. `GET /video/list` - 영상 목록 조회 (페이지네이션) """, }, { @@ -111,15 +113,17 @@ tags_metadata = [ ## 주요 기능 -- `GET /api/v1/archive/videos/` - 완료된 영상 목록 페이지네이션 조회 -- `DELETE /api/v1/archive/videos/{task_id}` - 아카이브 영상 삭제 (CASCADE) +- `GET /archive/videos/` - 완료된 영상 목록 페이지네이션 조회 +- `DELETE /archive/videos/delete/{task_id}` - 아카이브 영상 소프트 삭제 ## 참고 +- **본인 소유의 데이터만 조회/삭제 가능합니다.** - status가 'completed'인 영상만 반환됩니다. - 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다. - created_at 기준 내림차순 정렬됩니다. -- 삭제 시 관련 Lyric, Song, Video가 CASCADE로 함께 삭제됩니다. +- 삭제는 소프트 삭제(is_deleted=True) 방식으로 처리되며, 데이터 복구가 가능합니다. +- 삭제 대상: Video, SongTimestamp, Song, Lyric, Image, Project """, }, ] @@ -137,8 +141,8 @@ if prj_settings.DEBUG: ## 테스트 흐름 -1. `POST /api/v1/user/auth/test/create-user` - 테스트 사용자 생성 -2. `POST /api/v1/user/auth/test/generate-token` - JWT 토큰 발급 +1. `POST /user/auth/test/create-user` - 테스트 사용자 생성 +2. `POST /user/auth/test/generate-token` - JWT 토큰 발급 """, } )