Ranked posts

This commit is contained in:
LittleSheep 2025-06-26 19:17:28 +08:00
parent d492c9ce1f
commit da58e10d88
2 changed files with 93 additions and 9 deletions

View File

@ -1,19 +1,43 @@
using DysonNetwork.Sphere.Account; using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Post; using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Publisher; using DysonNetwork.Sphere.Publisher;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using NodaTime; using NodaTime;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DysonNetwork.Sphere.Activity; 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) public async Task<List<Activity>> GetActivitiesForAnyone(int take, Instant? cursor)
{ {
var activities = new List<Activity>(); var activities = new List<Activity>();
// Crunching up data if (cursor == null)
var posts = await db.Posts {
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.RepliedPost)
.Include(e => e.ForwardedPost) .Include(e => e.ForwardedPost)
.Include(e => e.Categories) .Include(e => e.Categories)
@ -22,8 +46,9 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
.Where(p => cursor == null || p.PublishedAt < cursor) .Where(p => cursor == null || p.PublishedAt < cursor)
.OrderByDescending(p => p.PublishedAt) .OrderByDescending(p => p.PublishedAt)
.FilterWithVisibility(null, [], [], isListing: true) .FilterWithVisibility(null, [], [], isListing: true)
.Take(take) .Take(take * 5); // Fetch more posts to have a good pool for ranking
.ToListAsync();
var posts = await postsQuery.ToListAsync();
posts = await ps.LoadPostInfo(posts, null, true); posts = await ps.LoadPostInfo(posts, null, true);
var postsId = posts.Select(e => e.Id).ToList(); var postsId = posts.Select(e => e.Id).ToList();
@ -32,8 +57,17 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
post.ReactionsCount = post.ReactionsCount =
reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>(); 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 // Formatting data
foreach (var post in posts) foreach (var post in rankedPosts)
activities.Add(post.ToActivity()); activities.Add(post.ToActivity());
if (activities.Count == 0) if (activities.Count == 0)
@ -53,6 +87,15 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
var userFriends = await rels.ListAccountFriends(currentUser); var userFriends = await rels.ListAccountFriends(currentUser);
var userPublishers = await pub.GetUserPublishers(currentUser.Id); 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 // Get publishers based on filter
var filteredPublishers = filter switch var filteredPublishers = filter switch
{ {
@ -81,7 +124,7 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
// Complete the query with visibility filtering and execute // Complete the query with visibility filtering and execute
var posts = await postsQuery var posts = await postsQuery
.FilterWithVisibility(currentUser, userFriends, filter is null ? userPublishers : [], isListing: true) .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(); .ToListAsync();
posts = await ps.LoadPostInfo(posts, currentUser, true); 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()); 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 // Formatting data
foreach (var post in posts) foreach (var post in rankedPosts)
activities.Add(post.ToActivity()); activities.Add(post.ToActivity());
if (activities.Count == 0) if (activities.Count == 0)
@ -106,4 +158,4 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
return activities; return activities;
} }
} }

View 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,
};
}
}
}