Filter on activities

This commit is contained in:
LittleSheep 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)
{
private const string UserFriendsCacheKeyPrefix = "user:friends:";
private const string UserFriendsCacheKeyPrefix = "accounts:friends:";
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.
/// </summary>
[HttpGet]
public async Task<ActionResult<List<Activity>>> ListActivities([FromQuery] string? cursor,
[FromQuery] int take = 20)
public async Task<ActionResult<List<Activity>>> ListActivities(
[FromQuery] string? cursor,
[FromQuery] string? filter,
[FromQuery] int take = 20
)
{
Instant? cursorTimestamp = null;
if (!string.IsNullOrEmpty(cursor))
@ -42,6 +45,6 @@ public class ActivityController(
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
return currentUserValue is not Account.Account currentUser
? 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;
}
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 userFriends = await rels.ListAccountFriends(currentUser);
var userPublishers = await pub.GetUserPublishers(currentUser.Id);
var publishersId = userPublishers.Select(e => e.Id).ToList();
// Crunching data
var posts = await db.Posts
// 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;
}
var filteredPublishersId = filteredPublishers?.Select(e => e.Id).ToList();
// Build the query based on the filter
var postsQuery = db.Posts
.Include(e => e.RepliedPost)
.Include(e => e.ForwardedPost)
.Include(e => e.Categories)
.Include(e => e.Tags)
.Where(e => e.RepliedPostId == null || publishersId.Contains(e.RepliedPost!.PublisherId))
.Where(p => cursor == null || p.PublishedAt < cursor)
.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)
.ToListAsync();
posts = await ps.LoadPostInfo(posts, currentUser, true);
var postsId = posts.Select(e => e.Id).ToList();

View File

@ -34,6 +34,7 @@ public partial class ChatService(
using var scope = scopeFactory.CreateScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDatabase>();
var webReader = scope.ServiceProvider.GetRequiredService<Connection.WebReader.WebReaderService>();
var newChat = scope.ServiceProvider.GetRequiredService<ChatService>();
// Preview the links in the message
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");
// Notify clients of the updated message
await DeliverMessageAsync(
await newChat.DeliverMessageAsync(
dbMessage,
dbMessage.Sender,
dbMessage.ChatRoom,

View File

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

View File

@ -9,6 +9,7 @@ namespace DysonNetwork.Sphere.Publisher;
public class PublisherService(AppDatabase db, FileReferenceService fileRefService, ICacheService cache)
{
private const string UserPublishersCacheKey = "accounts:{0}:publishers";
private const string UserPublishersBatchCacheKey = "accounts:batch:{0}:publishers";
public async Task<List<Publisher>> GetUserPublishers(Guid userId)
{
@ -34,6 +35,86 @@ public class PublisherService(AppDatabase db, FileReferenceService fileRefServic
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";
public async Task<List<PublisherMember>> GetPublisherMembers(Guid publisherId)