diff --git a/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs index b5195f5c..d78c484a 100644 --- a/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs @@ -4,6 +4,25 @@ namespace Iceshrimp.Backend.Core.Extensions; public static class ExpressionExtensions { + public static Expression> True() => f => true; + public static Expression> False() => f => false; + + public static Expression> Or( + this Expression> expr1, Expression> expr2 + ) + { + var invokedExpr = Expression.Invoke(expr2, expr1.Parameters); + return Expression.Lambda>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); + } + + public static Expression> And( + this Expression> expr1, Expression> expr2 + ) + { + var invokedExpr = Expression.Invoke(expr2, expr1.Parameters); + return Expression.Lambda>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); + } + public static Expression> Compose( this Expression> first, Expression> second diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs index a8d7bfab..30b6eca3 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs @@ -21,14 +21,16 @@ public static class QueryableFtsExtensions var caseSensitivity = parsed.OfType().LastOrDefault()?.Value ?? CaseFilterType.Insensitive; var matchType = parsed.OfType().LastOrDefault()?.Value ?? MatchFilterType.Substring; + query = query.ApplyFromFilters(parsed.OfType().ToList(), config, db); + return parsed.Aggregate(query, (current, filter) => filter switch { CaseFilter => current, MatchFilter => current, + FromFilter => current, AfterFilter afterFilter => current.ApplyAfterFilter(afterFilter), AttachmentFilter attachmentFilter => current.ApplyAttachmentFilter(attachmentFilter), BeforeFilter beforeFilter => current.ApplyBeforeFilter(beforeFilter), - FromFilter fromFilter => current.ApplyFromFilter(fromFilter, config, db), InFilter inFilter => current.ApplyInFilter(inFilter, user, db), InstanceFilter instanceFilter => current.ApplyInstanceFilter(instanceFilter, config), MentionFilter mentionFilter => current.ApplyMentionFilter(mentionFilter, config, db), @@ -75,10 +77,16 @@ public static class QueryableFtsExtensions this IQueryable query, MultiWordFilter filter, CaseFilterType caseSensitivity, MatchFilterType matchType ) => query.Where(p => p.FtsQueryOneOf(filter.Values, caseSensitivity, matchType)); - [Projectable] - private static IQueryable ApplyFromFilter( - this IQueryable query, FromFilter filter, Config.InstanceSection config, DatabaseContext db - ) => query.Where(p => p.User.UserSubqueryMatches(filter.Value, filter.Negated, config, db)); + private static IQueryable ApplyFromFilters( + this IQueryable query, List filters, Config.InstanceSection config, DatabaseContext db + ) + { + if (filters.Count == 0) return query; + var expr = ExpressionExtensions.False(); + expr = filters.Aggregate(expr, (current, filter) => current + .Or(p => p.User.UserSubqueryMatches(filter.Value, filter.Negated, config, db))); + return query.Where(expr); + } [Projectable] private static IQueryable ApplyInstanceFilter( @@ -105,7 +113,9 @@ public static class QueryableFtsExtensions ? query.ApplyInBookmarksFilter(user, filter.Negated, db) : filter.Value.Equals(InFilterType.Likes) ? query.ApplyInLikesFilter(user, filter.Negated, db) - : query.ApplyInReactionsFilter(user, filter.Negated, db); + : filter.Value.Equals(InFilterType.Reactions) + ? query.ApplyInReactionsFilter(user, filter.Negated, db) + : query.ApplyInInteractionsFilter(user, filter.Negated, db); [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] @@ -131,6 +141,14 @@ public static class QueryableFtsExtensions ? !db.Users.First(u => u == user).HasReacted(p) : db.Users.First(u => u == user).HasReacted(p)); + [Projectable] + [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] + internal static IQueryable ApplyInInteractionsFilter( + this IQueryable query, User user, bool negated, DatabaseContext db + ) => query.Where(p => negated + ? !db.Users.First(u => u == user).HasInteractedWith(p) + : db.Users.First(u => u == user).HasInteractedWith(p)); + [Projectable] private static IQueryable ApplyMiscFilter( this IQueryable query, MiscFilter filter, User user diff --git a/Iceshrimp.Parsing/SearchQuery.fs b/Iceshrimp.Parsing/SearchQuery.fs index b8391369..0bb3340d 100644 --- a/Iceshrimp.Parsing/SearchQuery.fs +++ b/Iceshrimp.Parsing/SearchQuery.fs @@ -63,6 +63,7 @@ module SearchQueryFilters = | Bookmarks | Likes | Reactions + | Interactions type InFilter(neg: bool, value: string) = inherit Filter() @@ -75,6 +76,7 @@ module SearchQueryFilters = | "favorites" -> Likes | "favourites" -> Likes | "reactions" -> Reactions + | "interactions" -> Interactions | _ -> failwith $"Invalid type: {value}" @@ -204,7 +206,7 @@ module private SearchQueryParser = <| fun n v -> MiscFilter(n.IsSome, v) :> Filter let inFilter = - negKeyFilter [ "in" ] [ "bookmarks"; "favorites"; "favourites"; "reactions"; "likes" ] + negKeyFilter [ "in" ] [ "bookmarks"; "favorites"; "favourites"; "reactions"; "likes"; "interactions" ] <| fun n v -> InFilter(n.IsSome, v) :> Filter let attachmentFilter = diff --git a/Iceshrimp.Tests/Parsing/SearchQueryTests.cs b/Iceshrimp.Tests/Parsing/SearchQueryTests.cs index e4e3e3a9..9f999f40 100644 --- a/Iceshrimp.Tests/Parsing/SearchQueryTests.cs +++ b/Iceshrimp.Tests/Parsing/SearchQueryTests.cs @@ -138,7 +138,7 @@ public class SearchQueryTests public void TestParseIn(bool negated) { var key = negated ? "-in" : "in"; - List candidates = ["bookmarks", "likes", "favorites", "favourites", "reactions"]; + List candidates = ["bookmarks", "likes", "favorites", "favourites", "reactions", "interactions"]; var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList(); List expectedResults = [ @@ -146,7 +146,8 @@ public class SearchQueryTests new InFilter(negated, "likes"), new InFilter(negated, "likes"), new InFilter(negated, "likes"), - new InFilter(negated, "reactions") + new InFilter(negated, "reactions"), + new InFilter(negated, "interactions") ]; results.Should() .HaveCount(expectedResults.Count)