[backend/masto-client] Correctly handle offset pagination (ISH-358)
This commit is contained in:
parent
fc2dcb5e97
commit
c46a5bd93d
3 changed files with 44 additions and 6 deletions
|
@ -1,3 +1,4 @@
|
|||
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
|
||||
using Iceshrimp.Backend.Core.Database;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
@ -5,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
|
|||
|
||||
namespace Iceshrimp.Backend.Controllers.Attributes;
|
||||
|
||||
public class LinkPaginationAttribute(int defaultLimit, int maxLimit) : ActionFilterAttribute
|
||||
public class LinkPaginationAttribute(int defaultLimit, int maxLimit, bool offset = false) : ActionFilterAttribute
|
||||
{
|
||||
public int DefaultLimit => defaultLimit;
|
||||
public int MaxLimit => maxLimit;
|
||||
|
@ -39,14 +40,23 @@ public class LinkPaginationAttribute(int defaultLimit, int maxLimit) : ActionFil
|
|||
var limit = Math.Min(query.Limit ?? defaultLimit, maxLimit);
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
var mpq = query as MastodonPaginationQuery;
|
||||
var offsetPg = offset || mpq is { Offset: not null, MaxId: null, MinId: null, SinceId: null };
|
||||
if (ids.Count >= limit)
|
||||
{
|
||||
var next = new QueryBuilder { { "limit", limit.ToString() }, { "max_id", ids.Last() } };
|
||||
var next = offsetPg
|
||||
? new QueryBuilder { { "offset", ((mpq?.Offset ?? 0) + limit).ToString() } }
|
||||
: new QueryBuilder { { "limit", limit.ToString() }, { "max_id", ids.Last() } };
|
||||
|
||||
links.Add($"<{GetUrl(request, next.ToQueryString())}>; rel=\"next\"");
|
||||
}
|
||||
|
||||
var prev = new QueryBuilder { { "limit", limit.ToString() }, { "min_id", ids.First() } };
|
||||
links.Add($"<{GetUrl(request, prev.ToQueryString())}>; rel=\"prev\"");
|
||||
var prev = offsetPg
|
||||
? new QueryBuilder { { "offset", Math.Max(0, (mpq?.Offset ?? 0) - limit).ToString() } }
|
||||
: new QueryBuilder { { "limit", limit.ToString() }, { "min_id", ids.First() } };
|
||||
|
||||
if (!offsetPg || (mpq?.Offset ?? 0) != 0)
|
||||
links.Add($"<{GetUrl(request, prev.ToQueryString())}>; rel=\"prev\"");
|
||||
|
||||
context.HttpContext.Response.Headers.Link = string.Join(", ", links);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class SearchController(
|
|||
|
||||
[HttpGet("/api/v1/accounts/search")]
|
||||
[Authorize("read:accounts")]
|
||||
[LinkPagination(20, 40)]
|
||||
[LinkPagination(20, 40, true)]
|
||||
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<AccountEntity>))]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound, Type = typeof(MastodonErrorResponse))]
|
||||
public async Task<IActionResult> SearchAccounts(
|
||||
|
@ -133,8 +133,8 @@ public class SearchController(
|
|||
.Where(p => p.DisplayNameOrUsernameOrFqnContainsCaseInsensitive(search.Query!,
|
||||
config.Value.AccountDomain))
|
||||
.Where(p => !search.Following || p.IsFollowedBy(user))
|
||||
.Paginate(pagination, ControllerContext) //TODO: this will mess up our sorting
|
||||
.OrderByDescending(p => p.NotesCount)
|
||||
.PaginateByOffset(pagination, ControllerContext)
|
||||
.RenderAllForMastodonAsync(userRenderer);
|
||||
}
|
||||
|
||||
|
|
|
@ -179,6 +179,34 @@ public static class QueryableExtensions
|
|||
return Paginate(query, pq, filter.DefaultLimit, filter.MaxLimit);
|
||||
}
|
||||
|
||||
public static IQueryable<T> PaginateByOffset<T>(
|
||||
this IQueryable<T> query,
|
||||
MastodonPaginationQuery pq,
|
||||
int defaultLimit,
|
||||
int maxLimit
|
||||
) where T : IEntity
|
||||
{
|
||||
if (pq.Limit is < 1)
|
||||
throw GracefulException.BadRequest("Limit cannot be less than 1");
|
||||
|
||||
return query.Skip(pq.Offset ?? 0).Take(Math.Min(pq.Limit ?? defaultLimit, maxLimit));
|
||||
}
|
||||
|
||||
public static IQueryable<T> PaginateByOffset<T>(
|
||||
this IQueryable<T> query,
|
||||
MastodonPaginationQuery pq,
|
||||
ControllerContext context
|
||||
) where T : IEntity
|
||||
{
|
||||
var filter = context.ActionDescriptor.FilterDescriptors.Select(p => p.Filter)
|
||||
.OfType<LinkPaginationAttribute>()
|
||||
.FirstOrDefault();
|
||||
if (filter == null)
|
||||
throw new GracefulException("Route doesn't have a LinkPaginationAttribute");
|
||||
|
||||
return PaginateByOffset(query, pq, filter.DefaultLimit, filter.MaxLimit);
|
||||
}
|
||||
|
||||
public static IQueryable<T> Paginate<T>(
|
||||
this IQueryable<T> query,
|
||||
Expression<Func<T, string>> predicate,
|
||||
|
|
Loading…
Add table
Reference in a new issue