Skip to content

Commit 155d18d

Browse files
committed
Strava API integration - Oauth and DB Sync
User authentication, access and refresh tokens retrieval, tokens encryption and storage. Added code to connect to Strava API, and download and insert to DB all activity data for the selected timeframe. Also few bug fixes, and general clean-up
1 parent 08c5cf3 commit 155d18d

15 files changed

+1141
-95
lines changed

src/athletedataapp_apache.py

Lines changed: 115 additions & 23 deletions
Large diffs are not rendered by default.

src/athletedataapp_flask.py

Lines changed: 117 additions & 25 deletions
Large diffs are not rendered by default.

src/database_ini_parser.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,23 @@ def config(filename, section, encr_pass=None):
114114
db[param[0]] = ""
115115
else:
116116
db[param[0]] = param[1]
117+
elif section == 'strava':
118+
params = parser.items(section)
119+
for param in params:
120+
if param[0] == 'strava_client_id':
121+
try:
122+
decrypted_param = decrypt(base64.b64decode(param[1]), encr_pass)
123+
db[param[0]] = str(decrypted_param)
124+
except:
125+
db[param[0]] = ""
126+
elif param[0] == 'strava_client_secret':
127+
try:
128+
decrypted_param = decrypt(base64.b64decode(param[1]), encr_pass)
129+
db[param[0]] = str(decrypted_param)
130+
except:
131+
db[param[0]] = ""
132+
else:
133+
db[param[0]] = param[1]
117134
elif section == 'anticaptcha':
118135
params = parser.items(section)
119136
for param in params:

src/db_auto_sync.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def get_databases_list(encr_pass):
9191
def retrieve_decrypt_creds(synch_req_db_list,encr_pass):
9292
sql_get_creds = """
9393
SELECT
94-
gc_email,gc_password,mfp_username,mfp_password,diasend_username,diasend_password,dropbox_access_token,glimp_export_link,libreview_export_link,mm_export_link,oura_refresh_token,ath_un
94+
gc_email,gc_password,mfp_username,mfp_password,diasend_username,diasend_password,dropbox_access_token,glimp_export_link,libreview_export_link,mm_export_link,oura_refresh_token,strava_refresh_token,ath_un
9595
FROM
9696
athlete;
9797
"""
@@ -129,6 +129,9 @@ def retrieve_decrypt_creds(synch_req_db_list,encr_pass):
129129

130130
oura_encr_token = None
131131
oura_decr_token = None
132+
133+
strava_encr_token = None
134+
strava_decr_token = None
132135

133136
glimp_encr_export_link = None
134137
glimp_decr_export_link = None
@@ -192,7 +195,8 @@ def retrieve_decrypt_creds(synch_req_db_list,encr_pass):
192195
libreview_encr_export_link=result[8]
193196
mm_encr_export_link=result[9]
194197
oura_encr_token=result[10]
195-
ath_un=result[11]
198+
strava_encr_token=result[11]
199+
ath_un=result[12]
196200

197201
#Now decrypt (gc_encr_pw,mfp_encr_pw,dbx_encr_token and others)
198202
if gc_encr_pw is not None:
@@ -211,9 +215,11 @@ def retrieve_decrypt_creds(synch_req_db_list,encr_pass):
211215
mm_decr_export_link = decrypt(base64.b64decode(mm_encr_export_link), encr_pass)
212216
if oura_encr_token is not None:
213217
oura_decr_token = decrypt(base64.b64decode(oura_encr_token), encr_pass)
218+
if strava_encr_token is not None:
219+
strava_decr_token = decrypt(base64.b64decode(strava_encr_token), encr_pass)
214220

215221
###Execute auto synch from "main_data_autosynch.py"###
216-
auto_synch(ath_un, db, db_host, superuser_un, superuser_pw, gc_un, gc_decr_pw, mfp_un, mfp_decr_pw, cgm_un, cgm_decr_pw, glimp_decr_export_link, libreview_decr_export_link, mm_decr_export_link, dbx_decr_token, oura_decr_token, encr_pass)
222+
auto_synch(ath_un, db, db_host, superuser_un, superuser_pw, gc_un, gc_decr_pw, mfp_un, mfp_decr_pw, cgm_un, cgm_decr_pw, glimp_decr_export_link, libreview_decr_export_link, mm_decr_export_link, dbx_decr_token, oura_decr_token, strava_decr_token, encr_pass)
217223

218224
except (Exception, psycopg2.DatabaseError) as error:
219225
with ConsolidatedProgressStdoutRedirection():

