예제 #1
0
        public async Task NotifyPlatformConnectionDataUpdate(string userId, IList <string> appIds,
                                                             string platformId, IAsyncDocumentSession session, CancellationToken cancellationToken = default)
        {
            var platformConnectionState = PlatformConnectionState.Connected;
            var data = await _platformDataManager.GetPlatformData(userId, platformId, session, cancellationToken);

            if (data != null)
            {
                platformConnectionState = PlatformConnectionState.Synced;
            }

            await NotifyPlatformDataUpdate(userId, appIds, platformId, session, NotificationReason.DataUpdate, platformConnectionState,
                                           cancellationToken : cancellationToken);
        }
        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.");
        }