Skip to content

Commit

Permalink
Merge pull request #39 from wafflestudio/link_with_kakao
Browse files Browse the repository at this point in the history
Link with kakao 기능 추가
  • Loading branch information
odumag99 authored Jan 24, 2025
2 parents 81abba6 + 8b6544c commit 258c6e8
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 4 deletions.
11 changes: 10 additions & 1 deletion snuvote/app/user/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,13 @@ def __init__(self) -> None:

class NotLinkedNaverAccountError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_401_UNAUTHORIZED, "Not linked Naver account")
super().__init__(HTTP_401_UNAUTHORIZED, "Not linked Naver account")

class KakaoApiError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_401_UNAUTHORIZED, "Kakao API error")

class InvalidKakaoTokenError(HTTPException):
def __init__(self) -> None:
super().__init__(HTTP_401_UNAUTHORIZED, "Invalid Kakao token")

29 changes: 28 additions & 1 deletion snuvote/app/user/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi import Depends
from snuvote.database.models import User
from snuvote.app.user.store import UserStore
from snuvote.app.user.errors import InvalidUsernameOrPasswordError, NotAccessTokenError, NotRefreshTokenError, InvalidTokenError, ExpiredTokenError, BlockedRefreshTokenError, InvalidPasswordError, NaverApiError, InvalidNaverTokenError
from snuvote.app.user.errors import InvalidUsernameOrPasswordError, NotAccessTokenError, NotRefreshTokenError, InvalidTokenError, ExpiredTokenError, BlockedRefreshTokenError, InvalidPasswordError, NaverApiError, InvalidNaverTokenError, KakaoApiError, InvalidKakaoTokenError

import jwt
from datetime import datetime, timedelta, timezone
Expand Down Expand Up @@ -184,6 +184,33 @@ async def signin_with_naver_access_token(self, naver_access_token: str):

return self.issue_tokens(user.userid)

async def get_kakao_id_with_kakao_access_token(self, kakao_access_token) -> int:
url = "https://kapi.kakao.com/v2/user/me" # 카카오 프로필 조회 API
headers = {
"Authorization": f"Bearer {kakao_access_token}",
"Content-type": "application/x-www-form-urlencoded;charset=utf-8"
}

async with httpx.AsyncClient() as client:
response = await client.get(url, headers=headers)
if response.status_code != 200:
if response.status_code == 401 and response.json().get("code") == -401: # "024": 카카오 인증 실패 에러 코드 https://developers.kakao.com/docs/latest/ko/rest-api/reference#response-format
raise InvalidKakaoTokenError()
else:
raise KakaoApiError()

data = response.json()
kakao_id = data.get("id") # 회원의 카카오 고유 식별 id

if kakao_id is None:
raise KakaoApiError()

return kakao_id

# 카카오 계정과 연동
async def link_with_kakao(self, user:User, kakao_access_token: str) -> None:
kakao_id = await self.get_kakao_id_with_kakao_access_token(kakao_access_token) # 카카오 access_token 이용해 User의 카카오 고유 식별 id 가져오기
self.user_store.link_with_kakao(user.userid, kakao_id) # User의 카카오 고유 식별 id 등록



10 changes: 9 additions & 1 deletion snuvote/app/user/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from fastapi import Depends
from snuvote.app.user.errors import EmailAlreadyExistsError, UserIdAlreadyExistsError, NotLinkedNaverAccountError, UserNotFoundError
from snuvote.database.models import User, BlockedRefreshToken, NaverUser
from snuvote.database.models import User, BlockedRefreshToken, NaverUser, KakaoUser

from snuvote.database.connection import get_db_session
from sqlalchemy import select, delete
Expand Down Expand Up @@ -64,6 +64,7 @@ def link_with_naver(self, userid: str, naver_id: str):
self.session.add(new_naveruser)
self.session.flush()

# 네이버 고유 식별 id로 유저 찾기
def get_user_by_naver_id(self, naver_id: str) -> User:
user_id = self.session.scalar(select(NaverUser.user_id).where(NaverUser.naver_id == naver_id))
if user_id is None:
Expand All @@ -74,3 +75,10 @@ def get_user_by_naver_id(self, naver_id: str) -> User:
raise UserNotFoundError()

return user

# 카카오 고유 식별 id 등록
def link_with_kakao(self, userid: str, kakao_id: int):
user = self.get_user_by_userid(userid)
new_naveruser = KakaoUser(user_id=user.id, kakao_id=kakao_id)
self.session.add(new_naveruser)
self.session.flush()
12 changes: 11 additions & 1 deletion snuvote/app/user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,14 @@ async def signin_with_naver_access_token(
):
access_token, refresh_token = await user_service.signin_with_naver_access_token(naver_access_token)

return UserSigninResponse(access_token=access_token, refresh_token=refresh_token)
return UserSigninResponse(access_token=access_token, refresh_token=refresh_token)

# 카카오 계정과 연동
@user_router.post("/link/kakao", status_code=HTTP_201_CREATED)
async def link_with_kakao(
user: Annotated[User, Depends(login_with_access_token)],
user_service: Annotated[UserService, Depends()],
kakao_access_token: Annotated[str, Body(...)]
):
await user_service.link_with_kakao(user, kakao_access_token)
return "Success"
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""DB에 KakaoUser Table 추가
Revision ID: 89c097a2daeb
Revises: e837333e71e1
Create Date: 2025-01-24 13:07:16.994176
"""
from typing import Sequence, Union

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision: str = '89c097a2daeb'
down_revision: Union[str, None] = 'e837333e71e1'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('kakao_user',
sa.Column('id', sa.BigInteger(), nullable=False),
sa.Column('user_id', sa.BigInteger(), nullable=False),
sa.Column('kakao_id', sa.BigInteger(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_kakao_user_kakao_id'), 'kakao_user', ['kakao_id'], unique=True)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_kakao_user_kakao_id'), table_name='kakao_user')
op.drop_table('kakao_user')
# ### end Alembic commands ###
12 changes: 12 additions & 0 deletions snuvote/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class User(Base):

naver_user: Mapped[Optional["NaverUser"]] = relationship("NaverUser", back_populates="user", uselist=False)

kakao_user: Mapped[Optional["KakaoUser"]] = relationship("KakaoUser", back_populates="user", uselist=False)

class NaverUser(Base):
__tablename__ = "naver_user"

Expand All @@ -36,6 +38,16 @@ class NaverUser(Base):

naver_id: Mapped[str] = mapped_column(String(50), unique=True, index=True)

class KakaoUser(Base):
__tablename__ = "kakao_user"

id: Mapped[int] = mapped_column(BigInteger, primary_key=True)

user_id: Mapped[int] = mapped_column(BigInteger, ForeignKey("user.id"))
user: Mapped["User"] = relationship("User", back_populates="kakao_user", uselist=False)

kakao_id: Mapped[str] = mapped_column(BigInteger, unique=True, index=True)

class Vote(Base):
__tablename__ = "vote"

Expand Down

0 comments on commit 258c6e8

Please sign in to comment.