from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.openapi.utils import get_openapi from fastapi.staticfiles import StaticFiles from scalar_fastapi import get_scalar_api_reference from app.admin_manager import init_admin from app.core.common import lifespan from app.database.session import engine # User 모델 import (테이블 메타데이터 등록용) from app.user.models import User, RefreshToken # noqa: F401 from app.archive.api.routers.v1.archive import router as archive_router from app.home.api.routers.v1.home import router as home_router from app.user.api.routers.v1.auth import router as auth_router, test_router as auth_test_router from app.lyric.api.routers.v1.lyric import router as lyric_router from app.song.api.routers.v1.song import router as song_router from app.video.api.routers.v1.video import router as video_router from app.utils.cors import CustomCORSMiddleware from config import prj_settings tags_metadata = [ { "name": "Auth", "description": """카카오 소셜 로그인 및 JWT 토큰 관리 API ## 인증 흐름 1. `GET /api/v1/user/auth/kakao/login` - 카카오 로그인 URL 획득 2. 사용자를 auth_url로 리다이렉트 → 카카오 로그인 3. 카카오에서 인가 코드(code) 발급 4. `POST /api/v1/user/auth/kakao/callback` - 인가 코드로 JWT 토큰 발급 5. 이후 API 호출 시 `Authorization: Bearer {access_token}` 헤더 사용 ## 토큰 관리 - **Access Token**: 1시간 유효, API 호출 시 사용 - **Refresh Token**: 7일 유효, Access Token 갱신 시 사용 ## Scalar에서 인증 사용하기 1. 카카오 로그인 또는 테스트 토큰 발급으로 `access_token` 획득 2. 우측 상단 **Authorize** 버튼 클릭 3. `access_token` 값 입력 (Bearer 접두사 없이 토큰만 입력) 4. **Authorize** 클릭하여 저장 5. 이후 인증이 필요한 API 호출 시 자동으로 토큰이 포함됨 """, }, # { # "name": "Home", # "description": "홈 화면 및 프로젝트 관리 API", # }, { "name": "Crawling", "description": """네이버 지도 크롤링 API - 장소 정보 및 이미지 수집 **인증: 불필요** (공개 API) """, }, { "name": "Image-Blob", "description": """이미지 업로드 API - Azure Blob Storage **인증: 필요** - `Authorization: Bearer {access_token}` 헤더 필수 """, }, { "name": "Lyric", "description": """가사 생성 및 관리 API **인증: 필요** - `Authorization: Bearer {access_token}` 헤더 필수 ## 가사 생성 흐름 1. `POST /api/v1/lyric/generate` - 가사 생성 요청 (백그라운드 처리) 2. `GET /api/v1/lyric/status/{task_id}` - 생성 상태 확인 3. `GET /api/v1/lyric/{task_id}` - 생성된 가사 조회 """, }, { "name": "Song", "description": """노래 생성 및 관리 API (Suno AI) **인증: 필요** - `Authorization: Bearer {access_token}` 헤더 필수 ## 노래 생성 흐름 1. `POST /api/v1/song/generate/{task_id}` - 노래 생성 요청 2. `GET /api/v1/song/status/{song_id}` - Suno API 상태 확인 """, }, { "name": "Video", "description": """영상 생성 및 관리 API (Creatomate) **인증: 필요** - `Authorization: Bearer {access_token}` 헤더 필수 ## 영상 생성 흐름 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 조회 """, }, { "name": "Archive", "description": """아카이브 API - 완료된 영상 목록 조회 및 삭제 **인증: 필요** - `Authorization: Bearer {access_token}` 헤더 필수 ## 주요 기능 - `GET /api/v1/archive/videos/` - 완료된 영상 목록 페이지네이션 조회 - `DELETE /api/v1/archive/videos/{task_id}` - 아카이브 영상 삭제 (CASCADE) ## 참고 - status가 'completed'인 영상만 반환됩니다. - 동일한 task_id가 있는 경우 가장 최근에 생성된 1개만 반환됩니다. - created_at 기준 내림차순 정렬됩니다. - 삭제 시 관련 Lyric, Song, Video가 CASCADE로 함께 삭제됩니다. """, }, ] # DEBUG 모드에서만 Test Auth 태그 추가 if prj_settings.DEBUG: tags_metadata.append( { "name": "Test Auth", "description": """테스트용 인증 API (DEBUG 모드 전용) **주의: 이 API는 DEBUG 모드에서만 사용 가능합니다.** 카카오 로그인 없이 테스트용 사용자 생성 및 토큰 발급이 가능합니다. ## 테스트 흐름 1. `POST /api/v1/user/auth/test/create-user` - 테스트 사용자 생성 2. `POST /api/v1/user/auth/test/generate-token` - JWT 토큰 발급 """, } ) app = FastAPI( title=prj_settings.PROJECT_NAME, version=prj_settings.VERSION, description=prj_settings.DESCRIPTION, lifespan=lifespan, docs_url=None, # 기본 Swagger UI 비활성화 redoc_url=None, # 기본 ReDoc 비활성화 openapi_tags=tags_metadata, ) def custom_openapi(): """커스텀 OpenAPI 스키마 생성 (Bearer 인증 추가)""" if app.openapi_schema: return app.openapi_schema openapi_schema = get_openapi( title=app.title, version=app.version, description=app.description, routes=app.routes, tags=tags_metadata, ) # Bearer 토큰 인증 스키마 추가 openapi_schema["components"]["securitySchemes"] = { "BearerAuth": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "JWT 액세스 토큰을 입력하세요. 카카오 로그인 후 발급받은 access_token을 사용합니다.", } } # 인증이 필요하지 않은 엔드포인트 (공개 API) public_endpoints = [ "/auth/kakao/login", "/auth/kakao/callback", "/auth/kakao/verify", "/auth/refresh", "/auth/test/", # 테스트 엔드포인트 "/crawling", "/autocomplete", ] # 보안이 필요한 엔드포인트에 security 적용 for path, path_item in openapi_schema["paths"].items(): for method, operation in path_item.items(): if method in ["get", "post", "put", "patch", "delete"]: # 공개 엔드포인트가 아닌 경우 인증 필요 is_public = any(public_path in path for public_path in public_endpoints) if not is_public and path.startswith("/api/"): operation["security"] = [{"BearerAuth": []}] app.openapi_schema = openapi_schema return app.openapi_schema app.openapi = custom_openapi init_admin(app, engine) custom_cors_middleware = CustomCORSMiddleware(app) custom_cors_middleware.configure_cors() app.mount("/static", StaticFiles(directory="static"), name="static") app.mount("/media", StaticFiles(directory="media"), name="media") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True, max_age=-1, ) @app.get("/docs", include_in_schema=False) def get_scalar_docs(): return get_scalar_api_reference( openapi_url=app.openapi_url, title="Scalar API", ) app.include_router(home_router) app.include_router(auth_router, prefix="/user") # Auth API 라우터 추가 app.include_router(lyric_router) app.include_router(song_router) app.include_router(video_router) app.include_router(archive_router) # Archive API 라우터 추가 # DEBUG 모드에서만 테스트 라우터 등록 if prj_settings.DEBUG: app.include_router(auth_test_router, prefix="/user") # Test Auth API 라우터