public async Task <IActionResult> GetAgeGroups( String name, CancellationToken cancellationToken) { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var ageGroupsTable = dbContext.Set <AgeGroup>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == name, cancellationToken ); if (challenge == null) { return(NotFound()); } return(new JsonResult( await ageGroupsTable .Where(ag => ag.ChallengeId == challenge.Id).ToListAsync(cancellationToken) )); }
private async Task RefreshAllChallengesAsync(CancellationToken cancellationToken) { logger.LogDebug("Refreshing all active challenges"); await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var updatesTable = dbContext.Set <Update>(); var pendingUpdates = await updatesTable .Where(u => u.EndTime == null && u.AthleteId == null) .CountAsync(cancellationToken); if (pendingUpdates > 0) { // Ruh-roh this.logger.LogWarning( "Unable to start auto refresh process because there is an unfinished update already running." ); return; } this.taskService.QueueTask <EffortRefresher>( (service, taskCancellationToken) => service.RefreshAllChallenges(taskCancellationToken) ); }
public async Task <IActionResult> UpdateSelf( [FromBody] AthleteProfile profile, CancellationToken cancellationToken) { if (!(User is JwtCookiePrincipal identity)) { return(Unauthorized()); } await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var athleteTable = dbContext.Set <Athlete>(); var athlete = await athleteTable.FindAsync(new Object[] { identity.UserId }, cancellationToken); if (athlete == null) { // Odd return(NotFound()); } else { athlete.BirthDate = profile.BirthDate; athlete.Gender = profile.Gender; athlete.Email = profile.Email; await dbContext.SaveChangesAsync(cancellationToken); Response.Cookies.Append( "id_token", StravaConnectController.CreateAthleteJwt( this.challengeConfiguration.Value, athlete ) ); return(new JsonResult(new AthleteProfile { Username = athlete.Username, FirstName = athlete.FirstName, LastName = athlete.LastName, BirthDate = athlete.BirthDate, Gender = athlete.Gender, Email = athlete.Email })); } }
public async Task <IActionResult> Register( String name, CancellationToken cancellationToken) { if (!(User is JwtCookiePrincipal identity)) { return(Unauthorized()); } await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var registrationTable = dbContext.Set <ChallengeRegistration>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == name, cancellationToken ); if (challenge == null) { return(NotFound()); } var registration = await registrationTable.SingleOrDefaultAsync( r => r.ChallengeId == challenge.Id && r.AthleteId == identity.UserId, cancellationToken ); if (registration == null) { await registrationTable.AddAsync( new ChallengeRegistration { ChallengeId = challenge.Id, AthleteId = identity.UserId }, cancellationToken ); await dbContext.SaveChangesAsync(cancellationToken); } return(new JsonResult(new { registered = true })); }
public async Task RefreshEfforts( Int32 updateId, String challengeName, CancellationToken cancellationToken) { this.logger.LogDebug( "Refreshing all Efforts for Challenge {ChallengeName} (Update {UpdateId})", challengeName, updateId ); try { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var updatesTable = dbContext.Set <Update>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == challengeName, cancellationToken ); if (challenge == null) { this.logger.LogError( "Refresh Efforts Failed. Challenge not found: {ChallengeName}", challengeName ); return; } var update = await updatesTable.FindAsync(new Object[] { updateId }, cancellationToken); await RefreshEffortsInternal(dbContext, challenge, update, cancellationToken); } catch (Exception ex) { this.logger.LogError( "Refresh Efforts Failed. Unexpected Exception: {Message}", 1, ex, ex.Message ); } }
public async Task <IActionResult> List(CancellationToken cancellationToken) { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); return(new JsonResult( await challengeTable .OrderByDescending(c => c.StartDate) .ToListAsync(cancellationToken: cancellationToken) )); }
public async Task RefreshAllChallenges(CancellationToken cancellationToken) { this.logger.LogDebug("RefreshAllChallenges Starting"); await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var updatesTable = dbContext.Set <Update>(); var activeChallenges = await challengeTable .Where(c => c.EndDate > DateTime.UtcNow) .ToListAsync(cancellationToken); foreach (var challenge in activeChallenges) { var pendingUpdates = await updatesTable .Where(u => u.EndTime == null && u.AthleteId == null) .CountAsync(cancellationToken); if (pendingUpdates > 0) { this.logger.LogWarning( "Unable to continue auto refresh of challenge efforts because another update is currently running." ); break; } var update = updatesTable.Add(new Update { ChallengeId = challenge.Id }); await dbContext.SaveChangesAsync(cancellationToken); logger.LogDebug("Refreshing all efforts for challenge {ChallengeId} (Update {UpdateId})", challenge.Id, update.Entity.Id); await this.RefreshEffortsInternal(dbContext, challenge, update.Entity, cancellationToken); } this.logger.LogDebug("RefreshAllChallenges Complete"); }
public async Task <IActionResult> AllAthletes(String name, CancellationToken cancellationToken) { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var athleteTable = dbContext.Set <Athlete>(); var registrationTable = dbContext.Set <ChallengeRegistration>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == name, cancellationToken ); if (challenge == null) { return(NotFound()); } var athletes = await athleteTable .Join( registrationTable, a => a.Id, r => r.AthleteId, (a, r) => new { Athlete = a, Registration = r }) .Where(row => row.Registration.ChallengeId == challenge.Id) .Select(row => row.Athlete) .ToListAsync(cancellationToken); return(new JsonResult( athletes .Where(a => a.Gender.HasValue && a.BirthDate.HasValue) .Select(a => new { id = a.Id, displayName = a.GetDisplayName(), gender = a.Gender.ToString(), age = challenge.StartDate.Year - a.BirthDate.Value.Year }) )); }
public async Task <IActionResult> GetSelf(CancellationToken cancellationToken) { if (!(User is JwtCookiePrincipal identity)) { return(Unauthorized()); } await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var athleteTable = dbContext.Set <Athlete>(); var athlete = await athleteTable.FindAsync(new Object[] { identity.UserId }, cancellationToken); if (athlete == null) { // Odd return(NotFound()); } else { return(new JsonResult(new AthleteProfile { Username = athlete.Username, FirstName = athlete.FirstName, LastName = athlete.LastName, BirthDate = athlete.BirthDate, Gender = athlete.Gender, Email = athlete.Email, IsAdmin = this.siteConfiguration.Value.Administrators != null && this.siteConfiguration.Value.Administrators.Contains(identity.UserId) })); } }
public async Task RefreshAthleteEfforts( Int32 updateId, String challengeName, Int64 athleteId, CancellationToken cancellationToken) { this.logger.LogDebug( "Refreshing all Efforts for Challenge {ChallengeName} (Update {UpdateId})", challengeName, updateId ); try { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var registrationsTable = dbContext.Set <ChallengeRegistration>(); var athletesTable = dbContext.Set <Athlete>(); var updatesTable = dbContext.Set <Update>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == challengeName, cancellationToken ); if (challenge == null) { this.logger.LogError( "Refresh Efforts Failed. Challenge not found: {ChallengeName}", challengeName ); return; } var athlete = await registrationsTable .Join( athletesTable, cr => cr.AthleteId, a => a.Id, (cr, a) => new { Registration = cr, Athlete = a }) .Where(ra => ra.Registration.ChallengeId == challenge.Id && ra.Athlete.Id == athleteId) .Where(ra => ra.Athlete.Gender != null && ra.Athlete.BirthDate != null) .Select(ra => ra.Athlete) .FirstOrDefaultAsync(cancellationToken); if (athlete == null) { this.logger.LogError( "Refresh Efforts Failed. Athlete not registered: {AthleteId} {ChallengeName}", athleteId, challengeName ); return; } if (athlete.Id < 0) { // This athlete is not a strava user. Manual upload required. return; } var update = await updatesTable.FindAsync(new Object[] { updateId }, cancellationToken); update.StartTime = DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); var(activitiesUpdated, activitiesSkipped, effortsUpdated, error) = await RefreshAthleteEffortsInternal( dbContext, update, challenge, athlete, cancellationToken); update.ActivityCount += activitiesUpdated; update.SkippedActivityCount += activitiesSkipped; update.EffortCount += effortsUpdated; if (error) { update.ErrorCount++; } update.AthleteCount = 1; update.Progress = 1; update.EndTime = DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); } catch (Exception ex) { this.logger.LogError( "Refresh Efforts Failed. Unexpected Exception: {Message}", new EventId(2), ex, ex.Message ); try { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var updatesTable = dbContext.Set <Update>(); var update = await updatesTable.SingleOrDefaultAsync(u => u.Id == updateId, cancellationToken); if (update != null) { update.EndTime = DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); } } catch (Exception ex2) { this.logger.LogError( "Unable to mark update {UpdateId} failed. Unexpected Exception: {Message}", new EventId(2), ex2, updateId, ex2.Message ); } } }
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); }
private async Task RefreshEffortsInternal( SegmentChallengeDbContext dbContext, Challenge challenge, Update update, CancellationToken cancellationToken) { var registrationsTable = dbContext.Set <ChallengeRegistration>(); var athletesTable = dbContext.Set <Athlete>(); var athletes = await registrationsTable .Join( athletesTable, cr => cr.AthleteId, a => a.Id, (cr, a) => new { Registration = cr, Athlete = a }) .Where(ra => ra.Registration.ChallengeId == challenge.Id) .Where(ra => ra.Athlete.Gender != null && ra.Athlete.BirthDate != null) .Select(ra => ra.Athlete) .ToListAsync(cancellationToken); update.StartTime = DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); foreach (var athlete in athletes) { this.logger.LogDebug( "Updating {ChallengeName} efforts for athlete {AthleteId}.", challenge.Name, athlete.Id ); // Update efforts try { var(activitiesUpdated, activitiesSkipped, effortsUpdated, error) = await RefreshAthleteEffortsInternal( dbContext, update, challenge, athlete, cancellationToken); update.ActivityCount += activitiesUpdated; update.SkippedActivityCount += activitiesSkipped; update.EffortCount += effortsUpdated; if (error) { update.ErrorCount++; } } catch (TaskCanceledException) { throw; } catch (OperationCanceledException) { throw; } catch (Exception ex) { update.ErrorCount++; this.logger.LogError( "An unexpected exception occurred while refreshing efforts for Athlete {AthleteId} (Challenge: {ChallengeId}", 2, ex, athlete.Id, challenge.Id ); } update.AthleteCount++; update.Progress = (Single)update.AthleteCount / athletes.Count; await dbContext.SaveChangesAsync(cancellationToken); } update.EndTime = DateTime.UtcNow; await dbContext.SaveChangesAsync(cancellationToken); }
public async Task <IActionResult> GetEfforts( String name, CancellationToken cancellationToken) { await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var challengeTable = dbContext.Set <Challenge>(); var registrationTable = dbContext.Set <ChallengeRegistration>(); var ageGroupsTable = dbContext.Set <AgeGroup>(); var effortsTable = dbContext.Set <Effort>(); var athleteTable = dbContext.Set <Athlete>(); var challenge = await challengeTable.SingleOrDefaultAsync( c => c.Name == name, cancellationToken ); if (challenge == null) { return(NotFound()); } var ageGroups = await ageGroupsTable .Where(ag => ag.ChallengeId == challenge.Id) .OrderBy(ag => ag.MaximumAge) .ToListAsync(cancellationToken); var efforts = await effortsTable .Where(e => e.SegmentId == challenge.SegmentId && e.StartDate >= challenge.StartDate && e.StartDate <= challenge.EndDate) .Join( athleteTable, e => e.AthleteId, a => a.Id, (e, a) => new { Effort = e, Athlete = a }) .Join( registrationTable, row => row.Athlete.Id, r => r.AthleteId, (row, reg) => new { Effort = row.Effort, Athlete = row.Athlete, Registration = reg }) .Where(row => row.Registration.ChallengeId == challenge.Id) .OrderBy(row => row.Athlete.Id) .ToListAsync(cancellationToken: cancellationToken); var results = new List <(Effort Effort, Athlete Athlete, Int32 LapCount)>(); if (challenge.Type == ChallengeType.MostLaps) { foreach (var effortGroup in efforts.GroupBy(e => e.Athlete.Id)) { var(effort, athlete, lapCount) = effortGroup.Aggregate( (effort: (Effort)null, athlete: (Athlete)null, lapCount: 0), (total, nextEffort) => { if (total.effort == null) { return(nextEffort.Effort, nextEffort.Athlete, 1); } else { return(total.effort.WithElapsedTime(total.effort.ElapsedTime + nextEffort.Effort.ElapsedTime), total.athlete, total.lapCount + 1); } }); results.Add((effort, effortGroup.First().Athlete, lapCount)); } } else { Athlete currentAthlete = null; Effort bestEffort = null; foreach (var effort in efforts.Append(null)) { if (effort == null || effort.Athlete.Id != currentAthlete?.Id) { if (bestEffort != null) { results.Add((bestEffort, currentAthlete, 1)); } if (effort != null) { currentAthlete = effort.Athlete; bestEffort = effort.Effort; } } else if (bestEffort == null || effort.Effort.ElapsedTime < bestEffort.ElapsedTime) { bestEffort = effort.Effort; } } } (String Gender, Int32 MaxAge) GetCategory(Athlete athlete) { var birthDateYear = (athlete.BirthDate?.Year).GetValueOrDefault(DateTime.UtcNow.Year - 90); var age = DateTime.UtcNow.Year - birthDateYear; var ageGroup = ageGroups.SkipWhile(ag => age > ag.MaximumAge).First(); return(athlete.Gender.GetValueOrDefault('M').ToString(), ageGroup.MaximumAge); } var resultsByCategory = new List <(Effort Effort, Athlete Athlete, Int32 LapCount, Boolean IsKOM)>(); (String Gender, Int32 MaxAge)currentCategory = (null, 0); foreach (var(effort, athlete, lapCount) in results.OrderBy(e => GetCategory(e.Athlete)).ThenByDescending(e => e.LapCount) .ThenBy(e => e.Effort.ElapsedTime)) { var category = GetCategory(athlete); if (category != currentCategory) { resultsByCategory.Add((effort, athlete, lapCount, true)); currentCategory = category; } else if (resultsByCategory.Count > 0 && resultsByCategory[^ 1].LapCount == lapCount && resultsByCategory[^ 1].Effort.ElapsedTime == effort.ElapsedTime) { // Tie for first resultsByCategory.Add((effort, athlete, lapCount, true)); } else { resultsByCategory.Add((effort, athlete, lapCount, false)); } } return(new JsonResult( resultsByCategory .OrderByDescending(e => e.LapCount) .ThenBy(e => e.Effort.ElapsedTime) .ThenBy(e => e.Effort.StartDate) .Select(e => new { id = e.Effort.Id, athleteId = e.Athlete.Id, athleteName = e.Athlete.GetDisplayName(), athleteGender = e.Athlete.Gender, athleteAge = e.Athlete.BirthDate?.ToRacingAge(challenge.StartDate), activityId = e.Effort.ActivityId, lapCount = e.LapCount, elapsedTime = e.Effort.ElapsedTime, startDate = e.Effort.StartDate, isKOM = e.IsKOM }) )); }
public async Task <IActionResult> Authorize( [FromQuery] String state, [FromQuery] String code, [FromQuery] String scope, CancellationToken cancellationToken) { var expected_state = Request.Cookies["authentication_state"]; if (!String.Equals(state, expected_state)) { this.logger.LogWarning( "The state {ActualState} did not match the expected authentication state {ExpectedState}", state, expected_state ); } var codeExchangeClient = new HttpClient { BaseAddress = new Uri("https://www.strava.com") }; codeExchangeClient.DefaultRequestHeaders.Add("Accept", "application/json"); var response = await this.apiHelper.MakeThrottledApiRequest( () => codeExchangeClient.PostAsync( "/api/v3/oauth/token", new FormUrlEncodedContent(new Dictionary <string, string> { { "client_id", this.stravaConfiguration.Value.ClientId }, { "client_secret", this.stravaConfiguration.Value.ClientSecret }, { "code", code }, { "grant_type", "authorization_code" } }), cancellationToken ), cancellationToken); if (response.IsSuccessStatusCode) { var session = await response.Content.ReadAsAsync <StravaSession>(cancellationToken); await using var connection = this.dbConnectionFactory(); await connection.OpenAsync(cancellationToken); await using var dbContext = new SegmentChallengeDbContext(connection); var athleteTable = dbContext.Set <Athlete>(); // Does user exist? If not create them. var existingAthlete = await athleteTable.SingleOrDefaultAsync(a => a.Id == session.Athlete.Id, cancellationToken); EntityEntry <Athlete> newAthlete = null; if (existingAthlete == null) { newAthlete = await athleteTable.AddAsync( new Athlete { Id = session.Athlete.Id, Username = session.Athlete.Username, FirstName = session.Athlete.FirstName, LastName = session.Athlete.LastName, Gender = !String.IsNullOrEmpty(session.Athlete.Sex) ? session.Athlete.Sex[0] : (Char?)null, ProfilePicture = session.Athlete.ProfileMedium ?? session.Athlete.Profile, AccessToken = session.AccessToken, RefreshToken = session.RefreshToken, TokenExpiration = StravaApiHelper.DateTimeFromUnixTime(session.ExpiresAt) }, cancellationToken ); } else { existingAthlete.Username = session.Athlete.Username; existingAthlete.FirstName = session.Athlete.FirstName; existingAthlete.LastName = session.Athlete.LastName; if (!String.IsNullOrEmpty(session.Athlete.Sex)) { existingAthlete.Gender = session.Athlete.Sex[0]; } existingAthlete.ProfilePicture = session.Athlete.ProfileMedium ?? session.Athlete.Profile; existingAthlete.AccessToken = session.AccessToken; existingAthlete.RefreshToken = session.RefreshToken; existingAthlete.TokenExpiration = StravaApiHelper.DateTimeFromUnixTime(session.ExpiresAt); athleteTable.Update(existingAthlete); } var changes = await dbContext.SaveChangesAsync(cancellationToken); if (changes != 1) { logger.LogWarning( $"Unexpected number of rows changed {(existingAthlete == null ? "creating" : "updating")} Athlete {{AthleteId}} ({{RowsChanged}})", session.Athlete.Id, changes ); } Response.Cookies.Append( "id_token", CreateAthleteJwt( this.challengeConfiguration.Value, existingAthlete ?? newAthlete?.Entity), new CookieOptions { Expires = DateTime.UtcNow.AddDays(this.challengeConfiguration.Value.TokenExpiration) } ); return(Redirect("/")); } else { logger.LogError( "Authentication Failed with HTTP Status {StatusCode}: {Content}", response.StatusCode, await response.Content.ReadAsStringAsync() ); return(this.Problem( $"An unexpected error occurred. Please contact {this.challengeConfiguration.Value.SupportContact}")); } }