src/db_schema.sql

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ CREATE TABLE public.athlete (
4141
libreview_export_link character varying(300),
4242
mm_export_link character varying(300),
4343
oura_refresh_token character varying(300)
44+
strava_refresh_token character varying(300)
4445
);
4546

4647
ALTER TABLE public.athlete OWNER TO postgres;
@@ -966,6 +967,105 @@ ALTER TABLE public.oura_sleep_detail_id_seq OWNER TO postgres;
966967

967968
ALTER SEQUENCE public.oura_sleep_detail_id_seq OWNED BY public.oura_sleep_detail.id;
968969

970+
--
971+
--STRAVA ACTIVITY SUMMARY
972+
---
973+
CREATE TABLE public.strava_activity_summary(
974+
id integer NOT NULL,
975+
athlete_id integer,
976+
strava_athlete_id bigint,
977+
name character varying,
978+
distance real,
979+
moving_time integer,
980+
elapsed_time integer,
981+
total_elevation_gain real,
982+
type character varying,
983+
workout_type integer,
984+
strava_activity_id bigint,
985+
external_id character varying,
986+
upload_id bigint,
987+
start_date character varying,
988+
start_date_local character varying,
989+
timezone character varying,
990+
utc_offset integer,
991+
start_latitude numeric,
992+
start_longitude numeric,
993+
end_latitude numeric,
994+
end_longitude numeric,
995+
location_city character varying,
996+
location_state character varying,
997+
location_country character varying,
998+
map character varying,
999+
summary_polyline character varying,
1000+
trainer boolean,
1001+
commute boolean,
1002+
manual boolean,
1003+
gear_id character varying,
1004+
average_speed real,
1005+
max_speed real,
1006+
average_cadence real,
1007+
average_temp real,
1008+
average_watts real,
1009+
weighted_average_watts real,
1010+
kilojoules real,
1011+
device_watts boolean,
1012+
average_heartrate real,
1013+
max_heartrate real,
1014+
max_watts real,
1015+
elev_high real,
1016+
elev_low real,
1017+
suffer_score real
1018+
);
1019+
1020+
ALTER TABLE public.strava_activity_summary OWNER TO postgres;
1021+
1022+
CREATE SEQUENCE public.strava_activity_summary_id_seq
1023+
AS integer
1024+
START WITH 1
1025+
INCREMENT BY 1
1026+
NO MINVALUE
1027+
NO MAXVALUE
1028+
CACHE 1;
1029+
1030+
ALTER TABLE public.strava_activity_summary_id_seq OWNER TO postgres;
1031+
1032+
ALTER SEQUENCE public.strava_activity_summary_id_seq OWNED BY public.strava_activity_summary.id;
1033+
1034+
--
1035+
--STRAVA ACTIVITY STREAMS
1036+
--
1037+
CREATE TABLE public.strava_activity_streams(
1038+
id integer NOT NULL,
1039+
activity_id integer,
1040+
time_gmt character varying,
1041+
distance real,
1042+
latitude numeric,
1043+
longitude numeric,
1044+
altitude real,
1045+
velocity_smooth real,
1046+
heartrate integer,
1047+
cadence integer,
1048+
watts integer,
1049+
temp integer,
1050+
moving boolean,
1051+
grade_smooth real
1052+
);
1053+
1054+
ALTER TABLE public.strava_activity_streams OWNER TO postgres;
1055+
1056+
CREATE SEQUENCE public.strava_activity_streams_id_seq
1057+
AS integer
1058+
START WITH 1
1059+
INCREMENT BY 1
1060+
NO MINVALUE
1061+
NO MAXVALUE
1062+
CACHE 1;
1063+
1064+
ALTER TABLE public.strava_activity_streams_id_seq OWNER TO postgres;
1065+
1066+
ALTER SEQUENCE public.strava_activity_streams_id_seq OWNED BY public.strava_activity_streams.id;
1067+
1068+
9691069

9701070
--
9711071
--AUTO INCREMENTS
@@ -1018,6 +1118,11 @@ ALTER TABLE ONLY public.oura_activity_detail ALTER COLUMN id SET DEFAULT nextval
10181118

10191119
ALTER TABLE ONLY public.oura_sleep_detail ALTER COLUMN id SET DEFAULT nextval('public.oura_sleep_detail_id_seq'::regclass);
10201120

1121+
ALTER TABLE ONLY public.strava_activity_summary ALTER COLUMN id SET DEFAULT nextval('public.strava_activity_summary_id_seq'::regclass);
1122+
1123+
ALTER TABLE ONLY public.strava_activity_streams ALTER COLUMN id SET DEFAULT nextval('public.strava_activity_streams_id_seq'::regclass);
1124+
1125+
10211126
--
10221127
--PRIMARY KEYS
10231128
---
@@ -1096,6 +1201,11 @@ ALTER TABLE ONLY public.oura_activity_detail
10961201
ALTER TABLE ONLY public.oura_sleep_detail
10971202
ADD CONSTRAINT oura_sleep_detail_pkey PRIMARY KEY (id);
10981203

1204+
ALTER TABLE ONLY public.strava_activity_summary
1205+
ADD CONSTRAINT strava_activity_summary_pkey PRIMARY KEY (id);
1206+
1207+
ALTER TABLE ONLY public.strava_activity_streams
1208+
ADD CONSTRAINT strava_activity_streams_pkey PRIMARY KEY (id);
10991209

11001210
--
11011211
--UNIQUES
@@ -1163,6 +1273,11 @@ ALTER TABLE ONLY public.oura_activity_detail
11631273
ALTER TABLE ONLY public.oura_sleep_detail
11641274
ADD CONSTRAINT unique_oura_sleep_detail UNIQUE (timestamp_gmt);
11651275

1276+
ALTER TABLE ONLY public.strava_activity_summary
1277+
ADD CONSTRAINT unique_strava_activity_summary UNIQUE (start_date);
1278+
1279+
ALTER TABLE ONLY public.strava_activity_streams
1280+
ADD CONSTRAINT unique_strava_activity_streams UNIQUE (time_gmt);
11661281

11671282
--
11681283
--INDEXES
@@ -1272,6 +1387,9 @@ ALTER TABLE ONLY public.oura_activity_detail
12721387
ALTER TABLE ONLY public.oura_sleep_detail
12731388
ADD CONSTRAINT fk_oura_sleep_detail_sleep_summary_id FOREIGN KEY (oura_sleep_id) REFERENCES public.oura_sleep_daily_summary(id);
12741389

1390+
ALTER TABLE ONLY public.strava_activity_summary
1391+
ADD CONSTRAINT fk_strava_activity_summary_athlete_id FOREIGN KEY (athlete_id) REFERENCES public.athlete(id);
12751392

1276-
1393+
ALTER TABLE ONLY public.strava_activity_streams
1394+
ADD CONSTRAINT fk_strava_activity_streams_strava_activity_id FOREIGN KEY (activity_id) REFERENCES public.strava_activity_summary(id);
12771395

src/db_strava_auth.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/python
2+
import psycopg2
3+
from database_ini_parser import config
4+
from Athlete_Data_Utills import StdoutRedirection,ErrorStdoutRedirection,ProgressStdoutRedirection
5+
import sys
6+
from db_encrypt import generate_key,pad_text,unpad_text
7+
import Crypto.Random
8+
from Crypto.Cipher import AES
9+
import base64
10+
import datetime
11+
12+
#----Crypto Variables----
13+
# salt size in bytes
14+
SALT_SIZE = 16
15+
# number of iterations in the key generation
16+
NUMBER_OF_ITERATIONS = 2000 # PG:Consider increasing number of iterations
17+
# the size multiple required for AES
18+
AES_MULTIPLE = 16
19+
20+
21+
def decrypt(ciphertext, password):
22+
salt = ciphertext[0:SALT_SIZE]
23+
#iv = ciphertext[:AES.block_size]
24+
ciphertext_sans_salt = ciphertext[SALT_SIZE:]
25+
key = generate_key(password, salt, NUMBER_OF_ITERATIONS)
26+
cipher = AES.new(key, AES.MODE_ECB) #PG: Consider changing to MODE_CBC
27+
padded_plaintext = cipher.decrypt(ciphertext_sans_salt)
28+
plaintext = unpad_text(padded_plaintext)
29+
return plaintext
30+
31+
def check_strava_token_exists(ath_un,db_host,db_name,superuser_un,superuser_pw,encr_pass):
32+
conn = None
33+
ath_un = ath_un
34+
db_name = db_name
35+
decrypted_strava_token = None
36+
37+
sql_check_strava_token_exists = """
38+
SELECT strava_refresh_token FROM athlete WHERE ath_un = %s;
39+
"""
40+
41+
try:
42+
# connect to the PostgreSQL server
43+
conn = psycopg2.connect(dbname=db_name, host=db_host, user=superuser_un, password=superuser_pw)
44+
45+
# create a cursor
46+
cur = conn.cursor()
47+
48+
# execute a statement
49+
try:
50+
cur.execute(sql_check_strava_token_exists,(ath_un,))
51+
result = cur.fetchone()
52+
if result[0] is not None:
53+
token_exists = True
54+
strava_token = result[0]
55+
#Decrypt strava token
56+
decrypted_strava_token = decrypt(base64.b64decode(strava_token), encr_pass)
57+
else:
58+
token_exists = False
59+
conn.commit()
60+
except Exception as e:
61+
with ErrorStdoutRedirection(ath_un):
62+
print((str(datetime.datetime.now()) + ' [' + sys._getframe().f_code.co_name + ']' + ' Error on line {}'.format(sys.exc_info()[-1].tb_lineno) + ' ' + str(e)))
63+
token_exists = False
64+
65+
# close the communication with the PostgreSQL
66+
cur.close()
67+
except (Exception, psycopg2.DatabaseError) as error:
68+
with ErrorStdoutRedirection(ath_un):
69+
print((str(datetime.datetime.now()) + ' [' + sys._getframe().f_code.co_name + ']' + ' Error on line {}'.format(sys.exc_info()[-1].tb_lineno) + ' ' + str(error)))
70+
71+
finally:
72+
if conn is not None:
73+
conn.close()
74+
75+
return token_exists, decrypted_strava_token
76+
77+

