diff --git a/codes/serializers.py b/codes/serializers.py new file mode 100644 index 0000000..ea32359 --- /dev/null +++ b/codes/serializers.py @@ -0,0 +1,10 @@ +from django.utils.timezone import now + +from .models import * +from rest_framework import serializers +from common.utils.codeManger import set_expire, generate_code + + +class CertificateCodeSerializer(serializers.Serializer): + identifier = serializers.CharField(max_length=40, write_only=True) + code = serializers.CharField(max_length=6, write_only=True, required=False) diff --git a/codes/services.py b/codes/services.py new file mode 100644 index 0000000..4449084 --- /dev/null +++ b/codes/services.py @@ -0,0 +1,82 @@ +import random +import string +import requests + +from django.db import IntegrityError +from django.conf import settings +from django.utils.timezone import now + +from rest_framework.views import APIView +from rest_framework.response import Response +from rest_framework import status + +from .models import CertificationCode + +from common.models.choiceModels import CertificateCodeUseType +from common.utils.codeManger import set_expire + +from solapi import SolapiMessageService +from solapi.model import RequestMessage + +# from .schemas import send_sms_post_schema # Swagger나 drf-spectacular 등에 사용되는 데코레이터 + +class CertificateService: + @staticmethod + def send(code, identifier): + pass + + # 유효시간 5분 설정해서 저장, 이전 같은 번호 sms 요청 값들 is_used=True로 바꾸기 + @staticmethod + def save_certificate_info(use_type: CertificateCodeUseType, code, identifier): + try: + befores = CertificationCode.objects.filter( + use_type=use_type, + identifier=identifier, + is_used=False + ).update(is_used=True) + instance = CertificationCode.objects.create( + use_type = use_type, + code = code, + expire_at = set_expire(5), + identifier = identifier + ) + return True + except IntegrityError as e: + return False + + # code 체크 후 is_used=True로 설정 + @staticmethod + def check_code(use_type: CertificateCodeUseType, code, identifier): + if check := CertificationCode.objects.filter( + use_type=use_type, + identifier=identifier, + is_used=False, + expire_at__gte=now() + ).order_by('-created_at').first(): + + if check.code == code: + check.is_used=True + check.save() + return True + return False + +class SmsService(CertificateService): + # 같은 전번에 대해 요청 텀 설정은 views 단에서 하자. + @staticmethod + def send(code, identifier): + message_service = SolapiMessageService( + api_key = settings.SOLAPI_API_KEY, api_secret = settings.SOLAPI_API_SECRET + ) + + message = RequestMessage( + from_ = settings.FROM_PHONE_NUMBER, + to = identifier, + text = "colio 서비스 회원가입 인증번호는 " + code +" 입니다." + ) + + try: + res = message_service.send(message) + return True + except Exception as e: + print(f"메시지 발송 실패: {str(e)}") + return False \ No newline at end of file diff --git a/codes/urls.py b/codes/urls.py new file mode 100644 index 0000000..2a1a33b --- /dev/null +++ b/codes/urls.py @@ -0,0 +1,10 @@ +from django.urls import path + +from .views import * + +app_name = 'codes' + +urlpatterns = [ + path('certificate/', CertificationAPIView.as_view()), + # path('invite/', ) +] \ No newline at end of file diff --git a/codes/views.py b/codes/views.py index 91ea44a..1457eb9 100644 --- a/codes/views.py +++ b/codes/views.py @@ -1,3 +1,56 @@ -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.permissions import AllowAny, IsAuthenticated + +from rest_framework import status +from rest_framework.response import Response +from django.core.mail import EmailMessage +from django.db import transaction + +from .serializers import * +from .services import * +from common.models.choiceModels import CertificateCodeUseType +from common.utils.codeManger import generate_code + + +certificate_use_type = { + "phone": SmsService, + # "email": EmailService +} + +class CertificationAPIView(APIView): + permission_classes = [AllowAny] + + # 인증 발송 + @transaction.atomic + def post(self, request): + use_type = request.query_params.get("type") + serv = certificate_use_type[use_type] + serializer = CertificateCodeSerializer(data=request.data) + if serializer.is_valid(): + create_code = generate_code(6) + if serv.save_certificate_info(use_type, create_code, serializer.validated_data['identifier']): + if serv.send(create_code, serializer.validated_data['identifier']): + return Response({'message': "success send and save"}) + else: # 전송 실패 + return Response({"message": "failed send"}) + else: # 코드 저장 실패 + return Response({'message': "failed save"}, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + + # 인증 확인 + @transaction.atomic + def patch(self, request): + use_type = request.query_params.get("type") + serv = certificate_use_type[use_type] + code = request.data.get('code', None) + if not code: + return Response({"message": "no code"}, status=status.HTTP_400_BAD_REQUEST) + serializer = CertificateCodeSerializer(data=request.data) + if serializer.is_valid(): + if serv.check_code(use_type, code, serializer.validated_data['identifier']): + return Response({"message": "certificated successfully"}, status=status.HTTP_200_OK) + return Response({"message": "wrong code, please retry"}, status=status.HTTP_400_BAD_REQUEST) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/common/models/choiceModels.py b/common/models/choiceModels.py index 73b72d3..0168b50 100644 --- a/common/models/choiceModels.py +++ b/common/models/choiceModels.py @@ -5,8 +5,8 @@ class GenderChoices(models.TextChoices): WOMAN = '여', '여성' class CertificateCodeUseType(models.TextChoices): - EMAIL = '이메일', '이메일' - PHONE = '휴대폰', '휴대폰' + EMAIL = 'email', 'email' + PHONE = 'phone', 'phone' class InviteCodeUseType(models.TextChoices): PROJECT = '프로젝트', '프로젝트' diff --git a/config/settings.py b/config/settings.py index 41cf535..609a60c 100644 --- a/config/settings.py +++ b/config/settings.py @@ -29,6 +29,10 @@ environ.Env.read_env(os.path.join(BASE_DIR, '.env')) SECRET_KEY = env('SECRET_KEY') +SOLAPI_API_KEY = env('SOLAPI_API_KEY') +SOLAPI_API_SECRET = env('SOLAPI_API_SECRET') +FROM_PHONE_NUMBER = env('FROM_PHONE_NUMBER') + DEBUG = env.bool('DEBUG') ALLOWED_HOSTS = ['*'] diff --git a/config/urls.py b/config/urls.py index b17ad1b..9a6a6f4 100644 --- a/config/urls.py +++ b/config/urls.py @@ -7,7 +7,7 @@ from django.conf.urls.static import static urlpatterns = [ path('admin/', admin.site.urls), path('api/user/', include('users.urls')), - # path('api/code/', include('codes.urls')), + path('api/code/', include('codes.urls')), path('api/portfolio/', include('portfolios.urls')), path('api/project/', include('projects.urls')), path('api/notification/', include('notifications.urls')), diff --git a/requirements.txt b/requirements.txt index 8ae4bec..d9f0322 100644 Binary files a/requirements.txt and b/requirements.txt differ