diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..063a8af
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,24 @@
+/static
+/media
+.idea/
+*.pyc
+media/
+*.name
+*.iml
+*.log
+*.xml
+*.pyo
+.DS_Store
+.idea
+venv/*
+static/*
+*.pem
+*.sqlite3
+/migrations/__pycache__/
+/__pycache__/
+/*.pyc
+*/__pycache__/*.pyc
+__pycache__/
+*.env
+ve/*
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..77c6fa8
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,11 @@
+FROM python:3.9
+ENV PYTHONUNBUFFERED 1
+RUN mkdir /usr/src/app
+WORKDIR /usr/src/app
+COPY . .
+RUN apt-get update
+RUN apt-get install wkhtmltopdf -y
+RUN apt install -y gdal-bin python3-gdal
+RUN pip install -r requirements.txt
+WORKDIR /usr/src/app
+
diff --git a/account/__init__.py b/account/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/account/admin.py b/account/admin.py
new file mode 100644
index 0000000..7dbf869
--- /dev/null
+++ b/account/admin.py
@@ -0,0 +1,31 @@
+"""Account admin"""
+from django.contrib import admin
+
+"""Import django app"""
+from .models import UserProfile, UserEmailOtp, UserPhoneOtp
+# Register your models here.
+@admin.register(UserProfile)
+class UserProfileAdmin(admin.ModelAdmin):
+ """User profile admin"""
+ list_display = ['user']
+
+ def __str__(self):
+ return self.user__email
+
+@admin.register(UserEmailOtp)
+class UserEmailOtpAdmin(admin.ModelAdmin):
+ """User Email otp admin"""
+ list_display = ['email']
+
+ def __str__(self):
+ """Return object in email and otp format"""
+ return self.email + '-' + self.otp
+
+@admin.register(UserPhoneOtp)
+class UserPhoneOtpAdmin(admin.ModelAdmin):
+ """User Phone otp admin"""
+ list_display = ['phone']
+
+ def __str__(self):
+ """Return object in phone number and otp format"""
+ return self.phone + '-' + self.otp
diff --git a/account/apps.py b/account/apps.py
new file mode 100644
index 0000000..2b08f1a
--- /dev/null
+++ b/account/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class AccountConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'account'
diff --git a/account/migrations/0001_initial.py b/account/migrations/0001_initial.py
new file mode 100644
index 0000000..3a50122
--- /dev/null
+++ b/account/migrations/0001_initial.py
@@ -0,0 +1,65 @@
+# Generated by Django 4.2.2 on 2023-06-23 12:05
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserEmailOtp',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('email', models.EmailField(max_length=254)),
+ ('otp', models.CharField(max_length=10)),
+ ('is_verified', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('expired_at', models.DateTimeField(blank=True, null=True)),
+ ('is_active', models.BooleanField(default=True)),
+ ],
+ options={
+ 'db_table': 'user_email_otp',
+ },
+ ),
+ migrations.CreateModel(
+ name='UserPhoneOtp',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('country_code', models.IntegerField()),
+ ('phone', models.CharField(max_length=17)),
+ ('otp', models.CharField(max_length=10)),
+ ('is_verified', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('expired_at', models.DateTimeField(blank=True, null=True)),
+ ('is_active', models.BooleanField(default=True)),
+ ],
+ options={
+ 'db_table': 'user_phone_otp',
+ },
+ ),
+ migrations.CreateModel(
+ name='UserProfile',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('user_type', models.CharField(blank=True, choices=[('1', 'junior'), ('2', 'guardian'), ('3', 'superuser')], default=None, max_length=15, null=True)),
+ ('is_verified', models.BooleanField(default=False)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('is_active', models.BooleanField(default=False)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_profile', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'db_table': 'user_profile',
+ },
+ ),
+ ]
diff --git a/account/migrations/__init__.py b/account/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/account/models.py b/account/models.py
new file mode 100644
index 0000000..ff8bb5d
--- /dev/null
+++ b/account/models.py
@@ -0,0 +1,75 @@
+from django.db import models
+import random
+from django.contrib.auth.models import User
+from base.constants import USER_TYPE
+# Create your models here.
+
+class UserProfile(models.Model):
+ """
+ User details
+ """
+ user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_profile')
+ user_type = models.CharField(max_length=15, choices=USER_TYPE, null=True, blank=True, default=None)
+ is_verified = models.BooleanField(default=False)
+
+ # OTP validity
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ is_active = models.BooleanField(default=False)
+
+ class Meta(object):
+ """ Meta information """
+ db_table = 'user_profile'
+
+ def __str__(self):
+ """return phone as an object"""
+ return f'{self.user}'
+
+class UserPhoneOtp(models.Model):
+ """
+ This class is used to verify user email and their contact no.
+ """
+ """user details"""
+ country_code = models.IntegerField()
+ phone = models.CharField(max_length=17)
+ """otp details"""
+ otp = models.CharField(max_length=10)
+ is_verified = models.BooleanField(default=False)
+
+ # OTP validity
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ expired_at = models.DateTimeField(blank=True, null=True)
+ is_active = models.BooleanField(default=True)
+
+ class Meta(object):
+ """ Meta information """
+ db_table = 'user_phone_otp'
+
+ def __str__(self):
+ """return phone as an object"""
+ return self.phone
+
+class UserEmailOtp(models.Model):
+ """
+ This class is used to verify user email and their contact no.
+ """
+ """user details"""
+ email = models.EmailField()
+ """otp details"""
+ otp = models.CharField(max_length=10)
+ is_verified = models.BooleanField(default=False)
+
+ # OTP validity
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+ expired_at = models.DateTimeField(blank=True, null=True)
+ is_active = models.BooleanField(default=True)
+
+ class Meta(object):
+ """ Meta information """
+ db_table = 'user_email_otp'
+
+ def __str__(self):
+ """return phone as an object"""
+ return self.email
diff --git a/account/serializers.py b/account/serializers.py
new file mode 100644
index 0000000..afd923c
--- /dev/null
+++ b/account/serializers.py
@@ -0,0 +1,178 @@
+from rest_framework import serializers
+from django.contrib.auth.models import User
+from guardian.models import Guardian
+from junior.models import Junior
+from account.models import UserProfile, UserEmailOtp, UserPhoneOtp
+from base.constants import GUARDIAN, JUNIOR, SUPERUSER
+from django.db import transaction
+from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
+from django.core.exceptions import ObjectDoesNotExist
+from django.contrib.auth import authenticate
+from rest_framework import viewsets, status
+from rest_framework.decorators import action
+from django.contrib.auth import authenticate, login
+from rest_framework_simplejwt.tokens import RefreshToken
+
+class GoogleSignInSerializer(serializers.Serializer):
+ """Google login Serializer"""
+ email = serializers.EmailField()
+
+ def create(self, validated_data):
+ """Create or update user model"""
+ with transaction.atomic():
+ print("validated_data====>",validated_data)
+ if User.objects.filter(email__iexact=self.validated_data['email']).exists():
+ return User.objects.get(email__iexact=self.validated_data['email'])
+
+ if not User.objects.filter(email__iexact=self.validated_data['email']).exists():
+ instance = User.objects.create(username=self.validated_data['email'],
+ email=self.validated_data['email'])
+ return instance
+
+
+class ResetPasswordSerializer(serializers.Serializer):
+ """Reset Password after verification"""
+ verification_code = serializers.CharField(max_length=10)
+ password = serializers.CharField(required=True)
+ class Meta(object):
+ """Meta info"""
+ model = User
+
+ def create(self, validated_data):
+ verification_code = validated_data.pop('verification_code')
+ password = validated_data.pop('password')
+ print("verification_code===>",verification_code)
+ print("password===>", password)
+ user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last()
+ print("user_opt_details===>",user_opt_details)
+ if user_opt_details:
+ print("qqqqqqqqqq")
+ user_details = User.objects.filter(email=user_opt_details.email).last()
+ if user_details:
+ print("333333333==>",user_details.password)
+ user_details.set_password(password)
+ user_details.save()
+ return {'password':password}
+ return user_opt_details
+ return ''
+
+class ChangePasswordSerializer(serializers.Serializer):
+ """Update Password after verification"""
+ current_password = serializers.CharField(max_length=100)
+ new_password = serializers.CharField(required=True)
+ class Meta(object):
+ """Meta info"""
+ model = User
+
+ def validate_current_password(self, value):
+ user = self.context
+ if self.context.password not in ('', None):
+ if user.check_password(value):
+ return value
+ raise serializers.ValidationError({"error":"Invalid Current password"})
+ def create(self, validated_data):
+ new_password = validated_data.pop('new_password')
+ user_details = User.objects.filter(email=self.context).last()
+ print("user_details==>", user_details)
+ if user_details:
+ print("333333333==>",user_details.password)
+ user_details.set_password(new_password)
+ user_details.save()
+ return {'password':new_password}
+ return ''
+
+
+
+class ForgotPasswordSerializer(serializers.Serializer):
+ """Forget password serializer"""
+ email = serializers.EmailField()
+
+class SuperUserSerializer(serializers.ModelSerializer):
+ user_type = serializers.SerializerMethodField('get_user_type')
+
+ def get_user_type(self, obj):
+ """user type"""
+ return SUPERUSER
+
+ class Meta(object):
+ """Meta info"""
+ model = User
+ fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type']
+
+
+class GuardianSerializer(serializers.ModelSerializer):
+ """guardian serializer"""
+ user_type = serializers.SerializerMethodField('get_user_type')
+ email = serializers.SerializerMethodField('get_auth')
+ first_name = serializers.SerializerMethodField('get_first_name')
+ last_name = serializers.SerializerMethodField('get_last_name')
+ auth_token = serializers.SerializerMethodField('get_auth_token')
+
+ def get_auth_token(self, obj):
+ refresh = RefreshToken.for_user(obj.user)
+ access_token = str(refresh.access_token)
+ return access_token
+
+ def get_user_type(self, obj):
+ """user type"""
+ return GUARDIAN
+
+ def get_auth(self, obj):
+ """user email address"""
+ return obj.user.username
+
+ def get_first_name(self, obj):
+ """user first name"""
+ return obj.user.first_name
+
+ def get_last_name(self, obj):
+ """user last name"""
+ return obj.user.last_name
+
+ class Meta(object):
+ """Meta info"""
+ model = Guardian
+ fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'family_name', 'gender', 'dob',
+ 'referral_code', 'is_active', 'is_complete_profile', 'passcode',
+ 'created_at', 'updated_at', 'user_type']
+
+
+class JuniorSerializer(serializers.ModelSerializer):
+ """junior serializer"""
+ user_type = serializers.SerializerMethodField('get_user_type')
+ email = serializers.SerializerMethodField('get_auth')
+ first_name = serializers.SerializerMethodField('get_first_name')
+ last_name = serializers.SerializerMethodField('get_last_name')
+ auth_token = serializers.SerializerMethodField('get_auth_token')
+
+ def get_auth_token(self, obj):
+ refresh = RefreshToken.for_user(obj.auth)
+ access_token = str(refresh.access_token)
+ return access_token
+
+ def get_user_type(self, obj):
+ return JUNIOR
+
+ def get_auth(self, obj):
+ return obj.auth.username
+
+ def get_first_name(self, obj):
+ return obj.auth.first_name
+
+ def get_last_name(self, obj):
+ return obj.auth.last_name
+
+ class Meta(object):
+ """Meta info"""
+ model = Junior
+ fields = ['auth_token', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
+ 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at',
+ 'updated_at', 'user_type']
+
+class EmailVerificationSerializer(serializers.ModelSerializer):
+ """Email verification serializer"""
+ class Meta(object):
+ """Meta info"""
+ model = UserEmailOtp
+ fields = '__all__'
+
diff --git a/account/templates/templated_email/email_base.email b/account/templates/templated_email/email_base.email
new file mode 100644
index 0000000..5721e28
--- /dev/null
+++ b/account/templates/templated_email/email_base.email
@@ -0,0 +1,54 @@
+
+{% block subject %}DinDin{% endblock %}
+{% load static %}
+
+{% block html %}
+
+
+
+
+ Zod Bank | OTP
+
+
+
+
+
+
+
+
+
+
+ |
+ |
+
+ {% block plain %}
+ {% endblock %}
+
+ |
+ -
+ Cheers!
+ Zod Bank Team
+ |
+
+
+ |
+
+
+
+
+
+
+{% endblock %}
diff --git a/account/templates/templated_email/email_otp_verification.email b/account/templates/templated_email/email_otp_verification.email
new file mode 100644
index 0000000..8b3c693
--- /dev/null
+++ b/account/templates/templated_email/email_otp_verification.email
@@ -0,0 +1,23 @@
+{% extends "templated_email/email_base.email" %}
+
+{% block subject %}
+ OTP Verification
+{% endblock %}
+
+{% block plain %}
+
+ |
+
+ Hi User,
+
+ |
+
+
+ |
+
+ You are receiving this email for email verification. Please use {{ otp }} as the verification code for your email address & username.
+
+
+ |
+
+{% endblock %}
diff --git a/account/templates/templated_email/email_reset_verification.email b/account/templates/templated_email/email_reset_verification.email
new file mode 100644
index 0000000..e2f8ebf
--- /dev/null
+++ b/account/templates/templated_email/email_reset_verification.email
@@ -0,0 +1,23 @@
+{% extends "templated_email/email_base.email" %}
+
+{% block subject %}
+ Password Reset Verification Code
+{% endblock %}
+
+{% block plain %}
+
+ |
+
+ Hi User,
+
+ |
+
+
+ |
+
+ You are receiving this email for reset password verification. Please use {{ verification_code }} as the verification code.
+
+
+ |
+
+{% endblock %}
diff --git a/account/tests.py b/account/tests.py
new file mode 100644
index 0000000..7ce503c
--- /dev/null
+++ b/account/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/account/urls.py b/account/urls.py
new file mode 100644
index 0000000..eab8be4
--- /dev/null
+++ b/account/urls.py
@@ -0,0 +1,26 @@
+""" Urls files"""
+"""Django import"""
+from django.urls import path, include
+from rest_framework.decorators import api_view
+"""Third party import"""
+from rest_framework import routers
+from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVerification, ReSendEmailOtp,
+ ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView)
+"""Router"""
+router = routers.SimpleRouter()
+
+"""API End points with router"""
+router.register('user', UserLogin, basename='user')
+router.register('admin', UserLogin, basename='admin')
+# router.register('google-login', GoogleLoginAPIViewset, basename='admin')
+router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp')
+router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification')
+router.register('user-email-verification', UserEmailVerification, basename='user-email-verification')
+router.register('resend-email-otp', ReSendEmailOtp, basename='resend-email-otp')
+
+urlpatterns = [
+ path('api/v1/', include(router.urls)),
+ path('api/v1/forgot-password/', ForgotPasswordAPIView.as_view()),
+ path('api/v1/reset-password/', ResetPasswordAPIView.as_view()),
+ path('api/v1/change-password/', ChangePasswordAPIView.as_view())
+]
diff --git a/account/utils.py b/account/utils.py
new file mode 100644
index 0000000..f20ef19
--- /dev/null
+++ b/account/utils.py
@@ -0,0 +1,38 @@
+"""Account utils"""
+"""Third party Django app"""
+from django.conf import settings
+from rest_framework import viewsets, status
+from rest_framework.response import Response
+
+from templated_email import send_templated_mail
+def send_otp_email(recipient_email, otp):
+ from_email = settings.EMAIL_HOST_USER
+ recipient_list = [recipient_email]
+ send_templated_mail(
+ template_name='email_otp_verification.email',
+ from_email=from_email,
+ recipient_list=recipient_list,
+ context={
+ 'otp': otp
+ }
+ )
+ return otp
+
+def custom_response(detail, data=None, response_status=status.HTTP_200_OK):
+ """Custom response code"""
+ if not data:
+ data = None
+
+ return Response({"data": data, "message": detail, "status": "success", "code": response_status})
+
+
+def custom_error_response(detail, response_status):
+ """
+ function is used for getting same global error response for all
+ :param detail: error message .
+ :param response_status: http status.
+ :return: Json response
+ """
+ if not detail:
+ detail = {}
+ return Response({"error": detail, "status": "failed", "code": response_status})
diff --git a/account/views.py b/account/views.py
new file mode 100644
index 0000000..69e4e95
--- /dev/null
+++ b/account/views.py
@@ -0,0 +1,220 @@
+from rest_framework import viewsets, status, views
+from rest_framework.decorators import action
+import random
+import logging
+from django.contrib.auth import authenticate, login
+from guardian.models import Guardian
+from junior.models import Junior
+from account.models import UserProfile, UserPhoneOtp, UserEmailOtp
+from django.contrib.auth.models import User
+from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
+ ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer,
+ GoogleSignInSerializer)
+from rest_framework_simplejwt.tokens import RefreshToken
+from base.messages import ERROR_CODE, SUCCESS_CODE
+from guardian.tasks import generate_otp
+from django.conf import settings
+from account.utils import send_otp_email
+from account.utils import custom_response, custom_error_response
+from django.core.mail import EmailMessage
+from django.core.mail import send_mail
+from rest_framework.response import Response
+from rest_framework.permissions import IsAuthenticated
+from templated_email import send_templated_mail
+
+# class GoogleLoginAPIViewset(viewsets.ModelViewSet):
+# """Google Login"""
+# serializer_class = GoogleSignInSerializer
+#
+# def create(self, request, *args, **kwargs):
+# """
+# Override default behaviour of create method
+# """
+# provider_type = []
+# serializer = self.get_serializer(data=request.data)
+# if serializer.is_valid(raise_exception=True):
+# # provider = self.get_provider_view(request.data.get('provider'))
+# # if User is not authenticated then send error message
+# # if not provider.is_authenticated(request):
+# # return custom_error_response({}, status.HTTP_400_BAD_REQUEST)
+#
+# user = serializer.save()
+# if User.objects.filter(email__iexact=user.email).exists():
+# print("ppppppppppppp")
+# return custom_response(SUCCESS_CODE["3003"], response_status=status.HTTP_200_OK)
+# return custom_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST)
+
+
+
+class ChangePasswordAPIView(views.APIView):
+ permission_classes = [IsAuthenticated]
+ def post(self, request):
+ serializer = ChangePasswordSerializer(context=request.user, data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+class ResetPasswordAPIView(views.APIView):
+ def post(self, request):
+ serializer = ResetPasswordSerializer(data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ return custom_response(SUCCESS_CODE['3006'], response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+class ForgotPasswordAPIView(views.APIView):
+ def post(self, request):
+ serializer = ForgotPasswordSerializer(data=request.data)
+ if serializer.is_valid():
+ email = serializer.validated_data['email']
+ try:
+ User.objects.get(email=email)
+ except User.DoesNotExist:
+ return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND)
+ verification_code = ''.join([str(random.randrange(9)) for _ in range(6)])
+ # Send the verification code to the user's email
+ from_email = settings.EMAIL_HOST_USER
+ recipient_list = [email]
+ send_templated_mail(
+ template_name='email_reset_verification.email',
+ from_email=from_email,
+ recipient_list=recipient_list,
+ context={
+ 'verification_code': verification_code
+ }
+ )
+ user_data, created = UserEmailOtp.objects.get_or_create(email=email)
+ if user_data:
+ user_data.otp = verification_code
+ user_data.save()
+ return custom_response(SUCCESS_CODE['3015'],
+ response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+class SendPhoneOtp(viewsets.ModelViewSet):
+ """Send otp on phone"""
+ def create(self, request, *args, **kwargs):
+ otp = generate_otp()
+ phone_number = self.request.data['phone']
+ if phone_number.isdigit() and len(phone_number) == 10:
+ phone_otp, created = UserPhoneOtp.objects.get_or_create(country_code=self.request.data['country_code'],
+ phone=self.request.data['phone'])
+ if phone_otp:
+ phone_otp.otp = otp
+ phone_otp.save()
+ return custom_response(None, {'phone_otp':otp}, response_status=status.HTTP_200_OK)
+ return custom_error_response(ERROR_CODE['2020'], response_status=status.HTTP_400_BAD_REQUEST)
+
+
+class UserPhoneVerification(viewsets.ModelViewSet):
+ """Send otp on phone"""
+ def list(self, request, *args, **kwargs):
+ try:
+ phone_data = UserPhoneOtp.objects.filter(phone=self.request.GET.get('phone'),
+ otp=self.request.GET.get('otp')).last()
+ if phone_data:
+ phone_data.is_verified = True
+ phone_data.save()
+ return custom_response(SUCCESS_CODE['3012'], response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
+ except Exception:
+ return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
+
+
+
+class UserLogin(viewsets.ViewSet):
+ @action(methods=['post'], detail=False)
+ def login(self, request):
+ username = request.data.get('username')
+ password = request.data.get('password')
+ user = authenticate(request, username=username, password=password)
+ try:
+ if user is not None:
+ login(request, user)
+ guardian_data = Guardian.objects.filter(user__username=username, is_complete_profile=True).last()
+ if guardian_data:
+ serializer = GuardianSerializer(guardian_data)
+ junior_data = Junior.objects.filter(auth__username=username, is_complete_profile=True).last()
+ if junior_data:
+ serializer = JuniorSerializer(junior_data)
+ return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
+ except Exception as e:
+ logging.error(e)
+ user_profile_data = UserProfile.objects.filter(user__username=username).last()
+ email_verified = UserEmailOtp.objects.filter(email=username).last()
+ refresh = RefreshToken.for_user(user)
+ access_token = str(refresh.access_token)
+ data = {"auth_token":access_token, "is_profile_complete": False,
+ "user_role": user_profile_data.user_type,
+ }
+ is_verified = False
+ if email_verified:
+ is_verified = email_verified.is_verified
+ if not is_verified:
+ otp = generate_otp()
+ email_verified.otp = otp
+ email_verified.save()
+ data.update({"email_otp":otp})
+ return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified},
+ response_status=status.HTTP_200_OK)
+ data.update({"is_email_verified": is_verified})
+ return custom_response(None, data, response_status=status.HTTP_200_OK)
+
+ @action(methods=['post'], detail=False)
+ def admin_login(self, request):
+ username = request.data.get('username')
+ password = request.data.get('password')
+ user = authenticate(request, username=username, password=password)
+ try:
+ if user is not None:
+ login(request, user)
+ if user.is_superuser:
+ serializer = SuperUserSerializer(user)
+ return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
+ except Exception as e:
+ logging.error(e)
+ refresh = RefreshToken.for_user(user)
+ access_token = str(refresh.access_token)
+ data = {"auth_token": access_token, "user_role": '3'}
+ return custom_response(None, data, response_status=status.HTTP_200_OK)
+
+class UserEmailVerification(viewsets.ModelViewSet):
+ """User Email verification"""
+ serializer_class = EmailVerificationSerializer
+
+ def list(self, request, *args, **kwargs):
+ try:
+ user_obj = User.objects.filter(username=self.request.GET.get('email')).last()
+ email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'),
+ otp=self.request.GET.get('otp')).last()
+ if email_data:
+ email_data.is_verified = True
+ email_data.save()
+ refresh = RefreshToken.for_user(user_obj)
+ access_token = str(refresh.access_token)
+ return custom_response(SUCCESS_CODE['3011'], {"auth_token":access_token}, response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
+ except Exception as e:
+ logging.error(e)
+ return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
+
+class ReSendEmailOtp(viewsets.ModelViewSet):
+ """Send otp on phone"""
+ def create(self, request, *args, **kwargs):
+ otp = generate_otp()
+ if User.objects.filter(email=request.data['email']):
+ email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email'])
+ if email_data:
+ email_data.otp = otp
+ email_data.save()
+ send_otp_email(request.data['email'], otp)
+ return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/base/__init__.py b/base/__init__.py
new file mode 100644
index 0000000..860fa73
--- /dev/null
+++ b/base/__init__.py
@@ -0,0 +1,3 @@
+"""
+This is init module of the Project Zod Bank
+"""
diff --git a/base/common_email.py b/base/common_email.py
new file mode 100644
index 0000000..4eb2a36
--- /dev/null
+++ b/base/common_email.py
@@ -0,0 +1,30 @@
+"""
+Common send_mail function
+"""
+import logging
+
+from django.core.mail import EmailMultiAlternatives
+
+
+def send_mail(subject, message, from_email, recipient_list, html_message=None, cc=None,
+ fail_silently=False):
+ """
+ Send Email
+ :param subject:
+ :param message:
+ :param from_email:
+ :param recipient_list:
+ :param html_message:
+ :param cc:
+ :param fail_silently:
+ :return:
+ """
+ try:
+ mail = EmailMultiAlternatives(subject, message, from_email, recipient_list, cc)
+ if html_message:
+ mail.attach_alternative(html_message, 'text/html')
+
+ return mail.send(fail_silently)
+ except Exception as e:
+ logging.error(e)
+ return False
diff --git a/base/constants.py b/base/constants.py
new file mode 100644
index 0000000..b66ed5f
--- /dev/null
+++ b/base/constants.py
@@ -0,0 +1,51 @@
+"""
+This module contains constants used throughout the project
+"""
+import os
+
+# GOOGLE_URL used for interact with google server to verify user existence.
+#GOOGLE_URL = "https://www.googleapis.com/plus/v1/"
+
+
+# Super Admin string constant for 'role'
+SUPER_ADMIN = "Super Admin"
+
+# Define jwt_token_expiration time in minutes for now token will expire after 3 days
+JWT_TOKEN_EXPIRATION = 3 * 24 * 60
+
+# Define common file extention
+FILE_EXTENSION = ("gif", "jpeg", "jpg", "png", "svg")
+
+# Define file size in bytes(5MB = 5 * 1024 * 1024)
+FILE_SIZE = 5 * 1024 * 1024
+
+# String constant for configurable date for allocation lock period
+ALLOCATION_LOCK_DATE = 1
+
+sort_dict = {
+ '1': 'name',
+ '2': '-name'
+}
+USER_TYPE = (
+ ('1', 'junior'),
+ ('2', 'guardian'),
+ ('3', 'superuser')
+)
+GENDERS = (
+ ('1', 'Male'),
+ ('2', 'Female')
+)
+# duplicate name used defined in constant PROJECT_NAME
+PROJECT_NAME = 'Zod Bank'
+GUARDIAN = 'guardian'
+JUNIOR = 'junior'
+SUPERUSER = 'superuser'
+# numbers used as a constant
+
+# Define the byte into kb
+BYTE_IMAGE_SIZE = 1024
+
+# validate file size
+MAX_FILE_SIZE = 1024 * 1024 * 5
+
+
diff --git a/base/image_constants.py b/base/image_constants.py
new file mode 100644
index 0000000..0737698
--- /dev/null
+++ b/base/image_constants.py
@@ -0,0 +1,16 @@
+"""
+This module contains constants used throughout the project
+"""
+from zod_bank.settings import BUCKET_NAME
+
+# Define S3 folder url
+S3_FOLDER_DIR = {
+ 'user_image': 'user_image/',
+}
+
+# S3 bucket url
+S3_URL = "https://"+BUCKET_NAME+".s3.amazonaws.com/"
+
+S3_FOLDER_URL = {
+ 'user_image_file': S3_URL+S3_FOLDER_DIR['user_image'],
+}
diff --git a/base/messages.py b/base/messages.py
new file mode 100644
index 0000000..37507fd
--- /dev/null
+++ b/base/messages.py
@@ -0,0 +1,94 @@
+"""
+This module contains all the messages used all across the project
+"""
+
+ERROR_CODE_REQUIRED = {
+ # Error code for email address
+ "1000": ["Required email address not found."],
+ # Error code for password
+ "1001": ["Required password not found."],
+ # Error code for Required Post parameters
+ "1002": ["Required POST parameters not found."],
+ # Error code for Required Get parameters
+ "1003": ["Required GET parameters not found."],
+ # Error code for Required Headers
+ "1004": ["Required headers were not found."],
+ # Error code for Required Put parameters
+ "1005": ["Required PUT parameters not found."],
+ # Error code for Required query parameters
+ "1006": ["Required query parameters is not valid."],
+ # Error code for Required Head parameters
+ "1008": ["Required HEAD parameters not found."]
+}
+
+# Error code
+ERROR_CODE = {
+ "2000": "Email not found.",
+ "2001": "Your account has not been verified. Please check your email and verify it.",
+ "2002": "Invalid login credentials.",
+ "2003": "An account already exists with this email address.",
+ "2004": "User not found.",
+ "2005": "Your account has been activated.",
+ "2006": "Your account is not activated.",
+ "2007": "Your account already activated.",
+ "2008": "Invalid OTP.",
+ "2009": "The user provided cannot be found or the reset password token has become invalid/timed out.",
+ "2010": "Invalid Link.",
+ "2011": "Your profile has not been completed yet.",
+ "2012": "Password and Confirm password should be same.",
+ "2013": "Invalid token.",
+ "2014": "Your old password doesn't match.",
+ "2015": "Invalid old password.",
+ "2016": "Invalid search.",
+ "2017": "{model} object with {pk} does not exist",
+ "2018": "Attached File not found",
+ "2019": "Either File extension or File size doesn't meet the requirements",
+ "2020": "Enter valid mobile number",
+ "2021": "Already register",
+ "2022":"Invalid Guardian code",
+ "2023":"Invalid user",
+ "2024":"Email not verified"
+}
+SUCCESS_CODE = {
+ # Success code for password
+ "3001": "Sign up successfully",
+ # Success code for Thank you
+ "3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.",
+ # Success code for account activation
+ "3003": "Log in successfully",
+ # Success code for password reset
+ "3004": "Password reset link has been sent to your email address",
+ # Success code for link verified
+ "3005": "Your link has been verified, it's valid",
+ # Success code for password reset
+ "3006": "Your password has been reset successfully.",
+ # Success code for password update
+ "3007": "Your password has been changed successfully.",
+ # Success code for valid link
+ "3008": "You have a valid link.",
+ # Success code for logged out
+ "3009": "You have successfully logged out!",
+ # Success code for check all fields
+ "3010": "All fields are valid",
+ "3011": "Email OTP Verified successfully",
+ "3012": "Phone OTP Verified successfully",
+ "3013": "Valid Guardian code",
+ "3014": "Password has been updated successfully.",
+ "3015": "Verification code sent on your email.",
+ "3016": "Send otp on your Email successfully"
+}
+
+STATUS_CODE_ERROR = {
+ # Status code for Invalid Input
+ "4001": ["Invalid input."],
+ # Status code for Authentication credentials
+ "4002": ["Authentication credentials were not provided."],
+ # Status code for Permission
+ "4003": ["You do not have permission to perform this action."],
+ # Status code for not found
+ "4004": ["Not found."],
+ # Status code for method not allowed
+ "4005": ["Method not allowed."]
+}
+
+
diff --git a/base/routers.py b/base/routers.py
new file mode 100644
index 0000000..e2df0e1
--- /dev/null
+++ b/base/routers.py
@@ -0,0 +1,17 @@
+"""
+Custom routers for job sourcing .
+"""
+# third party imports
+from rest_framework.routers import DefaultRouter
+
+
+class OptionalSlashRouter(DefaultRouter):
+ """
+ optional slash router class
+ """
+ def __init__(self):
+ """
+ explicitly appending '/' in urls if '/' doesn't exists for making common url patterns .
+ """
+ super(OptionalSlashRouter, self).__init__()
+ self.trailing_slash = '/?'
diff --git a/base/upload_file.py b/base/upload_file.py
new file mode 100644
index 0000000..2d47ad2
--- /dev/null
+++ b/base/upload_file.py
@@ -0,0 +1,177 @@
+"""
+This file used for file uploaded
+"""
+import datetime
+# python imports
+import logging
+import mimetypes
+import os
+
+import boto3
+from django.core.files.storage import FileSystemStorage
+# django imports
+from django.utils.crypto import get_random_string
+from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_200_OK
+
+from base import constants
+from base.constants import NUMBER
+# local import
+from zod_bank.settings import base_settings as settings
+from zod_bank.settings.base_settings import BASE_DIR
+
+
+def image_upload(folder, file_name, data):
+ """
+ Function to upload files
+ :param folder:folder location string
+ :param file_name:file_name without ext string
+ :param data:data file obj
+ :return:Dictionary
+ """
+ status = HTTP_400_BAD_REQUEST
+ img_name = None
+ error = None
+ try:
+ s3_client = boto3.client('s3',
+ aws_access_key_id=settings.AWS_ACCESS_KEY,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ region_name=settings.AWS_DEFAULT_REGION
+ )
+
+ bucket_name = settings.BUCKET_NAME
+ MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp')
+ fss = FileSystemStorage()
+ file = fss.save('tmp/' + str(file_name), data)
+ fss.url(file)
+ tmp_file = os.path.join(MEDIA_ROOT, str(file_name))
+ s3_client.upload_file(
+ tmp_file, bucket_name, folder + str(file_name),
+ ExtraArgs={'ACL': 'public-read', 'ContentType': data.content_type}
+ )
+ os.unlink(tmp_file)
+ img_name = file_name
+ status = HTTP_200_OK
+ except Exception as e:
+ error = e
+ logging.error(e)
+ return status, error, img_name
+
+
+def file_delete(folder, file_name):
+ """
+ To delete common file
+ :param folder: folder name str
+ :param file_name: file_name string type
+ """
+ status = HTTP_400_BAD_REQUEST
+ error = None
+ try:
+ s3_client = boto3.client('s3',
+ aws_access_key_id=settings.AWS_ACCESS_KEY,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ region_name=settings.AWS_DEFAULT_REGION
+ )
+
+ s3_client.delete_object(Bucket=settings.BUCKET_NAME, Key=str(folder) + str(file_name))
+ status = HTTP_200_OK
+ except Exception as e:
+ error = e
+ return status, error
+
+
+def get_aws_obj(folder, file_name):
+ """
+ To get aws file obj
+ :param folder: folder string type
+ :param file_name: file_name string type
+ """
+ status = HTTP_400_BAD_REQUEST
+ obj = None
+ try:
+ s3_client = boto3.client('s3',
+ aws_access_key_id=settings.AWS_ACCESS_KEY,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ region_name=settings.AWS_DEFAULT_REGION
+ )
+ file_name = folder + file_name
+ obj = s3_client.get_object(Bucket=settings.BUCKET_NAME, Key=file_name)
+ status = HTTP_200_OK
+ except Exception as e:
+ logging.error(e)
+ return status, obj
+
+
+def upload_image(post_data, folder):
+ """
+ :param post_data:
+ :param folder: string type
+ :return:
+ """
+ upload_obj = None
+ # Check Post data
+ if post_data:
+ date_now = datetime.datetime.now()
+ file_extension = os.path.splitext(str(post_data.name))
+ file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower()
+ rand = get_random_string(NUMBER['twelve'])
+ image_name = str(rand) + date_now.strftime("%s") + "." + file_extension
+ upload_obj = image_upload(folder, image_name, post_data)
+ return upload_obj
+
+
+def upload_voice_kit_image(post_data, folder, image_dir):
+ """
+ :param post_data:
+ :param folder: string type
+ :param image_dir: image_dir
+ :return:
+ """
+ upload_obj = None
+ # Check Post data
+ if post_data:
+ date_now = datetime.datetime.now()
+ file_extension = os.path.splitext(str(post_data))
+ file_extension = file_extension[constants.NUMBER['one']].split(".")[constants.NUMBER['one']].lower()
+ rand = get_random_string(NUMBER['twelve'])
+ image_name = str(rand) + date_now.strftime("%s") + "." + file_extension
+ upload_obj = voice_kit_image_upload(folder, image_name, post_data, image_dir)
+ return upload_obj
+
+
+def voice_kit_image_upload(folder, file_name, data, image_dir):
+ """
+ Function to upload files
+ :param folder:folder location string
+ :param file_name:file_name without ext string
+ :param data:data file obj
+ :return:Dictionary
+ """
+ status = HTTP_400_BAD_REQUEST
+ img_name = None
+ error = None
+ try:
+ s3_client = boto3.client('s3',
+ aws_access_key_id=settings.AWS_ACCESS_KEY,
+ aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
+ region_name=settings.AWS_DEFAULT_REGION
+ )
+
+ bucket_name = settings.BUCKET_NAME
+ MEDIA_ROOT = os.path.join(BASE_DIR, 'media/tmp')
+ fss = FileSystemStorage()
+ with open(image_dir+data, 'rb') as f:
+ file = fss.save('tmp/' + str(file_name), f)
+ fss.url(file)
+ tmp_file = os.path.join(MEDIA_ROOT, str(file_name))
+ s3_client.upload_file(
+ tmp_file, bucket_name, folder + str(file_name),
+ ExtraArgs={'ACL': 'public-read', 'ContentType': mimetypes.guess_type(file_name)[0]}
+ )
+ os.unlink(tmp_file)
+ img_name = file_name
+ status = HTTP_200_OK
+ except Exception as e:
+ error = e
+ logging.error(e)
+ return status, error, img_name
+
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..700bf1b
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,18 @@
+version: '3'
+services:
+ nginx:
+ image: nginx:latest
+ container_name: nginx
+ ports:
+ - "8000:8000"
+ volumes:
+ - ./nginx:/etc/nginx/conf.d
+ - .:/usr/src/app
+ depends_on:
+ - web
+ web:
+ build: .
+ container_name: django
+ command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
+ volumes:
+ - .:/usr/src/app
diff --git a/guardian/__init__.py b/guardian/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/guardian/admin.py b/guardian/admin.py
new file mode 100644
index 0000000..a3bd40c
--- /dev/null
+++ b/guardian/admin.py
@@ -0,0 +1,14 @@
+"""Guardian admin"""
+"""Third party Django app"""
+from django.contrib import admin
+"""Import Django app"""
+from .models import Guardian
+# Register your models here.
+@admin.register(Guardian)
+class GuardianAdmin(admin.ModelAdmin):
+ """Junior Admin"""
+ list_display = ['user', 'family_name']
+
+ def __str__(self):
+ """Return email id"""
+ return self.user__email
diff --git a/guardian/apps.py b/guardian/apps.py
new file mode 100644
index 0000000..fcaf209
--- /dev/null
+++ b/guardian/apps.py
@@ -0,0 +1,9 @@
+"""Guardian app file"""
+"""Third party Django app"""
+from django.apps import AppConfig
+
+
+class CustodianConfig(AppConfig):
+ """Guardian config"""
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'guardian'
diff --git a/guardian/migrations/0001_initial.py b/guardian/migrations/0001_initial.py
new file mode 100644
index 0000000..0038a3b
--- /dev/null
+++ b/guardian/migrations/0001_initial.py
@@ -0,0 +1,43 @@
+# Generated by Django 4.2.2 on 2023-06-23 12:05
+
+from django.conf import settings
+import django.contrib.postgres.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Guardian',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('country_code', models.IntegerField(blank=True, null=True)),
+ ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)),
+ ('family_name', models.CharField(blank=True, default=None, max_length=50, null=True)),
+ ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=15, null=True)),
+ ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)),
+ ('guardian_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('junior_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)),
+ ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('is_active', models.BooleanField(default=True)),
+ ('is_complete_profile', models.BooleanField(default=False)),
+ ('passcode', models.IntegerField(blank=True, default=None, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')),
+ ],
+ options={
+ 'verbose_name': 'Guardian',
+ 'db_table': 'guardians',
+ },
+ ),
+ ]
diff --git a/guardian/migrations/0002_remove_guardian_junior_code.py b/guardian/migrations/0002_remove_guardian_junior_code.py
new file mode 100644
index 0000000..6996d0a
--- /dev/null
+++ b/guardian/migrations/0002_remove_guardian_junior_code.py
@@ -0,0 +1,17 @@
+# Generated by Django 4.2.2 on 2023-06-27 06:15
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guardian', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='guardian',
+ name='junior_code',
+ ),
+ ]
diff --git a/guardian/migrations/0003_guardian_country_name.py b/guardian/migrations/0003_guardian_country_name.py
new file mode 100644
index 0000000..ea9858b
--- /dev/null
+++ b/guardian/migrations/0003_guardian_country_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-06-27 13:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('guardian', '0002_remove_guardian_junior_code'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='guardian',
+ name='country_name',
+ field=models.CharField(blank=True, default=None, max_length=30, null=True),
+ ),
+ ]
diff --git a/guardian/migrations/__init__.py b/guardian/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/guardian/models.py b/guardian/models.py
new file mode 100644
index 0000000..d9228b7
--- /dev/null
+++ b/guardian/models.py
@@ -0,0 +1,40 @@
+"""Guardian model file"""
+"""Third party Django app"""
+from django.db import models
+from django.contrib.auth import get_user_model
+"""Import Django app"""
+from base.constants import GENDERS
+User = get_user_model()
+# Create your models here.
+
+class Guardian(models.Model):
+ """Guardian model"""
+ user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='guardian_profile', verbose_name='Email')
+ """Contact details"""
+ country_code = models.IntegerField(blank=True, null=True)
+ phone = models.CharField(max_length=31, null=True, blank=True, default=None)
+ country_name = models.CharField(max_length=30, null=True, blank=True, default=None)
+ """Personal info"""
+ family_name = models.CharField(max_length=50, null=True, blank=True, default=None)
+ gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None)
+ dob = models.DateField(max_length=15, null=True, blank=True, default=None)
+ """Codes"""
+ guardian_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
+ """Profile activity"""
+ is_active = models.BooleanField(default=True)
+ is_complete_profile = models.BooleanField(default=False)
+ passcode = models.IntegerField(null=True, blank=True, default=None)
+ """Profile created and updated time"""
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta(object):
+ """ Meta class """
+ db_table = 'guardians'
+ verbose_name = 'Guardian'
+
+ def __str__(self):
+ """Return email id"""
+ return f'{self.user}'
diff --git a/guardian/serializers.py b/guardian/serializers.py
new file mode 100644
index 0000000..f2c0c46
--- /dev/null
+++ b/guardian/serializers.py
@@ -0,0 +1,119 @@
+"""Serializer of Guardian"""
+"""Third party Django app"""
+import logging
+import random
+from rest_framework import serializers
+from rest_framework_simplejwt.tokens import RefreshToken
+from django.db import transaction
+from django.contrib.auth.models import User
+"""Import Django app"""
+from .models import Guardian
+from account.models import UserProfile
+from base.messages import ERROR_CODE, SUCCESS_CODE
+class UserSerializer(serializers.ModelSerializer):
+ """User serializer"""
+ auth_token = serializers.SerializerMethodField('get_auth_token')
+
+ class Meta(object):
+ """Meta info"""
+ model = User
+ fields = ['email', 'password', 'auth_token']
+
+ def get_auth_token(self, obj):
+ """generate auth token"""
+ refresh = RefreshToken.for_user(obj)
+ access_token = str(refresh.access_token)
+ return access_token
+ def create(self, validated_data):
+ """fetch data"""
+ email = validated_data.get('email')
+ user_type = self.context
+ password = validated_data.get('password')
+ try:
+ """Create user profile"""
+ user = User.objects.create_user(username=email, email=email, password=password)
+ UserProfile.objects.create(user=user, user_type=user_type)
+ return user
+ except Exception as e:
+ """Error handling"""
+ logging.error(e)
+ raise serializers.ValidationError({"details":ERROR_CODE['2021']})
+
+ def save(self, **kwargs):
+ """save the data"""
+ with transaction.atomic():
+ instance = super().save(**kwargs)
+ return instance
+
+class CreateGuardianSerializer(serializers.ModelSerializer):
+ """Create guardian serializer"""
+ """Basic info"""
+ first_name = serializers.SerializerMethodField('get_first_name')
+ last_name = serializers.SerializerMethodField('get_last_name')
+ email = serializers.SerializerMethodField('get_email')
+ """Contact details"""
+ phone = serializers.CharField(max_length=20, required=False)
+ country_code = serializers.IntegerField(required=False)
+ family_name = serializers.CharField(max_length=100, required=False)
+ dob = serializers.DateField(required=False)
+ referral_code = serializers.CharField(max_length=100, required=False)
+
+ class Meta(object):
+ """Meta info"""
+ model = Guardian
+ fields = ['first_name', 'last_name', 'email', 'phone', 'family_name', 'gender', 'country_code',
+ 'dob', 'referral_code', 'passcode', 'is_complete_profile', 'referral_code_used',
+ 'country_name']
+
+ def get_first_name(self,obj):
+ """first name of guardian"""
+ return obj.user.first_name
+
+ def get_last_name(self,obj):
+ """last name of guardian"""
+ return obj.user.last_name
+
+ def get_email(self,obj):
+ """emailof guardian"""
+ return obj.user.email
+
+ def create(self, validated_data):
+ """Create guardian profile"""
+ user = User.objects.filter(username=self.context['user']).last()
+ if user:
+ """Save first and last name of guardian"""
+ if self.context.get('first_name') != '' and self.context.get('last_name') != '':
+ user.first_name = self.context.get('first_name', user.first_name)
+ user.last_name = self.context.get('last_name', user.last_name)
+ user.save()
+ """Create guardian data"""
+ guardian, created = Guardian.objects.get_or_create(user=self.context['user'])
+ if created:
+ """Create referral code and guardian code"""
+ guardian.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)])
+ guardian.guardian_code = ''.join([str(random.randrange(9)) for _ in range(4)])
+ if guardian:
+ """update details according to the data get from request"""
+ guardian.gender = validated_data.get('gender',guardian.gender)
+ guardian.family_name = validated_data.get('family_name', guardian.family_name)
+ guardian.dob = validated_data.get('dob',guardian.dob)
+ """Update country code and phone number"""
+ guardian.phone = validated_data.get('phone', guardian.phone)
+ guardian.country_code = validated_data.get('country_code', guardian.country_code)
+ guardian.passcode = validated_data.get('passcode', guardian.passcode)
+ guardian.country_name = validated_data.get('country_name', guardian.country_name)
+ guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used)
+ """Complete profile of the junior if below all data are filled"""
+ complete_profile_field = all([guardian.phone, guardian.gender, guardian.family_name, guardian.country_name,
+ guardian.dob, guardian.country_code, user.first_name, user.last_name])
+ guardian.is_complete_profile = False
+ if complete_profile_field:
+ guardian.is_complete_profile = True
+ guardian.save()
+ return guardian
+
+ def save(self, **kwargs):
+ """Save the data into junior table"""
+ with transaction.atomic():
+ instance = super().save(**kwargs)
+ return instance
diff --git a/guardian/tasks.py b/guardian/tasks.py
new file mode 100644
index 0000000..7a5dd90
--- /dev/null
+++ b/guardian/tasks.py
@@ -0,0 +1,6 @@
+"""task files"""
+"""Django import"""
+import random
+def generate_otp():
+ """generate random otp"""
+ return ''.join([str(random.randrange(9)) for _ in range(6)])
diff --git a/guardian/tests.py b/guardian/tests.py
new file mode 100644
index 0000000..3036e8b
--- /dev/null
+++ b/guardian/tests.py
@@ -0,0 +1,5 @@
+"""Test file of Guardian"""
+"""Third party Django app"""
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/guardian/urls.py b/guardian/urls.py
new file mode 100644
index 0000000..5399f2b
--- /dev/null
+++ b/guardian/urls.py
@@ -0,0 +1,19 @@
+""" Urls files"""
+"""Django import"""
+from django.urls import path, include
+from .views import SignupViewset, UpdateGuardianProfile
+"""Third party import"""
+from rest_framework import routers
+
+"""Define Router"""
+router = routers.SimpleRouter()
+
+"""API End points with router"""
+"""Sign up API"""
+router.register('sign-up', SignupViewset, basename='sign-up')
+"""Create guardian profile API"""
+router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
+"""Define Url pattern"""
+urlpatterns = [
+ path('api/v1/', include(router.urls)),
+]
diff --git a/guardian/views.py b/guardian/views.py
new file mode 100644
index 0000000..72e5b94
--- /dev/null
+++ b/guardian/views.py
@@ -0,0 +1,47 @@
+"""Views of Guardian"""
+"""Third party Django app"""
+from rest_framework.permissions import IsAuthenticated
+from rest_framework import viewsets, status
+"""Import Django app"""
+from .serializers import UserSerializer
+from .serializers import CreateGuardianSerializer
+from account.models import UserEmailOtp
+from .tasks import generate_otp
+from account.utils import send_otp_email
+from account.utils import custom_response, custom_error_response
+from base.messages import ERROR_CODE, SUCCESS_CODE
+# Create your views here.
+class SignupViewset(viewsets.ModelViewSet):
+ """Signup view set"""
+ serializer_class = UserSerializer
+
+ def create(self, request, *args, **kwargs):
+ """Create user profile"""
+ serializer = UserSerializer(context=request.data['user_type'], data=request.data)
+ if serializer.is_valid():
+ serializer.save()
+ """Generate otp"""
+ otp = generate_otp()
+ UserEmailOtp.objects.create(email=request.data['email'], otp=otp)
+ """Send email to the register user"""
+ send_otp_email(request.data['email'], otp)
+ return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp},
+ response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+class UpdateGuardianProfile(viewsets.ViewSet):
+ """Update guardian profile"""
+ serializer_class = CreateGuardianSerializer
+ permission_classes = [IsAuthenticated]
+
+ def create(self, request, *args, **kwargs):
+ """Create guardian profile"""
+ serializer = CreateGuardianSerializer(context={"user":request.user,
+ "first_name":request.data.get('first_name', ''),
+ "last_name": request.data.get('last_name',' ')},
+ data=request.data)
+ if serializer.is_valid():
+ """save serializer"""
+ serializer.save()
+ return custom_response(None, serializer.data,response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/junior/__init__.py b/junior/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/junior/admin.py b/junior/admin.py
new file mode 100644
index 0000000..87cd7d8
--- /dev/null
+++ b/junior/admin.py
@@ -0,0 +1,14 @@
+"""Junior admin"""
+"""Third party Django app"""
+from django.contrib import admin
+"""Import Django app"""
+from .models import Junior
+# Register your models here.
+@admin.register(Junior)
+class JuniorAdmin(admin.ModelAdmin):
+ """Junior Admin"""
+ list_display = ['auth']
+
+ def __str__(self):
+ """Return email id"""
+ return self.auth__email
diff --git a/junior/apps.py b/junior/apps.py
new file mode 100644
index 0000000..f3df25e
--- /dev/null
+++ b/junior/apps.py
@@ -0,0 +1,8 @@
+"""App file"""
+"""Import AppConfig"""
+from django.apps import AppConfig
+
+class JuniorConfig(AppConfig):
+ """Junior config"""
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'junior'
diff --git a/junior/migrations/0001_initial.py b/junior/migrations/0001_initial.py
new file mode 100644
index 0000000..e451c2e
--- /dev/null
+++ b/junior/migrations/0001_initial.py
@@ -0,0 +1,42 @@
+# Generated by Django 4.2.2 on 2023-06-23 12:05
+
+from django.conf import settings
+import django.contrib.postgres.fields
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Junior',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('phone', models.CharField(blank=True, default=None, max_length=31, null=True)),
+ ('country_code', models.IntegerField(blank=True, null=True)),
+ ('gender', models.CharField(blank=True, choices=[('1', 'Male'), ('2', 'Female')], default=None, max_length=10, null=True)),
+ ('dob', models.DateField(blank=True, default=None, max_length=15, null=True)),
+ ('junior_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('guardian_code', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None)),
+ ('referral_code', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('referral_code_used', models.CharField(blank=True, default=None, max_length=10, null=True)),
+ ('is_active', models.BooleanField(default=True)),
+ ('is_complete_profile', models.BooleanField(default=False)),
+ ('passcode', models.IntegerField(blank=True, default=None, null=True)),
+ ('created_at', models.DateTimeField(auto_now_add=True)),
+ ('updated_at', models.DateTimeField(auto_now=True)),
+ ('auth', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_profile', to=settings.AUTH_USER_MODEL, verbose_name='Email')),
+ ],
+ options={
+ 'verbose_name': 'Junior',
+ 'db_table': 'junior',
+ },
+ ),
+ ]
diff --git a/junior/migrations/0002_junior_country_name.py b/junior/migrations/0002_junior_country_name.py
new file mode 100644
index 0000000..0dd74bd
--- /dev/null
+++ b/junior/migrations/0002_junior_country_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.2 on 2023-06-27 13:59
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('junior', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='junior',
+ name='country_name',
+ field=models.CharField(blank=True, default=None, max_length=30, null=True),
+ ),
+ ]
diff --git a/junior/migrations/__init__.py b/junior/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/junior/models.py b/junior/models.py
new file mode 100644
index 0000000..b389e3a
--- /dev/null
+++ b/junior/models.py
@@ -0,0 +1,42 @@
+"""Junior model """
+"""Import django"""
+from django.db import models
+from django.contrib.auth import get_user_model
+from django.contrib.postgres.fields import ArrayField
+"""Import django app"""
+from base.constants import GENDERS
+User = get_user_model()
+# Create your models here.
+
+class Junior(models.Model):
+ """Junior model"""
+ auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email')
+ """Contact details"""
+ phone = models.CharField(max_length=31, null=True, blank=True, default=None)
+ country_code = models.IntegerField(blank=True, null=True)
+ country_name = models.CharField(max_length=30, null=True, blank=True, default=None)
+ """Personal info"""
+ gender = models.CharField(max_length=10, choices=GENDERS, null=True, blank=True, default=None)
+ dob = models.DateField(max_length=15, null=True, blank=True, default=None)
+ # image = models.ImageField(upload_to='images/')
+ """Codes"""
+ junior_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ guardian_code = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None),null=True)
+ referral_code = models.CharField(max_length=10, null=True, blank=True, default=None)
+ referral_code_used = models.CharField(max_length=10, null=True, blank=True, default=None)
+ """Profile activity"""
+ is_active = models.BooleanField(default=True)
+ is_complete_profile = models.BooleanField(default=False)
+ passcode = models.IntegerField(null=True, blank=True, default=None)
+ """Profile created and updated time"""
+ created_at = models.DateTimeField(auto_now_add=True)
+ updated_at = models.DateTimeField(auto_now=True)
+
+ class Meta(object):
+ """ Meta class """
+ db_table = 'junior'
+ verbose_name = 'Junior'
+
+ def __str__(self):
+ """Return email id"""
+ return f'{self.auth}'
diff --git a/junior/serializers.py b/junior/serializers.py
new file mode 100644
index 0000000..a741203
--- /dev/null
+++ b/junior/serializers.py
@@ -0,0 +1,95 @@
+"""Serializer file for junior"""
+"""Import Django 3rd party app"""
+from rest_framework import serializers
+from django.contrib.auth.models import User
+from django.db import transaction
+import random
+"""Import django app"""
+from junior.models import Junior
+
+class ListCharField(serializers.ListField):
+ """Serializer for Array field"""
+ child = serializers.CharField()
+
+ def to_representation(self, data):
+ """to represent the data"""
+ return data
+
+ def to_internal_value(self, data):
+ """internal value"""
+ if isinstance(data, list):
+ return data
+ raise serializers.ValidationError({"details":"Invalid input. Expected a list of strings."})
+
+
+class CreateJuniorSerializer(serializers.ModelSerializer):
+ """Create junior serializer"""
+ first_name = serializers.SerializerMethodField('get_first_name')
+ last_name = serializers.SerializerMethodField('get_last_name')
+ email = serializers.SerializerMethodField('get_email')
+ phone = serializers.CharField(max_length=20, required=False)
+ country_code = serializers.IntegerField(required=False)
+ dob = serializers.DateField(required=False)
+ referral_code = serializers.CharField(max_length=100, required=False)
+ guardian_code = ListCharField(required=False)
+
+ class Meta(object):
+ """Meta info"""
+ model = Junior
+ fields = ['first_name', 'last_name', 'email', 'phone', 'gender', 'country_code', 'dob', 'referral_code',
+ 'passcode', 'is_complete_profile', 'guardian_code', 'referral_code_used',
+ 'country_name']
+
+ def get_first_name(self,obj):
+ """first name of junior"""
+ return obj.auth.first_name
+
+ def get_last_name(self,obj):
+ """last name of junior"""
+ return obj.auth.last_name
+
+ def get_email(self,obj):
+ """email of junior"""
+ return obj.auth.email
+
+ def create(self, validated_data):
+ """Create junior profile"""
+ user = User.objects.filter(username=self.context['user']).last()
+ if user:
+ """Save first and last name of junior"""
+ if self.context.get('first_name') != '' and self.context.get('last_name') != '':
+ user.first_name = self.context.get('first_name', user.first_name)
+ user.last_name = self.context.get('last_name', user.last_name)
+ user.save()
+ """Create junior data"""
+ junior, created = Junior.objects.get_or_create(auth=self.context['user'])
+ if created:
+ """Create referral code and junior code"""
+ junior.referral_code = ''.join([str(random.randrange(9)) for _ in range(4)])
+ junior.junior_code = ''.join([str(random.randrange(9)) for _ in range(4)])
+ if junior:
+ """update details according to the data get from request"""
+ junior.gender = validated_data.get('gender',junior.gender)
+ """Update guardian code"""
+ junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code)
+ junior.dob = validated_data.get('dob',junior.dob)
+ junior.passcode = validated_data.get('passcode', junior.passcode)
+ junior.country_name = validated_data.get('country_name', junior.country_name)
+ """Update country code and phone number"""
+ junior.phone = validated_data.get('phone', junior.phone)
+ junior.country_code = validated_data.get('country_code', junior.country_code)
+ junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used)
+ """Complete profile of the junior if below all data are filled"""
+ complete_profile_field = all([junior.phone, junior.gender, junior.country_name,
+ junior.dob, junior.country_code, user.first_name, user.last_name])
+ junior.is_complete_profile = False
+ if complete_profile_field:
+ junior.is_complete_profile = True
+ junior.save()
+ return junior
+
+ def save(self, **kwargs):
+ """Save the data into junior table"""
+ with transaction.atomic():
+ instance = super().save(**kwargs)
+ return instance
diff --git a/junior/tests.py b/junior/tests.py
new file mode 100644
index 0000000..1a75974
--- /dev/null
+++ b/junior/tests.py
@@ -0,0 +1,5 @@
+"""Junior test file"""
+"""Import TestCase"""
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/junior/urls.py b/junior/urls.py
new file mode 100644
index 0000000..2b64fe4
--- /dev/null
+++ b/junior/urls.py
@@ -0,0 +1,19 @@
+""" Urls files"""
+"""Django import"""
+from django.urls import path, include
+from .views import UpdateJuniorProfile, ValidateGuardianCode
+"""Third party import"""
+from rest_framework import routers
+
+"""Router"""
+router = routers.SimpleRouter()
+
+"""API End points with router"""
+"""Create junior profile API"""
+router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update')
+"""validate guardian code API"""
+router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code')
+"""Define url pattern"""
+urlpatterns = [
+ path('api/v1/', include(router.urls)),
+]
diff --git a/junior/views.py b/junior/views.py
new file mode 100644
index 0000000..153bb41
--- /dev/null
+++ b/junior/views.py
@@ -0,0 +1,40 @@
+"""Junior view file"""
+from rest_framework import viewsets, status
+from rest_framework.permissions import IsAuthenticated
+"""Django app import"""
+from junior.models import Junior
+from .serializers import CreateJuniorSerializer
+from guardian.models import Guardian
+from base.messages import ERROR_CODE, SUCCESS_CODE
+from account.utils import custom_response, custom_error_response
+# Create your views here.
+class UpdateJuniorProfile(viewsets.ViewSet):
+ """Update junior profile"""
+ serializer_class = CreateJuniorSerializer
+ permission_classes = [IsAuthenticated]
+
+ def create(self, request, *args, **kwargs):
+ """Use CreateJuniorSerializer"""
+ serializer = CreateJuniorSerializer(context={"user":request.user,
+ "first_name":request.data.get('first_name', ''),
+ "last_name": request.data.get('last_name',' ')},
+ data=request.data)
+ if serializer.is_valid():
+ """save serializer"""
+ serializer.save()
+ return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
+ return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
+
+class ValidateGuardianCode(viewsets.ViewSet):
+ """Check guardian code exist or not"""
+ permission_classes = [IsAuthenticated]
+
+ def list(self, request, *args, **kwargs):
+ """check guardian code"""
+ guardian_code = self.request.GET.get('guardian_code').split(',')
+ for code in guardian_code:
+ guardian_data = Guardian.objects.filter(guardian_code=code).exists()
+ if guardian_data:
+ return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK)
+ else:
+ return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST)
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..fe9e065
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+"""Django import"""
+import os
+import sys
+
+
+def main():
+ """Main function"""
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings')
+ try:
+ """Import execute from command line function"""
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ """Show Exception error"""
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/nginx/django.conf b/nginx/django.conf
new file mode 100644
index 0000000..488e2c0
--- /dev/null
+++ b/nginx/django.conf
@@ -0,0 +1,24 @@
+upstream web {
+ ip_hash;
+ server web:8000;
+ }
+
+ # portal
+ server {
+ location / {
+ proxy_pass http://web/;
+ proxy_set_header Host $http_host;
+ }
+ listen 8000;
+ client_max_body_size 512M;
+ server_name localhost;
+ proxy_read_timeout 900;
+ proxy_connect_timeout 900;
+ proxy_send_timeout 900;
+ #proxy_set_header Host $http_host;
+
+ location /static {
+ autoindex on;
+ alias /usr/src/app/zod_bank/static/;
+ }
+ }
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..a875fbf
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,61 @@
+aliyun-python-sdk-core==2.13.36
+aliyun-python-sdk-dysmsapi==2.1.2
+amqp==5.1.1
+asgiref==3.7.2
+async-timeout==4.0.2
+billiard==4.1.0
+boto3==1.26.157
+botocore==1.29.157
+celery==5.3.1
+cffi==1.15.1
+channels==4.0.0
+channels-redis==4.1.0
+click==8.1.3
+click-didyoumean==0.3.0
+click-plugins==1.1.1
+click-repl==0.3.0
+cron-descriptor==1.4.0
+cryptography==41.0.1
+decouple==0.0.7
+Django==4.2.2
+django-celery-beat==2.5.0
+django-celery-results==2.5.1
+django-cors-headers==4.1.0
+django-dotenv==1.4.2
+django-extensions==3.2.3
+django-phonenumber-field==7.1.0
+django-render-block==0.9.2
+django-ses==3.5.0
+django-smtp-ssl==1.0
+django-storages==1.13.2
+django-templated-email==3.0.1
+django-timezone-field==5.1
+djangorestframework==3.14.0
+djangorestframework-simplejwt==5.2.2
+drf-yasg==1.21.6
+gunicorn==20.1.0
+inflection==0.5.1
+jmespath==0.10.0
+kombu==5.3.1
+msgpack==1.0.5
+packaging==23.1
+phonenumbers==8.13.15
+prompt-toolkit==3.0.38
+psycopg==3.1.9
+pycparser==2.21
+PyJWT==2.7.0
+python-crontab==2.7.1
+python-dateutil==2.8.2
+python-dotenv==1.0.0
+pytz==2023.3
+PyYAML==6.0
+redis==4.5.5
+s3transfer==0.6.1
+six==1.16.0
+sqlparse==0.4.4
+typing_extensions==4.6.3
+tzdata==2023.3
+uritemplate==4.1.1
+urllib3==1.26.16
+vine==5.0.0
+wcwidth==0.2.6
diff --git a/zod_bank/__init__.py b/zod_bank/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/zod_bank/asgi.py b/zod_bank/asgi.py
new file mode 100644
index 0000000..967882b
--- /dev/null
+++ b/zod_bank/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for ZOD_Bank project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings')
+
+application = get_asgi_application()
diff --git a/zod_bank/settings.py b/zod_bank/settings.py
new file mode 100644
index 0000000..1c758b7
--- /dev/null
+++ b/zod_bank/settings.py
@@ -0,0 +1,191 @@
+"""
+Django settings for ZOD_Bank project.
+
+Generated by 'django-admin startproject' using Django 3.0.14.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.0/ref/settings/
+"""
+
+import os
+from dotenv import load_dotenv
+from datetime import timedelta
+load_dotenv()
+# OR, the same with increased verbosity:
+load_dotenv(verbose=True)
+env_path = os.path.join(os.path.abspath(os.path.join('.env', os.pardir)), '.env')
+load_dotenv(dotenv_path=env_path)
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+# OR, the same with increased verbosity:
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = '-pb+8w#)6qsh+w&tr+q$tholf7=54v%05e^9!lneiqqgtddg6q'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = ['*']
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django_extensions',
+ 'storages',
+ 'drf_yasg',
+ 'corsheaders',
+ 'django.contrib.postgres',
+ 'rest_framework',
+ 'django_ses',
+ 'account',
+ 'junior',
+ 'guardian',
+]
+
+MIDDLEWARE = [
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+ROOT_URLCONF = 'zod_bank.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'zod_bank.wsgi.application'
+REST_FRAMEWORK = {
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ # 'rest_framework.authentication.SessionAuthentication',
+ 'rest_framework.authentication.BasicAuthentication',
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
+]
+}
+SIMPLE_JWT = {
+ 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=15),
+ 'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
+}
+# Database
+# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.contrib.gis.db.backends.postgis',
+ 'NAME':os.getenv('DB_NAME'),
+ 'USER':os.getenv('DB_USERNAME'),
+ 'PASSWORD':os.getenv('DB_PASSWORD'),
+ 'HOST':os.getenv('DB_HOST'),
+ 'PORT':os.getenv('DB_PORT'),
+ }
+}
+# Password validation
+# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.0/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'UTC'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+# cors header settings
+SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
+
+CORS_ORIGIN_ALLOW_ALL = True
+
+CORS_ALLOW_METHODS = (
+ 'DELETE',
+ 'GET',
+ 'OPTIONS',
+ 'PATCH',
+ 'POST',
+ 'PUT',
+)
+
+CORS_ALLOW_HEADERS = (
+ 'accept',
+ 'accept-encoding',
+ 'authorization',
+ 'content-type',
+ 'dnt',
+ 'origin',
+ 'account-agent',
+ 'x-csrftoken',
+ 'x-requested-with',
+)
+
+"""Static files (CSS, JavaScript, Images)
+https://docs.djangoproject.com/en/3.0/howto/static-files/"""
+
+
+# Email settings
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
+EMAIL_HOST = 'smtp.gmail.com'
+EMAIL_PORT = 587
+EMAIL_USE_TLS = True
+EMAIL_HOST_USER = 'infozodbank@gmail.com'
+# Replace with your Gmail email password or App password
+EMAIL_HOST_PASSWORD = 'ghwdmznwwslvchga'
+
+# EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS')
+# EMAIL_HOST = os.getenv('EMAIL_HOST')
+# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
+# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
+# EMAIL_PORT = os.getenv('EMAIL_PORT')
+
+STATIC_URL = 'static/'
+STATIC_ROOT = 'static'
diff --git a/zod_bank/urls.py b/zod_bank/urls.py
new file mode 100644
index 0000000..274451e
--- /dev/null
+++ b/zod_bank/urls.py
@@ -0,0 +1,33 @@
+"""ZOD_Bank URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+ https://docs.djangoproject.com/en/3.0/topics/http/urls/
+Examples:
+Function views
+ 1. Add an import: from my_app import views
+ 2. Add a URL to urlpatterns: path('', views.home, name='home')
+Class-based views
+ 1. Add an import: from other_app.views import Home
+ 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
+Including another URLconf
+ 1. Import the include() function: from django.urls import include, path
+ 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
+"""
+# third-party import
+from django.conf.urls.static import static
+from django.contrib import admin
+from django.urls import path, include
+from drf_yasg import openapi
+from drf_yasg.views import get_schema_view
+from django.urls import path
+
+
+schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, )
+
+urlpatterns = [
+ path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
+ path('admin/', admin.site.urls),
+ path('', include(('account.urls', 'account'), namespace='account')),
+ path('', include('guardian.urls')),
+ path('', include(('junior.urls', 'junior'), namespace='junior')),
+]
diff --git a/zod_bank/wsgi.py b/zod_bank/wsgi.py
new file mode 100644
index 0000000..e6b5c4f
--- /dev/null
+++ b/zod_bank/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for ZOD_Bank project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'zod_bank.settings')
+
+application = get_wsgi_application()