protected override async Task TaskActionAsync(Guid?id) { var parameters = new Dictionary <string, object>(); parameters.Add("Method", "TaskActionAsync"); if (id.HasValue) { parameters.Add("Job ID", id); } // Attempt to convert the tweet response JSON string into a SearchStreamResult object. SearchStreamResult searchStreamResult; try { searchStreamResult = JsonConvert.DeserializeObject <SearchStreamResult>(_tweetResponse); } catch (Exception exception) { // Throws exception, if it's unable to convert it into a SearchStreamResult object. throw new ApiException("Unable to convert JSON to type SearchStreamResult.", exception, JObject.Parse(_tweetResponse)); } if (searchStreamResult != null && searchStreamResult.Tweet == null) { // No tweet, so throw an exception. throw new ApiException("Tweet does not have an instance of Tweet.", JObject.Parse(_tweetResponse)); } if (searchStreamResult != null && searchStreamResult.Tweet != null && (searchStreamResult.Tweet.ReferencedTweets == null || searchStreamResult.Tweet.ReferencedTweets.Count == 0)) { var tweetParameters = new Dictionary <string, object>(); foreach (var param in parameters) { tweetParameters.Add(param.Key, param.Value); } tweetParameters.Add("Twitter API Tweet Id", searchStreamResult.Tweet.Id); _logger.LogWithParameters(LogLevel.Information, "Start importing incoming realtime tweet", tweetParameters); Tweet tweet = null; List <DashboardTweet> dashboardTweets = null; // Create a new scope from the service provider. using (var scope = _serviceProvider.CreateScope()) { // Gets the services freom the scope. var blashDbContext = scope.ServiceProvider.GetService <BlashDbContext>(); var dashboardService = scope.ServiceProvider.GetService <IDashboardService>(); var dashboardTweetService = scope.ServiceProvider.GetService <IDashboardTweetService>(); var tweetService = scope.ServiceProvider.GetService <ITweetService>(); var authorService = scope.ServiceProvider.GetService <IAuthorService>(); var dashboards = new List <Dashboard>(); IList <DashboardTweet> dashboardTweetsToDelete = null; using (var dbContextTransaction = await blashDbContext.Database.BeginTransactionAsync()) { // Go through each rule that matches a tweet from the Twitter API. foreach (var matchingRule in searchStreamResult.MatchingRules) { var dashboard = await dashboardService.GetByTwitterRuleAsync(matchingRule.Id); // Dashboard may have been deleted, so check it still exists. if (dashboard == null) { // No dashboard exists, so continue with the next rule. _logger.LogWithParameters(LogLevel.Information, "Dashboard no longer exists so finish processing.", tweetParameters); continue; } dashboards.Add(dashboard); // Add to the list of dashboards. } if (dashboards == null || dashboards.Count == 0) { // If there are no dashboards assoicated with the tweet, finish with the task. return; } // Create or update the tweet to the Blash database. tweet = await TaskExtensions.CreateUpdateTweetAsync(scope, _logger, searchStreamResult.Tweet, searchStreamResult.Includes.Users, searchStreamResult.Includes.Media); if (tweet == null) { return; } // Add it into a dashboard/tweet relationship. dashboardTweets = null; foreach (var dashboard in dashboards) { if (dashboardTweets == null) { dashboardTweets = new List <DashboardTweet>(); } // Add all the relationships associated with the tweet. dashboardTweets.Add(await TaskExtensions.CreateDashboardTweetRelationshipAsync(scope, _logger, dashboard.Id, tweet.Id)); // Ensure that we only have have the maximum number of tweets per dashboard as defined in appsettings.json under Api > Tweets > MaxPerDashboard. dashboardTweetsToDelete = await dashboardTweetService.GetByDashboardAndAfterPositionAsync(dashboard.Id, _apiConfiguration.Value.Tweets?.MaxPerDashboard ?? 10); if (dashboardTweetsToDelete != null && dashboardTweetsToDelete.Count > 0) { // Delete any dashboard/tweets that exceed the maximum number of tweets allowed for a dashboard. await dashboardTweetService.DeleteMultipleAsync(dashboardTweetsToDelete.Select(dashboardTweet => dashboardTweet.Id).ToList()); } // Make sure tweets are removed that aren't needed. await tweetService.DeleteMissingTweetsFromDashboardAsync(); await authorService.DeleteMissingTweetsAsync(); } // Commit the transaction to the database. await dbContextTransaction.CommitAsync(); } // Send the fact that a tweet has been created to the hub's connected clinets through SignalR. await _blashHub.CreateTweetAsync(new CreateTweetResult { Data = new List <CreateTweetData> { new CreateTweetData(tweet, dashboardTweets) } }, _cancellationToken); if (dashboardTweetsToDelete != null && dashboardTweetsToDelete.Count > 0) { // Send the fact that tweets have been deleted from dashboards to the hub's connected clinets through SignalR. await _blashHub.DeleteDashboardTweetAsync(dashboardTweetsToDelete, _cancellationToken); } } _logger.LogWithParameters(LogLevel.Information, "Finish importing incoming realtime tweet", tweetParameters); } }
/// <summary> /// Performs the recent tweets from the Twitter API for each dashboard. /// </summary> /// <param name="id">The job identifier.</param> /// <returns>An instance of <see cref="Task"/>.</returns> protected override async Task TaskActionAsync(Guid?id) { var parameters = new Dictionary <string, object>(); parameters.Add("Method", "TaskActionAsync"); if (id.HasValue) { parameters.Add("Job ID", id); } _logger.LogWithParameters(LogLevel.Information, "Start importing recent tweets from Twitter API", parameters); using (var scope = _serviceProvider.CreateScope()) { // Gets the services freom the scope. var dashboardService = scope.ServiceProvider.GetService <IDashboardService>(); var dashboardTweetService = scope.ServiceProvider.GetService <IDashboardTweetService>(); var authorService = scope.ServiceProvider.GetService <IAuthorService>(); var tweetService = scope.ServiceProvider.GetService <ITweetService>(); var blashDbContext = scope.ServiceProvider.GetService <BlashDbContext>(); var maxResults = _apiConfiguration.Value.Tweets.MaxPerDashboard.HasValue ? _apiConfiguration.Value.Tweets.MaxPerDashboard.Value : 100; var updatedTweetIds = new List <int>(); List <Dashboard> dashboards = null; using (var dbContextTransaction = await blashDbContext.Database.BeginTransactionAsync()) { _logger.LogWithParameters(LogLevel.Debug, "Get all dashboards from database", parameters); dashboards = _dashboard != null ? new List <Dashboard> { _dashboard } : await dashboardService.GetAllAsync(); // Get all dashboards (or one if we have specified a dashboard). _logger.LogWithParameters(LogLevel.Debug, string.Format("{0} dashboard{1} returned from the database", dashboards.Count, dashboards.Count() != 1 ? "s" : ""), parameters); foreach (var dashboard in dashboards) { _logger.LogWithParameters(LogLevel.Information, "Start importing recent tweets for dashboard", parameters); TweetResult recentTweets = null; var recentTweetSearchParameters = new Dictionary <string, object>(); foreach (var param in parameters) { recentTweetSearchParameters.Add(param.Key, param.Value); } recentTweetSearchParameters.Add("Search Query", dashboard.SearchQuery); recentTweetSearchParameters.Add("MaxResults", maxResults); // Get recent tweets based on the dashboard's search query. _logger.LogWithParameters(LogLevel.Debug, "Get recent tweets from Twitter API.", recentTweetSearchParameters); try { recentTweets = await _twitterApiTweetService.GetRecentTweetsAsync(dashboard.SearchQuery, maxResults); } catch (Exception) { // Set as null if the API throws an exception. recentTweets = null; } _logger.LogWithParameters(LogLevel.Debug, string.Format("{0} recent tweet{1} returned from the Twitter API.", recentTweets != null && recentTweets.Tweets != null ? recentTweets.Tweets.Count : "0", recentTweets == null || recentTweets.Tweets == null || recentTweets.Tweets.Count() != 1 ? "s" : ""), recentTweetSearchParameters); if (recentTweets == null || recentTweets.Tweets == null || recentTweets.Tweets.Count == 0) { continue; } foreach (var recentTweet in recentTweets.Tweets) { // Go through each tweet returned from the Twitter API. var recentTweetParameters = new Dictionary <string, object>(); foreach (var param in recentTweetSearchParameters) { recentTweetParameters.Add(param.Key, param.Value); } recentTweetParameters.Add("Twitter API Tweet Id", recentTweet.Id); _logger.LogWithParameters(LogLevel.Information, "Start importing tweet", recentTweetParameters); if (recentTweet.ReferencedTweets != null && recentTweet.ReferencedTweets.Count > 0) { continue; } // Does tweet exist in the databse. var tweet = await TaskExtensions.CreateUpdateTweetAsync(scope, _logger, recentTweet, recentTweets.Includes.Users, recentTweets.Includes.Media); if (tweet == null) { continue; } // Keep a list of all the updated tweets. updatedTweetIds.Add(tweet.Id); // Update the dashboard & tweet relationship in the database. await TaskExtensions.CreateDashboardTweetRelationshipAsync(scope, _logger, dashboard.Id, tweet.Id); _logger.LogWithParameters(LogLevel.Debug, string.Format("Tweet has been added to Dashboard (id: '{0}').", dashboard.Id), recentTweetParameters); _logger.LogWithParameters(LogLevel.Information, "Finish importing tweet", recentTweetParameters); } } // Commit the transaction. await dbContextTransaction.CommitAsync(); } // Delete any tweets that have not been updated. if (_dashboard == null) { using (var dbContextTransaction = blashDbContext.Database.BeginTransaction()) { await dashboardTweetService.DeleteNonUpdatedTweetsAsync(updatedTweetIds); // Remove non-updated dashboard & tweet relationships. await tweetService.DeleteMissingTweetsFromDashboardAsync(); // Delete any tweets that don't belong to a dashboard. await authorService.DeleteMissingTweetsAsync(); // Delete any authors that don't belong to any tweets. await dbContextTransaction.CommitAsync(); // Commit the transaction to the database. } } // Send the updated information to the clients connected through SignalR await _blashHub.RecentTweetsSyncAsync(await ApiExtensions.GetDashboardAndTweetsAsync(dashboards, tweetService), _dashboard == null, _cancellationToken); } _logger.LogWithParameters(LogLevel.Information, "Finish importing recent tweets from Twitter API", parameters); }