Compare commits
3 Commits
c1c17b5f4e
...
4b958a3c31
Author | SHA1 | Date | |
---|---|---|---|
|
4b958a3c31 | ||
|
1f9021d459 | ||
|
7ad9deaf70 |
@@ -1,7 +1,7 @@
|
||||
using DysonNetwork.Shared.Proto;
|
||||
using DysonNetwork.Sphere.WebReader;
|
||||
using DysonNetwork.Sphere.Discovery;
|
||||
using DysonNetwork.Sphere.Post;
|
||||
using DysonNetwork.Sphere.WebReader;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
@@ -27,58 +27,28 @@ public class ActivityService(
|
||||
public async Task<List<Activity>> GetActivitiesForAnyone(
|
||||
int take,
|
||||
Instant? cursor,
|
||||
HashSet<string>? debugInclude = null
|
||||
)
|
||||
HashSet<string>? debugInclude = null)
|
||||
{
|
||||
var activities = new List<Activity>();
|
||||
debugInclude ??= new HashSet<string>();
|
||||
|
||||
// Add realm discovery if needed
|
||||
if (cursor == null && (debugInclude.Contains("realms") || Random.Shared.NextDouble() < 0.2))
|
||||
{
|
||||
var realms = await ds.GetCommunityRealmAsync(null, 5, 0, true);
|
||||
if (realms.Count > 0)
|
||||
{
|
||||
activities.Add(new DiscoveryActivity(
|
||||
realms.Select(x => new DiscoveryItem("realm", x)).ToList()
|
||||
).ToActivity());
|
||||
}
|
||||
var realmActivity = await GetRealmDiscoveryActivity();
|
||||
if (realmActivity != null)
|
||||
activities.Add(realmActivity);
|
||||
}
|
||||
|
||||
// Add article discovery if needed
|
||||
if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2)
|
||||
{
|
||||
var recentFeedIds = await db.WebArticles
|
||||
.GroupBy(a => a.FeedId)
|
||||
.OrderByDescending(g => g.Max(a => a.PublishedAt))
|
||||
.Take(10) // Get recent 10 distinct feeds
|
||||
.Select(g => g.Key)
|
||||
.ToListAsync();
|
||||
|
||||
// For each feed, get one random article
|
||||
var recentArticles = new List<WebArticle>();
|
||||
var random = new Random();
|
||||
|
||||
foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next()))
|
||||
{
|
||||
var article = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.Where(a => a.FeedId == feedId)
|
||||
.OrderBy(_ => EF.Functions.Random())
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (article == null) continue;
|
||||
recentArticles.Add(article);
|
||||
if (recentArticles.Count >= 5) break; // Limit to 5 articles
|
||||
}
|
||||
|
||||
if (recentArticles.Count > 0)
|
||||
{
|
||||
activities.Add(new DiscoveryActivity(
|
||||
recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()
|
||||
).ToActivity());
|
||||
}
|
||||
var articleActivity = await GetArticleDiscoveryActivity();
|
||||
if (articleActivity != null)
|
||||
activities.Add(articleActivity);
|
||||
}
|
||||
|
||||
// Fetch a larger batch of recent posts to rank
|
||||
// Get and process posts
|
||||
var postsQuery = db.Posts
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
@@ -89,31 +59,13 @@ public class ActivityService(
|
||||
.Where(p => cursor == null || p.PublishedAt < cursor)
|
||||
.OrderByDescending(p => p.PublishedAt)
|
||||
.FilterWithVisibility(null, [], [], isListing: true)
|
||||
.Take(take * 5); // Fetch more posts to have a good pool for ranking
|
||||
.Take(take * 5);
|
||||
|
||||
var posts = await postsQuery.ToListAsync();
|
||||
posts = await ps.LoadPostInfo(posts, null, true);
|
||||
|
||||
var postsId = posts.Select(e => e.Id).ToList();
|
||||
var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
|
||||
foreach (var post in posts)
|
||||
post.ReactionsCount =
|
||||
reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>();
|
||||
|
||||
// Rank and sort
|
||||
// TODO: This feature is disabled for now
|
||||
/*
|
||||
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)
|
||||
activities.Add(post.ToActivity());
|
||||
var posts = await GetAndProcessPosts(postsQuery);
|
||||
posts = RankPosts(posts, take);
|
||||
|
||||
// Add posts to activities
|
||||
activities.AddRange(posts.Select(post => post.ToActivity()));
|
||||
|
||||
if (activities.Count == 0)
|
||||
activities.Add(Activity.Empty());
|
||||
@@ -126,135 +78,74 @@ public class ActivityService(
|
||||
Instant? cursor,
|
||||
Account currentUser,
|
||||
string? filter = null,
|
||||
HashSet<string>? debugInclude = null
|
||||
)
|
||||
HashSet<string>? debugInclude = null)
|
||||
{
|
||||
var activities = new List<Activity>();
|
||||
debugInclude ??= [];
|
||||
|
||||
// Get user's friends and publishers
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{
|
||||
AccountId = currentUser.Id
|
||||
});
|
||||
var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||
debugInclude ??= [];
|
||||
|
||||
// Add discovery activities if no specific filter is applied
|
||||
if (string.IsNullOrEmpty(filter))
|
||||
{
|
||||
// Add realm discovery if needed
|
||||
if (cursor == null && (debugInclude.Contains("realms") || Random.Shared.NextDouble() < 0.2))
|
||||
{
|
||||
var realms = await ds.GetCommunityRealmAsync(null, 5, 0, true);
|
||||
if (realms.Count > 0)
|
||||
{
|
||||
activities.Add(new DiscoveryActivity(
|
||||
realms.Select(x => new DiscoveryItem("realm", x)).ToList()
|
||||
).ToActivity());
|
||||
}
|
||||
var realmActivity = await GetRealmDiscoveryActivity();
|
||||
if (realmActivity != null)
|
||||
activities.Add(realmActivity);
|
||||
}
|
||||
|
||||
// Add publisher discovery if needed
|
||||
if (cursor == null && (debugInclude.Contains("publishers") || Random.Shared.NextDouble() < 0.2))
|
||||
{
|
||||
var popularPublishers = await GetPopularPublishers(5);
|
||||
if (popularPublishers.Count > 0)
|
||||
{
|
||||
activities.Add(new DiscoveryActivity(
|
||||
popularPublishers.Select(x => new DiscoveryItem("publisher", x)).ToList()
|
||||
).ToActivity());
|
||||
}
|
||||
var publisherActivity = await GetPublisherDiscoveryActivity();
|
||||
if (publisherActivity != null)
|
||||
activities.Add(publisherActivity);
|
||||
}
|
||||
|
||||
// Add article discovery if needed
|
||||
if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2)
|
||||
{
|
||||
var recentFeedIds = await db.WebArticles
|
||||
.GroupBy(a => a.FeedId)
|
||||
.OrderByDescending(g => g.Max(a => a.PublishedAt))
|
||||
.Take(10) // Get recent 10 distinct feeds
|
||||
.Select(g => g.Key)
|
||||
.ToListAsync();
|
||||
|
||||
// For each feed, get one random article
|
||||
var recentArticles = new List<WebArticle>();
|
||||
var random = new Random();
|
||||
|
||||
foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next()))
|
||||
{
|
||||
var article = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.Where(a => a.FeedId == feedId)
|
||||
.OrderBy(_ => EF.Functions.Random())
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (article == null) continue;
|
||||
recentArticles.Add(article);
|
||||
if (recentArticles.Count >= 5) break; // Limit to 5 articles
|
||||
}
|
||||
|
||||
if (recentArticles.Count > 0)
|
||||
{
|
||||
activities.Add(new DiscoveryActivity(
|
||||
recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()
|
||||
).ToActivity());
|
||||
}
|
||||
var articleActivity = await GetArticleDiscoveryActivity();
|
||||
if (articleActivity != null)
|
||||
activities.Add(articleActivity);
|
||||
}
|
||||
}
|
||||
|
||||
// Get publishers based on filter
|
||||
var filteredPublishers = filter switch
|
||||
{
|
||||
"subscriptions" => await pub.GetSubscribedPublishers(Guid.Parse(currentUser.Id)),
|
||||
"friends" => (await pub.GetUserPublishersBatch(userFriends)).SelectMany(x => x.Value)
|
||||
.DistinctBy(x => x.Id)
|
||||
.ToList(),
|
||||
_ => null
|
||||
};
|
||||
|
||||
var filteredPublishers = await GetFilteredPublishers(filter, currentUser, userFriends);
|
||||
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)
|
||||
.Include(e => e.Realm)
|
||||
.Where(e => e.RepliedPostId == null)
|
||||
.Where(p => cursor == null || p.PublishedAt < cursor)
|
||||
.OrderByDescending(p => p.PublishedAt)
|
||||
.AsQueryable();
|
||||
// Build and execute the posts query
|
||||
var postsQuery = BuildPostsQuery(cursor, filteredPublishersId);
|
||||
|
||||
// Apply visibility filtering and execute
|
||||
postsQuery = postsQuery
|
||||
.FilterWithVisibility(
|
||||
currentUser,
|
||||
userFriends,
|
||||
filter is null ? userPublishers : [],
|
||||
isListing: true)
|
||||
.Take(take * 5);
|
||||
|
||||
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 * 5) // Fetch more posts to have a good pool for ranking
|
||||
.ToListAsync();
|
||||
|
||||
posts = await ps.LoadPostInfo(posts, currentUser, true);
|
||||
|
||||
var postsId = posts.Select(e => e.Id).ToList();
|
||||
var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
|
||||
foreach (var post in posts)
|
||||
{
|
||||
post.ReactionsCount =
|
||||
reactionMaps.TryGetValue(post.Id, out var count) ? count : new Dictionary<string, int>();
|
||||
|
||||
// Track view for each post in the feed
|
||||
await ps.IncreaseViewCount(post.Id, currentUser.Id.ToString());
|
||||
}
|
||||
|
||||
// Rank and sort
|
||||
// TODO: This feature is disabled for now
|
||||
/*
|
||||
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
|
||||
// Get, process and rank posts
|
||||
var posts = await GetAndProcessPosts(
|
||||
postsQuery,
|
||||
currentUser,
|
||||
userFriends,
|
||||
userPublishers,
|
||||
trackViews: true);
|
||||
|
||||
posts = RankPosts(posts, take);
|
||||
|
||||
// Add posts to activities
|
||||
activities.AddRange(posts.Select(post => post.ToActivity()));
|
||||
|
||||
if (activities.Count == 0)
|
||||
@@ -263,11 +154,20 @@ public class ActivityService(
|
||||
return activities;
|
||||
}
|
||||
|
||||
private static double CalculatePopularity(List<Post.Post> posts)
|
||||
private static List<Post.Post> RankPosts(List<Post.Post> posts, int take)
|
||||
{
|
||||
var score = posts.Sum(p => p.Upvotes - p.Downvotes);
|
||||
var postCount = posts.Count;
|
||||
return score + postCount;
|
||||
// TODO: This feature is disabled for now
|
||||
// Uncomment and implement when ready
|
||||
/*
|
||||
var now = SystemClock.Instance.GetCurrentInstant();
|
||||
return posts
|
||||
.Select(p => new { Post = p, Rank = CalculateHotRank(p, now) })
|
||||
.OrderByDescending(x => x.Rank)
|
||||
.Select(x => x.Post)
|
||||
.Take(take)
|
||||
.ToList();
|
||||
*/
|
||||
return posts.Take(take).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<Publisher.Publisher>> GetPopularPublishers(int take)
|
||||
@@ -282,7 +182,7 @@ public class ActivityService(
|
||||
var publisherIds = posts.Select(p => p.PublisherId).Distinct().ToList();
|
||||
var publishers = await db.Publishers.Where(p => publisherIds.Contains(p.Id)).ToListAsync();
|
||||
|
||||
var rankedPublishers = publishers
|
||||
return publishers
|
||||
.Select(p => new
|
||||
{
|
||||
Publisher = p,
|
||||
@@ -292,7 +192,121 @@ public class ActivityService(
|
||||
.Select(x => x.Publisher)
|
||||
.Take(take)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
return rankedPublishers;
|
||||
private async Task<Activity?> GetRealmDiscoveryActivity(int count = 5)
|
||||
{
|
||||
var realms = await ds.GetCommunityRealmAsync(null, count, 0, true);
|
||||
return realms.Count > 0
|
||||
? new DiscoveryActivity(realms.Select(x => new DiscoveryItem("realm", x)).ToList()).ToActivity()
|
||||
: null;
|
||||
}
|
||||
|
||||
private async Task<Activity?> GetPublisherDiscoveryActivity(int count = 5)
|
||||
{
|
||||
var popularPublishers = await GetPopularPublishers(count);
|
||||
return popularPublishers.Count > 0
|
||||
? new DiscoveryActivity(popularPublishers.Select(x => new DiscoveryItem("publisher", x)).ToList()).ToActivity()
|
||||
: null;
|
||||
}
|
||||
|
||||
private async Task<Activity?> GetArticleDiscoveryActivity(int count = 5, int feedSampleSize = 10)
|
||||
{
|
||||
var recentFeedIds = await db.WebArticles
|
||||
.GroupBy(a => a.FeedId)
|
||||
.OrderByDescending(g => g.Max(a => a.PublishedAt))
|
||||
.Take(feedSampleSize)
|
||||
.Select(g => g.Key)
|
||||
.ToListAsync();
|
||||
|
||||
var recentArticles = new List<WebArticle>();
|
||||
var random = new Random();
|
||||
|
||||
foreach (var feedId in recentFeedIds.OrderBy(_ => random.Next()))
|
||||
{
|
||||
var article = await db.WebArticles
|
||||
.Include(a => a.Feed)
|
||||
.Where(a => a.FeedId == feedId)
|
||||
.OrderBy(_ => EF.Functions.Random())
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (article == null) continue;
|
||||
recentArticles.Add(article);
|
||||
if (recentArticles.Count >= count) break;
|
||||
}
|
||||
|
||||
return recentArticles.Count > 0
|
||||
? new DiscoveryActivity(recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()).ToActivity()
|
||||
: null;
|
||||
}
|
||||
|
||||
private async Task<List<Post.Post>> GetAndProcessPosts(
|
||||
IQueryable<Post.Post> baseQuery,
|
||||
Account? currentUser = null,
|
||||
List<Guid>? userFriends = null,
|
||||
List<Publisher.Publisher>? userPublishers = null,
|
||||
bool trackViews = true)
|
||||
{
|
||||
var posts = await baseQuery.ToListAsync();
|
||||
posts = await ps.LoadPostInfo(posts, currentUser, true);
|
||||
|
||||
var postsId = posts.Select(e => e.Id).ToList();
|
||||
var reactionMaps = await ps.GetPostReactionMapBatch(postsId);
|
||||
|
||||
foreach (var post in posts)
|
||||
{
|
||||
post.ReactionsCount = reactionMaps.GetValueOrDefault(post.Id, new Dictionary<string, int>());
|
||||
|
||||
if (trackViews && currentUser != null)
|
||||
{
|
||||
await ps.IncreaseViewCount(post.Id, currentUser.Id.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
private IQueryable<Post.Post> BuildPostsQuery(Instant? cursor, List<Guid>? filteredPublishersId = null)
|
||||
{
|
||||
var query = db.Posts
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.Include(e => e.Realm)
|
||||
.Where(e => e.RepliedPostId == null)
|
||||
.Where(p => cursor == null || p.PublishedAt < cursor)
|
||||
.OrderByDescending(p => p.PublishedAt)
|
||||
.AsQueryable();
|
||||
|
||||
if (filteredPublishersId != null && filteredPublishersId.Any())
|
||||
{
|
||||
query = query.Where(p => filteredPublishersId.Contains(p.PublisherId));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
private async Task<List<Publisher.Publisher>?> GetFilteredPublishers(
|
||||
string? filter,
|
||||
Account currentUser,
|
||||
List<Guid> userFriends)
|
||||
{
|
||||
return filter?.ToLower() switch
|
||||
{
|
||||
"subscriptions" => await pub.GetSubscribedPublishers(Guid.Parse(currentUser.Id)),
|
||||
"friends" => (await pub.GetUserPublishersBatch(userFriends))
|
||||
.SelectMany(x => x.Value)
|
||||
.DistinctBy(x => x.Id)
|
||||
.ToList(),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private static double CalculatePopularity(List<Post.Post> posts)
|
||||
{
|
||||
var score = posts.Sum(p => p.Upvotes - p.Downvotes);
|
||||
var postCount = posts.Count;
|
||||
return score + postCount;
|
||||
}
|
||||
}
|
||||
|
@@ -60,6 +60,7 @@
|
||||
<PackageReference Include="StackExchange.Redis" Version="2.8.41"/>
|
||||
<PackageReference Include="StackExchange.Redis.Extensions.AspNetCore" Version="11.0.0"/>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3"/>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.3" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="9.0.3"/>
|
||||
<PackageReference Include="System.ServiceModel.Syndication" Version="9.0.7" />
|
||||
<PackageReference Include="TencentCloudSDK.Tmt" Version="3.0.1276" />
|
||||
|
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
using Swashbuckle.AspNetCore.Annotations;
|
||||
using PublisherService = DysonNetwork.Sphere.Publisher.PublisherService;
|
||||
|
||||
namespace DysonNetwork.Sphere.Post;
|
||||
@@ -37,8 +38,39 @@ public class PostController(
|
||||
return Ok(posts);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves a paginated list of posts with optional filtering and sorting.
|
||||
/// </summary>
|
||||
/// <param name="includeReplies">Whether to include reply posts in the results. If false, only root posts are returned.</param>
|
||||
/// <param name="offset">The number of posts to skip for pagination.</param>
|
||||
/// <param name="take">The maximum number of posts to return (default: 20).</param>
|
||||
/// <param name="pubName">Filter posts by publisher name.</param>
|
||||
/// <param name="realmName">Filter posts by realm slug.</param>
|
||||
/// <param name="type">Filter posts by post type (as integer).</param>
|
||||
/// <param name="categories">Filter posts by category slugs.</param>
|
||||
/// <param name="tags">Filter posts by tag slugs.</param>
|
||||
/// <param name="queryTerm">Search term to filter posts by title, description, or content.</param>
|
||||
/// <param name="queryVector">If true, uses vector search with the query term. If false, performs a simple ILIKE search.</param>
|
||||
/// <param name="onlyMedia">If true, only returns posts that have attachments.</param>
|
||||
/// <param name="shuffle">If true, returns posts in random order. If false, orders by published/created date (newest first).</param>
|
||||
/// <returns>
|
||||
/// Returns an ActionResult containing a list of Post objects that match the specified criteria.
|
||||
/// Includes an X-Total header with the total count of matching posts before pagination.
|
||||
/// </returns>
|
||||
/// <response code="200">Returns the list of posts matching the criteria.</response>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<Post>))]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[SwaggerOperation(
|
||||
Summary = "Retrieves a paginated list of posts",
|
||||
Description = "Gets posts with various filtering and sorting options. Supports pagination and advanced search capabilities.",
|
||||
OperationId = "ListPosts",
|
||||
Tags = ["Posts"]
|
||||
)]
|
||||
[SwaggerResponse(StatusCodes.Status200OK, "Successfully retrieved the list of posts", typeof(List<Post>))]
|
||||
[SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request parameters")]
|
||||
public async Task<ActionResult<List<Post>>> ListPosts(
|
||||
[FromQuery(Name = "replies")] bool? includeReplies,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery(Name = "pub")] string? pubName = null,
|
||||
@@ -48,7 +80,6 @@ public class PostController(
|
||||
[FromQuery(Name = "tags")] List<string>? tags = null,
|
||||
[FromQuery(Name = "query")] string? queryTerm = null,
|
||||
[FromQuery(Name = "vector")] bool queryVector = false,
|
||||
[FromQuery(Name = "replies")] bool includeReplies = false,
|
||||
[FromQuery(Name = "media")] bool onlyMedia = false,
|
||||
[FromQuery(Name = "shuffle")] bool shuffle = false
|
||||
)
|
||||
@@ -60,7 +91,7 @@ public class PostController(
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
@@ -83,10 +114,15 @@ public class PostController(
|
||||
query = query.Where(p => p.Categories.Any(c => categories.Contains(c.Slug)));
|
||||
if (tags is { Count: > 0 })
|
||||
query = query.Where(p => p.Tags.Any(c => tags.Contains(c.Slug)));
|
||||
if (!includeReplies)
|
||||
query = query.Where(e => e.RepliedPostId == null);
|
||||
if (onlyMedia)
|
||||
query = query.Where(e => e.Attachments.Count > 0);
|
||||
|
||||
query = includeReplies switch
|
||||
{
|
||||
false => query.Where(e => e.RepliedPostId == null),
|
||||
true => query.Where(e => e.RepliedPostId != null),
|
||||
_ => query
|
||||
};
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(queryTerm))
|
||||
{
|
||||
@@ -106,10 +142,9 @@ public class PostController(
|
||||
var totalCount = await query
|
||||
.CountAsync();
|
||||
|
||||
if (shuffle)
|
||||
query = query.OrderBy(e => EF.Functions.Random());
|
||||
else
|
||||
query = query.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt);
|
||||
query = shuffle
|
||||
? query.OrderBy(e => EF.Functions.Random())
|
||||
: query.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt);
|
||||
|
||||
var posts = await query
|
||||
.Include(e => e.RepliedPost)
|
||||
@@ -134,7 +169,7 @@ public class PostController(
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
@@ -168,7 +203,7 @@ public class PostController(
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
@@ -193,59 +228,6 @@ public class PostController(
|
||||
return Ok(post);
|
||||
}
|
||||
|
||||
[HttpGet("search")]
|
||||
[Obsolete("Use the new ListPost API")]
|
||||
public async Task<ActionResult<List<Post>>> SearchPosts(
|
||||
[FromQuery] string query,
|
||||
[FromQuery] int offset = 0,
|
||||
[FromQuery] int take = 20,
|
||||
[FromQuery] bool useVector = true
|
||||
)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
return BadRequest("Search query cannot be empty");
|
||||
|
||||
HttpContext.Items.TryGetValue("CurrentUser", out var currentUserValue);
|
||||
var currentUser = currentUserValue as Account;
|
||||
List<Guid> userFriends = [];
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
var userPublishers = currentUser is null ? [] : await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||
|
||||
var queryable = db.Posts
|
||||
.FilterWithVisibility(currentUser, userFriends, userPublishers, isListing: true)
|
||||
.AsQueryable();
|
||||
if (useVector)
|
||||
queryable = queryable.Where(p => p.SearchVector.Matches(EF.Functions.ToTsQuery(query)));
|
||||
else
|
||||
queryable = queryable.Where(p =>
|
||||
(p.Title != null && EF.Functions.ILike(p.Title, $"%{query}%")) ||
|
||||
(p.Description != null && EF.Functions.ILike(p.Description, $"%{query}%")) ||
|
||||
(p.Content != null && EF.Functions.ILike(p.Content, $"%{query}%"))
|
||||
);
|
||||
|
||||
var totalCount = await queryable.CountAsync();
|
||||
|
||||
var posts = await queryable
|
||||
.Include(e => e.RepliedPost)
|
||||
.Include(e => e.ForwardedPost)
|
||||
.Include(e => e.Categories)
|
||||
.Include(e => e.Tags)
|
||||
.OrderByDescending(e => e.PublishedAt ?? e.CreatedAt)
|
||||
.Skip(offset)
|
||||
.Take(take)
|
||||
.ToListAsync();
|
||||
posts = await ps.LoadPostInfo(posts, currentUser, true);
|
||||
|
||||
Response.Headers["X-Total"] = totalCount.ToString();
|
||||
return Ok(posts);
|
||||
}
|
||||
|
||||
[HttpGet("{id:guid}/reactions")]
|
||||
public async Task<ActionResult<List<PostReaction>>> GetReactions(
|
||||
Guid id,
|
||||
@@ -279,7 +261,7 @@ public class PostController(
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
@@ -315,7 +297,7 @@ public class PostController(
|
||||
if (currentUser != null)
|
||||
{
|
||||
var friendsResponse = await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id });
|
||||
{ AccountId = currentUser.Id });
|
||||
userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
}
|
||||
|
||||
@@ -502,7 +484,7 @@ public class PostController(
|
||||
|
||||
var friendsResponse =
|
||||
await accounts.ListFriendsAsync(new ListRelationshipSimpleRequest
|
||||
{ AccountId = currentUser.Id.ToString() });
|
||||
{ AccountId = currentUser.Id.ToString() });
|
||||
var userFriends = friendsResponse.AccountsId.Select(Guid.Parse).ToList();
|
||||
var userPublishers = await pub.GetUserPublishers(Guid.Parse(currentUser.Id));
|
||||
|
||||
@@ -697,4 +679,4 @@ public class PostController(
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user