예제 #1
0
        public async Task Handle(FetchDataForPlatformConnectionMessage message)
        {
            using var _ = _logger.BeginNamedScopeWithMessage(nameof(DataFetchCompleteHandler),
                                                             _messageContext.Message.GetMessageId(),
                                                             (LoggerPropertyNames.PlatformId, message.PlatformId),
                                                             (LoggerPropertyNames.UserId, message.UserId),
                                                             (LoggerPropertyNames.PlatformIntegrationType, message.PlatformIntegrationType));

            _logger.LogInformation("Will start data fetching data for platform and user.");

            var cancellationToken = _messageContext.GetCancellationToken();

            using var session = _documentStore.OpenAsyncSession();
            var user = await session.LoadAsync <User>(message.UserId, cancellationToken);

            var syncLog = new DataSyncLog(user.Id, message.PlatformId);

            using var __ = _logger.BeginPropertyScope((LoggerPropertyNames.DataSyncLogId, syncLog.ExternalId));

            var platformConnection = user.PlatformConnections.SingleOrDefault(pc => pc.PlatformId == message.PlatformId);

            if (platformConnection == null)
            {
                await LogErrorAndForwardMessageToErrorQueue("Platform connection for user for the given platform does not exist. Will forward message to error queue.");

                return;
            }

            var connectionInfo = platformConnection.ConnectionInfo;


            await session.StoreAsync(syncLog);

            try
            {
                switch (message.PlatformIntegrationType)
                {
                case PlatformIntegrationType.AirbnbIntegration:
                case PlatformIntegrationType.UpworkIntegration:
                case PlatformIntegrationType.Manual:
                    var logMessage1 = "Data fetch for given platform integration type not implemented. Will forward message to error queue.";
                    await LogErrorAndForwardMessageToErrorQueue(logMessage1);

                    syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Failed, logMessage: logMessage1));
                    await session.SaveChangesAsync(cancellationToken);

                    return;

                case PlatformIntegrationType.GigDataPlatformIntegration:
                    var oauthOrEmailPlatformConnectionInfo = OAuthOrEmailPlatformConnectionInfo.FromIPlatformConnectionInfo(connectionInfo);
                    oauthOrEmailPlatformConnectionInfo = await _gigPlatformDataFetcher.StartDataFetch(message.UserId,
                                                                                                      message.PlatformId, oauthOrEmailPlatformConnectionInfo,
                                                                                                      platformConnection, syncLog, cancellationToken);

                    connectionInfo = OAuthOrEmailPlatformConnectionInfo.FromOAuthOrEmailPlatformConnectionInfo(oauthOrEmailPlatformConnectionInfo, connectionInfo);
                    break;

                case PlatformIntegrationType.FreelancerIntegration:
                    connectionInfo = await _freelancerDataFetcher.StartDataFetch(message.UserId, message.PlatformId,
                                                                                 (OAuthPlatformConnectionInfo)connectionInfo, platformConnection, syncLog, cancellationToken);

                    syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Started));
                    break;

                default:
                    var logMessage2 = "Unrecognized platform integration type. Will forward message to error queue.";
                    await LogErrorAndForwardMessageToErrorQueue(logMessage2);

                    syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Failed, logMessage: logMessage2));
                    await session.SaveChangesAsync(cancellationToken);

                    return;
                }
            }
            catch (UnsupportedPlatformConnectionAuthenticationTypeException ex)
            {
                var logMessage = "Unsupported connection authentication type. Will forward message to error queue.";
                await LogErrorAndForwardMessageToErrorQueue(
                    "Unsupported connection authentication type. Will forward message to error queue.", ex);

                syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Failed, logMessage: logMessage));
                await session.SaveChangesAsync(cancellationToken);

                return;
            }

            if (connectionInfo.IsDeleted)
            {
                //we no longer have a connection to the platform for the given user.
                _logger.LogWarning("The connection is no longer valid and will be marked as deleted.");
            }

            platformConnection.ConnectionInfo = connectionInfo;
            await session.SaveChangesAsync(cancellationToken);

            _logger.LogInformation("Data fetch successfully initialized.");
        }
예제 #2
0
 public Task <TConnectionInfo> StartDataFetch(string userId, string platformId, TConnectionInfo connectionInfo,
                                              PlatformConnection platformConnection, DataSyncLog syncLog, CancellationToken cancellationToken = default)
 {
     throw new NotImplementedException();
 }
