diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs index 30b6eca3..0c81d53e 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs @@ -43,6 +43,8 @@ public static class QueryableFtsExtensions }); } + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static (string username, string? host) UserToTuple(string filter, Config.InstanceSection config) { filter = filter.TrimStart('@'); @@ -55,24 +57,22 @@ public static class QueryableFtsExtensions /// /// The input variable host, or null if it matches the configured web or account domain. /// + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static string? LocalDomainCheck(string? host, Config.InstanceSection config) => host == null || host == config.WebDomain || host == config.AccountDomain ? null : host; - [Projectable] private static IQueryable ApplyAfterFilter(this IQueryable query, AfterFilter filter) => query.Where(p => p.CreatedAt >= filter.Value.ToDateTime(TimeOnly.MinValue).ToUniversalTime()); - [Projectable] private static IQueryable ApplyBeforeFilter(this IQueryable query, BeforeFilter filter) => query.Where(p => p.CreatedAt < filter.Value.ToDateTime(TimeOnly.MinValue).ToUniversalTime()); - [Projectable] private static IQueryable ApplyWordFilter( this IQueryable query, WordFilter filter, CaseFilterType caseSensitivity, MatchFilterType matchType ) => query.Where(p => p.FtsQueryPreEscaped(PreEscapeFtsQuery(filter.Value, matchType), filter.Negated, caseSensitivity, matchType)); - [Projectable] private static IQueryable ApplyMultiWordFilter( this IQueryable query, MultiWordFilter filter, CaseFilterType caseSensitivity, MatchFilterType matchType ) => query.Where(p => p.FtsQueryOneOf(filter.Values, caseSensitivity, matchType)); @@ -88,170 +88,157 @@ public static class QueryableFtsExtensions return query.Where(expr); } - [Projectable] private static IQueryable ApplyInstanceFilter( this IQueryable query, InstanceFilter filter, Config.InstanceSection config ) => query.Where(p => filter.Negated ? p.UserHost != LocalDomainCheck(filter.Value, config) : p.UserHost == LocalDomainCheck(filter.Value, config)); - [Projectable] private static IQueryable ApplyMentionFilter( this IQueryable query, MentionFilter filter, Config.InstanceSection config, DatabaseContext db ) => query.Where(p => p.Mentions.UserSubqueryContains(filter.Value, filter.Negated, config, db)); - [Projectable] private static IQueryable ApplyReplyFilter( this IQueryable query, ReplyFilter filter, Config.InstanceSection config, DatabaseContext db ) => query.Where(p => p.Reply != null && p.Reply.User.UserSubqueryMatches(filter.Value, filter.Negated, config, db)); - [Projectable] private static IQueryable ApplyInFilter( this IQueryable query, InFilter filter, User user, DatabaseContext db - ) => filter.Value.Equals(InFilterType.Bookmarks) - ? query.ApplyInBookmarksFilter(user, filter.Negated, db) - : filter.Value.Equals(InFilterType.Likes) - ? query.ApplyInLikesFilter(user, filter.Negated, db) - : filter.Value.Equals(InFilterType.Reactions) - ? query.ApplyInReactionsFilter(user, filter.Negated, db) - : query.ApplyInInteractionsFilter(user, filter.Negated, db); + ) + { + return filter.Value switch + { + { IsLikes: true } => query.ApplyInLikesFilter(user, filter.Negated, db), + { IsBookmarks: true } => query.ApplyInBookmarksFilter(user, filter.Negated, db), + { IsReactions: true } => query.ApplyInReactionsFilter(user, filter.Negated, db), + { IsInteractions: true } => query.ApplyInInteractionsFilter(user, filter.Negated, db), + _ => throw new ArgumentOutOfRangeException(nameof(filter), filter.Value, null) + }; + } - [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyInBookmarksFilter( + private static IQueryable ApplyInBookmarksFilter( this IQueryable query, User user, bool negated, DatabaseContext db ) => query.Where(p => negated ? !db.Users.First(u => u == user).HasBookmarked(p) : db.Users.First(u => u == user).HasBookmarked(p)); - [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyInLikesFilter( + private static IQueryable ApplyInLikesFilter( this IQueryable query, User user, bool negated, DatabaseContext db ) => query.Where(p => negated ? !db.Users.First(u => u == user).HasLiked(p) : db.Users.First(u => u == user).HasLiked(p)); - [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyInReactionsFilter( + private static IQueryable ApplyInReactionsFilter( this IQueryable query, User user, bool negated, DatabaseContext db ) => query.Where(p => negated ? !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( + private 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 - ) => filter.Value.Equals(MiscFilterType.Followers) - ? query.ApplyFollowersFilter(user, filter.Negated) - : filter.Value.Equals(MiscFilterType.Following) - ? query.ApplyFollowingFilter(user, filter.Negated) - : filter.Value.Equals(MiscFilterType.Replies) - ? query.ApplyRepliesFilter(filter.Negated) - : query.ApplyBoostsFilter(filter.Negated); + private static IQueryable ApplyMiscFilter(this IQueryable query, MiscFilter filter, User user) + { + return filter.Value switch + { + { IsFollowers: true } => query.ApplyFollowersFilter(user, filter.Negated), + { IsFollowing: true } => query.ApplyFollowingFilter(user, filter.Negated), + { IsRenotes: true } => query.ApplyRepliesFilter(filter.Negated), + { IsReplies: true } => query.ApplyBoostsFilter(filter.Negated), + _ => throw new ArgumentOutOfRangeException(nameof(filter)) + }; + } - [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyFollowersFilter( - this IQueryable query, User user, bool negated - ) => query.Where(p => negated - ? !p.User.IsFollowing(user) - : p.User.IsFollowing(user)); + private static IQueryable ApplyFollowersFilter(this IQueryable query, User user, bool negated) + => query.Where(p => negated ? !p.User.IsFollowing(user) : p.User.IsFollowing(user)); - [Projectable] [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyFollowingFilter( - this IQueryable query, User user, bool negated - ) => query.Where(p => negated - ? !p.User.IsFollowedBy(user) - : p.User.IsFollowedBy(user)); + private static IQueryable ApplyFollowingFilter(this IQueryable query, User user, bool negated) + => query.Where(p => negated ? !p.User.IsFollowedBy(user) : p.User.IsFollowedBy(user)); - [Projectable] - [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyRepliesFilter( - this IQueryable query, bool negated - ) => query.Where(p => negated - ? p.Reply == null - : p.Reply != null); + private static IQueryable ApplyRepliesFilter(this IQueryable query, bool negated) + => query.Where(p => negated ? p.Reply == null : p.Reply != null); - [Projectable] - [SuppressMessage("ReSharper", "EntityFramework.UnsupportedServerSideFunctionCall", Justification = "Projectables")] - internal static IQueryable ApplyBoostsFilter( - this IQueryable query, bool negated - ) => query.Where(p => negated - ? !p.IsPureRenote - : p.IsPureRenote); + private static IQueryable ApplyBoostsFilter(this IQueryable query, bool negated) + => query.Where(p => negated ? !p.IsPureRenote : p.IsPureRenote); - [Projectable] private static IQueryable ApplyAttachmentFilter(this IQueryable query, AttachmentFilter filter) => filter.Negated ? query.ApplyNegatedAttachmentFilter(filter) : query.ApplyRegularAttachmentFilter(filter); - [Projectable] - internal static IQueryable ApplyRegularAttachmentFilter(this IQueryable query, AttachmentFilter filter) - => AttachmentQuery(filter.Value) - ? filter.Value.Equals(AttachmentFilterType.Poll) - ? query.Where(p => p.HasPoll) - : query.Where(p => EF.Functions.ILike(p.RawAttachments, GetAttachmentILikeQuery(filter.Value))) - : filter.Value.Equals(AttachmentFilterType.Any) - ? query.Where(p => p.AttachedFileTypes.Count != 0) - : query.Where(p => p.AttachedFileTypes.Count != 0 && - (!EF.Functions.ILike(p.RawAttachments, - GetAttachmentILikeQuery(AttachmentFilterType.Image)) || - !EF.Functions.ILike(p.RawAttachments, - GetAttachmentILikeQuery(AttachmentFilterType.Video)) || - !EF.Functions.ILike(p.RawAttachments, - GetAttachmentILikeQuery(AttachmentFilterType.Audio)))); - - [Projectable] - internal static IQueryable ApplyNegatedAttachmentFilter(this IQueryable query, AttachmentFilter filter) - => AttachmentQuery(filter.Value) - ? filter.Value.Equals(AttachmentFilterType.Poll) - ? query.Where(p => !p.HasPoll) - : query.Where(p => !EF.Functions.ILike(p.RawAttachments, GetAttachmentILikeQuery(filter.Value))) - : filter.Value.Equals(AttachmentFilterType.Any) - ? query.Where(p => p.AttachedFileTypes.Count == 0) - : query.Where(p => EF.Functions - .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Image)) || - EF.Functions - .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Video)) || - EF.Functions - .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Audio))); - - internal static bool AttachmentQuery(AttachmentFilterType filter) + private static IQueryable ApplyRegularAttachmentFilter(this IQueryable query, AttachmentFilter filter) { - if (filter.Equals(AttachmentFilterType.Image)) - return true; - if (filter.Equals(AttachmentFilterType.Video)) - return true; - if (filter.Equals(AttachmentFilterType.Audio)) - return true; - if (filter.Equals(AttachmentFilterType.Poll)) - return true; - return false; + if (filter.Value.IsAny) + return query.Where(p => p.AttachedFileTypes.Count != 0); + if (filter.Value.IsPoll) + return query.Where(p => p.HasPoll); + + if (filter.Value.IsImage || filter.Value.IsVideo || filter.Value.IsAudio) + { + return query.Where(p => p.AttachedFileTypes.Count != 0 && + EF.Functions.ILike(p.RawAttachments, GetAttachmentILikeQuery(filter.Value))); + } + + if (filter.Value.IsFile) + { + return query.Where(p => p.AttachedFileTypes.Count != 0 && + (!EF.Functions.ILike(p.RawAttachments, + GetAttachmentILikeQuery(AttachmentFilterType.Image)) || + !EF.Functions.ILike(p.RawAttachments, + GetAttachmentILikeQuery(AttachmentFilterType.Video)) || + !EF.Functions.ILike(p.RawAttachments, + GetAttachmentILikeQuery(AttachmentFilterType.Audio)))); + } + + throw new ArgumentOutOfRangeException(nameof(filter), filter.Value, null); } + private static IQueryable ApplyNegatedAttachmentFilter(this IQueryable query, AttachmentFilter filter) + { + if (filter.Value.IsAny) + return query.Where(p => p.AttachedFileTypes.Count == 0); + if (filter.Value.IsPoll) + return query.Where(p => !p.HasPoll); + if (filter.Value.IsImage || filter.Value.IsVideo || filter.Value.IsAudio) + return query.Where(p => !EF.Functions.ILike(p.RawAttachments, GetAttachmentILikeQuery(filter.Value))); + + if (filter.Value.IsFile) + { + return query.Where(p => EF.Functions + .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Image)) || + EF.Functions + .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Video)) || + EF.Functions + .ILike(p.RawAttachments, GetAttachmentILikeQuery(AttachmentFilterType.Audio))); + } + + throw new ArgumentOutOfRangeException(nameof(filter), filter.Value, null); + } + + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static string GetAttachmentILikeQuery(AttachmentFilterType filter) { - if (filter.Equals(AttachmentFilterType.Image)) - return "%image/%"; - if (filter.Equals(AttachmentFilterType.Video)) - return "%video/%"; - if (filter.Equals(AttachmentFilterType.Audio)) - return "%audio/%"; - throw new ArgumentOutOfRangeException(nameof(filter)); + return filter switch + { + { IsImage: true } => "%image/%", + { IsVideo: true } => "%video/%", + { IsAudio: true } => "%audio/%", + _ => throw new ArgumentOutOfRangeException(nameof(filter), filter, null) + }; } [Projectable] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static bool UserSubqueryMatches( this User user, string filter, bool negated, Config.InstanceSection config, DatabaseContext db ) => negated @@ -259,6 +246,8 @@ public static class QueryableFtsExtensions : UserSubquery(UserToTuple(filter, config), db).Contains(user); [Projectable] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static bool UserSubqueryContains( this IEnumerable userIds, string filter, bool negated, Config.InstanceSection config, DatabaseContext db ) => negated @@ -266,10 +255,14 @@ public static class QueryableFtsExtensions : userIds.Any(p => p == UserSubquery(UserToTuple(filter, config), db).Select(i => i.Id).FirstOrDefault()); [Projectable] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static IQueryable UserSubquery((string username, string? host) filter, DatabaseContext db) => db.Users.Where(p => p.UsernameLower == filter.username && p.Host == filter.host); [Projectable] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static bool FtsQueryPreEscaped( this Note note, string query, bool negated, CaseFilterType caseSensitivity, MatchFilterType matchType ) => matchType.Equals(MatchFilterType.Substring) @@ -302,6 +295,8 @@ public static class QueryableFtsExtensions : EfHelpers.EscapeRegexQuery(query); [Projectable] + [SuppressMessage("ReSharper", "MemberCanBePrivate.Global", + Justification = "Projectable chain must have consistent visibility")] internal static bool FtsQueryOneOf( this Note note, IEnumerable words, CaseFilterType caseSensitivity, MatchFilterType matchType ) => words.Select(p => PreEscapeFtsQuery(p, matchType))