2025-12-17 22:05:13 +05:30
|
|
|
# accounts/views.py
|
|
|
|
|
import json
|
2026-04-07 10:48:04 +05:30
|
|
|
import secrets
|
2025-12-17 22:05:13 +05:30
|
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
|
from django.http import JsonResponse
|
|
|
|
|
from django.utils.decorators import method_decorator
|
|
|
|
|
from django.views import View
|
2026-04-07 10:48:04 +05:30
|
|
|
from rest_framework.views import APIView
|
2025-12-17 22:05:13 +05:30
|
|
|
from rest_framework.authtoken.models import Token
|
2025-12-19 19:35:38 +05:30
|
|
|
from mobile_api.forms import RegisterForm, LoginForm, WebRegisterForm
|
2025-12-17 22:05:13 +05:30
|
|
|
from rest_framework.authentication import TokenAuthentication
|
|
|
|
|
from django.contrib.auth import logout
|
|
|
|
|
from mobile_api.utils import validate_token_and_get_user
|
|
|
|
|
from utils.errors_json_convertor import simplify_form_errors
|
2025-12-19 19:35:38 +05:30
|
|
|
from accounts.models import User
|
2026-03-15 00:29:17 +05:30
|
|
|
from eventify_logger.services import log
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class RegisterView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body)
|
|
|
|
|
form = RegisterForm(data)
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
user = form.save()
|
|
|
|
|
token, _ = Token.objects.get_or_create(user=user)
|
2026-03-15 00:29:17 +05:30
|
|
|
log("info", "API user registration", request=request, user=user)
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse({'message': 'User registered successfully', 'token': token.key}, status=201)
|
2026-03-15 00:29:17 +05:30
|
|
|
log("warning", "API registration failed", request=request, logger_data=dict(errors=form.errors))
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse({'errors': form.errors}, status=400)
|
|
|
|
|
except Exception as e:
|
2026-03-15 00:29:17 +05:30
|
|
|
log("error", "API registration exception", request=request, logger_data={"error": str(e)})
|
2026-04-03 09:23:26 +05:30
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred. Please try again.'}, status=500)
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
2025-12-19 19:35:38 +05:30
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class WebRegisterView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
print('0')
|
|
|
|
|
print('*' * 100)
|
|
|
|
|
print(request.body)
|
|
|
|
|
print('*' * 100)
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body)
|
|
|
|
|
form = WebRegisterForm(data)
|
|
|
|
|
print('1')
|
|
|
|
|
print('*' * 100)
|
|
|
|
|
print(form.errors)
|
|
|
|
|
print('*' * 100)
|
|
|
|
|
if form.is_valid():
|
|
|
|
|
print('2')
|
|
|
|
|
user = form.save()
|
|
|
|
|
token, _ = Token.objects.get_or_create(user=user)
|
|
|
|
|
print('3')
|
2026-03-15 00:29:17 +05:30
|
|
|
log("info", "Web user registration", request=request, user=user)
|
2025-12-19 19:35:38 +05:30
|
|
|
response = {
|
|
|
|
|
'message': 'User registered successfully',
|
|
|
|
|
'token': token.key,
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'phone_number': user.phone_number,
|
2026-04-04 10:42:44 +05:30
|
|
|
'district': user.district or '',
|
|
|
|
|
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
|
|
|
|
|
'first_name': user.first_name,
|
|
|
|
|
'last_name': user.last_name,
|
|
|
|
|
'eventify_id': user.eventify_id or '',
|
2025-12-19 19:35:38 +05:30
|
|
|
}
|
|
|
|
|
return JsonResponse(response, status=201)
|
2026-03-15 00:29:17 +05:30
|
|
|
log("warning", "Web registration failed", request=request, logger_data=dict(errors=form.errors))
|
2025-12-19 19:35:38 +05:30
|
|
|
return JsonResponse({'errors': form.errors}, status=400)
|
|
|
|
|
except Exception as e:
|
2026-03-15 00:29:17 +05:30
|
|
|
log("error", "Web registration exception", request=request, logger_data={"error": str(e)})
|
2026-04-03 09:23:26 +05:30
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred. Please try again.'}, status=500)
|
2025-12-19 19:35:38 +05:30
|
|
|
|
|
|
|
|
|
2025-12-17 22:05:13 +05:30
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class LoginView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
print('0')
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body)
|
|
|
|
|
form = LoginForm(data)
|
|
|
|
|
print('1')
|
2026-03-15 00:29:17 +05:30
|
|
|
if form.is_valid():
|
2025-12-17 22:05:13 +05:30
|
|
|
print('2')
|
|
|
|
|
user = form.cleaned_data['user']
|
|
|
|
|
token, _ = Token.objects.get_or_create(user=user)
|
|
|
|
|
print('3')
|
2026-03-15 00:29:17 +05:30
|
|
|
log("info", "API login", request=request, user=user)
|
2025-12-17 22:05:13 +05:30
|
|
|
response = {
|
2026-04-02 10:25:25 +05:30
|
|
|
'message': 'Login successful',
|
2025-12-17 22:05:13 +05:30
|
|
|
'token': token.key,
|
2026-04-02 10:25:25 +05:30
|
|
|
'eventify_id': user.eventify_id,
|
2025-12-17 22:05:13 +05:30
|
|
|
'username': user.username,
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'phone_number': user.phone_number,
|
|
|
|
|
'first_name': user.first_name,
|
|
|
|
|
'last_name': user.last_name,
|
|
|
|
|
'role': user.role,
|
|
|
|
|
'pincode': user.pincode,
|
|
|
|
|
'district': user.district,
|
2026-04-04 10:42:44 +05:30
|
|
|
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
|
2025-12-17 22:05:13 +05:30
|
|
|
'state': user.state,
|
|
|
|
|
'country': user.country,
|
|
|
|
|
'place': user.place,
|
|
|
|
|
'latitude': user.latitude,
|
|
|
|
|
'longitude': user.longitude,
|
2026-03-26 09:50:03 +00:00
|
|
|
'profile_photo': user.profile_picture.url if user.profile_picture else ''
|
2025-12-17 22:05:13 +05:30
|
|
|
}
|
|
|
|
|
print('4')
|
|
|
|
|
print(response)
|
|
|
|
|
return JsonResponse(response, status=200)
|
2026-03-15 00:29:17 +05:30
|
|
|
|
|
|
|
|
log("warning", "API login failed", request=request, logger_data=dict(errors=form.errors))
|
2025-12-17 22:05:13 +05:30
|
|
|
return JsonResponse(simplify_form_errors(form), status=401)
|
|
|
|
|
except Exception as e:
|
2026-03-15 00:29:17 +05:30
|
|
|
log("error", "API login exception", request=request, logger_data={"error": str(e)})
|
2026-04-03 09:23:26 +05:30
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred. Please try again.'}, status=500)
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class StatusView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True)
|
|
|
|
|
if error_response:
|
|
|
|
|
return error_response
|
|
|
|
|
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
"status": "logged_in",
|
|
|
|
|
"username": user.username,
|
2026-04-03 09:14:37 +05:30
|
|
|
"email": user.email,
|
|
|
|
|
"eventify_id": user.eventify_id or '',
|
2026-04-04 10:42:44 +05:30
|
|
|
"district": user.district or '',
|
|
|
|
|
"district_changed_at": user.district_changed_at.isoformat() if user.district_changed_at else None,
|
2026-04-08 16:12:27 +05:30
|
|
|
"profile_photo": user.profile_picture.url if user.profile_picture else '',
|
2025-12-17 22:05:13 +05:30
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "API status exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."}, status=500)
|
2025-12-17 22:05:13 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class LogoutView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True)
|
|
|
|
|
if error_response:
|
|
|
|
|
return error_response
|
|
|
|
|
|
2026-03-15 00:29:17 +05:30
|
|
|
log("info", "API logout", request=request, user=user)
|
2025-12-17 22:05:13 +05:30
|
|
|
# 🔍 Call Django's built-in logout
|
|
|
|
|
logout(request)
|
|
|
|
|
|
|
|
|
|
# 🗑 Delete the token to invalidate future access
|
|
|
|
|
token.delete()
|
|
|
|
|
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
"status": "logged_out",
|
|
|
|
|
"message": "Logout successful"
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-03-15 00:29:17 +05:30
|
|
|
log("error", "API logout exception", request=request, logger_data={"error": str(e)})
|
2026-04-03 09:23:26 +05:30
|
|
|
return JsonResponse({"status": "error", "message": "An unexpected server error occurred."}, status=500)
|
2025-12-19 19:35:38 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class UpdateProfileView(View):
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
# Authenticate user using validate_token_and_get_user
|
|
|
|
|
user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True)
|
|
|
|
|
if error_response:
|
|
|
|
|
# Convert error response format to match our API response format
|
|
|
|
|
error_data = json.loads(error_response.content)
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': error_data.get('message', error_data.get('status', 'Authentication failed'))
|
|
|
|
|
}, status=error_response.status_code)
|
|
|
|
|
|
|
|
|
|
errors = {}
|
|
|
|
|
updated_fields = []
|
|
|
|
|
|
|
|
|
|
# Get update data - handle both JSON and multipart/form-data
|
|
|
|
|
is_multipart = request.content_type and 'multipart/form-data' in request.content_type
|
|
|
|
|
if is_multipart:
|
|
|
|
|
# For multipart, get data from POST (data already contains token/username from validation)
|
|
|
|
|
json_data = request.POST.dict()
|
|
|
|
|
else:
|
|
|
|
|
# For JSON, use data from validate_token_and_get_user
|
|
|
|
|
json_data = data if data else {}
|
|
|
|
|
|
|
|
|
|
# Update first_name
|
|
|
|
|
if 'first_name' in json_data:
|
|
|
|
|
first_name = json_data.get('first_name', '').strip()
|
|
|
|
|
if first_name:
|
|
|
|
|
user.first_name = first_name
|
|
|
|
|
updated_fields.append('first_name')
|
|
|
|
|
elif first_name == '':
|
|
|
|
|
user.first_name = ''
|
|
|
|
|
updated_fields.append('first_name')
|
|
|
|
|
|
|
|
|
|
# Update last_name
|
|
|
|
|
if 'last_name' in json_data:
|
|
|
|
|
last_name = json_data.get('last_name', '').strip()
|
|
|
|
|
if last_name:
|
|
|
|
|
user.last_name = last_name
|
|
|
|
|
updated_fields.append('last_name')
|
|
|
|
|
elif last_name == '':
|
|
|
|
|
user.last_name = ''
|
|
|
|
|
updated_fields.append('last_name')
|
|
|
|
|
|
|
|
|
|
# Update phone_number
|
|
|
|
|
if 'phone_number' in json_data:
|
|
|
|
|
phone_number = json_data.get('phone_number', '').strip()
|
|
|
|
|
if phone_number:
|
|
|
|
|
# Check if phone number is already taken by another user
|
|
|
|
|
if User.objects.filter(phone_number=phone_number).exclude(id=user.id).exists():
|
|
|
|
|
errors['phone_number'] = 'Phone number is already registered.'
|
|
|
|
|
else:
|
|
|
|
|
user.phone_number = phone_number
|
|
|
|
|
updated_fields.append('phone_number')
|
|
|
|
|
elif phone_number == '':
|
|
|
|
|
user.phone_number = None
|
|
|
|
|
updated_fields.append('phone_number')
|
|
|
|
|
|
|
|
|
|
# Update email
|
|
|
|
|
if 'email' in json_data:
|
|
|
|
|
email = json_data.get('email', '').strip().lower()
|
|
|
|
|
if email:
|
|
|
|
|
# Validate email format
|
|
|
|
|
if '@' not in email:
|
|
|
|
|
errors['email'] = 'Invalid email format.'
|
|
|
|
|
# Check if email is already taken by another user
|
|
|
|
|
elif User.objects.filter(email=email).exclude(id=user.id).exists():
|
|
|
|
|
errors['email'] = 'Email is already registered.'
|
|
|
|
|
else:
|
|
|
|
|
user.email = email
|
|
|
|
|
# Also update username if it was set to email
|
|
|
|
|
if user.username == user.email or not user.username:
|
|
|
|
|
user.username = email
|
|
|
|
|
updated_fields.append('email')
|
|
|
|
|
elif email == '':
|
|
|
|
|
errors['email'] = 'Email cannot be empty.'
|
|
|
|
|
|
|
|
|
|
# Update pincode
|
|
|
|
|
if 'pincode' in json_data:
|
|
|
|
|
pincode = json_data.get('pincode', '').strip()
|
|
|
|
|
if pincode:
|
|
|
|
|
user.pincode = pincode
|
|
|
|
|
updated_fields.append('pincode')
|
|
|
|
|
elif pincode == '':
|
|
|
|
|
user.pincode = None
|
|
|
|
|
updated_fields.append('pincode')
|
|
|
|
|
|
2026-04-04 10:42:44 +05:30
|
|
|
# Update district (with 6-month cooldown)
|
2025-12-19 20:46:11 +05:30
|
|
|
if 'district' in json_data:
|
2026-04-04 10:42:44 +05:30
|
|
|
from django.utils import timezone
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
from accounts.models import VALID_DISTRICTS
|
|
|
|
|
|
|
|
|
|
COOLDOWN = timedelta(days=183) # ~6 months
|
|
|
|
|
new_district = json_data.get('district', '').strip()
|
|
|
|
|
|
|
|
|
|
if new_district and new_district not in VALID_DISTRICTS:
|
|
|
|
|
errors['district'] = 'Invalid district.'
|
|
|
|
|
elif new_district and new_district != (user.district or ''):
|
|
|
|
|
if user.district_changed_at and timezone.now() < user.district_changed_at + COOLDOWN:
|
|
|
|
|
next_date = (user.district_changed_at + COOLDOWN).strftime('%d %b %Y')
|
|
|
|
|
errors['district'] = f'District can only be changed once every 6 months. Next change: {next_date}.'
|
|
|
|
|
else:
|
|
|
|
|
user.district = new_district
|
|
|
|
|
user.district_changed_at = timezone.now()
|
|
|
|
|
updated_fields.append('district')
|
|
|
|
|
elif new_district == '' and user.district:
|
|
|
|
|
if user.district_changed_at and timezone.now() < user.district_changed_at + COOLDOWN:
|
|
|
|
|
next_date = (user.district_changed_at + COOLDOWN).strftime('%d %b %Y')
|
|
|
|
|
errors['district'] = f'District can only be changed once every 6 months. Next change: {next_date}.'
|
|
|
|
|
else:
|
|
|
|
|
user.district = None
|
|
|
|
|
user.district_changed_at = timezone.now()
|
|
|
|
|
updated_fields.append('district')
|
2025-12-19 20:46:11 +05:30
|
|
|
|
|
|
|
|
# Update state
|
|
|
|
|
if 'state' in json_data:
|
|
|
|
|
state = json_data.get('state', '').strip()
|
|
|
|
|
if state:
|
|
|
|
|
user.state = state
|
|
|
|
|
updated_fields.append('state')
|
|
|
|
|
elif state == '':
|
|
|
|
|
user.state = None
|
|
|
|
|
updated_fields.append('state')
|
|
|
|
|
|
|
|
|
|
# Update country
|
|
|
|
|
if 'country' in json_data:
|
|
|
|
|
country = json_data.get('country', '').strip()
|
|
|
|
|
if country:
|
|
|
|
|
user.country = country
|
|
|
|
|
updated_fields.append('country')
|
|
|
|
|
elif country == '':
|
|
|
|
|
user.country = None
|
|
|
|
|
updated_fields.append('country')
|
|
|
|
|
|
|
|
|
|
# Update place
|
|
|
|
|
if 'place' in json_data:
|
|
|
|
|
place = json_data.get('place', '').strip()
|
|
|
|
|
if place:
|
|
|
|
|
user.place = place
|
|
|
|
|
updated_fields.append('place')
|
|
|
|
|
elif place == '':
|
|
|
|
|
user.place = None
|
|
|
|
|
updated_fields.append('place')
|
|
|
|
|
|
2025-12-19 19:35:38 +05:30
|
|
|
# Handle profile_picture (multipart form-data only)
|
|
|
|
|
if 'profile_photo' in request.FILES:
|
|
|
|
|
# Handle file upload from multipart/form-data
|
|
|
|
|
profile_photo = request.FILES['profile_photo']
|
|
|
|
|
# Validate file type
|
|
|
|
|
if not profile_photo.content_type.startswith('image/'):
|
|
|
|
|
errors['profile_photo'] = 'File must be an image.'
|
|
|
|
|
else:
|
|
|
|
|
user.profile_picture = profile_photo
|
|
|
|
|
updated_fields.append('profile_photo')
|
|
|
|
|
|
|
|
|
|
# Return errors if any
|
|
|
|
|
if errors:
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'success': False,
|
|
|
|
|
'errors': errors
|
|
|
|
|
}, status=400)
|
|
|
|
|
|
|
|
|
|
# Save user if any fields were updated
|
|
|
|
|
if updated_fields:
|
|
|
|
|
user.save()
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'success': True,
|
|
|
|
|
'message': 'Profile updated successfully',
|
|
|
|
|
'updated_fields': updated_fields,
|
|
|
|
|
'user': {
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'first_name': user.first_name,
|
|
|
|
|
'last_name': user.last_name,
|
|
|
|
|
'phone_number': user.phone_number,
|
|
|
|
|
'pincode': user.pincode,
|
2025-12-19 20:46:11 +05:30
|
|
|
'district': user.district,
|
2026-04-04 10:42:44 +05:30
|
|
|
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
|
2025-12-19 20:46:11 +05:30
|
|
|
'state': user.state,
|
|
|
|
|
'country': user.country,
|
|
|
|
|
'place': user.place,
|
2025-12-19 19:35:38 +05:30
|
|
|
'profile_picture': user.profile_picture.url if user.profile_picture else None,
|
|
|
|
|
}
|
|
|
|
|
}, status=200)
|
|
|
|
|
else:
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'success': False,
|
|
|
|
|
'error': 'No fields provided for update'
|
|
|
|
|
}, status=400)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-03 09:23:26 +05:30
|
|
|
log("error", "API update profile exception", request=request, logger_data={"error": str(e)})
|
2025-12-19 19:35:38 +05:30
|
|
|
return JsonResponse({
|
|
|
|
|
'success': False,
|
2026-04-03 09:23:26 +05:30
|
|
|
'error': 'An unexpected server error occurred. Please try again.'
|
2025-12-19 19:35:38 +05:30
|
|
|
}, status=500)
|
2026-04-07 10:48:04 +05:30
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class BulkUserPublicInfoView(APIView):
|
|
|
|
|
"""Internal endpoint for Node.js gamification server to resolve user details.
|
|
|
|
|
Accepts POST with { emails: [...] } (max 500).
|
|
|
|
|
Returns { users: { email: { district, display_name, eventify_id } } }
|
|
|
|
|
"""
|
|
|
|
|
authentication_classes = []
|
|
|
|
|
permission_classes = []
|
|
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
json_data = json.loads(request.body)
|
|
|
|
|
emails = json_data.get('emails', [])
|
|
|
|
|
if not emails or not isinstance(emails, list) or len(emails) > 500:
|
|
|
|
|
return JsonResponse({'error': 'Provide 1-500 emails'}, status=400)
|
|
|
|
|
|
|
|
|
|
users_qs = User.objects.filter(email__in=emails).values_list(
|
|
|
|
|
'email', 'first_name', 'last_name', 'district', 'eventify_id'
|
|
|
|
|
)
|
|
|
|
|
result = {}
|
|
|
|
|
for email, first, last, district, eid in users_qs:
|
|
|
|
|
name = f"{first} {last}".strip() or email.split('@')[0]
|
|
|
|
|
result[email] = {
|
|
|
|
|
'display_name': name,
|
|
|
|
|
'district': district or '',
|
|
|
|
|
'eventify_id': eid or '',
|
|
|
|
|
}
|
|
|
|
|
return JsonResponse({'users': result})
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log("error", "BulkUserPublicInfoView error", logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred.'}, status=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class GoogleLoginView(View):
|
|
|
|
|
"""Verify a Google ID token, find or create the user, return the same response shape as LoginView."""
|
|
|
|
|
def post(self, request):
|
|
|
|
|
try:
|
|
|
|
|
from google.oauth2 import id_token as google_id_token
|
|
|
|
|
from google.auth.transport import requests as google_requests
|
|
|
|
|
|
|
|
|
|
data = json.loads(request.body)
|
|
|
|
|
token = data.get('id_token')
|
|
|
|
|
if not token:
|
|
|
|
|
return JsonResponse({'error': 'id_token is required'}, status=400)
|
|
|
|
|
|
|
|
|
|
idinfo = google_id_token.verify_oauth2_token(token, google_requests.Request())
|
|
|
|
|
email = idinfo.get('email')
|
|
|
|
|
if not email:
|
|
|
|
|
return JsonResponse({'error': 'Email not found in Google token'}, status=400)
|
|
|
|
|
|
|
|
|
|
user, created = User.objects.get_or_create(
|
|
|
|
|
email=email,
|
|
|
|
|
defaults={
|
|
|
|
|
'username': email,
|
|
|
|
|
'first_name': idinfo.get('given_name', ''),
|
|
|
|
|
'last_name': idinfo.get('family_name', ''),
|
|
|
|
|
'role': 'customer',
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
if created:
|
|
|
|
|
user.set_password(secrets.token_urlsafe(32))
|
|
|
|
|
user.save()
|
|
|
|
|
log("info", "Google OAuth new user created", request=request, user=user)
|
|
|
|
|
|
|
|
|
|
auth_token, _ = Token.objects.get_or_create(user=user)
|
|
|
|
|
log("info", "Google OAuth login", request=request, user=user)
|
|
|
|
|
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'message': 'Login successful',
|
|
|
|
|
'token': auth_token.key,
|
|
|
|
|
'eventify_id': user.eventify_id or '',
|
|
|
|
|
'username': user.username,
|
|
|
|
|
'email': user.email,
|
|
|
|
|
'phone_number': user.phone_number or '',
|
|
|
|
|
'first_name': user.first_name,
|
|
|
|
|
'last_name': user.last_name,
|
|
|
|
|
'role': user.role,
|
|
|
|
|
'pincode': user.pincode or '',
|
|
|
|
|
'district': user.district or '',
|
|
|
|
|
'district_changed_at': user.district_changed_at.isoformat() if user.district_changed_at else None,
|
|
|
|
|
'state': user.state or '',
|
|
|
|
|
'country': user.country or '',
|
|
|
|
|
'place': user.place or '',
|
|
|
|
|
'latitude': user.latitude or '',
|
|
|
|
|
'longitude': user.longitude or '',
|
|
|
|
|
'profile_photo': user.profile_picture.url if user.profile_picture else '',
|
|
|
|
|
}, status=200)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
log("warning", "Google OAuth invalid token", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({'error': 'Invalid Google token'}, status=401)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log("error", "Google OAuth exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred.'}, status=500)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@method_decorator(csrf_exempt, name='dispatch')
|
|
|
|
|
class ScheduleCallView(View):
|
|
|
|
|
"""Public endpoint for the 'Schedule a Call' form on the consumer app."""
|
|
|
|
|
|
|
|
|
|
def post(self, request):
|
|
|
|
|
from admin_api.models import Lead
|
|
|
|
|
try:
|
|
|
|
|
data = json.loads(request.body)
|
|
|
|
|
name = (data.get('name') or '').strip()
|
|
|
|
|
email = (data.get('email') or '').strip()
|
|
|
|
|
phone = (data.get('phone') or '').strip()
|
|
|
|
|
event_type = (data.get('eventType') or '').strip()
|
|
|
|
|
message = (data.get('message') or '').strip()
|
|
|
|
|
|
|
|
|
|
errors = {}
|
|
|
|
|
if not name:
|
|
|
|
|
errors['name'] = ['This field is required.']
|
|
|
|
|
if not email:
|
|
|
|
|
errors['email'] = ['This field is required.']
|
|
|
|
|
if not phone:
|
|
|
|
|
errors['phone'] = ['This field is required.']
|
|
|
|
|
valid_event_types = [c[0] for c in Lead.EVENT_TYPE_CHOICES]
|
|
|
|
|
if not event_type or event_type not in valid_event_types:
|
|
|
|
|
errors['eventType'] = [f'Must be one of: {", ".join(valid_event_types)}']
|
|
|
|
|
|
|
|
|
|
if errors:
|
|
|
|
|
return JsonResponse({'errors': errors}, status=400)
|
|
|
|
|
|
2026-04-07 11:52:41 +05:30
|
|
|
# Auto-link to a consumer account if one exists with this email
|
|
|
|
|
from django.contrib.auth import get_user_model
|
|
|
|
|
_User = get_user_model()
|
|
|
|
|
try:
|
|
|
|
|
consumer_account = _User.objects.get(email=email)
|
|
|
|
|
except _User.DoesNotExist:
|
|
|
|
|
consumer_account = None
|
|
|
|
|
|
2026-04-07 10:48:04 +05:30
|
|
|
lead = Lead.objects.create(
|
|
|
|
|
name=name,
|
|
|
|
|
email=email,
|
|
|
|
|
phone=phone,
|
|
|
|
|
event_type=event_type,
|
|
|
|
|
message=message,
|
|
|
|
|
status='new',
|
|
|
|
|
source='schedule_call',
|
|
|
|
|
priority='medium',
|
2026-04-07 11:52:41 +05:30
|
|
|
user_account=consumer_account,
|
2026-04-07 10:48:04 +05:30
|
|
|
)
|
|
|
|
|
log("info", f"New schedule-call lead #{lead.pk} from {email}", request=request)
|
|
|
|
|
return JsonResponse({
|
|
|
|
|
'status': 'success',
|
|
|
|
|
'message': 'Your request has been submitted. Our team will get back to you soon.',
|
|
|
|
|
'lead_id': lead.pk,
|
|
|
|
|
}, status=201)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
return JsonResponse({'error': 'Invalid JSON body.'}, status=400)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log("error", "Schedule call exception", request=request, logger_data={"error": str(e)})
|
|
|
|
|
return JsonResponse({'error': 'An unexpected server error occurred.'}, status=500)
|