✨ Ranked posts
This commit is contained in:
parent
d492c9ce1f
commit
da58e10d88
@ -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)
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user