Skip to content

Commit

Permalink
Improve First-time Login Flow + Add Profiles Manually (#3)
Browse files Browse the repository at this point in the history
* Refactor /login and /authorize/check endpoints to return all user info

* Add /profile endpoint for parsing and saving profiles

The /profile endpoint recieves a URL from the frontend that is parsed with regex to extract the fragment containing profile info. That fragment is then inserted into an API URL, where the full profile info in JSON format is fetched. All the relevant profile info is then consolidated in a dictionary and saved to the users BattlenetAccount model. If data for the profile already exists the dictionaries are updated or merged.
  • Loading branch information
ZephyrBlu authored Jan 21, 2020
1 parent 8bed75d commit f0c6ac9
Show file tree
Hide file tree
Showing 12 changed files with 432 additions and 212 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions apps/api/authentication.py
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions apps/api/permissions.py
Original file line number Diff line number Diff line change
@@ -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
14 changes: 9 additions & 5 deletions apps/api/urls.py
Original file line number Diff line number Diff line change
@@ -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({
Expand All @@ -27,13 +29,15 @@
urlpatterns = [
path('replays/all/', BattlenetAccountReplays.as_view(), name='replay_list'),
path('replays/<str:race>/', user_replays, name='race_replays'),
path('replays/timeline/<str:file_hash>/', GetReplayTimeline.as_view(), name='replay_timeline'),
path('replays/timeline/<str:file_hash>/', 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'),
path('stats/<str:race>/', user_stats, name='race_stats'),
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'),
]
104 changes: 104 additions & 0 deletions apps/api/utils/filter_user_replays.py
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions apps/api/utils/find_main_race.py
Original file line number Diff line number Diff line change
@@ -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]
31 changes: 31 additions & 0 deletions apps/api/utils/get_user_info.py
Original file line number Diff line number Diff line change
@@ -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,
}
}
29 changes: 29 additions & 0 deletions apps/api/utils/parse_profile.py
Original file line number Diff line number Diff line change
@@ -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,
}
}
22 changes: 22 additions & 0 deletions apps/api/utils/scrape_profile_url.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion apps/api/utils/trends.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading

0 comments on commit f0c6ac9

Please sign in to comment.