예제 #3
0
        public new async Task <OAuthPlatformConnectionInfo> StartDataFetch(string userId, string platformId,
                                                                           OAuthPlatformConnectionInfo connectionInfo, PlatformConnection platformConnection, DataSyncLog syncLog,
                                                                           CancellationToken cancellationToken = default)
        {
            using var _ = Logger.BeginPropertyScope((LoggerPropertyNames.DataSyncLogId, syncLog.ExternalId));

            Logger.LogTrace($"Will start data fetch from Freelancer for user with id {userId}");
            if (connectionInfo.Token.HasExpired())
            {
                Logger.LogInformation($"Token has expired, will try to refresh it");
                try
                {
                    var newToken = await _freelancerAuthenticator.RefreshToken(new OAuthAccessToken
                    {
                        AccessToken  = connectionInfo.Token.AccessToken,
                        RefreshToken = connectionInfo.Token.RefreshToken,
                        ExpiresIn    = connectionInfo.Token.ExpiresInSeconds
                    });

                    connectionInfo.Token = new Token(newToken);
                }
                catch (UnauthorizedAccessException)
                {
                    Logger.LogInformation(
                        "The consent to access Freelancer seems to have been revoked. Will act accordingly");
                    return(await HandleUnauthorized(userId, platformId, syncLog.Id));
                }
            }

            _apiClient.SetAccessToken(connectionInfo.Token.AccessToken);
            string          rawReviews;
            ReviewApiResult freelancerReviews;
            string          rawUserProfile;

            UserInfoApiResult userProfile;

            try
            {
                (userProfile, rawUserProfile) = await _apiClient.GetUserProfile();

                (freelancerReviews, rawReviews) = await _apiClient.GetReviews(userProfile.Result.Id);
            }
            catch (UnauthorizedAccessException)
            {
                Logger.LogInformation(
                    "The consent to access Freelancer seems to have been revoked. Will act accordingly");
                return(await HandleUnauthorized(userId, platformId, syncLog.Id));
            }

            var(ratings, reviews) = GetRatingsAndReviews(freelancerReviews);

            var raw = $"{{data: [{rawUserProfile}, {rawReviews}]}}";

            var numberOfCompletedJobs = userProfile.Result.Reputation.EntireHistory.Complete;
            var averageRating         = numberOfCompletedJobs == 0
                ? null
                : new RatingDataFetchResult(userProfile.Result.Reputation.EntireHistory.Overall);

            //achievements
            var achievements = new List <AchievementFetchResult>();

            var qualifications = userProfile.Result.Qualifications;

            if (qualifications != null)
            {
                foreach (var qualification in qualifications)
                {
                    var score = new AchievementScoreFetchResult(
                        qualification.ScorePercentage.ToString(CultureInfo.InvariantCulture), "percent");

                    var achievement = new AchievementFetchResult($"freelancer_qualification_{qualification.Id}",
                                                                 qualification.Name,
                                                                 "qualification", PlatformAchievementType.QualificationAssessment, qualification.Description,
                                                                 $"https://www.freelancer.com{qualification.IconUrl}",
                                                                 score);

                    achievements.Add(achievement);
                }
            }

            var badges = userProfile.Result.Badges;

            if (badges != null)
            {
                foreach (var badge in badges)
                {
                    var achievement = new AchievementFetchResult($"freelancer_badge_{badge.Id}", badge.Name, "badge",
                                                                 PlatformAchievementType.Badge, badge.Description, $"https://www.freelancer.com{badge.IconUrl}",
                                                                 null);

                    achievements.Add(achievement);
                }
            }

            var registrationDate = DateTimeOffset.FromUnixTimeSeconds(userProfile.Result.RegistrationDate);

            var result = new PlatformDataFetchResult(numberOfCompletedJobs,
                                                     registrationDate, DateTimeOffset.UtcNow, averageRating, ratings, reviews, achievements, raw);

            await CompleteDataFetch(userId, platformId, result, syncLog.Id);


            Logger.LogTrace($"Freelancer data fetch completed for user with id {userId}");
            return(connectionInfo);
        }
