Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions promo_code/business/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 5.2b1 on 2025-03-25 14:18

from django.db import migrations, models


class Migration(migrations.Migration):

initial = True

dependencies = []

operations = [
migrations.CreateModel(
name='Company',
fields=[
(
'id',
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID',
),
),
(
'password',
models.CharField(max_length=128, verbose_name='password'),
),
(
'last_login',
models.DateTimeField(
blank=True, null=True, verbose_name='last login'
),
),
('email', models.EmailField(max_length=120, unique=True)),
('name', models.CharField(max_length=50)),
('token_version', models.IntegerField(default=0)),
('created_at', models.DateTimeField(auto_now_add=True)),
('is_active', models.BooleanField(default=True)),
],
options={
'abstract': False,
},
),
]
38 changes: 38 additions & 0 deletions promo_code/business/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import django.contrib.auth.models
import django.db.models


class CompanyManager(django.contrib.auth.models.BaseUserManager):
def create_company(self, email, name, password=None, **extra_fields):
if not email:
raise ValueError('The Email must be set')

email = self.normalize_email(email)
company = self.model(
email=email,
name=name,
**extra_fields,
)
company.set_password(password)
company.save(using=self._db)
return company


class Company(django.contrib.auth.models.AbstractBaseUser):
email = django.db.models.EmailField(
unique=True,
max_length=120,
)
name = django.db.models.CharField(max_length=50)

token_version = django.db.models.IntegerField(default=0)
created_at = django.db.models.DateTimeField(auto_now_add=True)
is_active = django.db.models.BooleanField(default=True)

objects = CompanyManager()

USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']

def __str__(self):
return self.name
92 changes: 92 additions & 0 deletions promo_code/business/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import business.models as business_models
import business.validators
import django.contrib.auth.password_validation
import django.core.exceptions
import django.core.validators
import rest_framework.exceptions
import rest_framework.serializers
import rest_framework.status


class CompanySignUpSerializer(rest_framework.serializers.ModelSerializer):
password = rest_framework.serializers.CharField(
write_only=True,
required=True,
validators=[django.contrib.auth.password_validation.validate_password],
min_length=8,
max_length=60,
style={'input_type': 'password'},
)
name = rest_framework.serializers.CharField(
required=True,
min_length=5,
max_length=50,
)
email = rest_framework.serializers.EmailField(
required=True,
min_length=8,
max_length=120,
validators=[
business.validators.UniqueEmailValidator(
'This email address is already registered.',
'email_conflict',
),
],
)

class Meta:
model = business_models.Company
fields = (
'name',
'email',
'password',
)

def create(self, validated_data):
try:
company = business_models.Company.objects.create_company(
email=validated_data['email'],
name=validated_data['name'],
password=validated_data['password'],
)
company.token_version += 1
company.save()
return company
except django.core.exceptions.ValidationError as e:
raise rest_framework.serializers.ValidationError(e.messages)


class CompanySignInSerializer(
rest_framework.serializers.Serializer,
):
email = rest_framework.serializers.EmailField(required=True)
password = rest_framework.serializers.CharField(
required=True,
write_only=True,
style={'input_type': 'password'},
)

def validate(self, attrs):
email = attrs.get('email')
password = attrs.get('password')

if not email or not password:
raise rest_framework.exceptions.ValidationError(
{'detail': 'Both email and password are required'},
code='required',
)

try:
company = business_models.Company.objects.get(email=email)
except business_models.Company.DoesNotExist:
raise rest_framework.serializers.ValidationError(
'Invalid credentials',
)

if not company.is_active or not company.check_password(password):
raise rest_framework.exceptions.AuthenticationFailed(
{'detail': 'Invalid credentials or inactive account'},
code='authentication_failed',
)

return attrs
18 changes: 18 additions & 0 deletions promo_code/business/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import business.views
import django.urls

app_name = 'api-business'


urlpatterns = [
django.urls.path(
'auth/sign-up',
business.views.CompanySignUpView.as_view(),
name='company-sign-up',
),
django.urls.path(
'auth/sign-in',
business.views.CompanySignInView.as_view(),
name='company-sign-in',
),
]
23 changes: 23 additions & 0 deletions promo_code/business/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import business.models
import rest_framework.exceptions


