Skip to content

Commit

Permalink
Merge pull request #301 from bounswe/feature/BE-onboarding-setup
Browse files Browse the repository at this point in the history
Implement login, registration, and email verification endpoints
  • Loading branch information
oguzpancuk authored Oct 20, 2024
2 parents b3045ad + f16f7ef commit d420c81
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 39 deletions.
52 changes: 32 additions & 20 deletions backend/backend/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,13 @@
from pathlib import Path
from dotenv import load_dotenv
import os
import datetime
load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-wuc2#&nmhoqp7266)y639mfopdfwxzzx4jlux-f$k0c(+dhju*'
SECRET_KEY = os.getenv('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
Expand All @@ -41,9 +37,9 @@
'django.contrib.messages',
'django.contrib.staticfiles',
'onboarding',
'drf_spectacular',
'rest_framework',
'rest_framework_swagger',
'drf_yasg'
'rest_framework_simplejwt.token_blacklist',
]

MIDDLEWARE = [
Expand All @@ -61,7 +57,7 @@
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
Expand All @@ -77,9 +73,6 @@
WSGI_APPLICATION = 'backend.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases


DATABASES = {
'default': {
Expand All @@ -92,8 +85,6 @@
}
}

# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
{
Expand All @@ -110,6 +101,24 @@
},
]

REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
# 'DEFAULT_FILTER_BACKENDS': [
# 'django_filters.rest_framework.DjangoFilterBackend'
# ],
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.BasicAuthentication',
],
'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
}


SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': datetime.timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': datetime.timedelta(days=1),
}

# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
Expand All @@ -125,14 +134,17 @@
USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/

STATIC_URL = '/static/'

# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

STATIC_ROOT = os.path.join(BASE_DIR,'staticfiles')

EMAIL_BACKEND = os.environ.get('EMAIL_BACKEND')
EMAIL_USE_TLS = True
EMAIL_HOST = os.environ.get('EMAIL_HOST')
EMAIL_PORT = os.environ.get('EMAIL_PORT')
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD =os.environ.get('EMAIL_HOST_PASSWORD')

AUTH_USER_MODEL = 'onboarding.User'
30 changes: 14 additions & 16 deletions backend/backend/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,29 @@
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
from drf_yasg.views import get_schema_view
from drf_yasg import openapi
from django.urls import path, include
from rest_framework import permissions
from django.shortcuts import render

from django.conf import settings
from django.conf.urls.static import static

from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView


# This serves static files during development

schema_view = get_schema_view(
openapi.Info(
title="Financial Markets Blog Project",
default_version='v1',
description="API documentation for Cmpe451 Project",
),
public=True,
permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
path('admin/', admin.site.urls),
path('docs/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
path(
"docs/",
SpectacularSwaggerView.as_view(
template_name="swagger-ui.html", url_name="schema"
),
name="swagger-ui",
),
path("", include("onboarding.urls")),

]

if settings.DEBUG:
Expand Down
2 changes: 2 additions & 0 deletions backend/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ services:
MYSQL_DATABASE: 'db'
MYSQL_USER: 'user'
MYSQL_PASSWORD: 'password'
env_file:
../.env
volumes:
- .:/backend
ports:
Expand Down
56 changes: 56 additions & 0 deletions backend/onboarding/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Generated by Django 4.2 on 2024-10-18 20:03

from django.conf import settings
import django.contrib.auth.models
import django.contrib.auth.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]

operations = [
migrations.CreateModel(
name='User',
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')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('is_verified', models.BooleanField(default=False)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='BlacklistedToken',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('token', models.CharField(max_length=500)),
('timestamp', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
13 changes: 12 additions & 1 deletion backend/onboarding/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
from django.db import models
from django.conf import settings
from django.contrib.auth.models import AbstractUser

# Create your models here.
class User(AbstractUser):
is_verified = models.BooleanField(default=False)

class BlacklistedToken(models.Model):
token = models.CharField(max_length=500)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
timestamp = models.DateTimeField(auto_now_add=True)

def __str__(self):
return self.token
47 changes: 47 additions & 0 deletions backend/onboarding/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from onboarding.models import User as User
from rest_framework import serializers
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from rest_framework.validators import UniqueValidator
from django.contrib.auth.password_validation import validate_password
from rest_framework_simplejwt.tokens import RefreshToken, TokenError


class RegisterSerializer(serializers.ModelSerializer):
email = serializers.EmailField(
required=True,
validators=[UniqueValidator(queryset=User.objects.all())]
)

password = serializers.CharField(write_only=True, required=True, validators=[validate_password])

class Meta:
model = User
fields = ('username', 'password', 'email')

def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
email=validated_data['email']
)

user.set_password(validated_data['password'])
user.save()

return user


class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
@classmethod
def get_token(cls, user):
token = super(MyTokenObtainPairSerializer, cls).get_token(user)

# Add custom claims
token['username'] = user.username
return token


class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['url', 'username', 'email']

17 changes: 17 additions & 0 deletions backend/onboarding/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.shortcuts import render
from django.urls import path
from onboarding.views import *
from rest_framework_simplejwt.views import (
TokenObtainPairView,
TokenRefreshView,
)

urlpatterns = [
path("token/", TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),

path('login/', MyObtainTokenPairView.as_view(), name='login'),
path('login/refresh/', TokenRefreshView.as_view(), name='login/refresh'),
path('register/', RegisterView.as_view(), name='auth_register'),
path('email-verify/', VerifyEmail.as_view(), name='email-verify'),
]
9 changes: 9 additions & 0 deletions backend/onboarding/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from django.core.mail import EmailMessage, EmailMultiAlternatives

class Util:
@staticmethod
def send_email( data):
email = EmailMultiAlternatives(subject=data['email_subject'], to=[data['to_email']])

email.attach_alternative(data['html_message'], "text/html")
email.send()
Loading

0 comments on commit d420c81

Please sign in to comment.