diff --git a/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs new file mode 100644 index 00000000..b3dd47b7 --- /dev/null +++ b/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs @@ -0,0 +1,27 @@ +using System.Linq.Expressions; + +namespace Iceshrimp.Backend.Core.Extensions; + +public static class ExpressionExtensions { + public static Expression> Compose( + this Expression> first, + Expression> second + ) { + var param = Expression.Parameter(typeof(TFirstParam), "param"); + + var newFirst = first.Body.Replace(first.Parameters[0], param); + var newSecond = second.Body.Replace(second.Parameters[0], newFirst); + + return Expression.Lambda>(newSecond, param); + } + + private static Expression Replace(this Expression expression, Expression searchEx, Expression replaceEx) { + return new ReplaceVisitor(searchEx, replaceEx).Visit(expression) ?? throw new NullReferenceException(); + } + + private class ReplaceVisitor(Expression from, Expression to) : ExpressionVisitor { + public override Expression? Visit(Expression? node) { + return node == from ? to : base.Visit(node); + } + } +} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index bc22acff..0b65c001 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -1,3 +1,4 @@ +using System.Linq.Expressions; using Iceshrimp.Backend.Controllers.Attributes; using Iceshrimp.Backend.Controllers.Mastodon.Renderers; using Iceshrimp.Backend.Controllers.Mastodon.Schemas; @@ -100,11 +101,17 @@ public static class QueryableExtensions { } public static IQueryable EnsureVisibleFor(this IQueryable query, User? user) { - if (user == null) - return query.Where(note => note.VisibilityIsPublicOrHome) - .Where(note => !note.LocalOnly); + return user == null + ? query.Where(note => note.VisibilityIsPublicOrHome && !note.LocalOnly) + : query.Where(note => note.IsVisibleFor(user)); + } - return query.Where(note => note.IsVisibleFor(user)); + public static IQueryable EnsureNoteVisibilityFor( + this IQueryable query, Expression> predicate, User? user + ) { + return query.Where(user == null + ? predicate.Compose(p => p == null || (p.VisibilityIsPublicOrHome && !p.LocalOnly)) + : predicate.Compose(p => p == null || p.IsVisibleFor(user))); } public static IQueryable PrecomputeVisibilities(this IQueryable query, User? user) { @@ -128,6 +135,29 @@ public static class QueryableExtensions { (!note.Reply.User.IsBlockedBy(user) && !note.Reply.User.IsBlocking(user))); } + public static IQueryable FilterBlocked( + this IQueryable query, Expression> predicate, User? user + ) { + return user == null ? query : query.Where(predicate.Compose(p => p == null || !p.IsBlocking(user))); + } + + public static IQueryable FilterBlocked( + this IQueryable query, Expression> predicate, User? user + ) { + if (user == null) + return query; + + return query.Where(predicate.Compose(note => note == null || + (!note.User.IsBlocking(user) && + !note.User.IsBlockedBy(user) && + (note.Renote == null || + (!note.Renote.User.IsBlockedBy(user) && + !note.Renote.User.IsBlocking(user))) && + (note.Reply == null || + (!note.Reply.User.IsBlockedBy(user) && + !note.Reply.User.IsBlocking(user)))))); + } + public static IQueryable FilterMuted(this IQueryable query, User user) { //TODO: handle muted instances