[backend/masto-client] Add pagination

This commit is contained in:
Laura Hausmann 2024-02-03 03:34:38 +01:00
parent af7a776337
commit 8e2a116ad9
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
5 changed files with 71 additions and 9 deletions

2
.gitignore vendored
View file

@ -98,3 +98,5 @@ Iceshrimp.Frontend/.yarn
Iceshrimp.Frontend/.pnp.*
*.DotSettings.user
TestResults/

View file

@ -23,13 +23,12 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen
[HttpGet("home")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Status>))]
public async Task<IActionResult> GetHomeTimeline() {
public async Task<IActionResult> GetHomeTimeline(PaginationQuery query) {
var user = HttpContext.GetOauthUser() ?? throw new GracefulException("Failed to get user from HttpContext");
var res = await db.Notes
.WithIncludes()
.FilterByFollowingAndOwn(user)
.OrderByIdDesc()
.Take(40)
.Paginate(query, 20, 40)
.RenderAllForMastodonAsync(noteRenderer);
return Ok(res);
@ -39,13 +38,12 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen
[HttpGet("public")]
[Produces("application/json")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(IEnumerable<Status>))]
public async Task<IActionResult> GetPublicTimeline() {
public async Task<IActionResult> GetPublicTimeline(PaginationQuery query) {
var res = await db.Notes
.WithIncludes()
.HasVisibility(Note.NoteVisibility.Public)
.OrderByIdDesc()
.Take(40)
.RenderAllForMastodonAsync(noteRenderer);
.WithIncludes()
.HasVisibility(Note.NoteVisibility.Public)
.Paginate(query, 20, 40)
.RenderAllForMastodonAsync(noteRenderer);
return Ok(res);
}

View file

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Mvc;
using B = Microsoft.AspNetCore.Mvc.BindPropertyAttribute;
namespace Iceshrimp.Backend.Controllers.Mastodon.Schemas;
public class PaginationQuery {
[FromQuery] [B(Name = "max_id")] public string? MaxId { get; set; }
[FromQuery] [B(Name = "since_id")] public string? SinceId { get; set; }
[FromQuery] [B(Name = "min_id")] public string? MinId { get; set; }
[FromQuery] [B(Name = "limit")] public int? Limit { get; set; }
}

View file

@ -1,6 +1,8 @@
using Iceshrimp.Backend.Controllers.Mastodon.Renderers;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas;
using Iceshrimp.Backend.Controllers.Mastodon.Schemas.Entities;
using Iceshrimp.Backend.Core.Database.Tables;
using Iceshrimp.Backend.Core.Middleware;
using Microsoft.EntityFrameworkCore;
namespace Iceshrimp.Backend.Core.Extensions;
@ -14,6 +16,30 @@ public static class NoteQueryableExtensions {
.ThenInclude(p => p != null ? p.User : null);
}
public static IQueryable<Note> Paginate(this IQueryable<Note> query, PaginationQuery p, int defaultLimit,
int maxLimit) {
if (p is { SinceId: not null, MinId: not null })
throw GracefulException.BadRequest("Can't use sinceId and minId params simultaneously");
query = p switch {
{ SinceId: not null, MaxId: not null } => query
.Where(note => note.Id.IsGreaterThan(p.SinceId) &&
note.Id.IsLessThan(p.MaxId))
.OrderByDescending(note => note.Id),
{ MinId: not null, MaxId: not null } => query
.Where(note => note.Id.IsGreaterThan(p.MinId) &&
note.Id.IsLessThan(p.MaxId))
.OrderBy(note => note.Id),
{ SinceId: not null } => query.Where(note => note.Id.IsGreaterThan(p.SinceId))
.OrderByDescending(note => note.Id),
{ MinId: not null } => query.Where(note => note.Id.IsGreaterThan(p.MinId)).OrderBy(note => note.Id),
{ MaxId: not null } => query.Where(note => note.Id.IsLessThan(p.MaxId)).OrderByDescending(note => note.Id),
_ => query
};
return query.Take(Math.Min(p.Limit ?? defaultLimit, maxLimit));
}
public static IQueryable<Note> HasVisibility(this IQueryable<Note> query, Note.NoteVisibility visibility) {
return query.Where(note => note.Visibility == visibility);
}

View file

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using EntityFrameworkCore.Projectables;
namespace Iceshrimp.Backend.Core.Extensions;
@ -15,3 +17,26 @@ public static class StringExtensions {
return new IdnMapping().GetUnicode(target);
}
}
[SuppressMessage("ReSharper", "StringCompareToIsCultureSpecific")]
public static class ProjectableStringExtensions {
[Projectable]
public static bool IsLessThan(this string a, string b) {
return a.CompareTo(b) < 0;
}
[Projectable]
public static bool IsLessOrEqualTo(this string a, string b) {
return a.CompareTo(b) <= 0;
}
[Projectable]
public static bool IsGreaterThan(this string a, string b) {
return a.CompareTo(b) > 0;
}
[Projectable]
public static bool IsGreaterOrEqualTo(this string a, string b) {
return a.CompareTo(b) >= 0;
}
}