diff --git a/.gitignore b/.gitignore index 4a89f31..8d7da37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,10 @@ env_settings/ apps/api/keys/ -apps/user_profile/secret.py +apps/user_profile/secret/ apps/process_replays/utils/replay_parser_production.py media/ node_modules/ +.vscode/ *.pyc .idea/ Zephyrus-f579da64eb8e.json diff --git a/apps/api/authentication.py b/apps/api/authentication.py new file mode 100644 index 0000000..bedb203 --- /dev/null +++ b/apps/api/authentication.py @@ -0,0 +1,14 @@ +from rest_framework.authentication import BaseAuthentication +from rest_framework.exceptions import AuthenticationFailed + + +class IsOptionsAuthentication(BaseAuthentication): + """ + Allows preflight requests to complete + Used for CORS requests in development + """ + def authenticate(self, request): + if request.method == 'OPTIONS': + return None + else: + raise AuthenticationFailed diff --git a/apps/api/permissions.py b/apps/api/permissions.py new file mode 100644 index 0000000..aeccbcb --- /dev/null +++ b/apps/api/permissions.py @@ -0,0 +1,25 @@ +from rest_framework.permissions import BasePermission + + +class IsOptionsPermission(BasePermission): + """ + Allows preflight requests to complete + Used for CORS requests in development + """ + def has_permission(self, request, view): + if request.method == 'OPTIONS': + return True + else: + return False + + +class IsPostPermission(BasePermission): + """ + Allows preflight requests to complete + Used for CORS requests in development + """ + def has_permission(self, request, view): + if request.method == 'POST': + return True + else: + return False diff --git a/apps/api/urls.py b/apps/api/urls.py index d9185cf..5495953 100644 --- a/apps/api/urls.py +++ b/apps/api/urls.py @@ -1,16 +1,18 @@ -from django.urls import path, include +from django.urls import path from .views import ( ExternalLogout, ExternalLogin, RaceReplayViewSet, BattlenetAccountReplays, - GetReplayTimeline, + FetchReplayTimeline, RaceStatsViewSet, Stats, UploadReplays, BattlenetAuthorizationUrl, SetBattlenetAccount, - CheckBattlenetAccount, + CheckUserInfo, + AddUserProfile, + ResendEmail, ) user_replays = RaceReplayViewSet.as_view({ @@ -27,7 +29,7 @@ urlpatterns = [ path('replays/all/', BattlenetAccountReplays.as_view(), name='replay_list'), path('replays//', user_replays, name='race_replays'), - path('replays/timeline//', GetReplayTimeline.as_view(), name='replay_timeline'), + path('replays/timeline//', FetchReplayTimeline.as_view(), name='replay_timeline'), path('login/', ExternalLogin.as_view(), name='external_login'), path('logout/', ExternalLogout.as_view(), name='external_logout'), path('stats/', Stats.as_view(), name='user_stats'), @@ -35,5 +37,7 @@ path('upload/', UploadReplays.as_view(), name='replay_upload'), path('authorize/url/', BattlenetAuthorizationUrl.as_view(), name='battlenet_authorization_url'), path('authorize/code/', SetBattlenetAccount.as_view(), name='set_battlenet_account'), - path('authorize/check/', CheckBattlenetAccount.as_view(), name='ping_battlenet_account') + path('authorize/check/', CheckUserInfo.as_view(), name='ping_battlenet_account'), + path('profile/', AddUserProfile.as_view(), name='add_user_profile'), + path('resend/', ResendEmail.as_view(), name='resend_email'), ] diff --git a/apps/api/utils/filter_user_replays.py b/apps/api/utils/filter_user_replays.py new file mode 100644 index 0000000..c11e8b4 --- /dev/null +++ b/apps/api/utils/filter_user_replays.py @@ -0,0 +1,104 @@ +import copy +import datetime +from math import floor +from django.http import HttpResponseNotFound +from zephyrus.settings import FRONTEND_URL +from allauth.account.models import EmailAddress +from apps.user_profile.models import Replay, BattlenetAccount +from ..models import ReplaySerializer + + +def filter_user_replays(request, race=None): + user = request.user + user_id = EmailAddress.objects.get(email=user.email) + + # check that user has a battlenet account linked + if BattlenetAccount.objects.filter(user_account_id=user_id).exists(): + battlenet_account = BattlenetAccount.objects.get( + user_account_id=user_id + ) + # if not return 404 response + else: + response = HttpResponseNotFound() + response['Access-Control-Allow-Origin'] = FRONTEND_URL + response['Access-Control-Allow-Headers'] = 'authorization' + return response + + replay_queryset = Replay.objects.filter( + battlenet_account_id=battlenet_account + ) + + serialized_replays = [] + replay_queryset = list(replay_queryset) + replay_queryset.sort(key=lambda x: x.played_at, reverse=True) + + # if there was a race param in the endpoint URL, + # filter out irrelevant replays + if race: + races = ['protoss', 'terran', 'zerg'] + if race not in races: + return None + + # use .filter() method? + race_replay_queryset = [] + for replay in replay_queryset: + player_id = str(replay.user_match_id) + if race == replay.players[player_id]['race'].lower(): + race_replay_queryset.append(replay) + replay_queryset = race_replay_queryset + + # limit returned replays to 100 for performance reasons + limited_queryset = replay_queryset[:100] + + # calculate how long ago since each match was played + # replace with native HTML? + for replay in limited_queryset: + date = replay.played_at + days = datetime.timedelta( + seconds=datetime.datetime.timestamp(datetime.datetime.now()) - datetime.datetime.timestamp(date) + ).days + + if days != -1: + weeks = int(round(days / 7, 0)) + months = int(floor(weeks / 4)) + else: + weeks = 0 + months = 0 + + if months > 0: + if weeks - (months * 4) == 0: + date_diff = f'{months}m' + else: + date_diff = f'{months}m' # *{weeks - (months * 4)}/4*' + elif weeks > 0: + date_diff = f'{weeks}w' + else: + if days == -1: + date_diff = 'Today' + elif days == 1: + date_diff = 'Yesterday' + else: + date_diff = f'{days}d' + + serializer = ReplaySerializer(replay) + serializer = copy.deepcopy(serializer.data) + serializer['played_at'] = date_diff + + new_serializer = {} + for stat, info in serializer.items(): + if stat == 'match_data': + new_serializer['match_data'] = {} + + # replace nested dict structure with flat one + # only resource stats are nested + for name, values in info.items(): + if type(values) is dict and 'minerals' in values: + for resource, value in values.items(): + new_serializer[stat][f'{name}_{resource}'] = value + else: + new_serializer[stat][name] = values + else: + new_serializer[stat] = info + serializer = new_serializer + serialized_replays.append(serializer) + return serialized_replays diff --git a/apps/api/utils/find_main_race.py b/apps/api/utils/find_main_race.py new file mode 100644 index 0000000..3b8baae --- /dev/null +++ b/apps/api/utils/find_main_race.py @@ -0,0 +1,14 @@ +from apps.user_profile.models import Replay + + +def find_main_race(user): + account_replays = Replay.objects.filter(user_account_id=user.email).exclude(battlenet_account_id__isnull=True) + race_count = {'protoss': 0, 'terran': 0, 'zerg': 0} + + for replay in account_replays: + user_race = replay.players[str(replay.user_match_id)]['race'] + race_count[user_race.lower()] += 1 + counts = list(race_count.values()) + races = list(race_count.keys()) + max_count_index = counts.index(max(counts)) + return races[max_count_index] diff --git a/apps/api/utils/get_user_info.py b/apps/api/utils/get_user_info.py new file mode 100644 index 0000000..9b08f24 --- /dev/null +++ b/apps/api/utils/get_user_info.py @@ -0,0 +1,31 @@ +from rest_framework.authtoken.models import Token +from allauth.account.models import EmailAddress +from apps.user_profile.models import BattlenetAccount +from .find_main_race import find_main_race + + +def get_user_info(user): + user_account = EmailAddress.objects.get(email=user.email) + user_battlenet = BattlenetAccount.objects.filter(user_account_id=user.email) + if user_battlenet: + battlenet_accounts = [] + for account in user_battlenet: + current_account = { + 'battletag': account.battletag, + 'profiles': account.region_profiles, + } + battlenet_accounts.append(current_account) + else: + battlenet_accounts = None + token = Token.objects.get(user=user) + main_race = find_main_race(user) + + return { + 'user': { + 'email': user.email, + 'verified': user_account.verified, + 'battlenet_accounts': battlenet_accounts, + 'token': token.key, + 'main_race': main_race, + } + } diff --git a/apps/api/utils/parse_profile.py b/apps/api/utils/parse_profile.py new file mode 100644 index 0000000..217bf87 --- /dev/null +++ b/apps/api/utils/parse_profile.py @@ -0,0 +1,29 @@ +import re +import requests + + +def parse_profile(url): + url_data = re.search('(?!/profile/)\d/\d/\d*', url) + + if not url_data: + return None + + region_id = int(url_data.group().split('/')[0]) + + data_url = f'https://starcraft2.com/en-us/api/sc2/profile/{url_data.group()}?locale=en_US' + profile_data = requests.get(data_url).json() + + realm_id = int(profile_data['summary']['realm']) + profile_id = int(profile_data['summary']['id']) + profile_name = profile_data['summary']['displayName'] + + regions = {1: 'NA', 2: 'EU', 3: 'KR'} + + return { + str(region_id): { + 'realm_id': realm_id, + 'profile_id': [profile_id], + 'region_name': regions[region_id], + 'profile_name': profile_name, + } + } diff --git a/apps/api/utils/scrape_profile_url.py b/apps/api/utils/scrape_profile_url.py new file mode 100644 index 0000000..3c87a10 --- /dev/null +++ b/apps/api/utils/scrape_profile_url.py @@ -0,0 +1,22 @@ +# import beatifulsoup scraping lib + + +def scrape_profile_url(url): + # check url, if invalid return None + # if valid, scrape data and return + + isUrlValid = True + + if not isUrlValid: + return None + + # scrape profile + + profile_info = { + 'profile_name': '', + 'profile_id': None, + 'region_id': 1, + 'realm_id': 1, + } + + return profile_info diff --git a/apps/api/utils/trends.py b/apps/api/utils/trends.py index 4db96a3..40bbe05 100644 --- a/apps/api/utils/trends.py +++ b/apps/api/utils/trends.py @@ -4,7 +4,7 @@ import datetime -def main(account_replays, battlenet_id_list, race=None): +def trends(account_replays, battlenet_id_list, race=None): account_replays = list(account_replays) account_replays.sort(key=lambda r: r.played_at, reverse=True) diff --git a/apps/api/views.py b/apps/api/views.py index a38a455..bcac460 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -1,84 +1,51 @@ -from django.contrib.auth import authenticate, login, logout -from django.http import HttpResponse, HttpResponseNotFound, HttpResponseBadRequest, HttpResponseServerError -from rest_framework.views import APIView -from rest_framework import viewsets -from rest_framework.decorators import action, api_view -from rest_framework.response import Response -from rest_framework.authentication import TokenAuthentication, BaseAuthentication -from rest_framework.exceptions import AuthenticationFailed -from rest_framework.permissions import BasePermission, IsAuthenticated -from rest_framework.authtoken.models import Token -from apps.user_profile.models import Replay, BattlenetAccount -from allauth.account.models import EmailAddress -from allauth.account.utils import send_email_confirmation -from .models import ReplaySerializer -from django.core.files import File -from apps.process_replays.views import process_file -from apps.user_profile.secret import CLIENT_ID, CLIENT_SECRET import requests -from .utils.trends import main as analyze_trends import io import hashlib import json -import copy -import datetime -from math import floor -from zephyrus.settings import TIMELINE_STORAGE, API_KEY, FRONTEND_URL import logging -logger = logging.getLogger(__name__) - - -class IsOptionsAuthentication(BaseAuthentication): - """ - Allows preflight requests to complete - Used for CORS requests in development - """ - def authenticate(self, request): - if request.method == 'OPTIONS': - return None - else: - raise AuthenticationFailed +from django.contrib.auth import authenticate, login, logout +from django.core.files import File +from django.http import ( + HttpResponseNotFound, + HttpResponseBadRequest, + HttpResponseServerError, +) +from rest_framework.views import APIView +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.response import Response +from rest_framework.authentication import TokenAuthentication +from rest_framework.permissions import IsAuthenticated -class IsOptionsPermission(BasePermission): - """ - Allows preflight requests to complete - Used for CORS requests in development - """ - def has_permission(self, request, view): - if request.method == 'OPTIONS': - return True - else: - return False +from allauth.account.models import EmailAddress +from allauth.account.utils import send_email_confirmation +from zephyrus.settings import TIMELINE_STORAGE, API_KEY, FRONTEND_URL +from apps.user_profile.models import Replay, BattlenetAccount +from apps.process_replays.views import process_file +from apps.user_profile.secret.master import CLIENT_ID, CLIENT_SECRET -class IsPostPermission(BasePermission): - """ - Allows preflight requests to complete - Used for CORS requests in development - """ - def has_permission(self, request, view): - if request.method == 'POST': - return True - else: - return False +from .utils.trends import trends as analyze_trends +from .utils.filter_user_replays import filter_user_replays +from .utils.get_user_info import get_user_info +from .utils.parse_profile import parse_profile +from .permissions import IsOptionsPermission, IsPostPermission +from .authentication import IsOptionsAuthentication +logger = logging.getLogger(__name__) -def find_main_race(user): - account_replays = Replay.objects.filter(user_account_id=user.email).exclude(battlenet_account_id__isnull=True) - race_count = {'protoss': 0, 'terran': 0, 'zerg': 0} - for replay in account_replays: - user_race = replay.players[str(replay.user_match_id)]['race'] - race_count[user_race.lower()] += 1 - counts = list(race_count.values()) - races = list(race_count.keys()) - max_count_index = counts.index(max(counts)) - return races[max_count_index] +class ExternalLogin(APIView): + """ + Handles user logins from the /login API endpoint + If a user does not have a verified email, a verification + email is sent. -class ExternalLogin(APIView): + The user's account information, Token and main race are sent in the response + """ authentication_classes = [] permission_classes = [IsOptionsPermission | IsPostPermission] @@ -96,14 +63,12 @@ def post(self, request): if user is not None: login(request, user) - send_email_confirmation(request, user) - token = Token.objects.get(user=user) - main_race = find_main_race(user) - - response = Response({ - 'token': token.key, - 'main_race': main_race, - }) + user_info = get_user_info(user) + + if not user_info['user']['verified']: + send_email_confirmation(request, user) + + response = Response(user_info) response['Access-Control-Allow-Origin'] = FRONTEND_URL response['Access-Control-Allow-Headers'] = 'cache-control, content-type' return response @@ -115,6 +80,9 @@ def post(self, request): class ExternalLogout(APIView): + """ + Handles user logouts from the /logout API endpoint + """ authentication_classes = [TokenAuthentication, IsOptionsAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -132,105 +100,10 @@ def get(self, request): return response -# returns particular replay based on pk ID in database -class AccountReplays(APIView): - authentication_classes = [TokenAuthentication] - permission_classes = [IsAuthenticated | IsOptionsPermission] - - def get(self, request): - user = request.user - user_id = EmailAddress.objects.get(email=user.email) - - replay_queryset = Replay.objects.filter(user_account=user_id) - serialized_replays = [] - for replay in list(replay_queryset): - serializer = ReplaySerializer(replay) - serialized_replays.append(serializer.data) - - return HttpResponse(serialized_replays) - - -def filter_user_replays(request, race=None): - user = request.user - user_id = EmailAddress.objects.get(email=user.email) - - if BattlenetAccount.objects.filter(user_account_id=user_id).exists(): - battlenet_account = BattlenetAccount.objects.get(user_account_id=user_id) - else: - response = HttpResponseNotFound() - response['Access-Control-Allow-Origin'] = FRONTEND_URL - response['Access-Control-Allow-Headers'] = 'authorization' - return response - - replay_queryset = Replay.objects.filter(battlenet_account_id=battlenet_account) - - serialized_replays = [] - replay_queryset = list(replay_queryset) - replay_queryset.sort(key=lambda x: x.played_at, reverse=True) - - if race: - races = ['protoss', 'terran', 'zerg'] - if race not in races: - return None - - race_replay_queryset = [] - for replay in replay_queryset: - player_id = str(replay.user_match_id) - if race == replay.players[player_id]['race'].lower(): - race_replay_queryset.append(replay) - replay_queryset = race_replay_queryset - - limited_queryset = replay_queryset[:100] - - for replay in limited_queryset: - date = replay.played_at - days = datetime.timedelta( - seconds=datetime.datetime.timestamp(datetime.datetime.now()) - datetime.datetime.timestamp(date) - ).days - if days != -1: - weeks = int(round(days / 7, 0)) - months = int(floor(weeks / 4)) - else: - weeks = 0 - months = 0 - - if months > 0: - if weeks - (months * 4) == 0: - date_diff = f'{months}m' - else: - date_diff = f'{months}m' # *{weeks - (months * 4)}/4*' - elif weeks > 0: - date_diff = f'{weeks}w' - else: - if days == -1: - date_diff = 'Today' - elif days == 1: - date_diff = 'Yesterday' - else: - date_diff = f'{days}d' - - serializer = ReplaySerializer(replay) - serializer = copy.deepcopy(serializer.data) - serializer['played_at'] = date_diff - - new_serializer = {} - for stat, info in serializer.items(): - if stat == 'match_data': - new_serializer['match_data'] = {} - for name, values in info.items(): - if type(values) is dict and 'minerals' in values: - for resource, value in values.items(): - new_serializer[stat][f'{name}_{resource}'] = value - else: - new_serializer[stat][name] = values - else: - new_serializer[stat] = info - serializer = new_serializer - serialized_replays.append(serializer) - return serialized_replays - - class RaceReplayViewSet(viewsets.ModelViewSet): + """ + Returns all user replays played with the given race param + """ authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -258,8 +131,10 @@ def retrieve(self, request, race=None): return response -# returns particular replay based on pk ID in database class BattlenetAccountReplays(APIView): + """ + Returns all the replays associated with a user's battlenet account + """ authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -283,7 +158,12 @@ def get(self, request): return response -class GetReplayTimeline(APIView): +class FetchReplayTimeline(APIView): + """ + Fetches the replay timeline related to the file hash param + + Returns the download URL for the timeline file + """ authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -306,6 +186,9 @@ def get(self, request, file_hash): class RaceStatsViewSet(viewsets.ModelViewSet): + """ + Returns the user's stats for the given race param + """ authentication_classes = [TokenAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -377,14 +260,18 @@ def get(self, request): user_id = EmailAddress.objects.get(email=user.email) if BattlenetAccount.objects.filter(user_account_id=user_id).exists(): - battlenet_account = BattlenetAccount.objects.get(user_account_id=user_id) + battlenet_account = BattlenetAccount.objects.get( + user_account_id=user_id + ) else: response = HttpResponseNotFound() response['Access-Control-Allow-Origin'] = FRONTEND_URL response['Access-Control-Allow-Headers'] = 'authorization' return response - account_replays = Replay.objects.filter(battlenet_account=battlenet_account) + account_replays = Replay.objects.filter( + battlenet_account=battlenet_account + ) if len(account_replays) <= 0: response = HttpResponseNotFound() @@ -405,6 +292,16 @@ def get(self, request): class UploadReplays(APIView): + """ + Handles replay uploads from the /upload API endpoint + + Creates a File wrapper around uploaded replay file data, + then hashes the file to create a unique ID. + + Then the replay file is processed and stored in a bucket, or if + processing is unsuccessful an error is returned and the replay is + stored in an alternate bucket for failed replays. + """ authentication_classes = [TokenAuthentication, IsOptionsAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -417,6 +314,11 @@ def options(self, request): def post(self, request): file_data = request.body + def sha256sum(f): + h = hashlib.sha256() + h.update(f) + return h.hexdigest() + file_hash = sha256sum(file_data) replay_file = File(io.BufferedReader(io.BytesIO(file_data))) @@ -442,16 +344,15 @@ def post(self, request): return response -def sha256sum(f): - h = hashlib.sha256() - h.update(f) - return h.hexdigest() - - oauth_api_url = 'https://us.battle.net' class BattlenetAuthorizationUrl(APIView): + """ + Handles initial OAuth flow for a user linking their battlenet account + + Returns a redirect URL to begin the OAuth process + """ authentication_classes = [TokenAuthentication, IsOptionsAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -477,6 +378,13 @@ def get(self, request): class SetBattlenetAccount(APIView): + """ + Handles OAuth auth code --> token --> user info flow + + Gathers extra user profile information to automatically match replays + NOTE: This functionality is currently broken due to Blizzard disabling + the SC2 Community APIs + """ authentication_classes = [TokenAuthentication, IsOptionsAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -503,34 +411,35 @@ def post(self, request): user_info_url = f'{oauth_api_url}/oauth/userinfo' headers = {'Authorization': f'Bearer {access_token}'} json_info = requests.get(user_info_url, headers=headers) + user_info = json_info.json() current_account = EmailAddress.objects.get(email=request.user.email) user_id = user_info['id'] - profile_data_url = f'https://us.api.blizzard.com/sc2/player/{user_id}?access_token={access_token}' - profile_data = requests.get(profile_data_url) - profile_data = profile_data.json() - - regions = {1: 'NA', 2: 'EU', 3: 'KR'} - profiles = {} - - for profile in profile_data: - if int(profile['regionId']) in profiles: - profiles[int(profile['regionId'])]['profile_id'].append(int(profile['profileId'])) - else: - profiles[int(profile['regionId'])] = { - 'profile_name': profile['name'], - 'region_name': regions[profile['regionId']], - 'profile_id': [int(profile['profileId'])], - 'realm_id': int(profile['realmId']) - } + # profile_data_url = f'https://us.api.blizzard.com/sc2/player/{user_id}?access_token={access_token}' + # profile_data = requests.get(profile_data_url) + # profile_data = profile_data.json() + + # regions = {1: 'NA', 2: 'EU', 3: 'KR'} + # profiles = {} + + # for profile in profile_data: + # if int(profile['regionId']) in profiles: + # profiles[int(profile['regionId'])]['profile_id'].append(int(profile['profileId'])) + # else: + # profiles[int(profile['regionId'])] = { + # 'profile_name': profile['name'], + # 'region_name': regions[profile['regionId']], + # 'profile_id': [int(profile['profileId'])], + # 'realm_id': int(profile['realmId']) + # } authorized_account = BattlenetAccount( id=user_id, battletag=user_info['battletag'], user_account=current_account, - region_profiles=profiles + region_profiles={} ) authorized_account.save() @@ -540,7 +449,10 @@ def post(self, request): return response -class CheckBattlenetAccount(APIView): +class CheckUserInfo(APIView): + """ + Handles checking if a user has at least 1 battlenet account linked + """ authentication_classes = [TokenAuthentication, IsOptionsAuthentication] permission_classes = [IsAuthenticated | IsOptionsPermission] @@ -553,14 +465,78 @@ def options(self, request): def get(self, request): user = request.user - battlenet_accounts = BattlenetAccount.objects.filter(user_account=user.email) + # send_email_confirmation(request, user) + + user_info = get_user_info(user) + + response = Response(user_info) + response['Access-Control-Allow-Origin'] = FRONTEND_URL + response['Access-Control-Allow-Headers'] = 'authorization' + return response + + +class AddUserProfile(APIView): + """ + Handles validating, parsing and adding game profiles + """ + authentication_classes = [TokenAuthentication, IsOptionsAuthentication] + permission_classes = [IsAuthenticated | IsOptionsPermission] + + def options(self, request): + response = Response() + response['Access-Control-Allow-Origin'] = FRONTEND_URL + response['Access-Control-Allow-Headers'] = 'authorization' + return response + + def post(self, request): + user = request.user + + user_profile_url = request.body.decode('utf-8') + profile_data = parse_profile(user_profile_url) + + if profile_data: + # save data to user model + + user_battlenet_account = BattlenetAccount.objects.get( + user_account_id=user.email, + ) + + region_id = list(profile_data.keys())[0] + new_profile_id = profile_data[region_id]['profile_id'][0] + + if region_id in user_battlenet_account.region_profiles: + current_profile_id = user_battlenet_account.region_profiles[region_id]['profile_id'] + if new_profile_id not in current_profile_id: + current_profile_id.append(new_profile_id) + else: + user_battlenet_account.region_profiles.update(profile_data) + user_battlenet_account.save() - if len(battlenet_accounts) > 0: response = Response() response['Access-Control-Allow-Origin'] = FRONTEND_URL response['Access-Control-Allow-Headers'] = 'authorization' else: - response = HttpResponseNotFound() + response = HttpResponseBadRequest() response['Access-Control-Allow-Origin'] = FRONTEND_URL response['Access-Control-Allow-Headers'] = 'authorization' return response + + +class ResendEmail(APIView): + authentication_classes = [TokenAuthentication, IsOptionsAuthentication] + permission_classes = [IsAuthenticated | IsOptionsPermission] + + def options(self, request): + response = Response() + response['Access-Control-Allow-Origin'] = FRONTEND_URL + response['Access-Control-Allow-Headers'] = 'authorization' + return response + + def get(self, request): + user = request.user + send_email_confirmation(request, user) + + response = Response() + response['Access-Control-Allow-Origin'] = FRONTEND_URL + response['Access-Control-Allow-Headers'] = 'authorization' + return response diff --git a/zephyrus/settings.py b/zephyrus/settings.py index 94c7981..da0ed12 100644 --- a/zephyrus/settings.py +++ b/zephyrus/settings.py @@ -197,7 +197,6 @@ ACCOUNT_ADAPTER = 'zephyrus.users.adapter.CustomAccountAdapter' ACCOUNT_USER_MODEL_USERNAME_FIELD = None ACCOUNT_EMAIL_REQUIRED = True -ACCOUNT_EMAIL_VERIFICATION = True ACCOUNT_CONFIRM_EMAIL_ON_GET = True ACCOUNT_USERNAME_REQUIRED = False ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True @@ -208,6 +207,7 @@ DEFAULT_FROM_EMAIL = 'hello@zephyrus.gg' ACCOUNT_EMAIL_SUBJECT_PREFIX = '[zephyrus.gg] ' ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = FRONTEND_URL +ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = FRONTEND_URL EMAIL_HOST = socket.gethostbyname('smtp.mailgun.com') EMAIL_PORT = 587