Skip to content

Commit

Permalink
Merge pull request #1115 from jefer94/feature/accept-invites-thought-…
Browse files Browse the repository at this point in the history
…of-an-api-rest

accept an invite using an api rest
  • Loading branch information
alesanchezr authored Oct 10, 2023
2 parents 79c7a9f + a31ed6e commit c78b2d4
Show file tree
Hide file tree
Showing 5 changed files with 452 additions and 314 deletions.
114 changes: 114 additions & 0 deletions breathecode/authenticate/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -902,3 +902,117 @@ def get_app(pk: str | int) -> App:
raise Exception('App not found')

return app


def accept_invite_action(data={}, token=None, lang='en'):
from breathecode.payments.models import Invoice, Bag, Plan

password1 = data.get('password', None)
password2 = data.get('repeat_password', None)

invite = UserInvite.objects.filter(token=str(token), status='PENDING', email__isnull=False).first()
if invite is None:
raise Exception(
translation(lang,
en='Invalid or expired invitation ' + str(token),
es='Invitación inválida o expirada ' + str(token)))

first_name = data.get('first_name', None)
last_name = data.get('last_name', None)
if first_name is None or first_name == '' or last_name is None or last_name == '':
raise Exception(translation(lang, en='Invalid first or last name', es='Nombre o apellido inválido'))

if password1 != password2:
raise Exception(translation(lang, en='Passwords don\'t match', es='Las contraseñas no coinciden'))

if not password1:
raise Exception(translation(lang, en='Password is empty', es='La contraseña está vacía'))

user = User.objects.filter(email=invite.email).first()
if user is None:
user = User(email=invite.email, first_name=first_name, last_name=last_name, username=invite.email)
user.save()
user.set_password(password1)
user.save()

if invite.academy is not None:
profile = ProfileAcademy.objects.filter(email=invite.email, academy=invite.academy).first()
if profile is None:
role = invite.role
if not role:
role = Role.objects.filter(slug='student').first()

if not role:
raise Exception(
translation(lang,
en='Unexpected error occurred with invite, please contact the '
'staff of 4geeks',
es='Ocurrió un error inesperado con la invitación, por favor '
'contacta al staff de 4geeks'))

profile = ProfileAcademy(email=invite.email,
academy=invite.academy,
role=role,
first_name=first_name,
last_name=last_name)

if invite.first_name is not None and invite.first_name != '':
profile.first_name = invite.first_name
if invite.last_name is not None and invite.last_name != '':
profile.last_name = invite.last_name

profile.user = user
profile.status = 'ACTIVE'
profile.save()

if invite.cohort is not None:
role = 'student'
if invite.role is not None and invite.role.slug != 'student':
role = invite.role.slug.upper()

cu = CohortUser.objects.filter(user=user, cohort=invite.cohort).first()
if cu is None:
cu = CohortUser(user=user, cohort=invite.cohort, role=role.upper())
cu.save()

plan = Plan.objects.filter(cohort_set__cohorts=invite.cohort, invites=invite).first()

if plan and invite.user and invite.cohort.academy.main_currency and (
invite.cohort.available_as_saas == True or
(invite.cohort.available_as_saas == None and invite.cohort.academy.available_as_saas == True)):
utc_now = timezone.now()

bag = Bag()
bag.chosen_period = 'NO_SET'
bag.status = 'PAID'
bag.type = 'INVITED'
bag.how_many_installments = 1
bag.academy = invite.cohort.academy
bag.user = user
bag.is_recurrent = False
bag.was_delivered = False
bag.token = None
bag.currency = invite.cohort.academy.main_currency
bag.expires_at = None

bag.save()

bag.plans.add(plan)
bag.selected_cohorts.add(invite.cohort)

invoice = Invoice(amount=0,
paid_at=utc_now,
user=invite.user,
bag=bag,
academy=bag.academy,
status='FULFILLED',
currency=bag.academy.main_currency)
invoice.save()

payment_tasks.build_plan_financing.delay(bag.id, invoice.id, is_free=True)

invite.status = 'ACCEPTED'
invite.is_email_validated = True
invite.save()

return invite
2 changes: 1 addition & 1 deletion breathecode/authenticate/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ def validate(self, data: dict[str, str]):
now = str(timezone.now())

if not self.instance:
data['token'] = hashlib.sha1((now + data['email']).encode('UTF-8')).hexdigest()
data['token'] = hashlib.sha512((data['email']).encode('UTF-8') + os.urandom(64)).hexdigest()

