diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
index fa1604d..315f491 100644
--- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
+++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj
@@ -60,6 +60,7 @@
+
diff --git a/DysonNetwork.Sphere/Post/PostController.cs b/DysonNetwork.Sphere/Post/PostController.cs
index c6ea9eb..d7a3af4 100644
--- a/DysonNetwork.Sphere/Post/PostController.cs
+++ b/DysonNetwork.Sphere/Post/PostController.cs
@@ -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);
}
+ ///
+ /// Retrieves a paginated list of posts with optional filtering and sorting.
+ ///
+ /// Whether to include reply posts in the results. If false, only root posts are returned.
+ /// The number of posts to skip for pagination.
+ /// The maximum number of posts to return (default: 20).
+ /// Filter posts by publisher name.
+ /// Filter posts by realm slug.
+ /// Filter posts by post type (as integer).
+ /// Filter posts by category slugs.
+ /// Filter posts by tag slugs.
+ /// Search term to filter posts by title, description, or content.
+ /// If true, uses vector search with the query term. If false, performs a simple ILIKE search.
+ /// If true, only returns posts that have attachments.
+ /// If true, returns posts in random order. If false, orders by published/created date (newest first).
+ ///
+ /// 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 the list of posts matching the criteria.
[HttpGet]
+ [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List))]
+ [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))]
+ [SwaggerResponse(StatusCodes.Status400BadRequest, "Invalid request parameters")]
public async Task>> 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? 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
)
@@ -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))
{
@@ -192,59 +228,6 @@ public class PostController(
return Ok(post);
}
- [HttpGet("search")]
- [Obsolete("Use the new ListPost API")]
- public async Task>> 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 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>> GetReactions(
Guid id,