class UniqueEmailValidator:
def __init__(self, default_detail=None, default_code=None):
self.status_code = 409
self.default_detail = (
default_detail or 'This email address is already registered.'
)
self.default_code = default_code or 'email_conflict'

def __call__(self, value):
if business.models.Company.objects.filter(email=value).exists():
exc = rest_framework.exceptions.APIException(
detail={
'status': 'error',
'message': self.default_detail,
'code': self.default_code,
},
)
exc.status_code = self.status_code
raise exc
99 changes: 99 additions & 0 deletions promo_code/business/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import business.models
import business.serializers
import rest_framework.exceptions
import rest_framework.generics
import rest_framework.response
import rest_framework.serializers
import rest_framework.status
import rest_framework_simplejwt.exceptions
import rest_framework_simplejwt.tokens
import rest_framework_simplejwt.views

import core.views


class CompanySignUpView(
core.views.BaseCustomResponseMixin,
rest_framework.generics.CreateAPIView,
):
def post(self, request):
try:
serializer = business.serializers.CompanySignUpSerializer(
data=request.data,
)
serializer.is_valid(raise_exception=True)
except (
rest_framework.serializers.ValidationError,
rest_framework_simplejwt.exceptions.TokenError,
) as e:
if isinstance(e, rest_framework.serializers.ValidationError):
return self.handle_validation_error()

raise rest_framework_simplejwt.exceptions.InvalidToken(str(e))

company = serializer.save()

refresh = rest_framework_simplejwt.tokens.RefreshToken()
refresh['user_type'] = 'company'
refresh['company_id'] = company.id
refresh['token_version'] = company.token_version

access_token = refresh.access_token
access_token['user_type'] = 'company'
access_token['company_id'] = company.id
refresh['token_version'] = company.token_version

response_data = {
'access': str(access_token),
'refresh': str(refresh),
}

return rest_framework.response.Response(
response_data,
status=rest_framework.status.HTTP_200_OK,
)


class CompanySignInView(
core.views.BaseCustomResponseMixin,
rest_framework_simplejwt.views.TokenObtainPairView,
):
def post(self, request):
try:
serializer = business.serializers.CompanySignInSerializer(
data=request.data,
)
serializer.is_valid(raise_exception=True)
except (
rest_framework.serializers.ValidationError,
rest_framework_simplejwt.exceptions.TokenError,
) as e:
if isinstance(e, rest_framework.serializers.ValidationError):
return self.handle_validation_error()

raise rest_framework_simplejwt.exceptions.InvalidToken(str(e))

company = business.models.Company.objects.get(
email=serializer.validated_data['email'],
)
company.token_version += 1
company.save()

refresh = rest_framework_simplejwt.tokens.RefreshToken()
refresh['user_type'] = 'company'
refresh['company_id'] = company.id
refresh['token_version'] = company.token_version

access_token = refresh.access_token
access_token['user_type'] = 'company'
access_token['company_id'] = company.id

response_data = {
'access': str(access_token),
'refresh': str(refresh),
}

return rest_framework.response.Response(
response_data,
status=rest_framework.status.HTTP_200_OK,
)
11 changes: 11 additions & 0 deletions promo_code/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
import django.views
import rest_framework.permissions
import rest_framework.response
import rest_framework.status
import rest_framework.views


class BaseCustomResponseMixin:
error_response = {'status': 'error', 'message': 'Error in request data.'}

def handle_validation_error(self):
return rest_framework.response.Response(
self.error_response,
status=rest_framework.status.HTTP_400_BAD_REQUEST,
)


class PingView(django.views.View):
def get(self, request, *args, **kwargs):
return django.http.HttpResponse('PROOOOOOOOOOOOOOOOOD', status=200)
Expand Down
3 changes: 2 additions & 1 deletion promo_code/promo_code/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import django.urls

urlpatterns = [
django.urls.path('api/ping/', django.urls.include('core.urls')),
django.urls.path('api/business/', django.urls.include('business.urls')),
django.urls.path('api/user/', django.urls.include('user.urls')),
django.urls.path('api/ping/', django.urls.include('core.urls')),
django.urls.path('admin/', django.contrib.admin.site.urls),
]
Loading