diff --git a/Iceshrimp.Backend/Controllers/Mastodon/MastodonTimelineController.cs b/Iceshrimp.Backend/Controllers/Mastodon/MastodonTimelineController.cs index 31e0af2f..3c033018 100644 --- a/Iceshrimp.Backend/Controllers/Mastodon/MastodonTimelineController.cs +++ b/Iceshrimp.Backend/Controllers/Mastodon/MastodonTimelineController.cs @@ -35,6 +35,7 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen .FilterBlocked(user) .FilterMuted(user) .Paginate(query, ControllerContext) + .PrecomputeVisibilities(user) .RenderAllForMastodonAsync(noteRenderer); return Ok(res); @@ -53,6 +54,7 @@ public class MastodonTimelineController(DatabaseContext db, NoteRenderer noteRen .FilterBlocked(user) .FilterMuted(user) .Paginate(query, ControllerContext) + .PrecomputeVisibilities(user) .RenderAllForMastodonAsync(noteRenderer); return Ok(res); diff --git a/Iceshrimp.Backend/Core/Database/Tables/Note.cs b/Iceshrimp.Backend/Core/Database/Tables/Note.cs index de45fa58..1ef6530f 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/Note.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/Note.cs @@ -236,16 +236,30 @@ public class Note : IEntity { [InverseProperty(nameof(UserNotePin.Note))] public virtual ICollection UserNotePins { get; set; } = new List(); + [NotMapped] public bool? PrecomputedIsReplyVisible { get; private set; } = false; + [NotMapped] public bool? PrecomputedIsRenoteVisible { get; private set; } = false; + [Key] [Column("id")] [StringLength(32)] public string Id { get; set; } = null!; [Projectable] - public bool IsVisibleFor(User user) => VisibilityIsPublicOrHome - || User == user - || VisibleUserIds.Contains(user.Id) - || Mentions.Any(p => p == user.Id) - || (Visibility == NoteVisibility.Followers && - (User.IsFollowedBy(user) || ReplyUserId == user.Id)); + public bool IsVisibleFor(User? user) => VisibilityIsPublicOrHome || (user != null && IsVisibleForUser(user)); + + [Projectable] + public bool IsVisibleForUser(User user) => User == user + || VisibleUserIds.Contains(user.Id) + || Mentions.Contains(user.Id) + || (Visibility == NoteVisibility.Followers && + (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; + } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 8fc97611..75344069 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -84,6 +84,12 @@ public static class NoteQueryableExtensions { return query.Where(note => note.IsVisibleFor(user)); } + public static IQueryable PrecomputeVisibilities(this IQueryable 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 FilterBlocked(this IQueryable query, User user) { return query.Where(note => !note.User.IsBlocking(user) && !note.User.IsBlockedBy(user)) .Where(note => note.Renote == null || @@ -105,9 +111,21 @@ public static class NoteQueryableExtensions { p.UserList.HideFromHomeTl)); } + public static IEnumerable EnforceRenoteReplyVisibility(this IList 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> RenderAllForMastodonAsync( this IQueryable notes, NoteRenderer renderer) { - var list = await notes.ToListAsync(); + var list = (await notes.ToListAsync()) + .EnforceRenoteReplyVisibility(); return await renderer.RenderManyAsync(list); } } \ No newline at end of file diff --git a/Iceshrimp.Backend/Startup.cs b/Iceshrimp.Backend/Startup.cs index 4fc54f8c..ecb9a5e7 100644 --- a/Iceshrimp.Backend/Startup.cs +++ b/Iceshrimp.Backend/Startup.cs @@ -30,7 +30,7 @@ builder.Services.AddViteServices(options => { options.Server.UseFullDevUrl = true; }); //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.AddRedis(builder.Configuration); builder.Services.AddSlidingWindowRateLimiter();