diff --git a/DysonNetwork.Shared/Models/ActivityHeatmap.cs b/DysonNetwork.Shared/Models/ActivityHeatmap.cs new file mode 100644 index 0000000..b5aa8fb --- /dev/null +++ b/DysonNetwork.Shared/Models/ActivityHeatmap.cs @@ -0,0 +1,19 @@ +using NodaTime; + +namespace DysonNetwork.Shared.Models; + +public class ActivityHeatmap +{ + public string Unit { get; set; } = "posts"; + + public Instant PeriodStart { get; set; } + public Instant PeriodEnd { get; set; } + + public List Items { get; set; } = new(); +} + +public class ActivityHeatmapItem +{ + public Instant Date { get; set; } + public int Count { get; set; } +} \ No newline at end of file diff --git a/DysonNetwork.Sphere/Publisher/PublisherController.cs b/DysonNetwork.Sphere/Publisher/PublisherController.cs index d4cb862..0ec75fc 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherController.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherController.cs @@ -37,6 +37,14 @@ public class PublisherController( return Ok(publisher); } + + [HttpGet("{name}/heatmap")] + public async Task> GetPublisherHeatmap(string name) + { + var heatmap = await ps.GetPublisherHeatmap(name); + if (heatmap is null) return NotFound(); + return Ok(heatmap); + } [HttpGet("{name}/stats")] public async Task> GetPublisherStats(string name) @@ -693,4 +701,4 @@ public class PublisherController( return NoContent(); } -} \ No newline at end of file +} diff --git a/DysonNetwork.Sphere/Publisher/PublisherService.cs b/DysonNetwork.Sphere/Publisher/PublisherService.cs index f52ccfe..092889b 100644 --- a/DysonNetwork.Sphere/Publisher/PublisherService.cs +++ b/DysonNetwork.Sphere/Publisher/PublisherService.cs @@ -283,6 +283,7 @@ public class PublisherService( } private const string PublisherStatsCacheKey = "PublisherStats_{0}"; + private const string PublisherHeatmapCacheKey = "PublisherHeatmap_{0}"; private const string PublisherFeatureCacheKey = "PublisherFeature_{0}_{1}"; public async Task GetPublisherStats(string name) @@ -325,6 +326,45 @@ public class PublisherService( return stats; } + public async Task GetPublisherHeatmap(string name) + { + var cacheKey = string.Format(PublisherHeatmapCacheKey, name); + var heatmap = await cache.GetAsync(cacheKey); + if (heatmap is not null) + return heatmap; + + var publisher = await db.Publishers.FirstOrDefaultAsync(e => e.Name == name); + if (publisher is null) return null; + + var now = SystemClock.Instance.GetCurrentInstant(); + var periodStart = now.Minus(Duration.FromDays(365)); + var periodEnd = now; + + var postGroups = await db.Posts + .Where(p => p.PublisherId == publisher.Id && p.CreatedAt >= periodStart && p.CreatedAt <= periodEnd) + .Select(p => p.CreatedAt.InUtc().Date) + .GroupBy(d => d) + .Select(g => new { Date = g.Key, Count = g.Count() }) + .ToListAsync(); + + var items = postGroups.Select(p => new ActivityHeatmapItem + { + Date = p.Date.AtStartOfDayInZone(DateTimeZone.Utc).ToInstant(), + Count = p.Count + }).ToList(); + + heatmap = new ActivityHeatmap + { + Unit = "posts", + PeriodStart = periodStart, + PeriodEnd = periodEnd, + Items = items.OrderBy(i => i.Date).ToList() + }; + + await cache.SetAsync(cacheKey, heatmap, TimeSpan.FromMinutes(5)); + return heatmap; + } + public async Task SetFeatureFlag(Guid publisherId, string flag) { var featureFlag = await db.PublisherFeatures @@ -397,4 +437,4 @@ public class PublisherService( return m; })]; } -} \ No newline at end of file +}