merge with my-archive

insta
Dohyun Lim 2026-01-28 19:41:18 +09:00
parent c07a2f6dae
commit 247a9f3322
11 changed files with 67 additions and 37 deletions

View File

@ -51,6 +51,7 @@ GET /archive/videos/?page=1&page_size=10
``` ```
## 참고 ## 참고
- **본인이 소유한 프로젝트의 영상만 반환됩니다.**
- status가 'completed' 영상만 반환됩니다. - status가 'completed' 영상만 반환됩니다.
- 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다. - 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다.
- created_at 기준 내림차순 정렬됩니다. - created_at 기준 내림차순 정렬됩니다.
@ -58,6 +59,7 @@ GET /archive/videos/?page=1&page_size=10
response_model=PaginatedResponse[VideoListItem], response_model=PaginatedResponse[VideoListItem],
responses={ responses={
200: {"description": "영상 목록 조회 성공"}, 200: {"description": "영상 목록 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
500: {"description": "조회 실패"}, 500: {"description": "조회 실패"},
}, },
) )
@ -243,6 +245,7 @@ task_id에 해당하는 프로젝트와 관련된 모든 데이터를 소프트
""", """,
responses={ responses={
200: {"description": "삭제 요청 성공"}, 200: {"description": "삭제 요청 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
403: {"description": "삭제 권한 없음"}, 403: {"description": "삭제 권한 없음"},
404: {"description": "프로젝트를 찾을 수 없음"}, 404: {"description": "프로젝트를 찾을 수 없음"},
500: {"description": "삭제 실패"}, 500: {"description": "삭제 실패"},

View File

@ -716,6 +716,7 @@ curl -X POST "http://localhost:8000/image/upload/blob" \\
responses={ responses={
200: {"description": "이미지 업로드 성공"}, 200: {"description": "이미지 업로드 성공"},
400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse}, 400: {"description": "이미지가 제공되지 않음", "model": ErrorResponse},
401: {"description": "인증 실패 (토큰 없음/만료)"},
}, },
tags=["Image-Blob"], tags=["Image-Blob"],
openapi_extra={ openapi_extra={

View File

@ -8,11 +8,11 @@ Lyric API Router
- POST /lyric/generate: 가사 생성 - POST /lyric/generate: 가사 생성
- GET /lyric/status/{task_id}: 가사 생성 상태 조회 - GET /lyric/status/{task_id}: 가사 생성 상태 조회
- GET /lyric/{task_id}: 가사 상세 조회 - GET /lyric/{task_id}: 가사 상세 조회
- GET /lyrics: 가사 목록 조회 (페이지네이션) - GET /lyric/list: 가사 목록 조회 (페이지네이션)
사용 예시: 사용 예시:
from app.lyric.api.routers.v1.lyric import router from app.lyric.api.routers.v1.lyric import router
app.include_router(router, prefix="/api/v1") app.include_router(router)
다른 서비스에서 재사용: 다른 서비스에서 재사용:
# 이 파일의 헬퍼 함수들을 import하여 사용 가능 # 이 파일의 헬퍼 함수들을 import하여 사용 가능
@ -197,7 +197,7 @@ async def get_lyric_by_task_id(
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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 "Authorization: Bearer {access_token}" \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-d '{ -d '{
@ -223,6 +223,7 @@ curl -X POST "http://localhost:8000/api/v1/lyric/generate" \\
response_model=GenerateLyricResponse, response_model=GenerateLyricResponse,
responses={ responses={
200: {"description": "가사 생성 요청 접수 성공"}, 200: {"description": "가사 생성 요청 접수 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
500: {"description": "서버 내부 오류"}, 500: {"description": "서버 내부 오류"},
}, },
) )
@ -392,13 +393,14 @@ task_id로 가사 생성 작업의 현재 상태를 조회합니다.
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -H "Authorization: Bearer {access_token}"
``` ```
""", """,
response_model=LyricStatusResponse, response_model=LyricStatusResponse,
responses={ responses={
200: {"description": "상태 조회 성공"}, 200: {"description": "상태 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
404: {"description": "해당 task_id를 찾을 수 없음"}, 404: {"description": "해당 task_id를 찾을 수 없음"},
}, },
) )
@ -412,7 +414,7 @@ async def get_lyric_status(
@router.get( @router.get(
"s/", "/list",
summary="가사 목록 조회 (페이지네이션)", summary="가사 목록 조회 (페이지네이션)",
description=""" description="""
생성 완료된 가사를 페이지네이션으로 조회합니다. 생성 완료된 가사를 페이지네이션으로 조회합니다.
@ -436,15 +438,15 @@ async def get_lyric_status(
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```bash
# 기본 조회 (1페이지, 20개) # 기본 조회 (1페이지, 20개)
curl -X GET "http://localhost:8000/api/v1/lyrics/" \\ curl -X GET "http://localhost:8000/lyric/list" \\
-H "Authorization: Bearer {access_token}" -H "Authorization: Bearer {access_token}"
# 2페이지 조회 # 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}" -H "Authorization: Bearer {access_token}"
# 50개씩 조회 # 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}" -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], response_model=PaginatedResponse[LyricListItem],
responses={ responses={
200: {"description": "가사 목록 조회 성공"}, 200: {"description": "가사 목록 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
}, },
) )
async def list_lyrics( async def list_lyrics(
@ -496,13 +499,14 @@ task_id로 생성된 가사의 상세 정보를 조회합니다.
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -H "Authorization: Bearer {access_token}"
``` ```
""", """,
response_model=LyricDetailResponse, response_model=LyricDetailResponse,
responses={ responses={
200: {"description": "가사 조회 성공"}, 200: {"description": "가사 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
404: {"description": "해당 task_id를 찾을 수 없음"}, 404: {"description": "해당 task_id를 찾을 수 없음"},
}, },
) )

View File

@ -9,7 +9,7 @@ Song API Router
사용 예시: 사용 예시:
from app.song.api.routers.v1.song import 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 from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
@ -61,7 +61,7 @@ Suno API를 통해 노래 생성을 요청합니다.
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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 "Authorization: Bearer {access_token}" \\
-H "Content-Type: application/json" \\ -H "Content-Type: application/json" \\
-d '{ -d '{
@ -79,6 +79,7 @@ curl -X POST "http://localhost:8000/api/v1/song/generate/019123ab-cdef-7890-abcd
response_model=GenerateSongResponse, response_model=GenerateSongResponse,
responses={ responses={
200: {"description": "노래 생성 요청 성공"}, 200: {"description": "노래 생성 요청 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
404: {"description": "Project 또는 Lyric을 찾을 수 없음"}, 404: {"description": "Project 또는 Lyric을 찾을 수 없음"},
500: {"description": "노래 생성 요청 실패"}, 500: {"description": "노래 생성 요청 실패"},
}, },
@ -334,7 +335,7 @@ SUCCESS 상태인 경우 백그라운드에서 MP3 파일을 다운로드하고
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -H "Authorization: Bearer {access_token}"
``` ```
@ -354,6 +355,7 @@ curl -X GET "http://localhost:8000/api/v1/song/status/{song_id}" \\
response_model=PollingSongResponse, response_model=PollingSongResponse,
responses={ responses={
200: {"description": "상태 조회 성공"}, 200: {"description": "상태 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
}, },
) )
async def get_song_status( async def get_song_status(

View File

@ -238,6 +238,10 @@ async def refresh_token(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
summary="로그아웃", summary="로그아웃",
description="현재 세션의 리프레시 토큰을 폐기합니다.", description="현재 세션의 리프레시 토큰을 폐기합니다.",
responses={
204: {"description": "로그아웃 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
},
) )
async def logout( async def logout(
body: RefreshTokenRequest, body: RefreshTokenRequest,
@ -263,6 +267,10 @@ async def logout(
status_code=status.HTTP_204_NO_CONTENT, status_code=status.HTTP_204_NO_CONTENT,
summary="모든 기기에서 로그아웃", summary="모든 기기에서 로그아웃",
description="사용자의 모든 리프레시 토큰을 폐기합니다.", description="사용자의 모든 리프레시 토큰을 폐기합니다.",
responses={
204: {"description": "모든 기기에서 로그아웃 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
},
) )
async def logout_all( async def logout_all(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
@ -286,6 +294,10 @@ async def logout_all(
response_model=UserResponse, response_model=UserResponse,
summary="내 정보 조회", summary="내 정보 조회",
description="현재 로그인한 사용자의 정보를 반환합니다.", description="현재 로그인한 사용자의 정보를 반환합니다.",
responses={
200: {"description": "조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
},
) )
async def get_me( async def get_me(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),

View File

@ -21,7 +21,7 @@ class KakaoLoginResponse(BaseModel):
model_config = { model_config = {
"json_schema_extra": { "json_schema_extra": {
"example": { "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"
} }
} }
} }

View File

@ -7,11 +7,11 @@ Video API Router
- POST /video/generate/{task_id}: 영상 생성 요청 (task_id로 Project/Lyric/Song 연결) - POST /video/generate/{task_id}: 영상 생성 요청 (task_id로 Project/Lyric/Song 연결)
- GET /video/status/{creatomate_render_id}: Creatomate API 영상 생성 상태 조회 - GET /video/status/{creatomate_render_id}: Creatomate API 영상 생성 상태 조회
- GET /video/download/{task_id}: 영상 다운로드 상태 조회 (DB polling) - GET /video/download/{task_id}: 영상 다운로드 상태 조회 (DB polling)
- GET /videos/: 완료된 영상 목록 조회 (페이지네이션) - GET /video/list: 완료된 영상 목록 조회 (페이지네이션)
사용 예시: 사용 예시:
from app.video.api.routers.v1.video import router from app.video.api.routers.v1.video import router
app.include_router(router, prefix="/api/v1") app.include_router(router)
""" """
import json import json
@ -76,11 +76,11 @@ Creatomate API를 통해 영상 생성을 요청합니다.
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -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}" -H "Authorization: Bearer {access_token}"
``` ```
@ -96,6 +96,7 @@ curl -X GET "http://localhost:8000/api/v1/video/generate/0694b716-dbff-7219-8000
responses={ responses={
200: {"description": "영상 생성 요청 성공"}, 200: {"description": "영상 생성 요청 성공"},
400: {"description": "Song의 음악 URL, 가사(song_prompt) 또는 이미지가 없음"}, 400: {"description": "Song의 음악 URL, 가사(song_prompt) 또는 이미지가 없음"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
404: {"description": "Project, Lyric, Song 또는 Image를 찾을 수 없음"}, 404: {"description": "Project, Lyric, Song 또는 Image를 찾을 수 없음"},
500: {"description": "영상 생성 요청 실패"}, 500: {"description": "영상 생성 요청 실패"},
}, },
@ -480,7 +481,7 @@ succeeded 상태인 경우 백그라운드에서 MP4 파일을 다운로드하
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -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, response_model=PollingVideoResponse,
responses={ responses={
200: {"description": "상태 조회 성공"}, 200: {"description": "상태 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
500: {"description": "상태 조회 실패"}, 500: {"description": "상태 조회 실패"},
}, },
) )
@ -633,7 +635,7 @@ completed인 경우 Project 정보와 영상 URL을 반환합니다.
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -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, response_model=DownloadVideoResponse,
responses={ responses={
200: {"description": "조회 성공"}, 200: {"description": "조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
404: {"description": "Video를 찾을 수 없음"}, 404: {"description": "Video를 찾을 수 없음"},
500: {"description": "조회 실패"}, 500: {"description": "조회 실패"},
}, },
@ -730,7 +733,7 @@ async def download_video(
@router.get( @router.get(
"s/", "/list",
summary="생성된 영상 목록 조회", summary="생성된 영상 목록 조회",
description=""" description="""
완료된 영상 목록을 페이지네이션하여 조회합니다. 완료된 영상 목록을 페이지네이션하여 조회합니다.
@ -753,7 +756,7 @@ async def download_video(
## 사용 예시 (cURL) ## 사용 예시 (cURL)
```bash ```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}" -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], response_model=PaginatedResponse[VideoListItem],
responses={ responses={
200: {"description": "영상 목록 조회 성공"}, 200: {"description": "영상 목록 조회 성공"},
401: {"description": "인증 실패 (토큰 없음/만료)"},
500: {"description": "조회 실패"}, 500: {"description": "조회 실패"},
}, },
) )

View File

@ -216,7 +216,7 @@ class KakaoSettings(BaseSettings):
KAKAO_CLIENT_ID: str = Field(default="", description="카카오 REST API 키") KAKAO_CLIENT_ID: str = Field(default="", description="카카오 REST API 키")
KAKAO_CLIENT_SECRET: str = Field(default="", description="카카오 Client Secret (선택)") KAKAO_CLIENT_SECRET: str = Field(default="", description="카카오 Client Secret (선택)")
KAKAO_REDIRECT_URI: str = Field( 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", description="카카오 로그인 후 리다이렉트 URI",
) )

34
main.py
View File

@ -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로 리다이렉트 카카오 로그인 2. 사용자를 auth_url로 리다이렉트 카카오 로그인
3. 카카오에서 인가 코드(code) 발급 3. 카카오에서 인가 코드(code) 발급
4. `POST /api/v1/user/auth/kakao/callback` - 인가 코드로 JWT 토큰 발급 4. `POST /user/auth/kakao/callback` - 인가 코드로 JWT 토큰 발급
5. 이후 API 호출 `Authorization: Bearer {access_token}` 헤더 사용 5. 이후 API 호출 `Authorization: Bearer {access_token}` 헤더 사용
## 토큰 관리 ## 토큰 관리
@ -73,9 +73,10 @@ tags_metadata = [
## 가사 생성 흐름 ## 가사 생성 흐름
1. `POST /api/v1/lyric/generate` - 가사 생성 요청 (백그라운드 처리) 1. `POST /lyric/generate` - 가사 생성 요청 (백그라운드 처리)
2. `GET /api/v1/lyric/status/{task_id}` - 생성 상태 확인 2. `GET /lyric/status/{task_id}` - 생성 상태 확인
3. `GET /api/v1/lyric/{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}` - 노래 생성 요청 1. `POST /song/generate/{task_id}` - 노래 생성 요청
2. `GET /api/v1/song/status/{song_id}` - Suno API 상태 확인 2. `GET /song/status/{song_id}` - Suno API 상태 확인
""", """,
}, },
{ {
@ -98,9 +99,10 @@ tags_metadata = [
## 영상 생성 흐름 ## 영상 생성 흐름
1. `GET /api/v1/video/generate/{task_id}` - 영상 생성 요청 1. `GET /video/generate/{task_id}` - 영상 생성 요청
2. `GET /api/v1/video/status/{creatomate_render_id}` - Creatomate 상태 확인 2. `GET /video/status/{creatomate_render_id}` - Creatomate 상태 확인
3. `GET /api/v1/video/download/{task_id}` - 영상 다운로드 URL 조회 3. `GET /video/download/{task_id}` - 영상 다운로드 URL 조회
4. `GET /video/list` - 영상 목록 조회 (페이지네이션)
""", """,
}, },
{ {
@ -111,15 +113,17 @@ tags_metadata = [
## 주요 기능 ## 주요 기능
- `GET /api/v1/archive/videos/` - 완료된 영상 목록 페이지네이션 조회 - `GET /archive/videos/` - 완료된 영상 목록 페이지네이션 조회
- `DELETE /api/v1/archive/videos/{task_id}` - 아카이브 영상 삭제 (CASCADE) - `DELETE /archive/videos/delete/{task_id}` - 아카이브 영상 소프트 삭제
## 참고 ## 참고
- **본인 소유의 데이터만 조회/삭제 가능합니다.**
- status가 'completed' 영상만 반환됩니다. - status가 'completed' 영상만 반환됩니다.
- 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다. - 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다.
- created_at 기준 내림차순 정렬됩니다. - 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` - 테스트 사용자 생성 1. `POST /user/auth/test/create-user` - 테스트 사용자 생성
2. `POST /api/v1/user/auth/test/generate-token` - JWT 토큰 발급 2. `POST /user/auth/test/generate-token` - JWT 토큰 발급
""", """,
} }
) )