[backend/core] Add in:interactions search query filter, allow multiple from: filters

This commit is contained in:
Laura Hausmann 2024-06-17 20:07:53 +02:00
parent af5e5752d5
commit 1d02bd7119
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
4 changed files with 49 additions and 9 deletions

View file

@ -4,6 +4,25 @@ namespace Iceshrimp.Backend.Core.Extensions;
public static class ExpressionExtensions
{
public static Expression<Func<T, bool>> True<T>() => f => true;
public static Expression<Func<T, bool>> False<T>() => f => false;
public static Expression<Func<T, bool>> Or<T>(
this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2
)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<T, bool>> And<T>(
this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2
)
{
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
}
public static Expression<Func<TFirstParam, TResult>> Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second

View file

@ -21,14 +21,16 @@ public static class QueryableFtsExtensions
var caseSensitivity = parsed.OfType<CaseFilter>().LastOrDefault()?.Value ?? CaseFilterType.Insensitive;
var matchType = parsed.OfType<MatchFilter>().LastOrDefault()?.Value ?? MatchFilterType.Substring;
query = query.ApplyFromFilters(parsed.OfType<FromFilter>().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<Note> query, MultiWordFilter filter, CaseFilterType caseSensitivity, MatchFilterType matchType
) => query.Where(p => p.FtsQueryOneOf(filter.Values, caseSensitivity, matchType));
[Projectable]
private static IQueryable<Note> ApplyFromFilter(
this IQueryable<Note> query, FromFilter filter, Config.InstanceSection config, DatabaseContext db
) => query.Where(p => p.User.UserSubqueryMatches(filter.Value, filter.Negated, config, db));
private static IQueryable<Note> ApplyFromFilters(
this IQueryable<Note> query, List<FromFilter> filters, Config.InstanceSection config, DatabaseContext db
)
{
if (filters.Count == 0) return query;
var expr = ExpressionExtensions.False<Note>();
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<Note> 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<Note> ApplyInInteractionsFilter(
this IQueryable<Note> 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<Note> ApplyMiscFilter(
this IQueryable<Note> query, MiscFilter filter, User user

View file

@ -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 =

View file

@ -138,7 +138,7 @@ public class SearchQueryTests
public void TestParseIn(bool negated)
{
var key = negated ? "-in" : "in";
List<string> candidates = ["bookmarks", "likes", "favorites", "favourites", "reactions"];
List<string> candidates = ["bookmarks", "likes", "favorites", "favourites", "reactions", "interactions"];
var results = candidates.Select(v => $"{key}:{v}").SelectMany(SearchQuery.parse).ToList();
List<Filter> 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)