return data

Expand Down
108 changes: 108 additions & 0 deletions breathecode/authenticate/tests/urls/tests_member_invite_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from unittest.mock import MagicMock, patch
from django.template import loader
from django.urls.base import reverse_lazy
import pytest
from rest_framework import status
from django.http.request import HttpRequest
from random import randint
Expand Down Expand Up @@ -139,6 +140,28 @@ def salt(self):
return 'salt'


def post_serializer(data={}):
return {
'created_at': ...,
'email': None,
'id': 0,
'sent_at': None,
'status': 'PENDING',
**data,
}


created_at = None


@pytest.fixture(autouse=True)
def setup(monkeypatch, utc_now):
global created_at
created_at = utc_now
monkeypatch.setattr('django.utils.timezone.now', MagicMock(return_value=utc_now))
yield


class AuthenticateTestSuite(AuthTestCase):
"""Authentication test suite"""
"""
Expand Down Expand Up @@ -1020,3 +1043,88 @@ def test_member_invite_token__post__with_cohort__without_role(self):
])

self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])

"""
🔽🔽🔽 POST JSON password is empty, UserInvite with email
"""

@patch('django.template.loader.render_to_string', MagicMock(side_effect=render_to_string_mock))
@patch('django.contrib.auth.hashers.get_hasher', MagicMock(side_effect=GetHasherMock))
@patch('django.db.models.signals.pre_delete.send', MagicMock(return_value=None))
@patch('breathecode.admissions.signals.student_edu_status_updated.send', MagicMock(return_value=None))
def test__post__json__password_is_empty(self):
user_invite = {'email': '[email protected]'}
model = self.bc.database.create(user_invite=user_invite)
url = reverse_lazy('authenticate:member_invite_token', kwargs={'token': model.user_invite.token})

data = {'first_name': 'abc', 'last_name': 'xyz'}
response = self.client.post(url, data, format='json')

json = response.json()
expected = {'detail': 'Password is empty', 'status_code': 400}

self.assertEqual(json, expected)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(self.bc.database.list_of('authenticate.UserInvite'), [
self.bc.format.to_dict(model.user_invite),
])

self.assertEqual(self.bc.database.list_of('auth.User'), [])
self.assertEqual(self.bc.database.list_of('authenticate.ProfileAcademy'), [])
self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])

"""
🔽🔽🔽 POST JSON with first name, last name and passwords, UserInvite with email
"""

@patch('django.template.loader.render_to_string', MagicMock(side_effect=render_to_string_mock))
@patch('django.contrib.auth.hashers.get_hasher', MagicMock(side_effect=GetHasherMock))
@patch('django.db.models.signals.pre_delete.send', MagicMock(return_value=None))
@patch('breathecode.admissions.signals.student_edu_status_updated.send', MagicMock(return_value=None))
def test__post__json__with_first_name_last_name_and_passwords(self):
user_invite = {'email': '[email protected]'}
model = self.bc.database.create(user_invite=user_invite)
url = reverse_lazy('authenticate:member_invite_token', kwargs={'token': model.user_invite.token})
data = {
'first_name': 'abc',
'last_name': 'xyz',
'password': '^3^3uUppppp',
'repeat_password': '^3^3uUppppp',
}
response = self.client.post(url, data, format='json')

json = response.json()
expected = post_serializer({
'id': 1,
'created_at': self.bc.datetime.to_iso_string(created_at),
'status': 'ACCEPTED',
'email': '[email protected]',
})

self.assertEqual(json, expected)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.bc.database.list_of('authenticate.UserInvite'),
[{
**self.bc.format.to_dict(model.user_invite),
'status': 'ACCEPTED',
'is_email_validated': True,
}])

user_db = [
x for x in self.bc.database.list_of('auth.User') if x['date_joined'] and x.pop('date_joined')
]
self.assertEqual(user_db, [{
'email': '[email protected]',
'first_name': 'abc',
'id': 1,
'is_active': True,
'is_staff': False,
'is_superuser': False,
'last_login': None,
'last_name': 'xyz',
'password': CSRF_TOKEN,
'username': '[email protected]'
}])

self.assertEqual(self.bc.database.list_of('authenticate.ProfileAcademy'), [])
self.assertEqual(self.bc.database.list_of('admissions.CohortUser'), [])
Loading

0 comments on commit c78b2d4

Please sign in to comment.