diff --git a/rowers/models.py b/rowers/models.py index 2a317423..b676f4a7 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -849,6 +849,7 @@ class Rower(models.Model): max_length=30, choices=stravatypes, verbose_name="Export Workouts to Strava as") + strava_owner_id = models.BigIntegerField(default=0) strava_auto_export = models.BooleanField(default=False) strava_auto_import = models.BooleanField(default=False) diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index f0cbf6e2..e46649d5 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -33,7 +33,8 @@ from rowers.tasks import handle_strava_sync from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, - STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET + STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, + SITE_URL ) try: @@ -43,6 +44,9 @@ except ImportError: from rowers.imports import * +webhookverification = "kudos_to_rowing" +webhooklink = SITE_URL+'/rowers/strava/webhooks/' + headers = {'Accept': 'application/json', 'Api-Key': STRAVA_CLIENT_ID, 'Content-Type': 'application/json', @@ -71,6 +75,8 @@ def get_token(code): return imports_get_token(code, oauth_data) def strava_open(user): + if user.rower.strava_owner_id == 0: + strava_owner_id = set_strava_athlete_id(user) return imports_open(user, oauth_data) def do_refresh_token(refreshtoken): @@ -95,6 +101,48 @@ def rower_strava_token_refresh(user): def make_authorization_url(request): return imports_make_authorization_url(oauth_data) +def strava_establish_push(): + url = "https://www.strava.com/api/v3/push_subscriptions" + post_data = { + 'client_id': STRAVA_CLIENT_ID, + 'client_secret': STRAVA_CLIENT_SECRET, + 'callback_url': webhooklink, + 'verify_token': webhookverification, + } + headers = {'user-agent': 'sanderroosendaal', + 'Accept': 'application/json', + 'Content-Type': oauth_data['content_type']} + + response = requests.post(url,data=post_data) + + print(response.json()) + + return response.status_code + + +def set_strava_athlete_id(user): + r = Rower.objects.get(user=user) + if (r.stravatoken == '') or (r.stravatoken is None): + s = "Token doesn't exist. Need to authorize" + return custom_exception_handler(401,s) + elif (r.stravatokenexpirydate is None or timezone.now()+timedelta(seconds=3599)>r.stravatokenexpirydate): + s = "Token expired. Needs to refresh." + return custom_exception_handler(401,s) + else: + authorizationstring = str('Bearer ' + r.stravatoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + url = "https://www.strava.com/api/v3/athlete" + + response = requests.get(url,headers=headers,params={}) + + r.strava_owner_id = response.json()['id'] + r.save() + + return response.json()['id'] + + # Get list of workouts available on Strava def get_strava_workout_list(user,limit_n=0): r = Rower.objects.get(user=user) diff --git a/rowers/tests/mocks.py b/rowers/tests/mocks.py index 5fb400a5..08e90110 100644 --- a/rowers/tests/mocks.py +++ b/rowers/tests/mocks.py @@ -606,6 +606,8 @@ def mocked_requests(*args, **kwargs): uastrokesjson = json.load(open('rowers/tests/testdata/uastrokes.txt','r')) uauserjson = json.load(open('rowers/tests/testdata/uauser.txt','r')) + stravaathletejson = json.load(open('rowers/tests/testdata/strava_athlete.txt')) + class MockResponse: def __init__(self, json_data, status_code): self.json_data = json_data @@ -660,6 +662,10 @@ def mocked_requests(*args, **kwargs): c2workoutlistregex = '.*?concept2\.com\/api\/users\/me\/results\?page=\d' c2workoutlisttester = re.compile(c2workoutlistregex) + stravaathleteregex = '.*?strava\.com\/api\/v3\/athlete$' + stravaathletetester = re.compile(stravaathleteregex) + + stravaworkoutlistregex = '.*?strava\.com\/api\/v3\/athlete\/activities' stravaworkoutlisttester = re.compile(stravaworkoutlistregex) @@ -703,6 +709,10 @@ def mocked_requests(*args, **kwargs): tpuploadregex = '.*?trainingpeaks\.com\/v1\/file' tpuploadtester = re.compile(tpuploadregex) + if stravaathletetester.match(args[0]): + json_data = stravaathletejson + return MockResponse(json_data,200) + if polartester.match(args[0]): json_data = polar_json return MockResponse(json_data,200) diff --git a/rowers/tests/testdata/strava_athlete.txt b/rowers/tests/testdata/strava_athlete.txt new file mode 100644 index 00000000..0108efef --- /dev/null +++ b/rowers/tests/testdata/strava_athlete.txt @@ -0,0 +1 @@ +{"id": 47155909, "username": null, "resource_state": 2, "firstname": "Rowsandall", "lastname": "Testing", "city": "Zliv", "state": "Jiho\u010desk\u00fd kraj", "country": "Czechia", "sex": "M", "premium": false, "summit": false, "created_at": "2019-10-06T06:59:54Z", "updated_at": "2020-05-15T11:52:33Z", "badge_type_id": 0, "profile_medium": "avatar/athlete/medium.png", "profile": "avatar/athlete/large.png", "friend": null, "follower": null} \ No newline at end of file diff --git a/rowers/urls.py b/rowers/urls.py index 6e74b0df..5e53e52f 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -417,6 +417,7 @@ urlpatterns = [ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/delete/$',login_required( views.WorkoutDelete.as_view()), name='workout_delete'), + re_path(r'^strava/webhooks/',views.strava_webhook_view,name='strava_webhook_view'), re_path(r'^garmin/summaries/',views.garmin_summaries_view,name='garmin_summaries_view'), re_path(r'^garmin/files/',views.garmin_newfiles_ping,name='garmin_newfiles_ping'), re_path(r'^garmin/activities/',views.garmin_details_view,name='garmin_details_view'), diff --git a/rowers/views/importviews.py b/rowers/views/importviews.py index d1981dc9..a0149a41 100644 --- a/rowers/views/importviews.py +++ b/rowers/views/importviews.py @@ -1008,6 +1008,19 @@ def workout_stravaimport_view(request,message="",userid=0): return HttpResponse(res) +# for Strava webhook request validation +def strava_webhook_view(request): + if request.method == 'GET': + challenge = request.GET.get('hub.challenge') + verificationtoken = request.GET.get('hub.verify_token') + if verificationtoken != stravastuff.webhookverification: + return HttpResponse(status=403) + data = {"hub.challenge":challenge} + return JSONResponse(data) + + # POST - does nothing so far + return HttpResponse(status=200) + # For push notifications from Garmin @csrf_exempt def garmin_summaries_view(request):