-
Notifications
You must be signed in to change notification settings - Fork 0
/
athletes.go
493 lines (449 loc) · 16.9 KB
/
athletes.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
package handlers
import (
"net/http"
"strconv"
"time"
"github.com/go-chi/chi"
"github.com/jrzimmerman/bestrida-server-go/models"
log "github.com/sirupsen/logrus"
strava "github.com/strava/go.strava"
)
// UpdateAthleteFromStrava updates athlete information from Strava
func UpdateAthleteFromStrava(numID int64) (*models.User, error) {
user, err := models.GetUserByID(numID)
if err != nil {
log.WithField("ID", numID).Error("unable to retrieve user from database")
return nil, err
}
client := strava.NewClient(user.Token)
log.Info("Fetching athlete info...\n")
// retrieve athlete info from Strava API
athlete, err := strava.NewCurrentAthleteService(client).Get().Do()
if err != nil {
log.Error("Unable to retrieve athlete info from Strava")
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.Infof("athlete %v retrieved from strava", athlete.Id)
u, err := user.UpdateAthlete(athlete)
if err != nil {
log.WithError(err).Errorf("unable to update athlete %d", athlete.Id)
return nil, err
}
return u, err
}
// GetAthleteByIDFromStrava returns the strava athlete with the specified ID
func GetAthleteByIDFromStrava(w http.ResponseWriter, r *http.Request) {
res := New(w)
id := chi.URLParam(r, "id")
numID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
log.Error("unable to convert ID param")
res.Render(http.StatusBadRequest, map[string]interface{}{
"error": "unable to convert ID param",
"stack": err,
})
return
}
u, err := UpdateAthleteFromStrava(numID)
if err != nil {
log.Errorf("unable to update athlete %d from Strava", numID)
res.Render(http.StatusBadRequest, map[string]interface{}{
"error": "unable to update athlete from Strava",
"stack": err,
})
return
}
res.Render(http.StatusOK, &u)
}
// UpdateAllUsersFromStrava returns all updated users from Strava
func UpdateAllUsersFromStrava(w http.ResponseWriter, r *http.Request) {
res := New(w)
// get all users
users, err := models.GetAllUsers()
if err != nil {
log.Error("unable to get users")
return
}
// iterate over all users
for _, u := range users {
// remove friends and segments if older imported user
oldCreatedDate := time.Date(2017, 8, 29, 0, 0, 0, 0, time.UTC)
log.Infof("user %d created before %v", u.ID, oldCreatedDate)
oldUpdatedDate := time.Date(2017, 9, 9, 12, 0, 0, 0, time.UTC)
log.Infof("user %d updated before %v", u.ID, oldUpdatedDate)
if u.CreatedAt.Before(oldCreatedDate) && u.CreatedAt.Before(oldUpdatedDate) {
err := u.RemoveFriendsSegmentsFromUser()
if err != nil {
log.Errorf("unable to remove segments and friends from user %d:\n %v", u.ID, err)
return
}
}
// update athlete
_, err = UpdateAthleteFromStrava(u.ID)
if err != nil {
log.Errorf("unable to update user %d", u.ID)
return
}
// update athlete friends
_, err = GetFriendsFromStrava(u.ID)
if err != nil {
log.Errorf("unable to update user %d friends", u.ID)
return
}
// update athlete segments
_, err = GetUserSegmentsFromStrava(u.ID, 1)
if err != nil {
log.Errorf("unable to update user %d segments", u.ID)
return
}
}
log.Info("all users updated successfully")
res.Render(http.StatusOK, "all users updated successfully")
}
// GetFriendsFromStrava gets a users friends from Strava
func GetFriendsFromStrava(numID int64) (friends []*models.Friend, err error) {
// get user based on ID
user, err := models.GetUserByID(numID)
if err != nil {
log.WithField("USER ID", numID).Errorf("unable to retrieve user %v from database", numID)
return nil, err
}
// friends map to store unique friend info
friendMap := make(map[int64]*models.Friend, len(user.Friends))
// iterate over users existing friends to populate friend map
for _, friend := range user.Friends {
friendMap[friend.ID] = friend
}
client := strava.NewClient(user.Token)
log.Info("Fetching athlete friends info...\n")
// retrieve a list of users friends from Strava API
stravaFriends, err := strava.NewCurrentAthleteService(client).ListFriends().Do()
if err != nil {
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.Infof("Finished fetching %v athlete friends from Strava...\n", len(stravaFriends))
// update friends map based upon strava friend data
for _, stravaFriend := range stravaFriends {
if _, ok := friendMap[stravaFriend.Id]; !ok {
// new friend set new information
friendMap[stravaFriend.Id] = &models.Friend{
ID: stravaFriend.Id,
FirstName: stravaFriend.FirstName,
LastName: stravaFriend.LastName,
FullName: stravaFriend.FirstName + " " + stravaFriend.LastName,
Photo: stravaFriend.Profile,
}
} else {
count := 0
wins := 0
losses := 0
if friend, ok := friendMap[stravaFriend.Id]; ok {
count = friend.ChallengeCount
wins = friend.Wins
losses = friend.Losses
}
// existing friend, update info and keep count
friendMap[stravaFriend.Id] = &models.Friend{
ID: stravaFriend.Id,
FirstName: stravaFriend.FirstName,
LastName: stravaFriend.LastName,
FullName: stravaFriend.FirstName + " " + stravaFriend.LastName,
Photo: stravaFriend.Profile,
ChallengeCount: count,
Wins: wins,
Losses: losses,
}
}
}
// range over unique friends to store updated info in slice
for _, friend := range friendMap {
friends = append(friends, friend)
}
// store friends slice for user
err = user.SaveUserFriends(friends)
if err != nil {
log.WithError(err).Errorf("unable to save user friends for user %d to database", user.ID)
return nil, err
}
return friends, nil
}
// GetFriendsByUserIDFromStrava returns a list of friends for a specific user by ID from strava
func GetFriendsByUserIDFromStrava(w http.ResponseWriter, r *http.Request) {
res := New(w)
id := chi.URLParam(r, "id")
numID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
log.WithField("ID", numID).Error("unable to convert ID param")
res.Render(http.StatusBadRequest, map[string]interface{}{
"error": "unable to convert ID param",
"stack": err,
})
return
}
// Get users friends from strava and save to DB
friends, err := GetFriendsFromStrava(numID)
if err != nil {
log.WithField("USER ID", numID).Errorf("unable to retrieve user %v friends from strava", numID)
res.Render(http.StatusInternalServerError, map[string]interface{}{
"error": "unable to retrieve users friends from strava",
"stack": err,
})
return
}
log.Infof("found %d friends for athlete %d from strava", len(friends), numID)
res.Render(http.StatusOK, friends)
}
// GetUserSegmentsFromStrava gets a users recently completed segments from Strava
func GetUserSegmentsFromStrava(numID int64, page int) (userSegmentSlice []*models.UserSegment, err error) {
// find user by numID to retrieve strava token
user, err := models.GetUserByID(numID)
if err != nil {
log.WithField("ID", numID).Error("unable to retrieve user from database")
return nil, err
}
// unique segment map to store a users segments
userSegments := make(map[int64]*models.UserSegment, len(user.Segments))
for _, segment := range user.Segments {
userSegments[segment.ID] = segment
}
// create new strava client with user token
client := strava.NewClient(user.Token)
log.Info("Fetching starred segments from Strava...\n")
starred, err := strava.NewCurrentAthleteService(client).ListStarredSegments().Do()
if err != nil {
return nil, err
}
// range over a users starred segments
// to obtain segment details to cache
for _, seg := range starred {
log.Infof("segment %v was starred by user", seg.Id)
log.WithField("SEGMENT", seg.Name).Info("segment effort from activity detail")
// check if segment is in database
segment, err := models.GetSegmentByID(seg.Id)
if err != nil {
// segment not found, make request to strava
log.WithField("SEGMENT ID", seg.Id).Infof("segment %v not found in database... saving", seg.Id)
segmentDetail, err := strava.NewSegmentsService(client).Get(seg.Id).Do()
if err != nil {
log.WithFields(log.Fields{
"SEGMENT NAME": seg.Name,
"SEGMENT ID": seg.Id,
}).Errorf("unable to retrieve segment detail for %d %s", seg.Id, seg.Name)
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.WithField("SEGMENT DETAIL ID", segmentDetail.Id).Infof("segment %d returned from strava", segmentDetail.Id)
saved, err := models.SaveSegment(segmentDetail)
if err != nil {
log.WithError(err).Errorf("unable to save segment detail %d to database", segmentDetail.Id)
return nil, err
}
log.WithField("SEGMENT ID", saved.ID).Infof("segment %d stored in DB", saved.ID)
// store saved segment in userSegments map
userSegments[saved.ID] = &models.UserSegment{
ID: saved.ID,
Name: saved.Name,
ActivityType: saved.ActivityType,
Count: 0,
}
} else if time.Now().After(segment.UpdatedAt.AddDate(0, 0, 7)) {
// update segment in DB if segment data is stale
// this is required by Strava API license agreement
log.WithField("SEGMENT ID", seg.Id).Infof("segment %v is greater than 7 days old... updating", seg.Id)
segmentDetail, err := strava.NewSegmentsService(client).Get(seg.Id).Do()
if err != nil {
log.WithFields(log.Fields{
"SEGMENT NAME": seg.Name,
"SEGMENT ID": seg.Id,
}).Errorf("unable to retrieve segment detail for %d %s", seg.Id, seg.Name)
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.WithField("SEGMENT DETAIL ID", segmentDetail.Id).Infof("segment %d returned from strava", segmentDetail.Id)
updated, err := segment.UpdateSegment(segmentDetail)
if err != nil {
log.WithError(err).Errorf("unable to save segment detail %d to database", segmentDetail.Id)
return nil, err
}
log.WithField("SEGMENT ID", updated.ID).Infof("segment %d updated in DB", updated.ID)
count := 0
if segment, ok := userSegments[segment.ID]; ok {
count = segment.Count
}
// store updated segment in userSegments map
userSegments[updated.ID] = &models.UserSegment{
ID: updated.ID,
Name: updated.Name,
ActivityType: updated.ActivityType,
Count: count,
}
} else {
count := 0
if segment, ok := userSegments[segment.ID]; ok {
count = segment.Count
}
// segment was found and returned
// add segment to userSegments
userSegments[segment.ID] = &models.UserSegment{
ID: segment.ID,
Name: segment.Name,
ActivityType: segment.ActivityType,
Count: count,
}
}
}
log.Info("Fetching athlete activities from Strava...\n")
activities, err := strava.NewCurrentAthleteService(client).ListActivities().Page(1).PerPage(page).Do()
if err != nil {
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.Infof("Finished fetching %v athlete activities from Strava...\n", len(activities))
// range over activity summary to get activity details
// the activity summary does not contain segment effort information
for _, activitySummary := range activities {
log.WithFields(log.Fields{
"NAME": activitySummary.Name,
"ID": activitySummary.Id,
}).Info("activity summary")
// request activity detail from strava to obtain segment effort information
activityDetail, err := strava.NewActivitiesService(client).Get(activitySummary.Id).IncludeAllEfforts().Do()
if err != nil {
log.WithFields(log.Fields{
"NAME": activityDetail.Name,
"ID": activityDetail.Id,
}).Errorf("unable to retrieve activity detail: \n%v", err)
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
// range over segment efforts from the activity detail
// to obtain segment details to cache
for _, effort := range activityDetail.SegmentEfforts {
log.WithField("SEGMENT", effort.Segment.Name).Info("segment effort from activity detail")
// check if segment is in database
segment, err := models.GetSegmentByID(effort.Segment.Id)
if err != nil {
// segment not found, make request to strava
log.WithField("SEGMENT ID", effort.Segment.Id).Infof("segment %v not found in database... saving", effort.Segment.Id)
segmentDetail, err := strava.NewSegmentsService(client).Get(effort.Segment.Id).Do()
if err != nil {
log.WithFields(log.Fields{
"SEGMENT NAME": effort.Segment.Name,
"SEGMENT ID": effort.Segment.Id,
}).Errorf("unable to retrieve segment detail for %d %s", effort.Segment.Id, effort.Segment.Name)
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.WithField("SEGMENT DETAIL ID", segmentDetail.Id).Infof("segment %d returned from strava", segmentDetail.Id)
saved, err := models.SaveSegment(segmentDetail)
if err != nil {
log.WithError(err).Errorf("unable to save segment detail %d to database", segmentDetail.Id)
return nil, err
}
log.WithField("SEGMENT ID", saved.ID).Infof("segment %d stored in DB", saved.ID)
// store saved segment in userSegments map
userSegments[saved.ID] = &models.UserSegment{
ID: saved.ID,
Name: saved.Name,
ActivityType: saved.ActivityType,
Count: 0,
}
} else if time.Now().After(segment.UpdatedAt.AddDate(0, 0, 7)) {
// update segment in DB if segment data is stale
// this is required by Strava API license agreement
log.WithField("SEGMENT ID", effort.Segment.Id).Infof("segment %v is greater than 7 days old... updating", effort.Segment.Id)
segmentDetail, err := strava.NewSegmentsService(client).Get(effort.Segment.Id).Do()
if err != nil {
log.WithFields(log.Fields{
"SEGMENT NAME": effort.Segment.Name,
"SEGMENT ID": effort.Segment.Id,
}).Errorf("unable to retrieve segment detail for %d %s", effort.Segment.Id, effort.Segment.Name)
return nil, err
}
log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
log.WithField("SEGMENT DETAIL ID", segmentDetail.Id).Infof("segment %d returned from strava", segmentDetail.Id)
updated, err := segment.UpdateSegment(segmentDetail)
if err != nil {
log.WithError(err).Errorf("unable to save segment detail %d to database", segmentDetail.Id)
return nil, err
}
log.WithField("SEGMENT ID", updated.ID).Infof("segment %d updated in DB", updated.ID)
count := 0
if segment, ok := userSegments[segment.ID]; ok {
count = segment.Count
}
// store updated segment in userSegments map
userSegments[updated.ID] = &models.UserSegment{
ID: updated.ID,
Name: updated.Name,
ActivityType: updated.ActivityType,
Count: count,
}
} else {
count := 0
if segment, ok := userSegments[segment.ID]; ok {
count = segment.Count
}
// segment was found and returned
// add segment to userSegments
userSegments[segment.ID] = &models.UserSegment{
ID: segment.ID,
Name: segment.Name,
ActivityType: segment.ActivityType,
Count: count,
}
}
}
}
for _, userSegment := range userSegments {
userSegmentSlice = append(userSegmentSlice, userSegment)
}
// store segment map for user
err = user.SaveUserSegments(userSegmentSlice)
if err != nil {
log.WithError(err).Errorf("unable to save user segments for user %d to database", user.ID)
return nil, err
}
return userSegmentSlice, nil
}
// GetSegmentsByUserIDFromStrava returns a list of segments for a specific user by ID from strava
func GetSegmentsByUserIDFromStrava(w http.ResponseWriter, r *http.Request) {
res := New(w)
id := chi.URLParam(r, "id")
// convert user id string from url param to number
numID, err := strconv.ParseInt(id, 10, 64)
if err != nil {
log.WithField("USER ID", numID).Error("unable to convert USER ID param")
res.Render(http.StatusInternalServerError, map[string]interface{}{
"error": "unable to convert USER ID param",
"stack": err,
})
return
}
// Get users friends from strava and save to DB to prevent transactional overwrites
_, err = GetFriendsFromStrava(numID)
if err != nil {
log.WithField("USER ID", numID).Errorf("unable to retrieve user %v friends from strava", numID)
res.Render(http.StatusInternalServerError, map[string]interface{}{
"error": "unable to retrieve users friends from strava",
"stack": err,
})
return
}
// page is the amount of activites to request from Strava
var page = 30
// get users segments from Strava
userSegments, err := GetUserSegmentsFromStrava(numID, page)
if err != nil {
log.WithField("USER ID", numID).Errorf("unable to retrieve user %v segments from strava", numID)
res.Render(http.StatusInternalServerError, map[string]interface{}{
"error": "unable to retrieve users segments from strava",
"stack": err,
})
return
}
log.WithField("USER ID", numID).Infof("found %d segments for user %v", len(userSegments), numID)
res.Render(http.StatusOK, userSegments)
}