@@ -4,6 +4,11 @@ from common.models.baseModels import BaseModel
|
|||||||
from common.models.choiceModels import CertificateCodeUseType, InviteCodeUseType
|
from common.models.choiceModels import CertificateCodeUseType, InviteCodeUseType
|
||||||
from common.utils.codeManger import set_expire
|
from common.utils.codeManger import set_expire
|
||||||
|
|
||||||
|
# from django.contrib.contenttypes.fields import GenericForeignKey
|
||||||
|
# from django.contrib.contenttypes.models import ContentType
|
||||||
|
|
||||||
|
# from projects.models import Project
|
||||||
|
|
||||||
|
|
||||||
class CertificationCode(BaseModel):
|
class CertificationCode(BaseModel):
|
||||||
use_type = models.CharField(choices=CertificateCodeUseType.choices, max_length=5)
|
use_type = models.CharField(choices=CertificateCodeUseType.choices, max_length=5)
|
||||||
@@ -17,3 +22,4 @@ class InviteCode(BaseModel):
|
|||||||
code = models.CharField(max_length=10)
|
code = models.CharField(max_length=10)
|
||||||
expire_at = models.DateTimeField(default=set_expire) # 일주일은 10080분
|
expire_at = models.DateTimeField(default=set_expire) # 일주일은 10080분
|
||||||
identifier = models.CharField(max_length=40)
|
identifier = models.CharField(max_length=40)
|
||||||
|
|
||||||
|
|||||||
@@ -8,3 +8,7 @@ from common.utils.codeManger import set_expire, generate_code
|
|||||||
class CertificateCodeSerializer(serializers.Serializer):
|
class CertificateCodeSerializer(serializers.Serializer):
|
||||||
identifier = serializers.CharField(max_length=40, write_only=True)
|
identifier = serializers.CharField(max_length=40, write_only=True)
|
||||||
code = serializers.CharField(max_length=6, write_only=True, required=False)
|
code = serializers.CharField(max_length=6, write_only=True, required=False)
|
||||||
|
|
||||||
|
class InviteCodeSerializer(serializers.Serializer):
|
||||||
|
identifier = serializers.CharField(max_length=40, write_only=True)
|
||||||
|
code = serializers.CharField(max_length=10, write_only=True, required=False)
|
||||||
@@ -10,16 +10,25 @@ from rest_framework.views import APIView
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
from .models import CertificationCode
|
from .models import *
|
||||||
|
|
||||||
from common.models.choiceModels import CertificateCodeUseType
|
from common.models.choiceModels import CertificateCodeUseType, InviteCodeUseType
|
||||||
from common.utils.codeManger import set_expire
|
from common.utils.codeManger import set_expire
|
||||||
|
|
||||||
from solapi import SolapiMessageService
|
from solapi import SolapiMessageService
|
||||||
from solapi.model import RequestMessage
|
from solapi.model import RequestMessage
|
||||||
|
|
||||||
|
from users.models import User
|
||||||
|
|
||||||
|
from projects.models import Project, ProjectTeamList
|
||||||
|
|
||||||
# from .schemas import send_sms_post_schema # Swagger나 drf-spectacular 등에 사용되는 데코레이터
|
# from .schemas import send_sms_post_schema # Swagger나 drf-spectacular 등에 사용되는 데코레이터
|
||||||
|
|
||||||
|
INVITE_CHOICE_USE_TYPE ={
|
||||||
|
'p': InviteCodeUseType.PROJECT,
|
||||||
|
'h': InviteCodeUseType.HACKATHON
|
||||||
|
}
|
||||||
|
|
||||||
class CertificateService:
|
class CertificateService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def send(code, identifier):
|
def send(code, identifier):
|
||||||
@@ -80,3 +89,45 @@ class SmsService(CertificateService):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
# print(f"메시지 발송 실패: {str(e)}")
|
# print(f"메시지 발송 실패: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class InviteService:
|
||||||
|
|
||||||
|
# 종류마다 사용하는 테이블이 다르므로 오버라이딩
|
||||||
|
@staticmethod
|
||||||
|
def add_member(invitee, work):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# url 생성
|
||||||
|
@staticmethod
|
||||||
|
def create_invite_object_and_url(use_type, identifier, code) -> str:
|
||||||
|
InviteCode.objects.create(
|
||||||
|
use_type = INVITE_CHOICE_USE_TYPE[use_type],
|
||||||
|
code = code,
|
||||||
|
expire_at = set_expire(10080),
|
||||||
|
identifier = identifier,
|
||||||
|
)
|
||||||
|
return f"https://{settings.DOMAIN_NAME}/invite?t={use_type}&i={identifier}&c={code}"
|
||||||
|
|
||||||
|
# 코드 유효성 검사
|
||||||
|
@staticmethod
|
||||||
|
def check_code(use_type, identifier, code) -> bool:
|
||||||
|
if InviteCode.objects.filter(
|
||||||
|
use_type = INVITE_CHOICE_USE_TYPE[use_type],
|
||||||
|
identifier = identifier,
|
||||||
|
code = code,
|
||||||
|
expire_at__gte=now()
|
||||||
|
).exists():
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectInviteService(InviteService):
|
||||||
|
|
||||||
|
# 사용자 초대
|
||||||
|
@staticmethod
|
||||||
|
def add_member(invitee: User, work: Project):
|
||||||
|
return ProjectTeamList.objects.create(user=invitee, project=work)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ app_name = 'codes'
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('certificate/', CertificationAPIView.as_view()),
|
path('certificate/', CertificationAPIView.as_view()),
|
||||||
# path('invite/', )
|
path('invite/', InviteByLinkAPIView.as_view())
|
||||||
]
|
]
|
||||||
@@ -14,11 +14,21 @@ from common.models.choiceModels import CertificateCodeUseType
|
|||||||
from common.utils.codeManger import generate_code
|
from common.utils.codeManger import generate_code
|
||||||
|
|
||||||
|
|
||||||
certificate_use_type = {
|
CERTIFICATE_SERVICE_USE_TYPE = {
|
||||||
"phone": SmsService,
|
"phone": SmsService,
|
||||||
# "email": EmailService
|
# "email": EmailService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INVITE_USE_TYPE = {
|
||||||
|
"p": {
|
||||||
|
"word": "project",
|
||||||
|
"service": ProjectInviteService,
|
||||||
|
"model": Project,
|
||||||
|
"team_model": ProjectTeamList
|
||||||
|
}
|
||||||
|
# "h": HackathonInviteService
|
||||||
|
}
|
||||||
|
|
||||||
class CertificationAPIView(APIView):
|
class CertificationAPIView(APIView):
|
||||||
permission_classes = [AllowAny]
|
permission_classes = [AllowAny]
|
||||||
|
|
||||||
@@ -26,7 +36,9 @@ class CertificationAPIView(APIView):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
use_type = request.query_params.get("type")
|
use_type = request.query_params.get("type")
|
||||||
serv = certificate_use_type[use_type]
|
if use_type not in CERTIFICATE_SERVICE_USE_TYPE:
|
||||||
|
return Response({"message": "Not defined use_type"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
serv = CERTIFICATE_SERVICE_USE_TYPE[use_type]
|
||||||
serializer = CertificateCodeSerializer(data=request.data)
|
serializer = CertificateCodeSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
create_code = generate_code(6)
|
create_code = generate_code(6)
|
||||||
@@ -44,7 +56,9 @@ class CertificationAPIView(APIView):
|
|||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def patch(self, request):
|
def patch(self, request):
|
||||||
use_type = request.query_params.get("type")
|
use_type = request.query_params.get("type")
|
||||||
serv = certificate_use_type[use_type]
|
if use_type not in CERTIFICATE_SERVICE_USE_TYPE:
|
||||||
|
return Response({"message": "Not defined use_type"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
serv = CERTIFICATE_SERVICE_USE_TYPE[use_type]
|
||||||
code = request.data.get('code', None)
|
code = request.data.get('code', None)
|
||||||
if not code:
|
if not code:
|
||||||
return Response({"message": "no code"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"message": "no code"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -54,3 +68,49 @@ class CertificationAPIView(APIView):
|
|||||||
return Response({"message": "certificated successfully"}, status=status.HTTP_200_OK)
|
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({"message": "wrong code, please retry"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
class InviteByLinkAPIView(APIView):
|
||||||
|
|
||||||
|
# 링크 초대(복사)
|
||||||
|
@transaction.atomic
|
||||||
|
def post(self, request):
|
||||||
|
use_type = request.query_params.get("type")
|
||||||
|
if use_type not in INVITE_USE_TYPE:
|
||||||
|
return Response({"message": "Not defined use_type"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
serv = INVITE_USE_TYPE[use_type]['service']
|
||||||
|
user = request.user
|
||||||
|
serializer = InviteCodeSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
work = get_object_or_404(INVITE_USE_TYPE[use_type]['model'], id=serializer.validated_data['identifier'])
|
||||||
|
if user != work.owner: # 유저 권한 추가될 시 수정 필요
|
||||||
|
return Response({"message": "Not owner"}, status=status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
|
create_code = generate_code(10)
|
||||||
|
invite_url = serv.create_invite_object_and_url(use_type, serializer.validated_data['identifier'], create_code)
|
||||||
|
return Response({"invite_url": invite_url})
|
||||||
|
|
||||||
|
# 링크 확인
|
||||||
|
@transaction.atomic
|
||||||
|
def patch(self, request):
|
||||||
|
use_type = request.query_params.get("type")
|
||||||
|
if use_type not in INVITE_USE_TYPE:
|
||||||
|
return Response({"message": "Not defined use_type"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
serv = INVITE_USE_TYPE[use_type]['service']
|
||||||
|
user = request.user
|
||||||
|
serializer = InviteCodeSerializer(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
if not serv.check_code(use_type, serializer.validated_data['identifier'], serializer.validated_data['code']):
|
||||||
|
return Response({"message": "Not correct or expired code"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
work = get_object_or_404(INVITE_USE_TYPE[use_type]['model'], id=serializer.validated_data['identifier'])
|
||||||
|
filter_dict = {INVITE_USE_TYPE[use_type]['word']:work}
|
||||||
|
|
||||||
|
if INVITE_USE_TYPE[use_type]['team_model'].objects.filter(user=user, **filter_dict).exists():
|
||||||
|
return Response({"message": "already invited member"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
if serv.add_member(user, work):
|
||||||
|
return Response({"message": "invite success"}, status=status.HTTP_200_OK)
|
||||||
|
return Response({"message": "invite failed"}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
@@ -9,8 +9,8 @@ class CertificateCodeUseType(models.TextChoices):
|
|||||||
PHONE = 'phone', 'phone'
|
PHONE = 'phone', 'phone'
|
||||||
|
|
||||||
class InviteCodeUseType(models.TextChoices):
|
class InviteCodeUseType(models.TextChoices):
|
||||||
PROJECT = '프로젝트', '프로젝트'
|
PROJECT = 'p', 'p'
|
||||||
HACKATHON = '해커톤', '해커톤'
|
HACKATHON = 'h', 'h'
|
||||||
|
|
||||||
class NotificationType(models.TextChoices):
|
class NotificationType(models.TextChoices):
|
||||||
INVITE = '초대', '초대'
|
INVITE = '초대', '초대'
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
|
|||||||
|
|
||||||
SECRET_KEY = env('SECRET_KEY')
|
SECRET_KEY = env('SECRET_KEY')
|
||||||
|
|
||||||
|
DOMAIN_NAME = 'colio.co.kr'
|
||||||
|
DEV_DOMAIN_NAME = env('DEV_DOMAIN_NAME')
|
||||||
|
|
||||||
SOLAPI_API_KEY = env('SOLAPI_API_KEY')
|
SOLAPI_API_KEY = env('SOLAPI_API_KEY')
|
||||||
SOLAPI_API_SECRET = env('SOLAPI_API_SECRET')
|
SOLAPI_API_SECRET = env('SOLAPI_API_SECRET')
|
||||||
FROM_PHONE_NUMBER = env('FROM_PHONE_NUMBER')
|
FROM_PHONE_NUMBER = env('FROM_PHONE_NUMBER')
|
||||||
|
|||||||
Reference in New Issue
Block a user