Files
goodtimes/accounts/api/views.py
2024-03-05 17:43:10 +05:30

760 lines
27 KiB
Python

from django.db import transaction
from django.shortcuts import get_object_or_404
from django.utils import timezone
from rest_framework import status
from rest_framework.views import APIView
from rest_framework_simplejwt.tokens import RefreshToken
from django.conf import settings
import requests
# from .authenticate import authticate_with_otp_and_passsword
from accounts.models import (
IAmPrincipal,
IAmPrincipalOtp,
IAmPrincipalSource,
IAmPrincipalType,
# IAmPrincipalKYCDetails,
)
from manage_referrals.models import ReferralCode, ReferralRecord
from goodtimes import constants
from goodtimes.services import SMSError, SMSService, EmailService
# from nifty11_project.services import SMSError, SMSService
from goodtimes.utils import ApiResponse
from accounts.resource_action import (
PRINCIPAL_TYPE_EVENT_USER,
PRINCIPAL_TYPE_EVENT_MANAGER,
PRINCIPAL_TYPE_FREE_USER,
)
from .serializers import (
EmailSerializers,
# RegistrationPasswordSerializer,
# PhoneSerializer,
EmailSerializer,
RegistrationPasswordSerializer,
RegistrationSerializer,
ReferralCodeSerializer,
ReferralRecordSerializer,
ProfileSerializer,
PasswordResetSerializer,
# PrincipalKYCDetailsSerializer,
)
from .utils import (
generate_token_and_user_data,
authticate_with_otp_and_passsword,
get_principal_by_email,
)
from rest_framework.permissions import IsAuthenticated
from rest_framework_simplejwt.authentication import JWTAuthentication
from rest_framework.response import Response
class RegistrationEmailView(APIView):
authentication_classes = []
permission_classes = []
model = IAmPrincipal
@transaction.atomic
def post(self, request):
serializer = EmailSerializer(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.REGISTRATION_FAIL,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
principal_type = serializer.validated_data.get("principal_type")
print("principal_type: ", principal_type)
principal = None # Declare user variable outside of try-except blocks
# default_email = f"nifty{phone_no[-10:]}@gmail.com"
default_password = f"GTMES{email[::-1]}"
try:
principal = self.model.objects.get(email=email)
principal_type = IAmPrincipalType.get_principal_type(principal_type)
print(f"principal_type in try {principal_type}")
if principal.email_verified:
return ApiResponse.error(
message=constants.EMAIL_EXISTS,
errors=constants.EMAIL_EXISTS,
status=status.HTTP_403_FORBIDDEN,
)
# Update the existing user (unverified) with a default email and password
# principal.email = email
principal.principal_type = principal_type
principal.username = email
principal.set_password(default_password)
principal.save()
except self.model.DoesNotExist:
# Phone number doesn't exist, create a new user
principal = self.model.objects.create(email=email, username=email)
principal_type = IAmPrincipalType.get_principal_type(principal_type)
principal.principal_type = principal_type
principal.set_password(f"GTMES{email[::-1]}")
principal.save()
except Exception as e:
return ApiResponse.error(message=str(e), errors=str(e))
try:
# Send OTP to the user
otp = SMSService().create_otp(
principal=principal, opt_purpose="registration"
)
# Create an instance of the EmailService
email_service = EmailService(
subject="Good Times - OTP",
to=[email],
from_email=settings.EMAIL_HOST_USER,
)
email_service.load_template(
"otp/otp.html", context={"OTP": otp, "action": "Register"}
)
# Send the email
email_service.send()
print("Email sent successfully!")
except SMSError as e:
return ApiResponse.error(message=e.message, errors=e.message)
return ApiResponse.success(
message=constants.OTP_SENT, data=int(otp)
) # this will change
class RegistrationDetailsView(APIView):
authentication_classes = []
permission_classes = []
@transaction.atomic
def post(self, request):
"""
Handle User Registration and Referral.
This view processes user registration, validates data, creates new users, and tracks referrals if a referral code is provided. It returns success or error responses based on the registration outcome.
Parameters:
- request (HttpRequest): HTTP request object with registration data.
Returns:
- ApiResponse: Registration success or error response.
Process:
1. Extract data and validate registration.
2. Create a new user and OTP.
3. Generate referral codes for users.
4. Track referrals if a referral code is provided and not alreay register thorugh referral.
5. Return a response indicating success or error.
"""
# Extract the phone number from the request data
# phone_no = request.data.get("phone_no")
email = request.data.get("email")
print("email: ", email)
referral_code = request.data.get("referral_code")
print("referral_code", referral_code)
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
# Validate the incoming data using the serializer
serializer = RegistrationSerializer(principal, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
# Save the principal and related OTP
try:
instance = serializer.save()
instance.register_complete = True
instance.save()
except Exception as e:
print("instance: E-", e)
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
# generating referrall_code of the player and merchant
try:
ReferralCode.create_referral_code_for_user_manager(
principal=principal, principal_type=principal.principal_type
)
except Exception as e:
print("ReferralCode: E-", e)
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.FAILURE,
"errors": str(e),
}
return ApiResponse.error(**error_response)
if referral_code:
already_register_through_referral = ReferralRecord.objects.filter(
referred_principal=instance
).exists()
if not already_register_through_referral:
try:
whos_referral_code = ReferralCode.objects.get(
referral_code=referral_code
)
except Exception as e:
print("whos_referral_code: E-", e)
error_response = {
"status": status.HTTP_404_NOT_FOUND,
"message": constants.RECORD_NOT_FOUND,
"errors": str(e),
}
return ApiResponse.error(**error_response)
# principal_type = IAmPrincipalType.objects
ReferralRecord.objects.create(
referrer_principal=whos_referral_code.principal, # principal id of the User who invited
referred_principal=instance, # principal id of the User who join through invitation
principal_type=whos_referral_code.principal_type, # principal type of the user who invited
is_completed=True,
)
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
return ApiResponse.success(
message=constants.REGISTRATION_SUCCESS, data=token_data
)
class RegistrationPasswordView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
email = request.data.get("email")
# type = request.data.get("type")
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
serializer = RegistrationPasswordSerializer(principal, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
principal = serializer.save()
principal.register_complete = True
principal.save()
except Exception as e:
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
return ApiResponse.success(
message=constants.REGISTRATION_SUCCESS, data=token_data
)
class OtpRequestView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
# Filter the user instance by phone number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
try:
otp = SMSService().create_otp(principal=principal, opt_purpose="login")
# Create an instance of the EmailService
email_service = EmailService(
subject="Good Times - OTP",
to=[email],
from_email=settings.EMAIL_HOST_USER,
)
email_service.load_template(
"otp/otp.html", context={"OTP": otp, "action": "Login"}
)
# Send the email
email_service.send()
except SMSError as e:
return ApiResponse.error(message=e.message, errors=e.message)
return ApiResponse.success(message=constants.OTP_SENT, data=int(otp))
class OTPVerificationView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": "Validation error",
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
email = serializer.validated_data.get("email")
otp = request.data.get("otp")
# Filter the user instance by phon
# ]
# 4\\\\\\\\\\\\\\\\\\\\\\\7e number through reusable function
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # returning error here as it is error instance
validation_result = authticate_with_otp_and_passsword(principal, otp=otp)
if isinstance(validation_result, Response):
return validation_result # Return the error response if validation fails
try:
principal.email_verified = True # set the phone_verified to true
principal.save()
except Exception as e:
error_response = {
"status": status.HTTP_500_INTERNAL_SERVER_ERROR,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
return ApiResponse.success(message=constants.OTP_VERIFIED)
class LoginView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
serializer = EmailSerializers(data=request.data)
if not serializer.is_valid():
return Response(
{
"message": "Validation error",
"errors": serializer.errors,
},
status=status.HTTP_400_BAD_REQUEST,
)
email = serializer.validated_data.get("email")
otp = request.data.get("otp")
password = request.data.get("password")
principal = get_principal_by_email(email)
if isinstance(principal, Response):
return principal # If get_principal_by_email returns a Response object, it's an error response.
if not principal.is_active:
return Response(
{
"message": constants.ACCOUNT_DEACTIVATED,
"errors": constants.ACCOUNT_DEACTIVATED,
},
status=status.HTTP_403_FORBIDDEN,
)
if not otp and not password:
return Response(
{
"message": constants.OTP_OR_PASSWORD_REQUIRED,
"errors": constants.OTP_OR_PASSWORD_REQUIRED,
},
status=status.HTTP_400_BAD_REQUEST,
)
if otp:
return self._process_otp_login(principal, otp)
elif password:
return self._process_password_login(principal, password)
return Response(
{"message": constants.LOGIN_FAIL}, status=status.HTTP_400_BAD_REQUEST
)
def _process_otp_login(self, principal, otp):
otp_instance = IAmPrincipalOtp.objects.filter(
principal=principal, otp_code=otp
).last()
if not otp_instance or otp_instance.is_expired():
return Response(
{
"message": (
constants.OTP_INVALID
if not otp_instance
else constants.OTP_EXPIRED
),
"errors": (
constants.OTP_INVALID
if not otp_instance
else constants.OTP_EXPIRED
),
},
status=status.HTTP_400_BAD_REQUEST,
)
otp_instance.is_used = True
otp_instance.save()
return self._login_success(principal)
def _process_password_login(self, principal, password):
if not principal.check_password(password):
return Response(
{
"message": constants.INVALID_PASSWORD,
"errors": constants.INVALID_PASSWORD,
},
status=status.HTTP_400_BAD_REQUEST,
)
return self._login_success(principal)
def _login_success(self, principal):
try:
principal.email_verified = True
principal.last_login = timezone.localtime(timezone.now())
principal.save()
except Exception as e:
return Response(
{
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
)
token_data = generate_token_and_user_data(principal)
return Response(
{
"message": constants.LOGIN_SUCCESS,
"data": token_data,
},
status=status.HTTP_200_OK,
)
class ProfileView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = IAmPrincipal
serializer = ProfileSerializer
def get_object(self):
return self.request.user
def get(self, request, *args, **kwargs):
instance = self.get_object()
context = {"request": request}
# context = {"principal_type": kwargs.get("principal_type"), "request": request}
serializer = self.serializer(instance, context=context)
success_response = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer.data,
}
return ApiResponse.success(**success_response)
def post(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.serializer(instance, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.VALIDATION_ERROR,
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save()
except Exception as e:
return ApiResponse.error(
message=constants.INTERNAL_SERVER_ERROR, errors=str(e)
)
return ApiResponse.success(message=constants.SUCCESS)
class ProfilePasswordResetView(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = IAmPrincipal
serializer = PasswordResetSerializer
def post(self, request, *args, **kwargs):
current_password = request.data.get("current_password")
principal_obj = request.user
print(f"current password is {current_password}")
if current_password is None:
return ApiResponse.error(
message="Current password is required",
errors="Current password is required",
)
if not principal_obj.check_password(current_password):
return ApiResponse.error(
message="Invalid current password.", errors="Invalid current password."
)
serializer = self.serializer(instance=principal_obj, data=request.data)
if not serializer.is_valid():
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": "Validation error",
"errors": serializer.errors,
}
return ApiResponse.error(**error_response)
try:
serializer.save()
except Exception as e:
error_response = {
"status": status.HTTP_400_BAD_REQUEST,
"message": constants.INTERNAL_SERVER_ERROR,
"errors": str(e),
}
return ApiResponse.error(**error_response)
return ApiResponse.success(message=constants.SUCCESS)
# class KycDocumentView(APIView):
# authentication_classes = [JWTAuthentication]
# permission_classes = [IsAuthenticated]
# model = IAmPrincipalKYCDetails
# serializer = PrincipalKYCDetailsSerializer
# def get_object(self):
# try:
# return self.model.objects.get(principal=self.request.user)
# except self.model.DoesNotExist:
# return None
# def get(self, request, *args, **kwargs):
# instance = self.get_object()
# serializer = self.serializer(context={"request": request})
# if instance is not None:
# serializer.instance = instance # Update that instance if record exist
# success_response = {
# "status": status.HTTP_200_OK,
# "message": constants.SUCCESS,
# "data": serializer.data,
# }
# return ApiResponse.success(**success_response)
# def post(self, request, *args, **kwargs):
# instance = self.get_object()
# serializer = self.serializer(
# data=request.data, context={"request": request}
# ) # passing request context to update the principal from serializer
# if instance is not None:
# serializer.instance = instance # Update that instance if record exist
# if not serializer.is_valid():
# error_response = {
# "status": status.HTTP_400_BAD_REQUEST,
# "message": constants.VALIDATION_ERROR,
# "errors": serializer.errors,
# }
# return ApiResponse.error(**error_response)
# try:
# serializer.save()
# except Exception as e:
# return ApiResponse.error(
# message=constants.INTERNAL_SERVER_ERROR, errors=str(e)
# )
# return ApiResponse.success(message=constants.SUCCESS)
class ReferralCodeViews(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = ReferralCode
serializer = ReferralCodeSerializer
def get(self, request, *args, **kwargs):
referral_obj = self.model.filter_referral_code(
principal=request.user, principal_type=kwargs.get("principal_type")
)
serializer_obj = self.serializer(referral_obj, many=True)
success_message = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": serializer_obj.data,
}
return ApiResponse.success(**success_message)
class ReferralRecordViews(APIView):
authentication_classes = [JWTAuthentication]
permission_classes = [IsAuthenticated]
model = ReferralRecord
serializer = ReferralRecordSerializer
def get(self, request, *args, **kwargs):
referral_obj = self.model.filter_invite_records(
referrer_principal=request.user, principal_type=kwargs.get("principal_type")
)
serializer_obj = self.serializer(referral_obj, many=True)
success_message = {
"status": status.HTTP_200_OK,
"message": constants.SUCCESS,
"data": {"count": referral_obj.count(), "record": serializer_obj.data},
}
return ApiResponse.success(**success_message)
class GoogleLoginAPIView(APIView):
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
access_token = request.data.get("access_token")
principal_type = request.data.get("principal_type")
if not access_token or not principal_type:
return Response(
{"error": "Access token & Principal Type is required"},
status=status.HTTP_400_BAD_REQUEST,
)
principal_info = self.get_google_user_data(access_token)
print("principal_info: ", principal_info)
# Check if there was an error in fetching user data
if "error" in principal_info:
error_message = principal_info.get("error", {}).get(
"error_description", "An error occurred while fetching user data."
)
return Response(
{"error": error_message},
status=principal_info.get("status", status.HTTP_400_BAD_REQUEST),
)
if not principal_info:
return Response(
{"error": "Failed to fetch user details or invalid access token"},
status=status.HTTP_400_BAD_REQUEST,
)
email = principal_info.get("email")
if not email:
return Response(
{"error": "Email is required but not provided."},
status=status.HTTP_400_BAD_REQUEST,
)
print("email: ", email)
serializer = EmailSerializer(
data={"email": email, "principal_type": principal_type}
)
if serializer.is_valid():
print("Serializer is Validated..")
defaults = {
"first_name": principal_info.get("given_name", ""),
"last_name": principal_info.get("family_name", ""),
"register_complete": True,
"email_verified": True,
# 'principal_type': principal_type, # Uncomment if principal_type should be updated on each login
}
# Use get_or_create to ensure the principal source for Google exists
google_source, _ = IAmPrincipalSource.objects.get_or_create(name="google")
principal, created = IAmPrincipal.objects.update_or_create(
email=email,
username=email,
defaults=defaults,
)
principal_type_instance = IAmPrincipalType.objects.get(name=principal_type)
# If newly created, set password
if created:
print("New Created")
default_password = f"GTMES{email[::-1]}"
principal.set_password(default_password)
principal.principal_type = principal_type_instance # Assuming principal_type should only be set on creation
principal.principal_source = google_source
principal.save()
token_data = generate_token_and_user_data(principal)
token_data["type"] = str(principal.principal_type)
message = "Already Registered and Verified User"
if created:
message = constants.REGISTRATION_SUCCESS
elif not principal.register_complete or not principal.email_verified:
message += ", details updated"
return ApiResponse.success(message=message, data=token_data)
return ApiResponse.error(
status=status.HTTP_400_BAD_REQUEST,
message=constants.FAILURE,
error=serializer.errors,
)
def get_google_user_data(self, access_token):
user_info_endpoint = "https://www.googleapis.com/oauth2/v3/userinfo"
response = requests.get(
user_info_endpoint, params={"access_token": access_token}
)
if response.status_code == 200:
return response.json()
if response.status_code != 200:
try:
error_details = response.json()
except ValueError: # Includes simplejson.decoder.JSONDecodeError
error_details = {
"error": "Failed to decode JSON response from Google API.",
"status": response.status_code,
}
return {
"error": error_details,
"status": response.status_code,
}