youtube bug fix, timezone 수정, lazyloading 수정 .

get_video
hbyang 2026-02-09 13:15:20 +09:00
parent 40afe9392c
commit e29e10eb29
3 changed files with 22 additions and 16 deletions

View File

@ -4,6 +4,7 @@ Social Account Service
소셜 계정 연동 관련 비즈니스 로직을 처리합니다. 소셜 계정 연동 관련 비즈니스 로직을 처리합니다.
""" """
import json
import logging import logging
import secrets import secrets
from datetime import timedelta from datetime import timedelta
@ -27,10 +28,8 @@ redis_client = Redis(
decode_responses=True, decode_responses=True,
) )
from app.social.exceptions import ( from app.social.exceptions import (
InvalidStateError,
OAuthStateExpiredError, OAuthStateExpiredError,
OAuthTokenRefreshError, OAuthTokenRefreshError,
SocialAccountAlreadyConnectedError,
SocialAccountNotFoundError, SocialAccountNotFoundError,
TokenExpiredError, TokenExpiredError,
) )
@ -90,7 +89,7 @@ class SocialAccountService:
await redis_client.setex( await redis_client.setex(
state_key, state_key,
social_oauth_settings.OAUTH_STATE_TTL_SECONDS, social_oauth_settings.OAUTH_STATE_TTL_SECONDS,
str(state_data), json.dumps(state_data), # JSON으로 직렬화
) )
logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}") logger.debug(f"[SOCIAL] OAuth state 저장 - key: {state_key}")
@ -126,9 +125,7 @@ class SocialAccountService:
SocialAccountResponse: 연동된 소셜 계정 정보 SocialAccountResponse: 연동된 소셜 계정 정보
Raises: Raises:
InvalidStateError: state 토큰이 유효하지 않은 경우 OAuthStateExpiredError: state 토큰이 만료되거나 유효하지 않은 경우
OAuthStateExpiredError: state 토큰이 만료된 경우
SocialAccountAlreadyConnectedError: 이미 연동된 계정인 경우
""" """
logger.info(f"[SOCIAL] OAuth 콜백 처리 시작 - state: {state[:20]}...") logger.info(f"[SOCIAL] OAuth 콜백 처리 시작 - state: {state[:20]}...")
@ -140,8 +137,8 @@ class SocialAccountService:
logger.warning(f"[SOCIAL] state 토큰 없음 또는 만료 - state: {state[:20]}...") logger.warning(f"[SOCIAL] state 토큰 없음 또는 만료 - state: {state[:20]}...")
raise OAuthStateExpiredError() raise OAuthStateExpiredError()
# state 데이터 파싱 # state 데이터 파싱 (JSON 역직렬화)
state_data = eval(state_data_str) # {"user_uuid": "...", "platform": "..."} state_data = json.loads(state_data_str)
user_uuid = state_data["user_uuid"] user_uuid = state_data["user_uuid"]
platform = SocialPlatform(state_data["platform"]) platform = SocialPlatform(state_data["platform"])
@ -307,7 +304,9 @@ class SocialAccountService:
if account.token_expires_at is None: if account.token_expires_at is None:
should_refresh = True should_refresh = True
else: 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: if account.token_expires_at <= buffer_time:
should_refresh = True should_refresh = True
@ -514,7 +513,9 @@ class SocialAccountService:
) )
should_refresh = True should_refresh = True
else: 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: if account.token_expires_at <= buffer_time:
logger.info( logger.info(
f"[SOCIAL] 토큰 만료 임박, 갱신 시작 - account_id: {account.id}" f"[SOCIAL] 토큰 만료 임박, 갱신 시작 - account_id: {account.id}"
@ -573,11 +574,13 @@ class SocialAccountService:
if token_response.refresh_token: if token_response.refresh_token:
account.refresh_token = token_response.refresh_token account.refresh_token = token_response.refresh_token
if token_response.expires_in: 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 seconds=token_response.expires_in
) )
await session.commit() await session.commit()
await session.refresh(account)
logger.info(f"[SOCIAL] 토큰 갱신 완료 - account_id: {account.id}") logger.info(f"[SOCIAL] 토큰 갱신 완료 - account_id: {account.id}")
return account.access_token return account.access_token
@ -631,10 +634,10 @@ class SocialAccountService:
Returns: Returns:
SocialAccount: 생성된 소셜 계정 SocialAccount: 생성된 소셜 계정
""" """
# 토큰 만료 시간 계산 # 토큰 만료 시간 계산 (DB에 naive datetime으로 저장)
token_expires_at = None token_expires_at = None
if token_response.expires_in: if token_response.expires_in:
token_expires_at = now() + timedelta( token_expires_at = now().replace(tzinfo=None) + timedelta(
seconds=token_response.expires_in seconds=token_response.expires_in
) )
@ -687,7 +690,8 @@ class SocialAccountService:
if token_response.refresh_token: if token_response.refresh_token:
account.refresh_token = token_response.refresh_token account.refresh_token = token_response.refresh_token
if token_response.expires_in: 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 seconds=token_response.expires_in
) )
if token_response.scope: if token_response.scope:
@ -703,7 +707,7 @@ class SocialAccountService:
# 재연결 시 연결 시간 업데이트 # 재연결 시 연결 시간 업데이트
if update_connected_at: if update_connected_at:
account.connected_at = now() account.connected_at = now().replace(tzinfo=None)
await session.commit() await session.commit()
await session.refresh(account) await session.refresh(account)

View File

@ -71,7 +71,7 @@ async def _update_upload_status(
if error_message: if error_message:
upload.error_message = error_message upload.error_message = error_message
if status == UploadStatus.COMPLETED: if status == UploadStatus.COMPLETED:
upload.uploaded_at = now() upload.uploaded_at = now().replace(tzinfo=None)
await session.commit() await session.commit()
logger.info( logger.info(

View File

@ -391,6 +391,7 @@ class RefreshToken(Base):
user: Mapped["User"] = relationship( user: Mapped["User"] = relationship(
"User", "User",
back_populates="refresh_tokens", back_populates="refresh_tokens",
lazy="selectin", # lazy loading 방지
) )
def __repr__(self) -> str: def __repr__(self) -> str:
@ -591,6 +592,7 @@ class SocialAccount(Base):
user: Mapped["User"] = relationship( user: Mapped["User"] = relationship(
"User", "User",
back_populates="social_accounts", back_populates="social_accounts",
lazy="selectin", # lazy loading 방지
) )
def __repr__(self) -> str: def __repr__(self) -> str: