public EffortRefresher( IOptions <StravaConfiguration> stravaConfiguration, Func <DbConnection> dbConnectionFactory, StravaApiHelper apiHelper, ILogger <EffortRefresher> logger) { this.stravaConfiguration = stravaConfiguration; this.dbConnectionFactory = dbConnectionFactory; this.apiHelper = apiHelper; this.logger = logger; }
RefreshAthleteEffortsInternal( SegmentChallengeDbContext dbContext, Update update, Challenge challenge, Athlete athlete, CancellationToken cancellationToken) { // var athletesTable = dbContext.Set<Athlete>(); var effortsTable = dbContext.Set <Effort>(); var activityUpdatesTable = dbContext.Set <ActivityUpdate>(); var stravaClient = new HttpClient { BaseAddress = new Uri("https://www.strava.com") }; // If the athlete's token is expired, refresh it // In theory we should do this before each call, but we'll fudge it by assuming we can // handle a single athlete in fewer than 10 minutes. if (athlete.TokenExpiration <= DateTime.UtcNow.AddMinutes(-10)) { var response = await this.apiHelper.MakeThrottledApiRequest( () => stravaClient.PostAsync( "/api/v3/oauth/token", new FormUrlEncodedContent(new Dictionary <string, string> { { "client_id", this.stravaConfiguration.Value.ClientId }, { "client_secret", this.stravaConfiguration.Value.ClientSecret }, { "refresh_token", athlete.RefreshToken }, { "grant_type", "refresh_token" } }), cancellationToken), cancellationToken ); if (response.IsSuccessStatusCode) { var session = await response.Content.ReadAsAsync <StravaSession>(cancellationToken); athlete.AccessToken = session.AccessToken; athlete.RefreshToken = session.RefreshToken; athlete.TokenExpiration = StravaApiHelper.DateTimeFromUnixTime(session.ExpiresAt); await dbContext.SaveChangesAsync(cancellationToken); } } stravaClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", athlete.AccessToken); var previousUpdates = await activityUpdatesTable .Where(u => u.AthleteId == athlete.Id && u.ChallengeId == challenge.Id) .ToDictionaryAsync(u => u.ActivityId, cancellationToken); var activitiesUpdated = 0; var activitiesSkipped = 0; var effortsUpdated = 0; // For each activity between start and end of challenge for (var pageNumber = 1; !cancellationToken.IsCancellationRequested; pageNumber++) { var response = await this.apiHelper.MakeThrottledApiRequest( () => stravaClient.GetAsync( $"/api/v3/athlete/activities?after={challenge.StartDate.AddDays(-1).ToUnixTime()}&before={challenge.EndDate.ToUnixTime()}&page={pageNumber}&per_page=200", cancellationToken), cancellationToken ); if (response.IsSuccessStatusCode) { var activities = await response.Content.ReadAsAsync <StravaActivity[]>(cancellationToken); response.Dispose(); response = null; if (activities == null || activities.Length == 0) { break; } foreach (var activity in activities) { if (activity.Type == "Ride") { if (activity.Flagged) { this.logger.LogInformation( "Skipping Activity {ActivityId} for Athlete {AthleteId} because it has been flagged.", activity.Id, athlete.Id ); } else if (previousUpdates.ContainsKey(activity.Id)) { activitiesSkipped++; } else { var activityDetailsResponse = await apiHelper.MakeThrottledApiRequest( () => stravaClient.GetAsync( $"/api/v3/activities/{activity.Id}?include_all_efforts=true", cancellationToken), cancellationToken ); if (activityDetailsResponse.IsSuccessStatusCode) { var activityDetails = await activityDetailsResponse.Content .ReadAsAsync <StravaActivityDetails>(cancellationToken); var relevantEfforts = activityDetails.SegmentEfforts .Where(e => e.Segment.Id == challenge.SegmentId) .Where(e => e.StartDate >= challenge.StartDate && e.StartDate.AddSeconds(e.ElapsedTime) <= challenge.EndDate) .ToList(); if (relevantEfforts.Count > 0) { // Save Efforts foreach (var effort in relevantEfforts) { var existingEffort = await effortsTable.SingleOrDefaultAsync(e => e.Id == effort.Id, cancellationToken : cancellationToken); if (existingEffort != null) { existingEffort.StartDate = effort.StartDate; existingEffort.ElapsedTime = challenge.UseMovingTime ? effort.MovingTime : effort.ElapsedTime; } else { await effortsTable.AddAsync( new Effort { Id = effort.Id, AthleteId = athlete.Id, ActivityId = activity.Id, SegmentId = challenge.SegmentId, ElapsedTime = challenge.UseMovingTime ? effort.MovingTime : effort.ElapsedTime, StartDate = effort.StartDate }, cancellationToken ); } } } await activityUpdatesTable.AddAsync( new ActivityUpdate { ChallengeId = challenge.Id, ActivityId = activity.Id, AthleteId = athlete.Id, UpdateId = update.Id, UpdatedAt = DateTime.UtcNow }, cancellationToken ); await dbContext.SaveChangesAsync(cancellationToken); activitiesUpdated++; effortsUpdated += relevantEfforts.Count; } else { logger.LogError( "An HTTP error occurred attempting to fetch activity details for Athlete {AthleteId} Activity {ActivityId} - Status {StatusCode}: {Content}", athlete.Id, activity.Id, activityDetailsResponse.StatusCode, await activityDetailsResponse.Content.ReadAsStringAsync() ); // Give up return(activitiesUpdated, activitiesSkipped, effortsUpdated, error : true); } } } } } else { logger.LogError( "An HTTP error occurred attempting to fetch activities for Athlete {AthleteId} - Status {StatusCode}: {Content}", athlete.Id, response.StatusCode, await response.Content.ReadAsStringAsync() ); // Give up return(activitiesUpdated, activitiesSkipped, effortsUpdated, true); } } return(activitiesUpdated, activitiesSkipped, effortsUpdated, false); }