diff --git a/DysonNetwork.Sphere/Post/PostViewFlushHandler.cs b/DysonNetwork.Sphere/Post/PostViewFlushHandler.cs new file mode 100644 index 0000000..5572413 --- /dev/null +++ b/DysonNetwork.Sphere/Post/PostViewFlushHandler.cs @@ -0,0 +1,52 @@ +using DysonNetwork.Shared.Cache; +using Microsoft.EntityFrameworkCore; +using Quartz; + +namespace DysonNetwork.Sphere.Post; + +public class PostViewFlushHandler(IServiceProvider serviceProvider) : IFlushHandler +{ + public async Task FlushAsync(IReadOnlyList items) + { + using var scope = serviceProvider.CreateScope(); + var db = scope.ServiceProvider.GetRequiredService(); + var cache = scope.ServiceProvider.GetRequiredService(); + + // Group views by post + var postViews = items + .GroupBy(x => x.PostId) + .ToDictionary(g => g.Key, g => g.ToList()); + + // Calculate total views and unique views per post + foreach (var postId in postViews.Keys) + { + // Calculate unique views by distinct viewer IDs (not null) + var uniqueViews = postViews[postId] + .Where(v => !string.IsNullOrEmpty(v.ViewerId)) + .Select(v => v.ViewerId) + .Distinct() + .Count(); + + // Total views is just the count of all items for this post + var totalViews = postViews[postId].Count; + + // Update the post in the database + await db.Posts + .Where(p => p.Id == postId) + .ExecuteUpdateAsync(p => p + .SetProperty(x => x.ViewsTotal, x => x.ViewsTotal + totalViews) + .SetProperty(x => x.ViewsUnique, x => x.ViewsUnique + uniqueViews)); + + // Invalidate any cache entries for this post + await cache.RemoveAsync($"post:{postId}"); + } + } +} + +public class PostViewFlushJob(FlushBufferService fbs, PostViewFlushHandler hdl) : IJob +{ + public async Task Execute(IJobExecutionContext context) + { + await fbs.FlushAsync(hdl); + } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs index 521a9c6..a2f180d 100644 --- a/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs +++ b/DysonNetwork.Sphere/Startup/ScheduledJobsConfiguration.cs @@ -1,3 +1,4 @@ +using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.WebReader; using Quartz; @@ -16,15 +17,15 @@ public static class ScheduledJobsConfiguration .WithIdentity("AppDatabaseRecyclingTrigger") .WithCronSchedule("0 0 0 * * ?")); - // var postViewFlushJob = new JobKey("PostViewFlush"); - // q.AddJob(opts => opts.WithIdentity(postViewFlushJob)); - // q.AddTrigger(opts => opts - // .ForJob(postViewFlushJob) - // .WithIdentity("PostViewFlushTrigger") - // .WithSimpleSchedule(o => o - // .WithIntervalInMinutes(1) - // .RepeatForever()) - // ); + var postViewFlushJob = new JobKey("PostViewFlush"); + q.AddJob(opts => opts.WithIdentity(postViewFlushJob)); + q.AddTrigger(opts => opts + .ForJob(postViewFlushJob) + .WithIdentity("PostViewFlushTrigger") + .WithSimpleSchedule(o => o + .WithIntervalInMinutes(1) + .RepeatForever()) + ); var webFeedScraperJob = new JobKey("WebFeedScraper"); q.AddJob(opts => opts.WithIdentity(webFeedScraperJob)); diff --git a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs index f7c72d7..5286bdf 100644 --- a/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs +++ b/DysonNetwork.Sphere/Startup/ServiceCollectionExtensions.cs @@ -143,6 +143,7 @@ public static class ServiceCollectionExtensions public static IServiceCollection AddAppFlushHandlers(this IServiceCollection services) { services.AddSingleton(); + services.AddScoped(); return services; }