public async Task Handle(PlatformConnectionUpdateNotificationMessage message) { using var _ = _logger.BeginNamedScopeWithMessage(nameof(DataFetchCompleteHandler), _messageContext.Message.GetMessageId(), (LoggerPropertyNames.ApplicationId, message.AppId), (LoggerPropertyNames.UserId, message.UserId)); using var session = _documentStore.OpenAsyncSession(); DataSyncLog syncLog = null; if (!string.IsNullOrEmpty(message.SyncLogId)) { syncLog = await session.LoadAsync <DataSyncLog>(message.SyncLogId); } using var __ = _logger.BeginPropertyScope((LoggerPropertyNames.DataSyncLogId, syncLog?.ExternalId)); var cancellationToken = _messageContext.GetCancellationToken(); var user = await session.LoadAsync <User>(message.UserId, cancellationToken); if (user == null) { var logMessage = "User with given id does not exist. Will move message to error queue."; _logger.LogWarning(logMessage); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage)); await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName); await session.SaveChangesAsync(); return; } var app = await _appManager.GetAppFromId(message.AppId, session, cancellationToken); if (app == null) { var logMessage = "App with given id does not exist. Will move message to error queue."; _logger.LogWarning(logMessage); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage)); await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName); await session.SaveChangesAsync(); return; } var platform = await session.LoadAsync <Platform>(message.PlatformId, cancellationToken); if (platform == null) { var logMessage = "Platform with given id does not exist. Will move message to error queue."; _logger.LogWarning(logMessage); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl)); await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName); await session.SaveChangesAsync(); return; } var notificationReason = message.Reason; var platformConnectionState = message.PlatformConnectionState; PlatformDataClaim?platformDataClaim = null; if (notificationReason != NotificationReason.ConnectionDeleted) { if (platformConnectionState == PlatformConnectionState.Connected || platformConnectionState == PlatformConnectionState.Synced) { var activePlatformConnection = user.PlatformConnections.SingleOrDefault(pc => pc.PlatformId == message.PlatformId && !pc.ConnectionInfo.IsDeleted); if (activePlatformConnection == null) { _logger.LogWarning( $"No active connection exists for the given platform. Will notify app about removed connection."); notificationReason = NotificationReason.ConnectionDeleted; platformConnectionState = PlatformConnectionState.Removed; } else { var notificationInfoForApp = activePlatformConnection.ConnectionInfo.NotificationInfos.SingleOrDefault(ni => ni.AppId == app.Id); if (notificationInfoForApp == null) { _logger.LogWarning( $"No active connection to the given app. Will notify app about removed connection."); notificationReason = NotificationReason.ConnectionDeleted; platformConnectionState = PlatformConnectionState.Removed; } else { platformDataClaim = notificationInfoForApp.PlatformDataClaim; } } } } PlatformData platformData = null; if (notificationReason == NotificationReason.DataUpdate) { platformData = await _platformDataManager.GetPlatformData(user.Id, platform.Id, session, cancellationToken); } var updatePayload = CreatePayload(platform.ExternalId, user.ExternalId, app.SecretKey, platform.Name, platformConnectionState, DateTimeOffset.UtcNow, notificationReason, platformData, platformDataClaim); if (string.IsNullOrWhiteSpace(app.DataUpdateCallbackUrl)) { var logMessage = "No notification endpoint was given. Will move message to error queue."; _logger.LogWarning(logMessage); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl)); await _bus.Advanced.TransportMessage.Forward(_rebusConfiguration.ErrorQueueName); await session.SaveChangesAsync(); return; } if (!Uri.TryCreate(app.DataUpdateCallbackUrl, UriKind.Absolute, out var uri)) { _logger.LogError("Could not create uri from {DataCallbackUrl}. Will ignore message.", app.DataUpdateCallbackUrl); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, "Could not create uri from app data callback url. Will ignore message.", app.Id, app.DataUpdateCallbackUrl)); await session.SaveChangesAsync(); return; } if (IsLocalHost(uri)) { _logger.LogWarning("Uri with url {DataCallbackUrl} is equal to localhost. Will ignore message.", app.DataUpdateCallbackUrl); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, "Uri is equal to localhost. Will ignore message.", app.Id, app.DataUpdateCallbackUrl)); await session.SaveChangesAsync(); return; } var serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var serializedPayload = JsonConvert.SerializeObject(updatePayload, serializerSettings); _logger.LogTrace("Payload to be sent: {Payload}", serializedPayload); var content = new StringContent(serializedPayload, Encoding.UTF8, "application/json"); content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); HttpResponseMessage response; try { var httpClient = new HttpClient(); response = await httpClient.PostAsync( uri, content, cancellationToken); } catch (Exception e) { var logMessage = "Got error calling endpoint. Will schedule retry."; _logger.LogError(e, logMessage); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage, app.Id, app.DataUpdateCallbackUrl)); await _bus.DeferMessageLocalWithExponentialBackOff(message, _messageContext.Headers, MaxMessageRetries, _rebusConfiguration.ErrorQueueName, logger : _logger); await session.SaveChangesAsync(); return; } if (!response.IsSuccessStatusCode) { if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500) { //we got a 40x status code. Inspect further to determine action. if (response.StatusCode == HttpStatusCode.Gone) { //If we get a 410 it means that the app is telling us that they don't want updates for the given platform for the user again. //remove the app notification from the platformconnection. _logger.LogInformation("App responded with {httpStatusCode}. Will remove notification info for app.", response.StatusCode); var platformConnection = user.PlatformConnections.SingleOrDefault(pc => pc.PlatformId == message.PlatformId); if (platformConnection != null) { int?notificationInfoIdx = null; var counter = 0; foreach (var notificationInfo in platformConnection.ConnectionInfo.NotificationInfos) { if (notificationInfo.AppId == message.AppId) { notificationInfoIdx = counter; break; } counter++; } if (notificationInfoIdx.HasValue) { platformConnection.ConnectionInfo.NotificationInfos.RemoveAt(notificationInfoIdx.Value); } } syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Succeeded, logMessage: $"App responded with {response.StatusCode}, indicating that the notification should be removed. App will not be notified again.", appId: app.Id, appWebHookUrl: app.DataUpdateCallbackUrl)); } else { //some other 400. We will not retry the request. But we will keep the connection info for the app and the app will be notified next update again. _logger.LogInformation("App responded with {httpStatusCode}. Will not retry current request. App will be notified on next data update", response.StatusCode); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, logMessage: $"App responded with {response.StatusCode}. Current request will not be retried. App will be notified again on next update.", appId: app.Id, appWebHookUrl: app.DataUpdateCallbackUrl)); } } else { _logger.LogError("Got non success status code ({HttpStatusCode}) calling endpoint. Will schedule retry.", response.StatusCode); syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Failed, $"Got non success status code ({response.StatusCode}) calling endpoint. Will schedule retry.", app.Id, app.DataUpdateCallbackUrl)); await _bus.DeferMessageLocalWithExponentialBackOff(message, _messageContext.Headers, MaxMessageRetries, _rebusConfiguration.ErrorQueueName, logger : _logger); } await session.SaveChangesAsync(cancellationToken); return; } syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.AppNotification, DataSyncStepState.Succeeded, appId: app.Id, appWebHookUrl: app.DataUpdateCallbackUrl)); await session.SaveChangesAsync(cancellationToken); _logger.LogInformation("App successfully notified about platform data update."); }
private static PlatformConnectionUpdateNotificationPayload CreatePayload(Guid externalPlatformId, Guid externalUserId, string appSecret, string platformName, PlatformConnectionState platformConnectionState, DateTimeOffset updated, NotificationReason notificationReason, PlatformData platformData = null, PlatformDataClaim?platformDataClaim = null) { var payload = new PlatformConnectionUpdateNotificationPayload { PlatformId = externalPlatformId, UserId = externalUserId, AppSecret = appSecret, PlatformName = platformName, PlatformConnectionState = platformConnectionState, Updated = updated.ToUnixTimeSeconds(), Reason = notificationReason }; if (platformData != null) { //if no platform data claim is provided, set to lowest claim by default platformDataClaim ??= PlatformDataClaim.Aggregated; var platformDataPayload = new PlatformDataPayload { NumberOfGigs = platformData.NumberOfGigs, PeriodStart = platformData.PeriodStart, PeriodEnd = platformData.PeriodEnd, NumberOfRatings = platformData.Ratings?.Count() ?? 0 }; if (platformData.AverageRating != null) { platformDataPayload.AverageRating = new PlatformRatingPayload( platformData.AverageRating); } if (platformData.Ratings != null && platformData.Ratings.Any()) { platformDataPayload.NumberOfRatingsThatAreDeemedSuccessful = platformData.Ratings.Count(r => r.Value >= r.SuccessLimit); } if (platformDataClaim == PlatformDataClaim.Full) { if (platformData.Reviews != null) { var reviewPayloads = new List <PlatformReviewPayload>(); foreach (var platformDataReview in platformData.Reviews ) { var platformReviewPayload = new PlatformReviewPayload { ReviewId = platformDataReview.ReviewIdentifier, ReviewDate = platformDataReview.ReviewDate, ReviewerName = platformDataReview.ReviewerName, ReviewText = platformDataReview.ReviewText, ReviewHeading = platformDataReview.ReviewHeading, ReviewerAvatarUri = platformDataReview.ReviewerAvatarUri }; if (platformDataReview.RatingId.HasValue) { platformReviewPayload.Rating = new PlatformRatingPayload( platformData.Ratings?.Single(r => r.Identifier == platformDataReview.RatingId)); } reviewPayloads.Add(platformReviewPayload); } if (reviewPayloads.Any()) { platformDataPayload.Reviews = reviewPayloads; } } if (platformData.Achievements != null) { var achievementPayloads = new List <PlatformAchievementPayload>(); foreach (var platformDataAchievement in platformData.Achievements) { PlatformAchievementScorePayload scorePayload = null; if (platformDataAchievement.Score != null) { scorePayload = new PlatformAchievementScorePayload { Value = platformDataAchievement.Score.Value, Label = platformDataAchievement.Score.Label }; } var platformAchievementPayload = new PlatformAchievementPayload { AchievementId = platformDataAchievement.AchievementIdentifier, AchievementPlatformType = platformDataAchievement.AchievementPlatformType, AchievementType = platformDataAchievement.AchievementType, Description = platformDataAchievement.Description, ImageUrl = platformDataAchievement.ImageUri, Name = platformDataAchievement.Name, Score = scorePayload }; achievementPayloads.Add(platformAchievementPayload); } platformDataPayload.Achievements = achievementPayloads; } } payload.PlatformData = platformDataPayload; } else { payload.PlatformData = null; } return(payload); }