""" SocialAccount API 라우터 소셜 계정 연동 CRUD 엔드포인트를 제공합니다. """ import logging from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.database.session import get_session from app.user.dependencies import get_current_user from app.user.models import User from app.user.schemas.social_account_schema import ( SocialAccountCreateRequest, SocialAccountDeleteResponse, SocialAccountListResponse, SocialAccountResponse, SocialAccountUpdateRequest, ) from app.user.services.social_account import SocialAccountService logger = logging.getLogger(__name__) router = APIRouter(prefix="/social-accounts", tags=["Social Account"]) # ============================================================================= # 소셜 계정 목록 조회 # ============================================================================= @router.get( "", response_model=SocialAccountListResponse, summary="소셜 계정 목록 조회", description=""" ## 개요 현재 로그인한 사용자의 연동된 소셜 계정 목록을 조회합니다. ## 인증 - Bearer 토큰 필수 ## 반환 정보 - **items**: 소셜 계정 목록 - **total**: 총 계정 수 """, ) async def get_social_accounts( current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> SocialAccountListResponse: """소셜 계정 목록 조회""" logger.info(f"[get_social_accounts] START - user_uuid: {current_user.user_uuid}") try: service = SocialAccountService(session) accounts = await service.get_list(current_user) response = SocialAccountListResponse( items=[SocialAccountResponse.model_validate(acc) for acc in accounts], total=len(accounts), ) logger.info(f"[get_social_accounts] SUCCESS - user_uuid: {current_user.user_uuid}, count: {len(accounts)}") return response except Exception as e: logger.error(f"[get_social_accounts] ERROR - user_uuid: {current_user.user_uuid}, error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="소셜 계정 목록 조회 중 오류가 발생했습니다.", ) # ============================================================================= # 소셜 계정 상세 조회 # ============================================================================= @router.get( "/{account_id}", response_model=SocialAccountResponse, summary="소셜 계정 상세 조회", description=""" ## 개요 특정 소셜 계정의 상세 정보를 조회합니다. ## 인증 - Bearer 토큰 필수 - 본인 소유의 계정만 조회 가능 ## 경로 파라미터 - **account_id**: 소셜 계정 ID """, responses={ 404: {"description": "소셜 계정을 찾을 수 없음"}, }, ) async def get_social_account( account_id: int, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> SocialAccountResponse: """소셜 계정 상세 조회""" logger.info(f"[get_social_account] START - user_uuid: {current_user.user_uuid}, account_id: {account_id}") try: service = SocialAccountService(session) account = await service.get_by_id(current_user, account_id) if not account: logger.warning(f"[get_social_account] NOT_FOUND - account_id: {account_id}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="소셜 계정을 찾을 수 없습니다.", ) logger.info(f"[get_social_account] SUCCESS - account_id: {account_id}, platform: {account.platform}") return SocialAccountResponse.model_validate(account) except HTTPException: raise except Exception as e: logger.error(f"[get_social_account] ERROR - account_id: {account_id}, error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="소셜 계정 조회 중 오류가 발생했습니다.", ) # ============================================================================= # 소셜 계정 생성 # ============================================================================= @router.post( "", response_model=SocialAccountResponse, status_code=status.HTTP_201_CREATED, summary="소셜 계정 연동", description=""" ## 개요 새로운 소셜 계정을 연동합니다. ## 인증 - Bearer 토큰 필수 ## 요청 본문 - **platform**: 플랫폼 구분 (youtube, instagram, facebook, tiktok) - **access_token**: OAuth 액세스 토큰 - **platform_user_id**: 플랫폼 내 사용자 고유 ID - 기타 선택 필드 ## 주의사항 - 동일한 플랫폼의 동일한 계정은 중복 연동할 수 없습니다. """, responses={ 400: {"description": "이미 연동된 계정"}, }, ) async def create_social_account( data: SocialAccountCreateRequest, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> SocialAccountResponse: """소셜 계정 연동""" logger.info( f"[create_social_account] START - user_uuid: {current_user.user_uuid}, " f"platform: {data.platform}, platform_user_id: {data.platform_user_id}" ) try: service = SocialAccountService(session) account = await service.create(current_user, data) logger.info( f"[create_social_account] SUCCESS - account_id: {account.id}, " f"platform: {account.platform}" ) return SocialAccountResponse.model_validate(account) except ValueError as e: logger.warning(f"[create_social_account] DUPLICATE - error: {e}") raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(e), ) except Exception as e: logger.error(f"[create_social_account] ERROR - error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="소셜 계정 연동 중 오류가 발생했습니다.", ) # ============================================================================= # 소셜 계정 수정 # ============================================================================= @router.patch( "/{account_id}", response_model=SocialAccountResponse, summary="소셜 계정 정보 수정", description=""" ## 개요 소셜 계정 정보를 수정합니다. (토큰 갱신 등) ## 인증 - Bearer 토큰 필수 - 본인 소유의 계정만 수정 가능 ## 경로 파라미터 - **account_id**: 소셜 계정 ID ## 요청 본문 - 수정할 필드만 전송 (PATCH 방식) """, responses={ 404: {"description": "소셜 계정을 찾을 수 없음"}, }, ) async def update_social_account( account_id: int, data: SocialAccountUpdateRequest, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> SocialAccountResponse: """소셜 계정 정보 수정""" logger.info( f"[update_social_account] START - user_uuid: {current_user.user_uuid}, " f"account_id: {account_id}, data: {data.model_dump(exclude_unset=True)}" ) try: service = SocialAccountService(session) account = await service.update(current_user, account_id, data) if not account: logger.warning(f"[update_social_account] NOT_FOUND - account_id: {account_id}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="소셜 계정을 찾을 수 없습니다.", ) logger.info(f"[update_social_account] SUCCESS - account_id: {account_id}") return SocialAccountResponse.model_validate(account) except HTTPException: raise except Exception as e: logger.error(f"[update_social_account] ERROR - account_id: {account_id}, error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="소셜 계정 수정 중 오류가 발생했습니다.", ) # ============================================================================= # 소셜 계정 삭제 # ============================================================================= @router.delete( "/{account_id}", response_model=SocialAccountDeleteResponse, summary="소셜 계정 연동 해제", description=""" ## 개요 소셜 계정 연동을 해제합니다. (소프트 삭제) ## 인증 - Bearer 토큰 필수 - 본인 소유의 계정만 삭제 가능 ## 경로 파라미터 - **account_id**: 소셜 계정 ID """, responses={ 404: {"description": "소셜 계정을 찾을 수 없음"}, }, ) async def delete_social_account( account_id: int, current_user: User = Depends(get_current_user), session: AsyncSession = Depends(get_session), ) -> SocialAccountDeleteResponse: """소셜 계정 연동 해제""" logger.info(f"[delete_social_account] START - user_uuid: {current_user.user_uuid}, account_id: {account_id}") try: service = SocialAccountService(session) deleted_id = await service.delete(current_user, account_id) if not deleted_id: logger.warning(f"[delete_social_account] NOT_FOUND - account_id: {account_id}") raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="소셜 계정을 찾을 수 없습니다.", ) logger.info(f"[delete_social_account] SUCCESS - deleted_id: {deleted_id}") return SocialAccountDeleteResponse( message="소셜 계정이 삭제되었습니다.", deleted_id=deleted_id, ) except HTTPException: raise except Exception as e: logger.error(f"[delete_social_account] ERROR - account_id: {account_id}, error: {e}") raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="소셜 계정 삭제 중 오류가 발생했습니다.", )