[backend/masto-client] Precompute reply/renote visibility in database query

This commit is contained in:
Laura Hausmann 2024-02-04 01:03:05 +01:00
parent c0dfce1e2c
commit 285c2ba531
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 42 additions and 8 deletions

View file

@ -35,6 +35,7 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen
.FilterBlocked(user) .FilterBlocked(user)
.FilterMuted(user) .FilterMuted(user)
.Paginate(query, ControllerContext) .Paginate(query, ControllerContext)
.PrecomputeVisibilities(user)
.RenderAllForMastodonAsync(noteRenderer); .RenderAllForMastodonAsync(noteRenderer);
return Ok(res); return Ok(res);
@ -53,6 +54,7 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen
.FilterBlocked(user) .FilterBlocked(user)
.FilterMuted(user) .FilterMuted(user)
.Paginate(query, ControllerContext) .Paginate(query, ControllerContext)
.PrecomputeVisibilities(user)
.RenderAllForMastodonAsync(noteRenderer); .RenderAllForMastodonAsync(noteRenderer);
return Ok(res); return Ok(res);

View file

@ -236,16 +236,30 @@ public class Note : IEntity {
[InverseProperty(nameof(UserNotePin.Note))] [InverseProperty(nameof(UserNotePin.Note))]
public virtual ICollection<UserNotePin> UserNotePins { get; set; } = new List<UserNotePin>(); public virtual ICollection<UserNotePin> UserNotePins { get; set; } = new List<UserNotePin>();
[NotMapped] public bool? PrecomputedIsReplyVisible { get; private set; } = false;
[NotMapped] public bool? PrecomputedIsRenoteVisible { get; private set; } = false;
[Key] [Key]
[Column("id")] [Column("id")]
[StringLength(32)] [StringLength(32)]
public string Id { get; set; } = null!; public string Id { get; set; } = null!;
[Projectable] [Projectable]
public bool IsVisibleFor(User user) => VisibilityIsPublicOrHome public bool IsVisibleFor(User? user) => VisibilityIsPublicOrHome || (user != null && IsVisibleForUser(user));
|| User == user
[Projectable]
public bool IsVisibleForUser(User user) => User == user
|| VisibleUserIds.Contains(user.Id) || VisibleUserIds.Contains(user.Id)
|| Mentions.Any(p => p == user.Id) || Mentions.Contains(user.Id)
|| (Visibility == NoteVisibility.Followers && || (Visibility == NoteVisibility.Followers &&
(User.IsFollowedBy(user) || ReplyUserId == user.Id)); (User.IsFollowedBy(user) || ReplyUserId == user.Id));
public Note WithPrecomputedVisibilities(bool reply, bool renote) {
if (Reply != null)
PrecomputedIsReplyVisible = reply;
if (Renote != null)
PrecomputedIsRenoteVisible = renote;
return this;
}
} }

View file

@ -84,6 +84,12 @@ public static class NoteQueryableExtensions {
return query.Where(note => note.IsVisibleFor(user)); return query.Where(note => note.IsVisibleFor(user));
} }
public static IQueryable<Note> PrecomputeVisibilities(this IQueryable<Note> query, User? user) {
return query.Select(p => p.WithPrecomputedVisibilities(p.Reply != null && p.Reply.IsVisibleFor(user),
p.Renote != null &&
p.Renote.IsVisibleFor(user)));
}
public static IQueryable<Note> FilterBlocked(this IQueryable<Note> query, User user) { public static IQueryable<Note> FilterBlocked(this IQueryable<Note> query, User user) {
return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user)) return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user))
.Where(note => note.Renote == null || .Where(note => note.Renote == null ||
@ -105,9 +111,21 @@ public static class NoteQueryableExtensions {
p.UserList.HideFromHomeTl)); p.UserList.HideFromHomeTl));
} }
public static IEnumerable<Note> EnforceRenoteReplyVisibility(this IList<Note> list) {
foreach (var note in list) {
if (!note.PrecomputedIsReplyVisible ?? false)
note.Reply = null;
if (!note.PrecomputedIsRenoteVisible ?? false)
note.Renote = null;
}
return list;
}
public static async Task<IEnumerable<Status>> RenderAllForMastodonAsync( public static async Task<IEnumerable<Status>> RenderAllForMastodonAsync(
this IQueryable<Note> notes, NoteRenderer renderer) { this IQueryable<Note> notes, NoteRenderer renderer) {
var list = await notes.ToListAsync(); var list = (await notes.ToListAsync())
.EnforceRenoteReplyVisibility();
return await renderer.RenderManyAsync(list); return await renderer.RenderManyAsync(list);
} }
} }

View file

@ -30,7 +30,7 @@ builder.Services.AddViteServices(options => {
options.Server.UseFullDevUrl = true; options.Server.UseFullDevUrl = true;
}); });
//TODO: single line only if there's no \n in the log msg (otherwise stacktraces don't work) //TODO: single line only if there's no \n in the log msg (otherwise stacktraces don't work)
builder.Services.AddLogging(logging => logging.AddSimpleConsole(options => { options.SingleLine = true; })); builder.Services.AddLogging(logging => logging.AddSimpleConsole(options => { options.SingleLine = false; }));
builder.Services.AddDatabaseContext(builder.Configuration); //TODO: maybe use a dbcontext factory? builder.Services.AddDatabaseContext(builder.Configuration); //TODO: maybe use a dbcontext factory?
builder.Services.AddRedis(builder.Configuration); builder.Services.AddRedis(builder.Configuration);
builder.Services.AddSlidingWindowRateLimiter(); builder.Services.AddSlidingWindowRateLimiter();