FIX(account): fix duplicated invite code

fix duplicated invite code
This commit is contained in:
Haydar Ghasemi 2026-07-25 17:16:18 +03:30
parent f6099af0e4
commit 0973d08637
4 changed files with 66 additions and 12 deletions

View file

@ -0,0 +1,18 @@
# Generated by Django 5.1.4 on 2026-07-25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0009_inviterequest'),
]
operations = [
migrations.AlterField(
model_name='inviterequest',
name='mobile_number',
field=models.CharField(db_index=True, max_length=30, unique=True, verbose_name='mobile number'),
),
]

View file

@ -278,7 +278,7 @@ class InviteRequest(BaseModel):
related_name='invite_requests',
verbose_name=_('inviter'),
)
mobile_number = models.CharField(_('mobile number'), max_length=30, db_index=True)
mobile_number = models.CharField(_('mobile number'), unique=True, max_length=30, db_index=True)
application = models.ForeignKey(
oauth2_settings.APPLICATION_MODEL,
on_delete=models.PROTECT,

View file

@ -7,7 +7,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from unidecode import unidecode
from utils.exceptions import UnprocessableEntity
from utils.exceptions import UnprocessableEntity, Conflict
phone_number_validator = RegexValidator(regex=r'^\+98[1-9]\d{9}$', message=_(
"Phone number must be entered in the format: '+989999999999'."
@ -150,6 +150,19 @@ class InviteRequestSerializer(serializers.ModelSerializer):
def validate_mobile_number(self, value):
value = unidecode(value)
phone_number_validator(value)
if User.objects.filter(phone_number=value).exists():
raise Conflict(
detail=_('This mobile number is already registered as a user.'),
code='user_already_exists',
)
if InviteRequest.objects.filter(mobile_number=value).exists():
raise Conflict(
detail=_('An invite request for this mobile number already exists.'),
code='invite_request_already_exists',
)
return value
def create(self, validated_data):

View file

@ -153,9 +153,11 @@ class InviteRequestTests(APITestCase):
user_uuid = uuid.UUID('c14e8b86-8f4a-44d9-b29d-badceb47005f')
inviter_uuid = uuid.UUID('d14e8b86-8f4a-44d9-b29d-badceb47005f')
application_uuid = uuid.UUID('e14e8b86-8f4a-44d9-b29d-badceb47005f')
access_token = 'inviteAccessToken123456789012345678901'
client_id = 'inviteClientId1234567890123456789012'
client_secret = 'inviteClientSecret123456789012345678'
mobile_number = '+989120000000'
expire_datetime = timezone.now() + timedelta(seconds=3600)
client = APIClient()
@ -177,9 +179,13 @@ class InviteRequestTests(APITestCase):
allowed_scope='accounts.invite:submit',
)
def basic_auth_header(self):
credentials = f'{self.client_id}:{self.client_secret}'.encode('utf-8')
return 'Basic ' + base64.b64encode(credentials).decode('utf-8')
AccessToken.objects.create(
token=self.access_token,
application_id=self.application_uuid,
scope='accounts.invite:submit',
expires=self.expire_datetime,
)
self.client.credentials(HTTP_AUTHORIZATION=f'Bearer {self.access_token}')
def test_create_invite_request_success(self):
response = self.client.post(
@ -189,7 +195,6 @@ class InviteRequestTests(APITestCase):
'mobile_number': self.mobile_number,
},
format='json',
HTTP_AUTHORIZATION=self.basic_auth_header(),
)
self.assertEqual(response.status_code, 201)
@ -208,13 +213,32 @@ class InviteRequestTests(APITestCase):
'mobile_number': self.mobile_number,
},
format='json',
HTTP_AUTHORIZATION=self.basic_auth_header(),
)
self.assertEqual(response.status_code, 400)
self.assertEqual(InviteRequest.objects.count(), 0)
def test_create_invite_request_invalid_client_credentials(self):
def test_create_invite_request_existing_user(self):
response = self.client.post(
reverse('users:invite_request_api'),
data={
'invite_code': str(self.inviter_uuid),
'mobile_number': self.inviter.phone_number,
},
format='json',
)
self.assertEqual(response.status_code, 409)
self.assertEqual(response.json()['details']['error'], 'user_already_exists')
self.assertEqual(InviteRequest.objects.count(), 0)
def test_create_invite_request_duplicate_mobile_number(self):
InviteRequest.objects.create(
inviter=self.inviter,
mobile_number=self.mobile_number,
application=self.application,
)
response = self.client.post(
reverse('users:invite_request_api'),
data={
@ -222,11 +246,11 @@ class InviteRequestTests(APITestCase):
'mobile_number': self.mobile_number,
},
format='json',
HTTP_AUTHORIZATION='Basic ' + base64.b64encode(b'wrong:credentials').decode('utf-8'),
)
self.assertEqual(response.status_code, 401)
self.assertEqual(InviteRequest.objects.count(), 0)
self.assertEqual(response.status_code, 409)
self.assertEqual(response.json()['details']['error'], 'invite_request_already_exists')
self.assertEqual(InviteRequest.objects.count(), 1)
def test_create_invite_request_missing_scope(self):
self.application.allowed_scope = ''
@ -239,7 +263,6 @@ class InviteRequestTests(APITestCase):
'mobile_number': self.mobile_number,
},
format='json',
HTTP_AUTHORIZATION=self.basic_auth_header(),
)
self.assertEqual(response.status_code, 403)