예제 #4
0
        public new async Task <OAuthOrEmailPlatformConnectionInfo> StartDataFetch(string userId, string platformId,
                                                                                  OAuthOrEmailPlatformConnectionInfo connectionInfo, PlatformConnection platformConnection, DataSyncLog syncLog,
                                                                                  CancellationToken cancellationToken = default)
        {
            using var _ = Logger.BeginPropertyScope((LoggerPropertyNames.UserId, userId),
                                                    (LoggerPropertyNames.PlatformId, platformId), (LoggerPropertyNames.PlatformName, platformConnection.PlatformName),
                                                    (LoggerPropertyNames.DataSyncLogId, syncLog.ExternalId));

            Logger.LogInformation("Will start data fetch from a Gig platform integrated platform for user.");

            if (connectionInfo.IsOAuthAuthentication)
            {
                Logger.LogError("Oauth connection not yet supported in Gig Platform API. Will throw.");
                throw new UnsupportedPlatformConnectionAuthenticationTypeException("Oauth connection not yet supported in Gig Platform API");
            }

            try
            {
                var result = await _apiClient.RequestLatest(platformConnection.ExternalPlatformId, connectionInfo.Email, cancellationToken);

                using var __ = Logger.BeginPropertyScope((LoggerPropertyNames.GigPlatformApiRequestId, result.RequestId));
                await _intermittentDataManager.RegisterRequestData(result.RequestId, userId, platformId, syncLog.Id);
            }
            catch (ExternalServiceErrorException ex)
            {
                //Error that can occur here is that we get either a 500, which means there is something wrong with platform api server. Or we get a 404, which means that the
                //platform we asked the api to fetch data from does not exist on the api side of things. Both these cases should result in a retry. So we throw here.
                var logMessage = "Got error when regestering data fetch. Will throw.";
                Logger.LogError(ex, logMessage);
                syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Failed, logMessage: logMessage));
                throw new GigDataPlatformApiInitiateDataFetchException();
            }

            syncLog.Steps.Add(new DataSyncStep(DataSyncStepType.PlatformDataFetch, DataSyncStepState.Started));
            Logger.LogInformation("Data fetch successfully started.");
            return(connectionInfo);
        }
        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.");
        }
예제 #6
0
        public async Task Handle(PlatformConnectionRemovedMessage message)
        {
            using var _ = _logger.BeginPropertyScope((LoggerPropertyNames.PlatformId, message.PlatformId),
                                                     (LoggerPropertyNames.UserId, message.UserId),
                                                     ("DeleteReason", message.DeleteReason));
            _logger.LogInformation("Will remove platform connection");

            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));

            //remove connection
            var user = await session.LoadAsync <User>(message.UserId);

            var index         = 0;
            var indexToRemove = -1;
            PlatformConnection platformConnectionToRemove = null;

            foreach (var userPlatformConnection in user.PlatformConnections)
            {
                if (userPlatformConnection.PlatformId == message.PlatformId)
                {
                    indexToRemove = index;
                    platformConnectionToRemove = userPlatformConnection;
                    break;
                }

                index++;
            }

            if (indexToRemove == -1 || platformConnectionToRemove == null)
            {
                _logger.LogWarning("Platform connection with platform id {PlatformId} was not found for user with id {UserId}",
                                   message.PlatformId, message.UserId);
                syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.RemovePlatformConnection, DataSyncStepState.Failed,
                                                    $"Platform connection with platform id {message.PlatformId} was not found for user with id {message.UserId}"));
                await session.SaveChangesAsync();

                return;
            }

            if (message.DeleteReason != Common.PlatformConnectionDeleteReason.Undefined) //if we have a delete reason, do soft delete
            {
                _logger.LogInformation("Delete reason was {DeleteReason}. Will do soft delete", message.DeleteReason);
                platformConnectionToRemove.ConnectionInfo.DeleteReason = message.DeleteReason;
                platformConnectionToRemove.ConnectionInfo.IsDeleted    = true;
            }
            else //no reason given, do hard delete
            {
                _logger.LogInformation("No delete reason was given. Will do hard delete");
                user.PlatformConnections.RemoveAt(indexToRemove);
            }

            syncLog?.Steps.Add(new DataSyncStep(DataSyncStepType.RemovePlatformConnection, DataSyncStepState.Succeeded));

            await session.SaveChangesAsync();

            await _appNotificationManager.NotifyPlatformConnectionRemoved(message.UserId,
                                                                          platformConnectionToRemove.ConnectionInfo.NotificationInfos.Select(ni => ni.AppId).ToList(),
                                                                          platformConnectionToRemove.PlatformId, session);
        }