Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge dev to main #85

Merged
merged 57 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
8b86965
feat: memo app 추가
yjoonjang Apr 1, 2024
18271e3
feat: memo 모델 정의
yjoonjang Apr 1, 2024
c330c17
fix: app 이름을 memos로 변경 및 urls 추가
yjoonjang Apr 1, 2024
c7b2d5f
feat: memo 작성 api
yjoonjang Apr 1, 2024
1bd05ed
fix: api schema detail 수정
yjoonjang Apr 1, 2024
db462b1
fix: memo model migrate 후 docker-compose, sh 파일 자동으로 migration 하도록 설정
yjoonjang Apr 1, 2024
07a7b9c
feat: memo models에 is_scrapped, is_finished 설정 및 페이지네이션을 활용한 전체 메모 가져…
yjoonjang Apr 1, 2024
b8ec111
feat: 특정 메모의 detail 가져오는 api
yjoonjang Apr 1, 2024
649f891
feat: memo delete api
yjoonjang Apr 1, 2024
3463618
feat: memo 검색 기능 추가 및 swagger detail 추가
yjoonjang Apr 2, 2024
9e23d3b
fix: memo model에서 is_scrapped, is_finished 삭제
yjoonjang Apr 2, 2024
0ffa404
fix: delete api swagger update
yjoonjang Apr 2, 2024
886dbc3
reformat: black을 활용하여 code reformat
yjoonjang Apr 2, 2024
c0dfc91
Merge pull request #69 from resum-ai/feat/memo
yjoonjang Apr 2, 2024
6c686ed
fix: client에서 code 요청 보내도록 수정
yjoonjang Apr 2, 2024
8cfeeab
Merge pull request #70 from resum-ai/feat/login
yjoonjang Apr 2, 2024
c086d12
feat: resume app 생성 및 모델 추가
yjoonjang Apr 2, 2024
9fa886b
feat: openai call 관련 utils 추가 및 guidline 생성 api 추가
yjoonjang Apr 2, 2024
d8a990a
feat: 자기소개서 생성 api 작업
yjoonjang Apr 4, 2024
123a6f5
feat: 자기소개서 생성 api 추가
yjoonjang Apr 5, 2024
668d3aa
refactor: black으로 reformatting
yjoonjang Apr 6, 2024
7b2dc8e
feat: resume model에 position 추가 및 자기소개서 post api 완성
yjoonjang Apr 6, 2024
f2be78f
feat: 자기소개서 생성 api
yjoonjang Apr 6, 2024
7a466c6
feat: 자기소개서 스크랩 api
yjoonjang Apr 6, 2024
f383ad6
Merge pull request #71 from resum-ai/feat/resume
yjoonjang Apr 6, 2024
9e809b9
feat: memo 수정 api
yjoonjang Apr 6, 2024
eec6dcd
Merge pull request #72 from resum-ai/feat/memo
yjoonjang Apr 6, 2024
08e268f
fix: add logger
yjoonjang Apr 6, 2024
482acba
Merge pull request #73 from resum-ai/feat/resume
yjoonjang Apr 6, 2024
66fe75d
add logger.fata
yjoonjang Apr 6, 2024
58926c1
Merge pull request #74 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
37fe8b9
fix: callback uri 변경
yjoonjang Apr 6, 2024
999c2f2
Merge pull request #75 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
99bd6f2
fix: 카카오 인가코드 전송 시 로그인 가입 후 모든 정보 반환하도록 수정
yjoonjang Apr 6, 2024
a2c6b8d
Merge pull request #76 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
0edb565
fix: redirect uri 수정 및 logger 추가
yjoonjang Apr 6, 2024
babed47
Merge pull request #77 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
3c34572
reformat: black을 사용하여 code reformatting
yjoonjang Apr 6, 2024
cc30d7e
Merge pull request #78 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
caee917
fix: SocialLoginView에서 APIView로 수정
yjoonjang Apr 6, 2024
8479b82
Merge pull request #79 from resum-ai/hotfix/login
yjoonjang Apr 6, 2024
358b4a6
feat: chat 기능 추가
yjoonjang Apr 9, 2024
73d9dbb
feat: 챗봇 api 1차 개발
yjoonjang Apr 9, 2024
bc305a2
fix: 모든 api url에 / 삭제
yjoonjang Apr 9, 2024
e94e8d6
Merge pull request #80 from resum-ai/feat/resume
yjoonjang Apr 9, 2024
d0e194e
feat: 자소서 model에 question 저장되도록 설정 후 generate 시 자소서 저장되도록 설정
yjoonjang Apr 10, 2024
c212726
feat: 특정 자소서 받아오는 api
yjoonjang Apr 10, 2024
ee7020e
reformat: black으로 code reformat 및 api uri 마지막에 / 처리
yjoonjang Apr 10, 2024
a59b3bb
Merge pull request #81 from resum-ai/feat/resume
yjoonjang Apr 10, 2024
b7d43c9
feat: memory를 활용하여 채팅 가능하도록 api 구현
yjoonjang Apr 10, 2024
7a34d84
reformat: black으로 코드 reformatting
yjoonjang Apr 10, 2024
a8b5a7d
Merge pull request #82 from resum-ai/feat/resume
yjoonjang Apr 10, 2024
1068942
feat: 채팅 내역 반환 api
yjoonjang Apr 11, 2024
d297322
feat: 자기소개서 삭제 api
yjoonjang Apr 11, 2024
7633638
Merge pull request #83 from resum-ai/feat/resume
yjoonjang Apr 11, 2024
7a3aebc
reformat: 쉼표 추가 및 black으로 코드 reformatting
yjoonjang Apr 11, 2024
4de5794
Merge pull request #84 from resum-ai/feat/resume
yjoonjang Apr 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accounts/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class KakaoTokenSerializer(serializers.Serializer):
class UserInfoUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ("username", "position", "profile_image")
fields = ("id", "username", "position", "profile_image")


