308 lines
10 KiB
Python
308 lines
10 KiB
Python
"""
|
|
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="소셜 계정 삭제 중 오류가 발생했습니다.",
|
|
)
|