[backend/masto-client] Precompute reply/renote visibility in database query
This commit is contained in:
parent
c0dfce1e2c
commit
285c2ba531
4 changed files with 42 additions and 8 deletions
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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();
|
||||||
|
|
Loading…
Add table
Reference in a new issue