✨ Ranked posts
This commit is contained in:
		| @@ -1,19 +1,43 @@ | ||||
|  | ||||
| using DysonNetwork.Sphere.Account; | ||||
| using DysonNetwork.Sphere.Discovery; | ||||
| using DysonNetwork.Sphere.Post; | ||||
| using DysonNetwork.Sphere.Publisher; | ||||
| using Microsoft.EntityFrameworkCore; | ||||
| using NodaTime; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Activity; | ||||
|  | ||||
| public class ActivityService(AppDatabase db, PublisherService pub, RelationshipService rels, PostService ps) | ||||
| public class ActivityService(AppDatabase db, PublisherService pub, RelationshipService rels, PostService ps, DiscoveryService ds) | ||||
| { | ||||
|     private double CalculateHotRank(Post.Post post, Instant now) | ||||
|     { | ||||
|         var score = post.Upvotes - post.Downvotes; | ||||
|         var postTime = post.PublishedAt ?? post.CreatedAt; | ||||
|         var hours = (now - postTime).TotalHours; | ||||
|         // Add 1 to score to prevent negative results for posts with more downvotes than upvotes | ||||
|         return (score + 1) / Math.Pow(hours + 2, 1.8); | ||||
|     } | ||||
|  | ||||
|     public async Task<List<Activity>> GetActivitiesForAnyone(int take, Instant? cursor) | ||||
|     { | ||||
|         var activities = new List<Activity>(); | ||||
|  | ||||
|         // Crunching up data | ||||
|         var posts = await db.Posts | ||||
|         if (cursor == null) | ||||
|         { | ||||
|             var realms = await ds.GetPublicRealmsAsync(null, null); | ||||
|             if (realms.Count > 0) | ||||
|             { | ||||
|                 activities.Add(new DiscoveryActivity("Explore Realms", realms.Cast<object>().ToList()).ToActivity()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Fetch a larger batch of recent posts to rank | ||||
|         var postsQuery = db.Posts | ||||
|             .Include(e => e.RepliedPost) | ||||
|             .Include(e => e.ForwardedPost) | ||||
|             .Include(e => e.Categories) | ||||
| @@ -22,8 +46,9 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|             .Where(p => cursor == null || p.PublishedAt < cursor) | ||||
|             .OrderByDescending(p => p.PublishedAt) | ||||
|             .FilterWithVisibility(null, [], [], isListing: true) | ||||
|             .Take(take) | ||||
|             .ToListAsync(); | ||||
|             .Take(take * 5); // Fetch more posts to have a good pool for ranking | ||||
|  | ||||
|         var posts = await postsQuery.ToListAsync(); | ||||
|         posts = await ps.LoadPostInfo(posts, null, true); | ||||
|  | ||||
|         var postsId = posts.Select(e => e.Id).ToList(); | ||||
| @@ -32,8 +57,17 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|             post.ReactionsCount = | ||||
|                 reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>(); | ||||
|  | ||||
|         // Rank and sort | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|         var rankedPosts = posts | ||||
|             .Select(p => new { Post = p, Rank = CalculateHotRank(p, now) }) | ||||
|             .OrderByDescending(x => x.Rank) | ||||
|             .Select(x => x.Post) | ||||
|             .Take(take) | ||||
|             .ToList(); | ||||
|  | ||||
|         // Formatting data | ||||
|         foreach (var post in posts) | ||||
|         foreach (var post in rankedPosts) | ||||
|             activities.Add(post.ToActivity()); | ||||
|  | ||||
|         if (activities.Count == 0) | ||||
| @@ -53,6 +87,15 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|         var userFriends = await rels.ListAccountFriends(currentUser); | ||||
|         var userPublishers = await pub.GetUserPublishers(currentUser.Id); | ||||
|  | ||||
|         if (cursor == null) | ||||
|         { | ||||
|             var realms = await ds.GetPublicRealmsAsync(null, null); | ||||
|             if (realms.Count > 0) | ||||
|             { | ||||
|                 activities.Add(new DiscoveryActivity("Explore Realms", realms.Cast<object>().ToList()).ToActivity()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Get publishers based on filter | ||||
|         var filteredPublishers = filter switch | ||||
|         { | ||||
| @@ -81,7 +124,7 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|         // Complete the query with visibility filtering and execute | ||||
|         var posts = await postsQuery | ||||
|             .FilterWithVisibility(currentUser, userFriends, filter is null ? userPublishers : [], isListing: true) | ||||
|             .Take(take) | ||||
|             .Take(take * 5) // Fetch more posts to have a good pool for ranking | ||||
|             .ToListAsync(); | ||||
|  | ||||
|         posts = await ps.LoadPostInfo(posts, currentUser, true); | ||||
| @@ -97,8 +140,17 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|             await ps.IncreaseViewCount(post.Id, currentUser.Id.ToString()); | ||||
|         } | ||||
|  | ||||
|         // Rank and sort | ||||
|         var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|         var rankedPosts = posts | ||||
|             .Select(p => new { Post = p, Rank = CalculateHotRank(p, now) }) | ||||
|             .OrderByDescending(x => x.Rank) | ||||
|             .Select(x => x.Post) | ||||
|             .Take(take) | ||||
|             .ToList(); | ||||
|  | ||||
|         // Formatting data | ||||
|         foreach (var post in posts) | ||||
|         foreach (var post in rankedPosts) | ||||
|             activities.Add(post.ToActivity()); | ||||
|  | ||||
|         if (activities.Count == 0) | ||||
| @@ -106,4 +158,4 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS | ||||
|  | ||||
|         return activities; | ||||
|     } | ||||
| } | ||||
| } | ||||
|   | ||||
							
								
								
									
										32
									
								
								DysonNetwork.Sphere/Activity/DiscoveryActivity.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								DysonNetwork.Sphere/Activity/DiscoveryActivity.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using NodaTime; | ||||
|  | ||||
| namespace DysonNetwork.Sphere.Activity | ||||
| { | ||||
|     public class DiscoveryActivity : IActivity | ||||
|     { | ||||
|         public string Title { get; set; } | ||||
|         public List<object> Items { get; set; } | ||||
|  | ||||
|         public DiscoveryActivity(string title, List<object> items) | ||||
|         { | ||||
|             Title = title; | ||||
|             Items = items; | ||||
|         } | ||||
|  | ||||
|         public Activity ToActivity() | ||||
|         { | ||||
|             var now = SystemClock.Instance.GetCurrentInstant(); | ||||
|             return new Activity | ||||
|             { | ||||
|                 Id = Guid.NewGuid(), | ||||
|                 Type = "discovery", | ||||
|                 ResourceIdentifier = "discovery", | ||||
|                 Data = this, | ||||
|                 CreatedAt = now, | ||||
|                 UpdatedAt = now, | ||||
|             }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user