class GetUserInfoSerializer(serializers.ModelSerializer):
Expand Down
9 changes: 4 additions & 5 deletions accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

# TODO swagger에 뜨는 api 관리
urlpatterns = [
path("kakao/login/", views.kakao_login, name="kakao_login"),
path("kakao/callback/", views.kakao_callback, name="kakao_callback"),
path("/kakao", views.kakao_login, name="kakao_login"),
path(
"kakao/login/finish/",
"/kakao/login",
views.KakaoLoginView.as_view(),
name="kakao_login_todjango",
),
path("update/", views.UpdateUserInfoView.as_view(), name="update_user_info"),
path("user/me", views.GetUserInfoView.as_view(), name="get_user_info"),
path("/update", views.UpdateUserInfoView.as_view(), name="update_user_info"),
path("/user/me", views.GetUserInfoView.as_view(), name="get_user_info"),
]
238 changes: 103 additions & 135 deletions accounts/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,26 @@

import environ
from pathlib import Path
from django.db import transaction
from drf_spectacular.utils import (
extend_schema,
OpenApiResponse,
OpenApiExample,
OpenApiParameter,
)
from django.http import JsonResponse
from rest_framework_simplejwt.tokens import RefreshToken

import requests
from django.shortcuts import redirect
from json.decoder import JSONDecodeError
from rest_framework.decorators import api_view, permission_classes
from django.contrib.auth import get_user_model
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import status

from allauth.socialaccount.providers.oauth2.client import OAuth2Client
from dj_rest_auth.registration.views import SocialLoginView
from allauth.socialaccount.providers.kakao import views as kakao_view
from .models import CustomUser
from .serializers import (
UserInfoUpdateSerializer,
GetUserInfoSerializer,
KakaoTokenSerializer,
)
import logging

Expand All @@ -42,148 +36,122 @@
env.read_env(env_file)

BASE_URL = env("BASE_URL")
KAKAO_CALLBACK_URI = BASE_URL + "accounts/kakao/callback/"
# KAKAO_CALLBACK_URI = BASE_URL + "accounts/kakao/callback/"
KAKAO_CALLBACK_URI = "http://localhost:5173/accounts/kakao/callback"
# KAKAO_CALLBACK_URI = "http://api.resumai.kr/accounts/kakao/callback/"
REST_API_KEY = env("KAKAO_REST_API_KEY")
CLIENT_SECRET = env("KAKAO_CLIENT_SECRET_KEY")

User = get_user_model()

@extend_schema(
summary="카카오 로그인",
description="카카오 로그인 페이지로 리다이렉트하여, 정보를 입력하면 카카오 **access_token, code**를 반환합니다.",
responses={200: KakaoTokenSerializer},
)
@api_view(["GET"])
@permission_classes([AllowAny])

@extend_schema(exclude=True)
def kakao_login(request):
return redirect(
logger.fatal(
f"https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={KAKAO_CALLBACK_URI}&response_type=code"
)


@permission_classes([AllowAny])
def kakao_callback(request):
code = request.GET.get("code")

# Access Token Request
token_req = requests.get(
f"https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={REST_API_KEY}&client_secret={CLIENT_SECRET}&redirect_uri={KAKAO_CALLBACK_URI}&code={code}"
return redirect(
f"https://kauth.kakao.com/oauth/authorize?client_id={REST_API_KEY}&redirect_uri={KAKAO_CALLBACK_URI}&response_type=code"
)

token_req_json = token_req.json()

error = token_req_json.get("error")
if error is not None:
raise JSONDecodeError(error)
class KakaoLoginView(APIView):

access_token = token_req_json.get("access_token")

# Email Request
profile_request = requests.get(
"https://kapi.kakao.com/v2/user/me",
headers={"Authorization": f"Bearer {access_token}"},
)
profile_data = profile_request.json()

kakao_oid = profile_data.get("id")
kakao_account = profile_data.get("kakao_account")
username = kakao_account["profile"]["nickname"]
profile_image_url = kakao_account["profile"]["profile_image_url"]
email = kakao_account.get("email")

data = {"access_token": access_token, "code": code}
# TODO 유저 프로필 이미지 저장하도록
return JsonResponse(data)

# try:
# user = CustomUser.objects.get(email=email)
# # 유저가 존재하는 경우
# logger.warning(f"user: {user}")
# logger.warning("유저 존재")
# accept = requests.post("https://api.resumai.kr/accounts/kakao/login/finish/", data=data)
# logger.warning(f"accept: {accept}")
# logger.warning(f"accept.reason: {accept.reason}")
# logger.warning(f"accept.history: {accept.history}")
# logger.warning(accept.content)
# accept_status = accept.status_code
# logger.warning(accept_status)
#
# if accept_status != 200:
# return Response({"err_msg": "failed to signin"}, status=accept_status)
#
# accept_json = accept.json()
# logger.warning(f"accept_json, {accept_json}")
# # key 이름 변경
# accept_json["accessToken"] = accept_json.pop("access")
# accept_json["refreshToken"] = accept_json.pop("refresh")
# accept_json["userProfile"] = accept_json.pop("user")
# accept_json["userProfile"]["id"] = accept_json["userProfile"].pop("pk")
# return JsonResponse(accept_json)
#
# except CustomUser.DoesNotExist:
# # 기존에 가입된 유저가 없으면 새로 가입
# logger.warning("유저 미존재")
# accept = requests.post("http://localhost:8000/accounts/kakao/login/finish/", data=data)
# logger.warning(f"accept: {accept}")
# logger.warning(f"accept.reason: {accept.reason}")
# logger.warning(f"accept.request: {accept.request}")
# logger.warning(f"accept.raw: {accept.raw}")
# accept_status = accept.status_code
# logger.warning(accept_status)
# if accept_status != 200:
# return Response({"err_msg": "failed to signup"}, status=accept_status)
#
# # user의 pk, email, first name, last name과 Access Token, Refresh token 가져옴
# accept_json = accept.json()
# logger.warning(f"accept_json, {accept_json}")
# # key 이름 변경
# accept_json["accessToken"] = accept_json.pop("access")
# accept_json["refreshToken"] = accept_json.pop("refresh")
# accept_json["userProfile"] = accept_json.pop("user")
# accept_json["userProfile"]["id"] = accept_json["userProfile"].pop("pk")
# return JsonResponse(accept_json)


# @extend_schema(exclude=True)
@extend_schema(
summary="카카오 로그인 마무리",
description="access token, code를 post 요청으로 보내면 access token, 유저 정보를 반환합니다. **(id_token은 불필요합니다.)**",
parameters=[
OpenApiParameter(
name="access_token",
type=str,
description="발급받은 카카오의 access_token 입니다.",
),
OpenApiParameter(
name="code", type=str, description="발급받은 카카오의 code 입니다."
),
],
request={
"application/json": {
"type": "object",
"properties": {
"access_token": {"type": "string"},
"code": {"type": "string"},
@extend_schema(
summary="카카오 로그인 마무리",
description="code (인가 코드)를 post 요청으로 보내면 access token, 유저 정보를 반환합니다. **(id_token은 불필요합니다.)**",
request={
"application/json": {
"type": "object",
"properties": {
"code": {"type": "string"},
},
},
},
},
examples=[
OpenApiExample(
response_only=True,
summary="Response Body Example입니다.",
name="success_example",
value={
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlI",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBl",
"user": {"pk": 6, "email": "[email protected]"},
examples=[
OpenApiExample(
response_only=True,
summary="Response Body Example입니다.",
name="success_example",
value={
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0",
"user_info": {
"id": 6,
"email": "[email protected]",
"username": "장영준",
"profile_image": "https://k.kakaocdn.net/dn/cI6qGf/btsCovDyklV/ydaQojxohw6VnLxtcdKwuk/img_640x640.jpg",
"is_created": False,
},
},
),
],
)
def post(self, request, *args, **kwargs):
code = request.data.get("code")

if not code:
return Response(
{"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST
)

# 카카오 인가코드를 사용해 access_token 획득
token_res = requests.get(
f"https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id={REST_API_KEY}&client_secret={CLIENT_SECRET}&redirect_uri={KAKAO_CALLBACK_URI}&code={code}"
)
logger.fatal(token_res)

if token_res.status_code != 200:
logger.fatal(token_res.json())
return Response(
{"error": "Failed to obtain access token"},
status=status.HTTP_400_BAD_REQUEST,
)

token_json = token_res.json()
access_token = token_json.get("access_token")

# 카카오 access_token으로부터 사용자 정보 획득
headers = {"Authorization": f"Bearer {access_token}"}
profile_res = requests.get("https://kapi.kakao.com/v2/user/me", headers=headers)

if profile_res.status_code != 200:
return Response(
{"error": "Failed to obtain user information"},
status=status.HTTP_400_BAD_REQUEST,
)

profile_json = profile_res.json()

kakao_oid = profile_json.get("id")
nickname = profile_json.get("properties")["nickname"]
profile_image = profile_json.get("properties")["profile_image"]
email = profile_json.get("kakao_account")["email"]

user, created = User.objects.get_or_create(
email=email,
defaults={
"username": f"{nickname}",
"kakao_oid": kakao_oid,
"profile_image": f"{profile_image}",
},
),
],
)
class KakaoLoginView(SocialLoginView):
adapter_class = kakao_view.KakaoOAuth2Adapter
client_class = OAuth2Client
callback_url = KAKAO_CALLBACK_URI
)

# 사용자에 대한 토큰 생성
refresh = RefreshToken.for_user(user)
data = {
"access_token": str(refresh.access_token),
"refresh_token": str(refresh),
"user_info": {
"id": user.id,
"email": user.email,
"username": user.username,
"profile_image": user.profile_image,
"is_created": created,
},
}

return Response(data, status=status.HTTP_200_OK)


class UpdateUserInfoView(APIView):
Expand Down
4 changes: 3 additions & 1 deletion config/docker/entrypoint.prod.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#!/bin/sh

python manage.py migrate --noinput
python manage.py makemigrations --no-input

python manage.py migrate --no-input

python manage.py collectstatic --no-input

Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ services:
build:
context: ./
dockerfile: Dockerfile
command: sh -c "python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=resumai.settings.dev"
command: sh -c "python manage.py makemigrations && python manage.py migrate && python manage.py runserver 0.0.0.0:8000 --settings=resumai.settings.dev"
environment:
DJANGO_ENV: development
env_file:
Expand Down
Empty file added memos/__init__.py
Empty file.
3 changes: 3 additions & 0 deletions memos/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from django.contrib import admin

# Register your models here.
6 changes: 6 additions & 0 deletions memos/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class MemoConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "memos"
42 changes: 42 additions & 0 deletions memos/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by Django 5.0.3 on 2024-04-01 17:21

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="Memo",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created_at", models.DateTimeField(auto_now_add=True)),
("title", models.CharField(max_length=255)),
("content", models.TextField()),
("updated_at", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
),
]
Loading