From 3a85a501b4a5e92b2948c108998ae4c59f6031dc Mon Sep 17 00:00:00 2001 From: sm4640 Date: Fri, 28 Mar 2025 01:02:05 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20[#13]=20model=20defau?= =?UTF-8?q?lt=EC=97=90=20=ED=95=A8=EC=88=98=20()=EB=B9=BC=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- codes/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codes/models.py b/codes/models.py index b95593f..43c84c7 100644 --- a/codes/models.py +++ b/codes/models.py @@ -8,12 +8,12 @@ from common.utils.codeManger import set_expire class CertificationCode(BaseModel): use_type = models.CharField(choices=CertificateCodeUseType.choices, max_length=5) code = models.CharField(max_length=6) - expire_at = models.DateTimeField(default=set_expire(minutes=5)) + expire_at = models.DateTimeField(default=set_expire) is_used = models.BooleanField(default=False) identifier = models.CharField(max_length=40) class InviteCode(BaseModel): use_type = models.CharField(choices=InviteCodeUseType.choices, max_length=5) code = models.CharField(max_length=10) - expire_at = models.DateTimeField(default=set_expire(minutes=10080)) # 일주일은 10080분 + expire_at = models.DateTimeField(default=set_expire) # 일주일은 10080분 identifier = models.CharField(max_length=40) From 1a12240e42b58a8adda5a3e63e420b9ad4950299 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Mon, 7 Apr 2025 17:10:30 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9E=95=20Dependency:=20[#13]=20request?= =?UTF-8?q?=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=84=A4?= =?UTF-8?q?=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | Bin 684 -> 870 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/requirements.txt b/requirements.txt index 513391e7c5aa1d03c23286c55b5b2c9a7844e8aa..3db3c27f420438fd4303226af4344a1751da55bd 100644 GIT binary patch delta 187 zcmZ3(`iyOYPiQhjDnk)N2}34B8W7t8p%H@t5SudSF&F}|F_7eC;9^K-$Y4kWsw@Vo zFJaJS$YaO{@^XP;=WPTtMfFJA;yS;$Zd pB#VLO6hmzU*<%E Date: Mon, 7 Apr 2025 20:18:47 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=90=9B=20Fix:=20[#13]=20user=20model?= =?UTF-8?q?=20custom=5Furl,=20realname=20=ED=95=84=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/models.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/users/models.py b/users/models.py index a08f0e7..032c4d4 100644 --- a/users/models.py +++ b/users/models.py @@ -28,18 +28,20 @@ class UserManager(BaseUserManager): class User(BaseModel, AbstractBaseUser, PermissionsMixin): email = models.EmailField(unique=True) - is_email_verified = models.BooleanField(default=False) + realname = models.CharField(max_length=10, blank=True) + # is_email_verified = models.BooleanField(default=False) is_plers_terms_of_service = models.BooleanField(default=False) is_terms_of_service_colio = models.BooleanField(default=False) is_consent_personal_info = models.BooleanField(default=False) is_consent_third_party_sharing = models.BooleanField(default=False) is_consent_marketing = models.BooleanField(default=False) phone = models.CharField(max_length=11, blank=True) - is_phone_verified = models.BooleanField(default=False) + # is_phone_verified = models.BooleanField(default=False) nickname = models.CharField(max_length=20, blank=True) gender = models.CharField(choices=GenderChoices.choices, max_length=1, blank=True) birth_date = models.CharField(max_length=10, blank=True) custom_url = models.CharField(max_length=20, blank=True) + is_custom_url = models.BooleanField(default=False) job_and_interests = ArrayField(models.CharField(max_length=20), default=list, blank=True) skills = ArrayField(models.CharField(max_length=20), default=list, blank=True) external_links = ArrayField(models.TextField(), default=list, blank=True) @@ -47,7 +49,7 @@ class User(BaseModel, AbstractBaseUser, PermissionsMixin): profile_image = models.ImageField(upload_to='', blank=True) is_staff = models.BooleanField(default=False) - is_active = models.BooleanField(default=False) + is_active = models.BooleanField(default=True) objects = UserManager() From 02ec10d43a74e437d14a4e96042e527fce706ca5 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Tue, 8 Apr 2025 01:03:16 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#13]=20join=20serializ?= =?UTF-8?q?er=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/serializers.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 users/serializers.py diff --git a/users/serializers.py b/users/serializers.py new file mode 100644 index 0000000..7b05ed5 --- /dev/null +++ b/users/serializers.py @@ -0,0 +1,23 @@ +from .models import * +from rest_framework import serializers + +class JoinSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = [ + 'email', + 'password', + 'is_plers_terms_of_service', + 'is_terms_of_service_colio', + 'is_consent_personal_info', + 'is_consent_third_party_sharing', + 'is_consent_marketing', + 'realname', + 'phone', + 'nickname', + 'gender', + 'birth_date' + ] + + def create(self, validated_data): + return User.objects.create_user(**validated_data) \ No newline at end of file From 65d0c10889041ffb9a47a22e1125c2beafa20fd4 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Tue, 8 Apr 2025 01:04:20 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#13]=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85,=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C=EA=B8=89,=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=A4=91=EB=B3=B5=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?url=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/urls.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 users/urls.py diff --git a/users/urls.py b/users/urls.py new file mode 100644 index 0000000..8d3fc65 --- /dev/null +++ b/users/urls.py @@ -0,0 +1,12 @@ +from django.urls import path + +from .views import * + +app_name = 'users' + +urlpatterns = [ + path('refresh-token/', RefreshAPIView.as_view()), + path('join/', JoinAPIView.as_view()), + path('login/', LoginAPIView.as_view()), + path('check/', NicknameAPIView.as_view()), +] \ No newline at end of file From a6225db2b27a27cbcd9444f472d5ff7a11f21ea9 Mon Sep 17 00:00:00 2001 From: sm4640 Date: Tue, 8 Apr 2025 01:05:05 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=A8=20Feat:=20[#13]=20=ED=9A=8C?= =?UTF-8?q?=EC=9B=90=EA=B0=80=EC=9E=85,=20=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20?= =?UTF-8?q?=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C=EA=B8=89,=20=EB=8B=89?= =?UTF-8?q?=EB=84=A4=EC=9E=84=20=EC=A4=91=EB=B3=B5=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- users/views.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/users/views.py b/users/views.py index 91ea44a..68efe8a 100644 --- a/users/views.py +++ b/users/views.py @@ -1,3 +1,97 @@ -from django.shortcuts import render +from django.shortcuts import get_object_or_404 -# Create your views here. +from rest_framework.views import APIView +from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenRefreshSerializer +from rest_framework_simplejwt.tokens import RefreshToken +from rest_framework_simplejwt.exceptions import TokenError, InvalidToken + +from rest_framework import status +from rest_framework.response import Response +from rest_framework.permissions import AllowAny, IsAuthenticated + +from django.contrib.auth import authenticate + +from .models import * +from .serializers import * + + +class RefreshAPIView(APIView): + permission_classes = [AllowAny] + # access token 재발급 + def post(self, request): + refresh = request.COOKIES.get("refresh") + if not refresh: + return Response({"message": "No refresh token"}, status=status.HTTP_400_BAD_REQUEST) + if request.data.get("user_id", None) != RefreshToken(refresh).payload.get("user_id", None): + return Response({"message": "Wrong userid"}, status=status.HTTP_400_BAD_REQUEST) + try: + serializer = TokenRefreshSerializer(data={'refresh': refresh}) + if serializer.is_valid(): + res = Response({"access": serializer.validated_data['access']}, status=status.HTTP_200_OK) + res.set_cookie("refresh", serializer.validated_data['refresh'], httponly=True, samesite="Lax", secure=True) + return res + except TokenError as e: + return Response({"message": f"Invalid token: {e}"}, status=status.HTTP_401_UNAUTHORIZED) + + + +class JoinAPIView(APIView): + permission_classes = [AllowAny] + # 회원가입 + def post(self, request): + serializer = JoinSerializer(data=request.data) + if serializer.is_valid(): + serializer.save() + res = Response({"message": "회원가입이 완료되었습니다."}, status=status.HTTP_200_OK) + return res + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class LoginAPIView(APIView): + permission_classes = [AllowAny] + # 로그인 + def post(self, request): + email=request.data.get("email", None) + password=request.data.get("password", None) + # 로그인 전 쿠키에 있는 리프레시 토큰 무효화 + try: + refresh = request.COOKIES.get('refresh') + if refresh: + refresh_token = RefreshToken(refresh) + refresh_token.blacklist() + except TokenError as e: + pass + + if user := authenticate(email=email, password=password): + serializer = TokenObtainPairSerializer(data={'email': email, 'password': password}) + # id, 비번 맞나 틀리나 검사 + if serializer.is_valid(): + res = Response( + { + "message": "login success", + "user_id": user.id, + "nickname": user.nickname, + "access": serializer.validated_data['access'], + "is_custom_url": user.is_custom_url + }, + status=status.HTTP_200_OK, + ) + res.set_cookie("refresh", serializer.validated_data['refresh'], httponly=True, samesite="Lax", secure=True) + return res + else: + return Response(serializer.errors) + else: # id, 비번 둘 중 하나가 틀렸을 때 + return Response({"message": "아이디 혹은 비밀번호가 맞지 않습니다."}, status=status.HTTP_400_BAD_REQUEST) + +class NicknameAPIView(APIView): + permission_classes = [AllowAny] + # 닉네임 중복 체크 + def get(self, request): + nickname = request.GET.get('nickname', None) + if not nickname: + return Response({"message": "닉네임을 입력하세요."}, status=status.HTTP_400_BAD_REQUEST) + try: + get_object_or_404(User, nickname=nickname) + return Response({"message": "해당 닉네임은 사용할 수 없습니다."}, status=status.HTTP_400_BAD_REQUEST) + except: + return Response({"message": "사용할 수 있는 닉네임입니다."}, status=status.HTTP_200_OK) + \ No newline at end of file