change register apis and save user phone in redis
This commit is contained in:
@@ -4,5 +4,5 @@ from core.apps.accounts.models.verification_code import VerificationCode
|
|||||||
|
|
||||||
@admin.register(VerificationCode)
|
@admin.register(VerificationCode)
|
||||||
class VerificationCodeAdmin(admin.ModelAdmin):
|
class VerificationCodeAdmin(admin.ModelAdmin):
|
||||||
list_display = ['id', 'user', 'code', 'is_expired', 'is_verify']
|
list_display = ['id', 'phone', 'code', 'is_expired', 'is_verify']
|
||||||
|
|
||||||
32
core/apps/accounts/cache/user.py
vendored
Normal file
32
core/apps/accounts/cache/user.py
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import redis
|
||||||
|
|
||||||
|
from django.contrib.auth.hashers import make_password
|
||||||
|
|
||||||
|
from config.env import env
|
||||||
|
|
||||||
|
r = redis.StrictRedis.from_url(env.str('REDIS_URL'))
|
||||||
|
|
||||||
|
|
||||||
|
def cache_user_credentials(phone_number, password, time):
|
||||||
|
hashed_password = make_password(password)
|
||||||
|
key = f"user_credentials:{phone_number}"
|
||||||
|
|
||||||
|
r.hmset(key, {
|
||||||
|
"phone": phone_number,
|
||||||
|
"password": hashed_password
|
||||||
|
})
|
||||||
|
|
||||||
|
r.expire(key, time)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_creadentials(phone_number):
|
||||||
|
key = f"user_credentials:{phone_number}"
|
||||||
|
data = r.hgetall(key)
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"phone": data.get(b"phone").decode() if data.get(b"phone") else None,
|
||||||
|
"password": data.get(b"password").decode() if data.get(b"password") else None
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 5.2 on 2025-07-14 15:16
|
# Generated by Django 5.2 on 2025-07-22 15:26
|
||||||
|
|
||||||
import core.apps.accounts.managers.user
|
import core.apps.accounts.managers.user
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
@@ -16,6 +16,24 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VerificationCode',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True)),
|
||||||
|
('code', models.PositiveIntegerField()),
|
||||||
|
('phone', models.CharField(max_length=13, validators=[django.core.validators.RegexValidator(message="Telefon raqam formatda bo'lishi kerak: +998XXXXXXXXX", regex='^\\+998\\d{9}$')])),
|
||||||
|
('is_expired', models.BooleanField(default=False)),
|
||||||
|
('is_verify', models.BooleanField(default=False)),
|
||||||
|
('expiration_time', models.TimeField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Verification Code',
|
||||||
|
'verbose_name_plural': 'Verification Codes',
|
||||||
|
'db_table': 'verification_codes',
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
# Generated by Django 5.2 on 2025-07-14 17:08
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='VerificationCode',
|
|
||||||
fields=[
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True)),
|
|
||||||
('code', models.PositiveIntegerField()),
|
|
||||||
('is_expired', models.BooleanField(default=False)),
|
|
||||||
('is_verify', models.BooleanField(default=False)),
|
|
||||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='verification_codes', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Verification Code',
|
|
||||||
'verbose_name_plural': 'Verification Codes',
|
|
||||||
'db_table': 'verification_codes',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 5.2 on 2025-07-14 17:17
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0002_verificationcode'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='verificationcode',
|
|
||||||
name='expiration_time',
|
|
||||||
field=models.TimeField(blank=True, null=True),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -30,7 +30,7 @@ class User(BaseModel, AbstractUser):
|
|||||||
expiration_time = timezone.now() + datetime.timedelta(minutes=2)
|
expiration_time = timezone.now() + datetime.timedelta(minutes=2)
|
||||||
VerificationCode.objects.create(
|
VerificationCode.objects.create(
|
||||||
code=code,
|
code=code,
|
||||||
user=self,
|
user=self.phone,
|
||||||
expiration_time=expiration_time,
|
expiration_time=expiration_time,
|
||||||
)
|
)
|
||||||
return code
|
return code
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from core.apps.shared.models.base import BaseModel
|
from core.apps.shared.models.base import BaseModel
|
||||||
|
from core.apps.accounts.validators.user import phone_regex
|
||||||
|
|
||||||
|
|
||||||
class VerificationCode(BaseModel):
|
class VerificationCode(BaseModel):
|
||||||
code = models.PositiveIntegerField()
|
code = models.PositiveIntegerField()
|
||||||
user = models.ForeignKey('User', on_delete=models.CASCADE, related_name='verification_codes')
|
phone = models.CharField(max_length=13, validators=[phone_regex])
|
||||||
is_expired = models.BooleanField(default=False)
|
is_expired = models.BooleanField(default=False)
|
||||||
is_verify = models.BooleanField(default=False)
|
is_verify = models.BooleanField(default=False)
|
||||||
expiration_time = models.TimeField(null=True, blank=True)
|
expiration_time = models.TimeField(null=True, blank=True)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from core.apps.accounts.tasks.user import create_and_send_sms_code
|
from core.apps.accounts.tasks.user import create_and_send_sms_code
|
||||||
from core.apps.accounts.enums.user import ROLE_CHOICES
|
from core.apps.accounts.enums.user import ROLE_CHOICES
|
||||||
|
from core.apps.accounts.models.verification_code import VerificationCode
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@@ -28,21 +30,10 @@ class RegisterSerializer(serializers.Serializer):
|
|||||||
phone = serializers.CharField()
|
phone = serializers.CharField()
|
||||||
password = serializers.CharField()
|
password = serializers.CharField()
|
||||||
|
|
||||||
def validate(self, data):
|
def validate_phone(self, value):
|
||||||
if User.objects.filter(phone=data.get('phone')).exists():
|
if User.objects.filter(phone=value).exists():
|
||||||
raise serializers.ValidationError({'detail': "User with this phone number already exists"})
|
raise serializers.ValidationError("User exists")
|
||||||
return data
|
return value
|
||||||
|
|
||||||
def create(self, validated_data):
|
|
||||||
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):
|
class ConfirmUserSerializer(serializers.Serializer):
|
||||||
@@ -50,28 +41,20 @@ class ConfirmUserSerializer(serializers.Serializer):
|
|||||||
code = serializers.IntegerField()
|
code = serializers.IntegerField()
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
try:
|
phone = data['phone']
|
||||||
user = User.objects.get(phone=data.get('phone'))
|
code = data['code']
|
||||||
except User.DoesNotExist:
|
confirmation = VerificationCode.objects.filter(code=code, phone=phone).first()
|
||||||
raise serializers.ValidationError({"detail": "User not found"})
|
if confirmation.is_verify:
|
||||||
if user.is_active:
|
raise serializers.ValidationError("Code is verified")
|
||||||
raise serializers.ValidationError({"detail": "User already activated"})
|
if confirmation.is_expired or confirmation.expiration_time < timezone.now().time():
|
||||||
data['user'] = user
|
raise serializers.ValidationError("Code is expired")
|
||||||
|
data['confirmation'] = confirmation
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class ChoiseRoleSerializer(serializers.Serializer):
|
class ChoiseRoleSerializer(serializers.Serializer):
|
||||||
phone = serializers.CharField()
|
|
||||||
role = serializers.ChoiceField(choices=ROLE_CHOICES)
|
role = serializers.ChoiceField(choices=ROLE_CHOICES)
|
||||||
|
|
||||||
def validate(self, data):
|
|
||||||
try:
|
|
||||||
user = User.objects.get(phone=data.get("phone"), is_active=True)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
raise serializers.ValidationError({"detail": "user not found"})
|
|
||||||
data['user'] = user
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class CompliteUserProfileSerializer(serializers.Serializer):
|
class CompliteUserProfileSerializer(serializers.Serializer):
|
||||||
first_name = serializers.CharField()
|
first_name = serializers.CharField()
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
import random
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
|
|
||||||
from core.apps.accounts.models.verification_code import VerificationCode
|
from core.apps.accounts.models.verification_code import VerificationCode
|
||||||
@@ -6,8 +11,11 @@ from core.services.sms import send_sms_eskiz
|
|||||||
from core.services.sms_via_bot import send_sms_code
|
from core.services.sms_via_bot import send_sms_code
|
||||||
|
|
||||||
@shared_task
|
@shared_task
|
||||||
def create_and_send_sms_code(user):
|
def create_and_send_sms_code(phone):
|
||||||
user = User.objects.get(id=user)
|
verification = VerificationCode.objects.create(
|
||||||
code = user.generate_code()
|
code=''.join([str(random.randint(1, 100) % 10) for _ in range(4)]),
|
||||||
|
phone=phone,
|
||||||
|
expiration_time=timezone.now() + timedelta(minutes=2)
|
||||||
|
)
|
||||||
# send_sms_eskiz(user.phone, code)
|
# send_sms_eskiz(user.phone, code)
|
||||||
send_sms_code(code, 'auth', user.phone)
|
send_sms_code(verification.code, 'auth', verification.phone)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ urlpatterns = [
|
|||||||
path('register/', RegisterApiView.as_view(), name='register'),
|
path('register/', RegisterApiView.as_view(), name='register'),
|
||||||
path('confirm_user/', ConfirUserApiView.as_view(), name='confirm-user'),
|
path('confirm_user/', ConfirUserApiView.as_view(), name='confirm-user'),
|
||||||
path('choise_user_role/', ChoiceUserRoleApiView.as_view(), name='choise-user-role'),
|
path('choise_user_role/', ChoiceUserRoleApiView.as_view(), name='choise-user-role'),
|
||||||
path('complite_user_profile/<str:phone>/', CompliteUserProfileApiView.as_view(), name='complite-user-profile'),
|
path('complite_user_profile/', CompliteUserProfileApiView.as_view(), name='complite-user-profile'),
|
||||||
]
|
]
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
@@ -8,6 +8,8 @@ from rest_framework_simplejwt.tokens import RefreshToken
|
|||||||
|
|
||||||
from core.apps.accounts.serializers import auth as auth_serializer
|
from core.apps.accounts.serializers import auth as auth_serializer
|
||||||
from core.apps.accounts.models.verification_code import VerificationCode
|
from core.apps.accounts.models.verification_code import VerificationCode
|
||||||
|
from core.apps.accounts.cache.user import cache_user_credentials, get_user_creadentials
|
||||||
|
from core.apps.accounts.tasks import user as user_tasks
|
||||||
|
|
||||||
User = get_user_model()
|
User = get_user_model()
|
||||||
|
|
||||||
@@ -26,47 +28,62 @@ class LoginApiView(generics.GenericAPIView):
|
|||||||
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
|
return Response(serializer.errors, status=status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
class RegisterApiView(generics.CreateAPIView):
|
class RegisterApiView(generics.GenericAPIView):
|
||||||
serializer_class = auth_serializer.RegisterSerializer
|
serializer_class = auth_serializer.RegisterSerializer
|
||||||
queryset = User.objects.all()
|
queryset = None
|
||||||
permission_classes = []
|
|
||||||
|
|
||||||
|
|
||||||
class ConfirUserApiView(generics.GenericAPIView):
|
|
||||||
serializer_class = auth_serializer.ConfirmUserSerializer
|
|
||||||
queryset = User.objects.all()
|
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
user = serializer.validated_data.get('user')
|
data = serializer.validated_data
|
||||||
code = serializer.validated_data.get('code')
|
cache_user_credentials(data['phone'], data['password'], 300)
|
||||||
code = VerificationCode.objects.filter(user=user, code=code).first()
|
user_tasks.create_and_send_sms_code.delay(data['phone'])
|
||||||
if code:
|
return Response(
|
||||||
if code.is_expired or code.expiration_time < timezone.now().time():
|
{'success': True, "message": "code send"},
|
||||||
return Response({"success": True, "message": "code is expired"}, status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_200_OK
|
||||||
if code.is_verify:
|
)
|
||||||
return Response({"success": True, "message": "code is verified"}, status=status.HTTP_400_BAD_REQUEST)
|
return Response(
|
||||||
user.is_active = True
|
{'success': True, "message": serializer.errors},
|
||||||
user.save()
|
status=status.HTTP_400_BAD_REQUEST
|
||||||
code.is_verify = True
|
)
|
||||||
code.is_expired = True
|
|
||||||
code.save()
|
|
||||||
return Response({"success": True, "message": "user activated"}, status=status.HTTP_202_ACCEPTED)
|
class ConfirUserApiView(generics.GenericAPIView):
|
||||||
return Response({"success": False, "message": "code is wrong"}, status=status.HTTP_400_BAD_REQUEST)
|
serializer_class = auth_serializer.ConfirmUserSerializer
|
||||||
|
queryset = None
|
||||||
|
permission_classes = []
|
||||||
|
|
||||||
|
def post(self, request):
|
||||||
|
serializer = self.serializer_class(data=request.data)
|
||||||
|
if serializer.is_valid():
|
||||||
|
phone = serializer.validated_data.get('phone')
|
||||||
|
confirmation = serializer.validated_data.get('confirmation')
|
||||||
|
data = get_user_creadentials(phone)
|
||||||
|
if not data:
|
||||||
|
return Response(
|
||||||
|
{'success': True, "message": 'not found'},
|
||||||
|
status=status.HTTP_404_NOT_FOUND
|
||||||
|
)
|
||||||
|
user = User.objects.create_user(phone=data['phone'], password=data['password'])
|
||||||
|
confirmation.is_verify = True
|
||||||
|
confirmation.save()
|
||||||
|
token = RefreshToken.for_user(user)
|
||||||
|
return Response(
|
||||||
|
{"access": str(token.access_token), "refresh": str(token)},
|
||||||
|
status=status.HTTP_202_ACCEPTED
|
||||||
|
)
|
||||||
return Response({"success": False, "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({"success": False, "message": serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
class ChoiceUserRoleApiView(generics.GenericAPIView):
|
class ChoiceUserRoleApiView(generics.GenericAPIView):
|
||||||
serializer_class = auth_serializer.ChoiseRoleSerializer
|
serializer_class = auth_serializer.ChoiseRoleSerializer
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
permission_classes = []
|
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
serializer = self.serializer_class(data=request.data)
|
serializer = self.serializer_class(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
user = serializer.validated_data.get('user')
|
user = request.user
|
||||||
role = serializer.validated_data.get('role')
|
role = serializer.validated_data.get('role')
|
||||||
user.role = role
|
user.role = role
|
||||||
user.save()
|
user.save()
|
||||||
@@ -77,15 +94,13 @@ class ChoiceUserRoleApiView(generics.GenericAPIView):
|
|||||||
class CompliteUserProfileApiView(generics.GenericAPIView):
|
class CompliteUserProfileApiView(generics.GenericAPIView):
|
||||||
serializer_class = auth_serializer.CompliteUserProfileSerializer
|
serializer_class = auth_serializer.CompliteUserProfileSerializer
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
permission_classes = []
|
|
||||||
|
|
||||||
def put(self, request, phone):
|
def put(self, request):
|
||||||
user = User.objects.filter(phone=phone, is_active=True).first()
|
user = request.user
|
||||||
if user:
|
if user:
|
||||||
serializer = self.serializer_class(data=request.data, instance=user)
|
serializer = self.serializer_class(data=request.data, instance=user)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
serializer.save()
|
serializer.save()
|
||||||
token = RefreshToken.for_user(user)
|
return Response({'success': True, "message": "Ok"}, status=status.HTTP_200_OK)
|
||||||
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': serializer.errors}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
return Response({'success': False, "message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
return Response({'success': False, "message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||||
Reference in New Issue
Block a user