From e29e10eb2996d973b2cc52f63c1dcdc0aa1ba0db Mon Sep 17 00:00:00 2001 From: hbyang Date: Mon, 9 Feb 2026 13:15:20 +0900 Subject: [PATCH] =?UTF-8?q?youtube=20bug=20fix,=20timezone=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20lazyloading=20=EC=88=98=EC=A0=95=20.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/social/services.py | 34 ++++++++++++++++++-------------- app/social/worker/upload_task.py | 2 +- app/user/models.py | 2 ++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/app/social/services.py b/app/social/services.py index 6e7936b..c48d014 100644 --- a/app/social/services.py +++ b/app/social/services.py @@ -4,6 +4,7 @@ Social Account Service 소셜 계정 연동 관련 비즈니스 로직을 처리합니다. """ +import json import logging import secrets from datetime import timedelta @@ -27,10 +28,8 @@ redis_client = Redis( decode_responses=True, ) from app.social.exceptions import ( - InvalidStateError, OAuthStateExpiredError, OAuthTokenRefreshError, - SocialAccountAlreadyConnectedError, SocialAccountNotFoundError, TokenExpiredError, ) @@ -90,7 +89,7 @@ class SocialAccountService: await redis_client.setex( state_key, social_oauth_settings.OAUTH_STATE_TTL_SECONDS, - str(state_data), + json.dumps(state_data), # JSON으로 직렬화 ) logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}") @@ -126,9 +125,7 @@ class SocialAccountService: SocialAccountResponse: 연동된 소셜 계정 정보 Raises: - InvalidStateError: state 토큰이 유효하지 않은 경우 - OAuthStateExpiredError: state 토큰이 만료된 경우 - SocialAccountAlreadyConnectedError: 이미 연동된 계정인 경우 + OAuthStateExpiredError: state 토큰이 만료되거나 유효하지 않은 경우 """ logger.info(f"[SOCIAL] OAuth 콜백 처리 시작 - state: {state[:20]}...") @@ -140,8 +137,8 @@ class SocialAccountService: logger.warning(f"[SOCIAL] state 토큰 없음 또는 만료 - state: {state[:20]}...") raise OAuthStateExpiredError() - # state 데이터 파싱 - state_data = eval(state_data_str) # {"user_uuid": "...", "platform": "..."} + # state 데이터 파싱 (JSON 역직렬화) + state_data = json.loads(state_data_str) user_uuid = state_data["user_uuid"] platform = SocialPlatform(state_data["platform"]) @@ -307,7 +304,9 @@ class SocialAccountService: if account.token_expires_at is None: should_refresh = True else: - buffer_time = now() + timedelta(hours=1) + # DB datetime은 naive, now()는 aware이므로 naive로 통일하여 비교 + current_time = now().replace(tzinfo=None) + buffer_time = current_time + timedelta(hours=1) if account.token_expires_at <= buffer_time: should_refresh = True @@ -514,7 +513,9 @@ class SocialAccountService: ) should_refresh = True else: - buffer_time = now() + timedelta(minutes=10) + # DB datetime은 naive, now()는 aware이므로 naive로 통일하여 비교 + current_time = now().replace(tzinfo=None) + buffer_time = current_time + timedelta(minutes=10) if account.token_expires_at <= buffer_time: logger.info( f"[SOCIAL] 토큰 만료 임박, 갱신 시작 - account_id: {account.id}" @@ -573,11 +574,13 @@ class SocialAccountService: if token_response.refresh_token: account.refresh_token = token_response.refresh_token if token_response.expires_in: - account.token_expires_at = now() + timedelta( + # DB에 naive datetime으로 저장 (MySQL DateTime은 timezone 미지원) + account.token_expires_at = now().replace(tzinfo=None) + timedelta( seconds=token_response.expires_in ) await session.commit() + await session.refresh(account) logger.info(f"[SOCIAL] 토큰 갱신 완료 - account_id: {account.id}") return account.access_token @@ -631,10 +634,10 @@ class SocialAccountService: Returns: SocialAccount: 생성된 소셜 계정 """ - # 토큰 만료 시간 계산 + # 토큰 만료 시간 계산 (DB에 naive datetime으로 저장) token_expires_at = None if token_response.expires_in: - token_expires_at = now() + timedelta( + token_expires_at = now().replace(tzinfo=None) + timedelta( seconds=token_response.expires_in ) @@ -687,7 +690,8 @@ class SocialAccountService: if token_response.refresh_token: account.refresh_token = token_response.refresh_token if token_response.expires_in: - account.token_expires_at = now() + timedelta( + # DB에 naive datetime으로 저장 + account.token_expires_at = now().replace(tzinfo=None) + timedelta( seconds=token_response.expires_in ) if token_response.scope: @@ -703,7 +707,7 @@ class SocialAccountService: # 재연결 시 연결 시간 업데이트 if update_connected_at: - account.connected_at = now() + account.connected_at = now().replace(tzinfo=None) await session.commit() await session.refresh(account) diff --git a/app/social/worker/upload_task.py b/app/social/worker/upload_task.py index 5dd727a..6e48f89 100644 --- a/app/social/worker/upload_task.py +++ b/app/social/worker/upload_task.py @@ -71,7 +71,7 @@ async def _update_upload_status( if error_message: upload.error_message = error_message if status == UploadStatus.COMPLETED: - upload.uploaded_at = now() + upload.uploaded_at = now().replace(tzinfo=None) await session.commit() logger.info( diff --git a/app/user/models.py b/app/user/models.py index 2c734dc..fea0cc0 100644 --- a/app/user/models.py +++ b/app/user/models.py @@ -391,6 +391,7 @@ class RefreshToken(Base): user: Mapped["User"] = relationship( "User", back_populates="refresh_tokens", + lazy="selectin", # lazy loading 방지 ) def __repr__(self) -> str: @@ -591,6 +592,7 @@ class SocialAccount(Base): user: Mapped["User"] = relationship( "User", back_populates="social_accounts", + lazy="selectin", # lazy loading 방지 ) def __repr__(self) -> str: