Filter on activities

This commit is contained in:
2025-06-21 22:21:20 +08:00
parent f1a47fd079
commit d1fb0b9b55
6 changed files with 129 additions and 14 deletions

View File

@ -6,7 +6,7 @@ namespace DysonNetwork.Sphere.Account;
public class RelationshipService(AppDatabase db, ICacheService cache) public class RelationshipService(AppDatabase db, ICacheService cache)
{ {
private const string UserFriendsCacheKeyPrefix = "user:friends:"; private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId) public async Task<bool> HasExistingRelationship(Guid accountId, Guid relatedId)
{ {

View File

@ -22,8 +22,11 @@ public class ActivityController(
/// Besides, when users are logged in, it will also mix the other kinds of data and who're plying to them. /// Besides, when users are logged in, it will also mix the other kinds of data and who're plying to them.
/// </summary> /// </summary>
[HttpGet] [HttpGet]
public async Task<ActionResult<List<Activity>>> ListActivities([FromQuery] string? cursor, public async Task<ActionResult<List<Activity>>> ListActivities(
[FromQuery] int take = 20) [FromQuery] string? cursor,
[FromQuery] string? filter,
[FromQuery] int take = 20
)
{ {
Instant? cursorTimestamp = null; Instant? cursorTimestamp = null;
if (!string.IsNullOrEmpty(cursor)) if (!string.IsNullOrEmpty(cursor))
@ -42,6 +45,6 @@ public class ActivityController(
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue); HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
return currentUserValue is not Account.Account currentUser return currentUserValue is not Account.Account currentUser
? Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp)) ? Ok(await acts.GetActivitiesForAnyone(take, cursorTimestamp))
: Ok(await acts.GetActivities(take, cursorTimestamp, currentUser)); : Ok(await acts.GetActivities(take, cursorTimestamp, currentUser, filter));
} }
} }

View File

@ -42,26 +42,57 @@ public class ActivityService(AppDatabase db, PublisherService pub, RelationshipS
return activities; return activities;
} }
public async Task<List<Activity>> GetActivities(int take, Instant? cursor, Account.Account currentUser) public async Task<List<Activity>> GetActivities(
int take,
Instant? cursor,
Account.Account currentUser,
string? filter = null
)
{ {
var activities = new List<Activity>(); var activities = new List<Activity>();
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);
var publishersId = userPublishers.Select(e => e.Id).ToList(); // Get publishers based on filter
List<Publisher.Publisher>? filteredPublishers = null;
switch (filter)
{
case "subscriptions":
filteredPublishers = await pub.GetSubscribedPublishers(currentUser.Id);
break;
case "friends":
{
filteredPublishers = (await pub.GetUserPublishersBatch(userFriends))
.SelectMany(x => x.Value)
.DistinctBy(x => x.Id)
.ToList();
break;
}
default:
break;
}
// Crunching data var filteredPublishersId = filteredPublishers?.Select(e => e.Id).ToList();
var posts = await db.Posts
// Build the query based on the filter
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)
.Include(e => e.Tags) .Include(e => e.Tags)
.Where(e => e.RepliedPostId == null || publishersId.Contains(e.RepliedPost!.PublisherId))
.Where(p => cursor == null || p.PublishedAt < cursor) .Where(p => cursor == null || p.PublishedAt < cursor)
.OrderByDescending(p => p.PublishedAt) .OrderByDescending(p => p.PublishedAt)
.FilterWithVisibility(currentUser, userFriends, userPublishers, isListing: true) .AsQueryable();
if (filteredPublishersId is not null)
postsQuery = postsQuery.Where(p => filteredPublishersId.Contains(p.PublisherId));
// 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)
.ToListAsync(); .ToListAsync();
posts = await ps.LoadPostInfo(posts, currentUser, true); posts = await ps.LoadPostInfo(posts, currentUser, true);
var postsId = posts.Select(e => e.Id).ToList(); var postsId = posts.Select(e => e.Id).ToList();

View File

@ -34,6 +34,7 @@ public partial class ChatService(
using var scope = scopeFactory.CreateScope(); using var scope = scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDatabase>(); var dbContext = scope.ServiceProvider.GetRequiredService<AppDatabase>();
var webReader = scope.ServiceProvider.GetRequiredService<Connection.WebReader.WebReaderService>(); var webReader = scope.ServiceProvider.GetRequiredService<Connection.WebReader.WebReaderService>();
var newChat = scope.ServiceProvider.GetRequiredService<ChatService>();
// Preview the links in the message // Preview the links in the message
var updatedMessage = await PreviewMessageLinkAsync(message, webReader); var updatedMessage = await PreviewMessageLinkAsync(message, webReader);
@ -62,7 +63,7 @@ public partial class ChatService(
logger.LogDebug($"Updated message {message.Id} with {embedsList.Count} link previews"); logger.LogDebug($"Updated message {message.Id} with {embedsList.Count} link previews");
// Notify clients of the updated message // Notify clients of the updated message
await DeliverMessageAsync( await newChat.DeliverMessageAsync(
dbMessage, dbMessage,
dbMessage.Sender, dbMessage.Sender,
dbMessage.ChatRoom, dbMessage.ChatRoom,

View File

@ -659,6 +659,5 @@ public static class PostQueryExtensions
.Where(e => e.Visibility != PostVisibility.Friends || .Where(e => e.Visibility != PostVisibility.Friends ||
(e.Publisher.AccountId != null && userFriends.Contains(e.Publisher.AccountId.Value)) || (e.Publisher.AccountId != null && userFriends.Contains(e.Publisher.AccountId.Value)) ||
publishersId.Contains(e.PublisherId)); publishersId.Contains(e.PublisherId));
;
} }
} }

View File

@ -9,6 +9,7 @@ namespace DysonNetwork.Sphere.Publisher;
public class PublisherService(AppDatabase db, FileReferenceService fileRefService, ICacheService cache) public class PublisherService(AppDatabase db, FileReferenceService fileRefService, ICacheService cache)
{ {
private const string UserPublishersCacheKey = "accounts:{0}:publishers"; private const string UserPublishersCacheKey = "accounts:{0}:publishers";
private const string UserPublishersBatchCacheKey = "accounts:batch:{0}:publishers";
public async Task<List<Publisher>> GetUserPublishers(Guid userId) public async Task<List<Publisher>> GetUserPublishers(Guid userId)
{ {
@ -34,6 +35,86 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic
return publishers; return publishers;
} }
public async Task<Dictionary<Guid, List<Publisher>>> GetUserPublishersBatch(List<Guid> userIds)
{
var result = new Dictionary<Guid, List<Publisher>>();
var missingIds = new List<Guid>();
// Try to get publishers from cache for each user
foreach (var userId in userIds)
{
var cacheKey = string.Format(UserPublishersCacheKey, userId);
var publishers = await cache.GetAsync<List<Publisher>>(cacheKey);
if (publishers != null)
result[userId] = publishers;
else
missingIds.Add(userId);
}
if (missingIds.Count <= 0) return result;
{
// Fetch missing data from database
var publisherMembers = await db.PublisherMembers
.Where(p => missingIds.Contains(p.AccountId))
.Select(p => new { p.AccountId, p.PublisherId })
.ToListAsync();
var publisherIds = publisherMembers.Select(p => p.PublisherId).Distinct().ToList();
var publishers = await db.Publishers
.Where(p => publisherIds.Contains(p.Id))
.ToListAsync();
// Group publishers by user id
foreach (var userId in missingIds)
{
var userPublisherIds = publisherMembers
.Where(p => p.AccountId == userId)
.Select(p => p.PublisherId)
.ToList();
var userPublishers = publishers
.Where(p => userPublisherIds.Contains(p.Id))
.ToList();
result[userId] = userPublishers;
// Cache individual results
var cacheKey = string.Format(UserPublishersCacheKey, userId);
await cache.SetAsync(cacheKey, userPublishers, TimeSpan.FromMinutes(5));
}
}
return result;
}
private const string SubscribedPublishersCacheKey = "accounts:{0}:subscribed-publishers";
public async Task<List<Publisher>> GetSubscribedPublishers(Guid userId)
{
var cacheKey = string.Format(SubscribedPublishersCacheKey, userId);
// Try to get publishers from the cache first
var publishers = await cache.GetAsync<List<Publisher>>(cacheKey);
if (publishers is not null)
return publishers;
// If not in cache, fetch from a database
var publishersId = await db.PublisherSubscriptions
.Where(p => p.AccountId == userId)
.Select(p => p.PublisherId)
.ToListAsync();
publishers = await db.Publishers
.Where(p => publishersId.Contains(p.Id))
.ToListAsync();
// Store in a cache for 5 minutes
await cache.SetAsync(cacheKey, publishers, TimeSpan.FromMinutes(5));
return publishers;
}
private const string PublisherMembersCacheKey = "publishers:{0}:members"; private const string PublisherMembersCacheKey = "publishers:{0}:members";
public async Task<List<PublisherMember>> GetPublisherMembers(Guid publisherId) public async Task<List<PublisherMember>> GetPublisherMembers(Guid publisherId)