src/db_user_insert.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def insert_last_synch_timestamp(ath_un,encr_pass,db_name):
7272
conn_localhost.close
7373

7474
@processify
75-
def user_tokens_insert(ath_un,db_host,db_name,superuser_un,superuser_pw,dbx_auth_token,oura_refresh_token,encr_pass,save_pwd):
75+
def user_tokens_insert(ath_un,db_host,db_name,superuser_un,superuser_pw,dbx_auth_token,oura_refresh_token,strava_refresh_token,encr_pass,save_pwd):
7676

7777
if save_pwd == True:
7878
#Encrypt dbx token
@@ -87,14 +87,22 @@ def user_tokens_insert(ath_un,db_host,db_name,superuser_un,superuser_pw,dbx_auth
8787
encrypted_oura_refresh_token = encrypted_oura_refresh_token.decode('utf-8')
8888
else:
8989
encrypted_oura_refresh_token = None
90+
#Encrypt strava token
91+
if strava_refresh_token is not None:
92+
encrypted_strava_refresh_token = base64.b64encode(encrypt(strava_refresh_token, encr_pass))
93+
encrypted_strava_refresh_token = encrypted_strava_refresh_token.decode('utf-8')
94+
else:
95+
encrypted_strava_refresh_token = None
9096
else:
9197
encrypted_dbx_auth_token = None
9298
encrypted_oura_refresh_token = None
99+
encrypted_strava_refresh_token = None
93100

94101
#Query params lists
95102
ath_user = (ath_un, )
96103
auth_token_tuple = (encrypted_dbx_auth_token, )
97104
oura_token_tuple = (encrypted_oura_refresh_token, )
105+
strava_token_tuple = (encrypted_strava_refresh_token, )
98106

99107
conn = None
100108

@@ -158,6 +166,23 @@ def user_tokens_insert(ath_un,db_host,db_name,superuser_un,superuser_pw,dbx_auth
158166
159167
"""
160168

169+
sql_insert_strava_refresh_token = """
170+
DO
171+
$do$
172+
BEGIN
173+
IF
174+
175+
EXISTS (SELECT id FROM athlete WHERE ath_un = %s) THEN
176+
177+
UPDATE athlete SET strava_refresh_token = %s where ath_un= %s;
178+
179+
END IF;
180+
181+
END
182+
$do$
183+
184+
"""
185+
161186
try:
162187

163188
# connect to the PostgreSQL server
@@ -184,6 +209,12 @@ def user_tokens_insert(ath_un,db_host,db_name,superuser_un,superuser_pw,dbx_auth
184209
cur.execute(sql_insert_oura_refresh_token,(ath_user,oura_token_tuple,ath_user))
185210
conn.commit()
186211

212+
if strava_refresh_token is not None:
213+
with ProgressStdoutRedirection(ath_un):
214+
print('Inserting Strava refresh token into postgreSQL:')
215+
cur.execute(sql_insert_strava_refresh_token,(ath_user,strava_token_tuple,ath_user))
216+
conn.commit()
217+
187218
# close the communication with the PostgreSQL
188219
cur.close()
189220
except (Exception, psycopg2.DatabaseError) as error:

0 commit comments

Comments
 (0)