""" 카카오 로그인 API 라우터 """ from datetime import datetime, timezone from fastapi import APIRouter, Depends, HTTPException, status from fastapi.responses import RedirectResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.auth.dependencies import get_current_user_optional from app.auth.models import User from app.auth.schemas import AuthStatusResponse, TokenResponse, UserResponse from app.auth.services.jwt import create_access_token from app.auth.services.kakao import kakao_client from app.database.session import get_session router = APIRouter(tags=["Auth"]) @router.get("/auth/kakao/login") async def kakao_login(): """ 카카오 로그인 페이지로 리다이렉트 프론트엔드에서 이 URL을 호출하면 카카오 로그인 페이지로 이동합니다. """ auth_url = kakao_client.get_authorization_url() return RedirectResponse(url=auth_url) @router.get("/kakao/callback", response_model=TokenResponse) async def kakao_callback( code: str, session: AsyncSession = Depends(get_session), ): """ 카카오 로그인 콜백 카카오 로그인 성공 후 인가 코드를 받아 JWT 토큰을 발급합니다. """ # 1. 인가 코드로 액세스 토큰 획득 token_data = await kakao_client.get_access_token(code) if "error" in token_data: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"카카오 토큰 발급 실패: {token_data.get('error_description', token_data.get('error'))}", ) access_token = token_data.get("access_token") if not access_token: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="액세스 토큰을 받지 못했습니다", ) # 2. 액세스 토큰으로 사용자 정보 조회 user_info = await kakao_client.get_user_info(access_token) if "id" not in user_info: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="사용자 정보를 가져오지 못했습니다", ) kakao_id = str(user_info["id"]) kakao_account = user_info.get("kakao_account", {}) profile = kakao_account.get("profile", {}) nickname = profile.get("nickname") email = kakao_account.get("email") profile_image = profile.get("profile_image_url") # 3. 기존 회원 확인 또는 신규 가입 result = await session.execute(select(User).where(User.kakao_id == kakao_id)) user = result.scalar_one_or_none() if user is None: # 신규 가입 user = User( kakao_id=kakao_id, nickname=nickname, email=email, profile_image=profile_image, ) session.add(user) await session.commit() await session.refresh(user) else: # 기존 회원 - 마지막 로그인 시간 및 정보 업데이트 user.nickname = nickname user.email = email user.profile_image = profile_image user.last_login_at = datetime.now(timezone.utc) await session.commit() await session.refresh(user) # 4. JWT 토큰 발급 jwt_token = create_access_token({"sub": str(user.id)}) return TokenResponse( access_token=jwt_token, user=UserResponse.model_validate(user), ) @router.get("/auth/me", response_model=AuthStatusResponse) async def get_auth_status( current_user: User | None = Depends(get_current_user_optional), ): """ 현재 인증 상태 확인 프론트엔드에서 로그인 상태를 확인할 때 사용합니다. 토큰이 유효하면 사용자 정보를, 아니면 is_authenticated=False를 반환합니다. """ if current_user is None: return AuthStatusResponse(is_authenticated=False) return AuthStatusResponse( is_authenticated=True, user=UserResponse.model_validate(current_user), )