260 lines
8.4 KiB
Python
260 lines
8.4 KiB
Python
"""
|
|
SocialAccount 서비스 레이어
|
|
|
|
소셜 계정 연동 관련 비즈니스 로직을 처리합니다.
|
|
"""
|
|
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from sqlalchemy import and_, select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.user.models import Platform, SocialAccount, User
|
|
from app.user.schemas.social_account_schema import (
|
|
SocialAccountCreateRequest,
|
|
SocialAccountUpdateRequest,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class SocialAccountService:
|
|
"""소셜 계정 서비스"""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
self.session = session
|
|
|
|
async def get_list(self, user: User) -> list[SocialAccount]:
|
|
"""
|
|
사용자의 소셜 계정 목록 조회
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
|
|
Returns:
|
|
list[SocialAccount]: 소셜 계정 목록
|
|
"""
|
|
logger.debug(f"[SocialAccountService.get_list] START - user_uuid: {user.user_uuid}")
|
|
|
|
result = await self.session.execute(
|
|
select(SocialAccount).where(
|
|
and_(
|
|
SocialAccount.user_uuid == user.user_uuid,
|
|
SocialAccount.is_deleted == False, # noqa: E712
|
|
)
|
|
).order_by(SocialAccount.created_at.desc())
|
|
)
|
|
accounts = list(result.scalars().all())
|
|
|
|
logger.debug(f"[SocialAccountService.get_list] SUCCESS - count: {len(accounts)}")
|
|
return accounts
|
|
|
|
async def get_by_id(self, user: User, account_id: int) -> Optional[SocialAccount]:
|
|
"""
|
|
ID로 소셜 계정 조회
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
account_id: 소셜 계정 ID
|
|
|
|
Returns:
|
|
SocialAccount | None: 소셜 계정 또는 None
|
|
"""
|
|
logger.debug(f"[SocialAccountService.get_by_id] START - user_uuid: {user.user_uuid}, account_id: {account_id}")
|
|
|
|
result = await self.session.execute(
|
|
select(SocialAccount).where(
|
|
and_(
|
|
SocialAccount.id == account_id,
|
|
SocialAccount.user_uuid == user.user_uuid,
|
|
SocialAccount.is_deleted == False, # noqa: E712
|
|
)
|
|
)
|
|
)
|
|
account = result.scalar_one_or_none()
|
|
|
|
if account:
|
|
logger.debug(f"[SocialAccountService.get_by_id] SUCCESS - platform: {account.platform}")
|
|
else:
|
|
logger.debug(f"[SocialAccountService.get_by_id] NOT_FOUND - account_id: {account_id}")
|
|
|
|
return account
|
|
|
|
async def get_by_platform(
|
|
self,
|
|
user: User,
|
|
platform: Platform,
|
|
platform_user_id: Optional[str] = None,
|
|
) -> Optional[SocialAccount]:
|
|
"""
|
|
플랫폼별 소셜 계정 조회
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
platform: 플랫폼
|
|
platform_user_id: 플랫폼 사용자 ID (선택)
|
|
|
|
Returns:
|
|
SocialAccount | None: 소셜 계정 또는 None
|
|
"""
|
|
logger.debug(
|
|
f"[SocialAccountService.get_by_platform] START - user_uuid: {user.user_uuid}, "
|
|
f"platform: {platform}, platform_user_id: {platform_user_id}"
|
|
)
|
|
|
|
conditions = [
|
|
SocialAccount.user_uuid == user.user_uuid,
|
|
SocialAccount.platform == platform,
|
|
SocialAccount.is_deleted == False, # noqa: E712
|
|
]
|
|
|
|
if platform_user_id:
|
|
conditions.append(SocialAccount.platform_user_id == platform_user_id)
|
|
|
|
result = await self.session.execute(
|
|
select(SocialAccount).where(and_(*conditions))
|
|
)
|
|
account = result.scalar_one_or_none()
|
|
|
|
if account:
|
|
logger.debug(f"[SocialAccountService.get_by_platform] SUCCESS - id: {account.id}")
|
|
else:
|
|
logger.debug(f"[SocialAccountService.get_by_platform] NOT_FOUND")
|
|
|
|
return account
|
|
|
|
async def create(
|
|
self,
|
|
user: User,
|
|
data: SocialAccountCreateRequest,
|
|
) -> SocialAccount:
|
|
"""
|
|
소셜 계정 생성
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
data: 생성 요청 데이터
|
|
|
|
Returns:
|
|
SocialAccount: 생성된 소셜 계정
|
|
|
|
Raises:
|
|
ValueError: 이미 연동된 계정이 존재하는 경우
|
|
"""
|
|
logger.debug(
|
|
f"[SocialAccountService.create] START - user_uuid: {user.user_uuid}, "
|
|
f"platform: {data.platform}, platform_user_id: {data.platform_user_id}"
|
|
)
|
|
|
|
# 중복 확인
|
|
existing = await self.get_by_platform(user, data.platform, data.platform_user_id)
|
|
if existing:
|
|
logger.warning(
|
|
f"[SocialAccountService.create] DUPLICATE - "
|
|
f"platform: {data.platform}, platform_user_id: {data.platform_user_id}"
|
|
)
|
|
raise ValueError(f"이미 연동된 {data.platform.value} 계정입니다.")
|
|
|
|
account = SocialAccount(
|
|
user_uuid=user.user_uuid,
|
|
platform=data.platform,
|
|
access_token=data.access_token,
|
|
refresh_token=data.refresh_token,
|
|
token_expires_at=data.token_expires_at,
|
|
scope=data.scope,
|
|
platform_user_id=data.platform_user_id,
|
|
platform_username=data.platform_username,
|
|
platform_data=data.platform_data,
|
|
is_active=True,
|
|
is_deleted=False,
|
|
)
|
|
|
|
self.session.add(account)
|
|
await self.session.commit()
|
|
await self.session.refresh(account)
|
|
|
|
logger.info(
|
|
f"[SocialAccountService.create] SUCCESS - id: {account.id}, "
|
|
f"platform: {account.platform}, platform_username: {account.platform_username}"
|
|
)
|
|
return account
|
|
|
|
async def update(
|
|
self,
|
|
user: User,
|
|
account_id: int,
|
|
data: SocialAccountUpdateRequest,
|
|
) -> Optional[SocialAccount]:
|
|
"""
|
|
소셜 계정 수정
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
account_id: 소셜 계정 ID
|
|
data: 수정 요청 데이터
|
|
|
|
Returns:
|
|
SocialAccount | None: 수정된 소셜 계정 또는 None
|
|
"""
|
|
logger.debug(
|
|
f"[SocialAccountService.update] START - user_uuid: {user.user_uuid}, account_id: {account_id}"
|
|
)
|
|
|
|
account = await self.get_by_id(user, account_id)
|
|
if not account:
|
|
logger.warning(f"[SocialAccountService.update] NOT_FOUND - account_id: {account_id}")
|
|
return None
|
|
|
|
# 변경된 필드만 업데이트
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if value is not None:
|
|
setattr(account, field, value)
|
|
|
|
await self.session.commit()
|
|
await self.session.refresh(account)
|
|
|
|
logger.info(
|
|
f"[SocialAccountService.update] SUCCESS - id: {account.id}, "
|
|
f"updated_fields: {list(update_data.keys())}"
|
|
)
|
|
return account
|
|
|
|
async def delete(self, user: User, account_id: int) -> Optional[int]:
|
|
"""
|
|
소셜 계정 소프트 삭제
|
|
|
|
Args:
|
|
user: 현재 로그인한 사용자
|
|
account_id: 소셜 계정 ID
|
|
|
|
Returns:
|
|
int | None: 삭제된 계정 ID 또는 None
|
|
"""
|
|
logger.debug(
|
|
f"[SocialAccountService.delete] START - user_uuid: {user.user_uuid}, account_id: {account_id}"
|
|
)
|
|
|
|
account = await self.get_by_id(user, account_id)
|
|
if not account:
|
|
logger.warning(f"[SocialAccountService.delete] NOT_FOUND - account_id: {account_id}")
|
|
return None
|
|
|
|
account.is_deleted = True
|
|
account.is_active = False
|
|
await self.session.commit()
|
|
|
|
logger.info(
|
|
f"[SocialAccountService.delete] SUCCESS - id: {account_id}, platform: {account.platform}"
|
|
)
|
|
return account_id
|
|
|
|
|
|
# =============================================================================
|
|
# 의존성 주입용 함수
|
|
# =============================================================================
|
|
async def get_social_account_service(session: AsyncSession) -> SocialAccountService:
|
|
"""SocialAccountService 인스턴스 반환"""
|
|
return SocialAccountService(session)
|