diff --git a/config/__init__.py b/config/__init__.py index e69de29..52e5024 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -0,0 +1,6 @@ +from .celery import app as celery_app + + +__all__ = ( + "celery_app" +) \ No newline at end of file diff --git a/config/conf/celery.py b/config/conf/celery.py index d11eea8..59b4482 100644 --- a/config/conf/celery.py +++ b/config/conf/celery.py @@ -1,7 +1,7 @@ from django.conf import settings from config.env import env -CELERY_BROKER_URL = env.str('REDIS_URL') +CELERY_BROKER_URL = 'redis://redis:6379/0' CELERY_ACCEPT_CONTENT = ['json'] CELERY_TASK_SERIALIZER = 'json' CELERY_TIMEZONE = settings.TIME_ZONE diff --git a/config/conf/redis.py b/config/conf/redis.py new file mode 100644 index 0000000..0621902 --- /dev/null +++ b/config/conf/redis.py @@ -0,0 +1,11 @@ +from config.env import env + +CACHES = { + "default": { + "BACKEND": "django_redis.cache.RedisCache", + "LOCATION": env("REDIS_URL"), + "OPTIONS": { + "CLIENT_CLASS": "django_redis.client.DefaultClient", + } + } +} \ No newline at end of file diff --git a/config/settings/base.py b/config/settings/base.py index c0ede8c..8945d42 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -140,3 +140,4 @@ from config.conf.drf_spectacular import * from config.conf.rest_framework import * from config.conf.simplejwt import * from config.conf.celery import * +from config.conf.redis import * \ No newline at end of file diff --git a/core/apps/accounts/serializers/auth.py b/core/apps/accounts/serializers/auth.py index 03a4899..6fe3edb 100644 --- a/core/apps/accounts/serializers/auth.py +++ b/core/apps/accounts/serializers/auth.py @@ -3,6 +3,9 @@ from django.contrib.auth import get_user_model from rest_framework import serializers +from core.apps.accounts.tasks.user import create_and_send_sms_code +from core.apps.accounts.enums.user import ROLE_CHOICES + User = get_user_model() class LoginSerializer(serializers.Serializer): @@ -34,9 +37,57 @@ class RegisterSerializer(serializers.Serializer): with transaction.atomic(): new_user = User.objects.create_user( phone=validated_data.pop('phone'), + is_active=False ) new_user.set_password(validated_data.pop('password')) new_user.save() + create_and_send_sms_code.delay(new_user.id) return new_user +class ConfirmUserSerializer(serializers.Serializer): + phone = serializers.CharField() + code = serializers.IntegerField() + + def validate(self, data): + try: + user = User.objects.get(phone=data.get('phone')) + except User.DoesNotExist: + raise serializers.ValidationError({"detail": "User not found"}) + if user.is_active: + raise serializers.ValidationError({"detail": "User already activated"}) + data['user'] = user + return data + + +class ChoiseRoleSerializer(serializers.Serializer): + phone = serializers.CharField() + role = serializers.ChoiceField(choices=ROLE_CHOICES) + + def validate(self, data): + try: + user = User.objects.get(phone=data.get("phone"), is_active=False) + except User.DoesNotExist: + raise serializers.ValidationError({"detail": "user not found"}) + data['user'] = user + return data + + +class CompliteUserProfileSerializer(serializers.Serializer): + first_name = serializers.CharField() + last_name = serializers.CharField() + email = serializers.EmailField() + + def validate(self, data): + user = User.objects.filter(email=data.get('email')).first() + if user: + raise serializers.ValidationError({'detail': "User with this email already exists"}) + return data + + def update(self, instance, validated_data): + with transaction.atomic(): + instance.first_name = validated_data.get('first_name') + instance.last_name = validated_data.get('last_name') + instance.email = validated_data.get('email') + instance.save() + return instance \ No newline at end of file diff --git a/core/apps/accounts/tasks/user.py b/core/apps/accounts/tasks/user.py index c265ed1..0d3d8c7 100644 --- a/core/apps/accounts/tasks/user.py +++ b/core/apps/accounts/tasks/user.py @@ -1,9 +1,11 @@ from celery import shared_task from core.apps.accounts.models.verification_code import VerificationCode +from core.apps.accounts.models.user import User from core.services.sms import send_sms_eskiz @shared_task def create_and_send_sms_code(user): + user = User.objects.get(id=user) code = user.generate_code() send_sms_eskiz(user.phone, code) \ No newline at end of file diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py index f57ccea..424f950 100644 --- a/core/apps/accounts/urls.py +++ b/core/apps/accounts/urls.py @@ -1,12 +1,15 @@ from django.urls import path, include -from core.apps.accounts.views.auth import LoginApiView, RegisterApiView +from core.apps.accounts.views.auth import LoginApiView, RegisterApiView, ConfirUserApiView, ChoiceUserRoleApiView, CompliteUserProfileApiView urlpatterns = [ path('auth/', include( [ path('login/', LoginApiView.as_view(), name='login'), path('register/', RegisterApiView.as_view(), name='login'), + path('confirm_user/', ConfirUserApiView.as_view(), name='confirm-user'), + path('choise_user_role/', ChoiceUserRoleApiView.as_view(), name='choise-user-role'), + path('complite_user_profile//', CompliteUserProfileApiView.as_view(), name='complite-user-profile'), ] )) ] \ No newline at end of file diff --git a/core/apps/accounts/views/auth.py b/core/apps/accounts/views/auth.py index c13c6f9..fc9bf38 100644 --- a/core/apps/accounts/views/auth.py +++ b/core/apps/accounts/views/auth.py @@ -1,4 +1,5 @@ from django.contrib.auth import get_user_model +from django.utils import timezone from rest_framework import generics, status, views from rest_framework.response import Response @@ -8,6 +9,7 @@ from rest_framework_simplejwt.tokens import RefreshToken from drf_spectacular.utils import extend_schema from core.apps.accounts.serializers import auth as auth_serializer +from core.apps.accounts.models.verification_code import VerificationCode User = get_user_model() @@ -29,4 +31,67 @@ class LoginApiView(generics.GenericAPIView): class RegisterApiView(generics.CreateAPIView): serializer_class = auth_serializer.RegisterSerializer queryset = User.objects.all() - permission_classes = [] \ No newline at end of file + permission_classes = [] + + +@extend_schema(tags=['auth']) +class ConfirUserApiView(generics.GenericAPIView): + serializer_class = auth_serializer.ConfirmUserSerializer + queryset = User.objects.all() + permission_classes = [] + + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + user = serializer.validated_data.get('user') + code = serializer.validated_data.get('code') + code = VerificationCode.objects.filter(user=user, code=code).first() + if code: + if code.is_expired or code.expiration_time < timezone.now().time(): + return Response({"success": True, "message": "code is expired"}, status=status.HTTP_400_BAD_REQUEST) + if code.is_verify: + return Response({"success": True, "message": "code is verified"}, status=status.HTTP_400_BAD_REQUEST) + user.save() + code.is_verify = True + code.is_expired = True + code.save() + return Response({"success": True, "message": "user activated"}, status=status.HTTP_202_ACCEPTED) + return Response({"success": False, "message": "code is wrong"}, status=status.HTTP_400_BAD_REQUEST) + return Response({"success": False, "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST) + + +@extend_schema(tags=['auth']) +class ChoiceUserRoleApiView(generics.GenericAPIView): + serializer_class = auth_serializer.ChoiseRoleSerializer + queryset = User.objects.all() + permission_classes = [] + + @extend_schema(description="roles -> PP(physcal person) or LP(legal person)") + def post(self, request): + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + user = serializer.validated_data.get('user') + role = serializer.validated_data.get('role') + user.role = role + user.save() + return Response({'success': True, 'message': "role is selected"}, status=status.HTTP_200_OK) + return Response({'success': False, "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST) + + +@extend_schema(tags=['auth']) +class CompliteUserProfileApiView(generics.GenericAPIView): + serializer_class = auth_serializer.CompliteUserProfileSerializer + queryset = User.objects.all() + permission_classes = [] + + def put(self, request, phone): + user = User.objects.filter(phone=phone, is_active=True).first() + if user: + serializer = self.serializer_class(data=request.data, instance=user) + if serializer.is_valid(): + serializer.save() + token = RefreshToken.for_user(user) + return Response({'access_token': str(token.access_token), "refresh_token": str(token), "role": user.role}, status=status.HTTP_200_OK) + return Response({'success': False, 'message': serializer.errors}, status=status.HTTP_400_BAD_REQUEST) + return Response({'success': False, "message": "User not found"}, status=status.HTTP_404_NOT_FOUND) + diff --git a/docker-compose.yaml b/docker-compose.yaml index 3d737c9..72654f6 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -31,12 +31,30 @@ services: command: sh resources/scripts/entrypoint.sh environment: - PYTHONPYCACHEPREFIX=/var/cache/pycache + - REDIS_URL=redis://redis:6379/0 + - CELERY_BROKER_URL=redis://redis:6379/0 + - CELERY_BACKEND_URL=redis://redis:6379/0 + + volumes: - './:/code' depends_on: - db - redis + celery: + build: + context: . + dockerfile: ./docker/Dockerfile.web + command: celery -A config worker --loglevel=info + environment: + - CELERY_BROKER_URL=redis://redis:6379/0 + depends_on: + - redis + - web + networks: + - trustme + db: image: postgres:16 networks: @@ -56,19 +74,7 @@ services: - trustme restart: always image: redis:latest - - celery: - networks: - - trustme - build: - context: . - dockerfile: ./docker/Dockerfile.web - command: celery -A config worker --loglevel=info - volumes: - - .:/code - depends_on: - - redis - - web - environment: - CELERY_BROKER_URL: ${REDIS_URL} + ports: + - 6379:6379 +