From 8998cd28740116beb2dce59665dfc4fc2abff7e8 Mon Sep 17 00:00:00 2001 From: kopper Date: Wed, 9 Oct 2024 18:30:08 +0200 Subject: [PATCH] [backend] Also search in alt text --- .../Core/Database/DatabaseContext.cs | 3 ++ .../DatabaseContextModelSnapshot.cs | 9 ++++ ...40527231353_AddNoteCombinedAltTextField.cs | 46 +++++++++++++++++++ .../Core/Database/Tables/Note.cs | 5 +- .../Core/Extensions/QueryableFtsExtensions.cs | 31 +++++++------ .../Core/Services/NoteService.cs | 10 +++- 6 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20240527231353_AddNoteCombinedAltTextField.cs diff --git a/Iceshrimp.Backend/Core/Database/DatabaseContext.cs b/Iceshrimp.Backend/Core/Database/DatabaseContext.cs index 00a39057..7586cc30 100644 --- a/Iceshrimp.Backend/Core/Database/DatabaseContext.cs +++ b/Iceshrimp.Backend/Core/Database/DatabaseContext.cs @@ -636,6 +636,9 @@ public class DatabaseContext(DbContextOptions options) entity.HasIndex(e => e.Cw, "GIN_TRGM_note_cw") .HasMethod("gin") .HasOperators("gin_trgm_ops"); + entity.HasIndex(e => e.CombinedAltText, "GIN_TRGM_note_combined_alt_text") + .HasMethod("gin") + .HasOperators("gin_trgm_ops"); entity.Property(e => e.AttachedFileTypes).HasDefaultValueSql("'{}'::character varying[]"); entity.Property(e => e.ChannelId).HasComment("The ID of source channel."); diff --git a/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs b/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs index f928c71b..60bd7b24 100644 --- a/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs +++ b/Iceshrimp.Backend/Core/Database/Migrations/DatabaseContextModelSnapshot.cs @@ -2350,6 +2350,10 @@ namespace Iceshrimp.Backend.Core.Database.Migrations .HasColumnName("channelId") .HasComment("The ID of source channel."); + b.Property("CombinedAltText") + .HasColumnType("text") + .HasColumnName("combinedAltText"); + b.Property("CreatedAt") .HasColumnType("timestamp with time zone") .HasColumnName("createdAt") @@ -2597,6 +2601,11 @@ namespace Iceshrimp.Backend.Core.Database.Migrations b.HasIndex("UserId", "Id"); + b.HasIndex(new[] { "CombinedAltText" }, "GIN_TRGM_note_combined_alt_text"); + + NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "CombinedAltText" }, "GIN_TRGM_note_combined_alt_text"), "gin"); + NpgsqlIndexBuilderExtensions.HasOperators(b.HasIndex(new[] { "CombinedAltText" }, "GIN_TRGM_note_combined_alt_text"), new[] { "gin_trgm_ops" }); + b.HasIndex(new[] { "Cw" }, "GIN_TRGM_note_cw"); NpgsqlIndexBuilderExtensions.HasMethod(b.HasIndex(new[] { "Cw" }, "GIN_TRGM_note_cw"), "gin"); diff --git a/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20240527231353_AddNoteCombinedAltTextField.cs b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20240527231353_AddNoteCombinedAltTextField.cs new file mode 100644 index 00000000..ffb7e163 --- /dev/null +++ b/Iceshrimp.Backend/Core/Database/Migrations/v2024.1-beta4/20240527231353_AddNoteCombinedAltTextField.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Infrastructure; + +#nullable disable + +namespace Iceshrimp.Backend.Core.Database.Migrations +{ + /// + [DbContext(typeof(DatabaseContext))] + [Migration("20240527231353_AddNoteCombinedAltTextField")] + public partial class AddNoteCombinedAltTextField : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "combinedAltText", + table: "note", + type: "text", + nullable: true); + + Console.WriteLine("Indexing drive file alt text, please hang tight!"); + Console.WriteLine("This may take a long time (15-30 minutes), especially if your database is unusually large or you're running low end hardware."); + migrationBuilder.Sql("""UPDATE note SET "combinedAltText"=(SELECT string_agg(comment, ' ') FROM drive_file WHERE id = ANY ("fileIds")) WHERE "fileIds" != '{}';"""); + + migrationBuilder.CreateIndex( + name: "GIN_TRGM_note_combined_alt_text", + table: "note", + column: "combinedAltText") + .Annotation("Npgsql:IndexMethod", "gin") + .Annotation("Npgsql:IndexOperators", new[] { "gin_trgm_ops" }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "GIN_TRGM_note_combined_alt_text", + table: "note"); + + migrationBuilder.DropColumn( + name: "combinedAltText", + table: "note"); + } + } +} diff --git a/Iceshrimp.Backend/Core/Database/Tables/Note.cs b/Iceshrimp.Backend/Core/Database/Tables/Note.cs index 3b73744f..63a292c4 100644 --- a/Iceshrimp.Backend/Core/Database/Tables/Note.cs +++ b/Iceshrimp.Backend/Core/Database/Tables/Note.cs @@ -219,6 +219,9 @@ public class Note : IEntity [Column("repliesFetchedAt")] public DateTime? RepliesFetchedAt { get;set; } + [Column("combinedAltText")] + public string? CombinedAltText { get; set; } + [ForeignKey(nameof(ChannelId))] [InverseProperty(nameof(Tables.Channel.Notes))] public virtual Channel? Channel { get; set; } @@ -366,4 +369,4 @@ public class Note : IEntity [J("username")] public required string Username { get; set; } [J("host")] public required string? Host { get; set; } } -} \ No newline at end of file +} diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs index b33614e2..76583672 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs @@ -37,8 +37,7 @@ public static class QueryableFtsExtensions MiscFilter miscFilter => current.ApplyMiscFilter(miscFilter, user), ReplyFilter replyFilter => current.ApplyReplyFilter(replyFilter, config, db), WordFilter wordFilter => current.ApplyWordFilter(wordFilter, caseSensitivity, matchType), - MultiWordFilter multiWordFilter => - current.ApplyMultiWordFilter(multiWordFilter, caseSensitivity, matchType), + MultiWordFilter multiWordFilter => current.ApplyMultiWordFilter(multiWordFilter, caseSensitivity, matchType), _ => throw new ArgumentOutOfRangeException(nameof(filter)) }); } @@ -264,31 +263,37 @@ public static class QueryableFtsExtensions [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) + internal static bool FtsQueryPreEscaped(this Note note, string query, bool negated, CaseFilterType caseSensitivity, MatchFilterType matchType) => matchType.Equals(MatchFilterType.Substring) ? caseSensitivity.Equals(CaseFilterType.Sensitive) ? negated ? !EF.Functions.Like(note.Text!, "%" + query + "%", @"\") && - !EF.Functions.Like(note.Cw!, "%" + query + "%", @"\") + !EF.Functions.Like(note.Cw!, "%" + query + "%", @"\") && + !EF.Functions.Like(note.CombinedAltText!, "%" + query + "%", @"\") : EF.Functions.Like(note.Text!, "%" + query + "%", @"\") || - EF.Functions.Like(note.Cw!, "%" + query + "%", @"\") + EF.Functions.Like(note.Cw!, "%" + query + "%", @"\") || + EF.Functions.Like(note.CombinedAltText!, "%" + query + "%", @"\") : negated ? !EF.Functions.ILike(note.Text!, "%" + query + "%", @"\") && - !EF.Functions.ILike(note.Cw!, "%" + query + "%", @"\") + !EF.Functions.ILike(note.Cw!, "%" + query + "%", @"\") && + !EF.Functions.ILike(note.CombinedAltText!, "%" + query + "%", @"\") : EF.Functions.ILike(note.Text!, "%" + query + "%", @"\") || - EF.Functions.ILike(note.Cw!, "%" + query + "%", @"\") + EF.Functions.ILike(note.Cw!, "%" + query + "%", @"\") || + EF.Functions.ILike(note.CombinedAltText!, "%" + query + "%", @"\") : caseSensitivity.Equals(CaseFilterType.Sensitive) ? negated ? !Regex.IsMatch(note.Text!, "\\y" + query + "\\y") && - !Regex.IsMatch(note.Cw!, "\\y" + query + "\\y") + !Regex.IsMatch(note.Cw!, "\\y" + query + "\\y") && + !Regex.IsMatch(note.CombinedAltText!, "\\y" + query + "\\y") : Regex.IsMatch(note.Text!, "\\y" + query + "\\y") || - Regex.IsMatch(note.Cw!, "\\y" + query + "\\y") + Regex.IsMatch(note.Cw!, "\\y" + query + "\\y") || + Regex.IsMatch(note.CombinedAltText!, "\\y" + query + "\\y") : negated ? !Regex.IsMatch(note.Text!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) && - !Regex.IsMatch(note.Cw!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) + !Regex.IsMatch(note.Cw!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) && + !Regex.IsMatch(note.CombinedAltText!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) : Regex.IsMatch(note.Text!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) || - Regex.IsMatch(note.Cw!, "\\y" + query + "\\y", RegexOptions.IgnoreCase); + Regex.IsMatch(note.Cw!, "\\y" + query + "\\y", RegexOptions.IgnoreCase) || + Regex.IsMatch(note.CombinedAltText!, "\\y" + query + "\\y", RegexOptions.IgnoreCase); internal static string PreEscapeFtsQuery(string query, MatchFilterType matchType) => matchType.Equals(MatchFilterType.Substring) diff --git a/Iceshrimp.Backend/Core/Services/NoteService.cs b/Iceshrimp.Backend/Core/Services/NoteService.cs index 0c0e5213..3cd287c7 100644 --- a/Iceshrimp.Backend/Core/Services/NoteService.cs +++ b/Iceshrimp.Backend/Core/Services/NoteService.cs @@ -239,6 +239,7 @@ public class NoteService( } } + var combinedAltText = data.Attachments?.Select(p => p.Comment).Where(c => c != null); policySvc.CallRewriteHooks(data, IRewritePolicy.HookLocationEnum.PostLogic); var note = new Note @@ -270,7 +271,8 @@ public class NoteService( Emojis = data.Emoji ?? [], ReplyUri = data.ReplyUri, RenoteUri = data.RenoteUri, - RepliesCollection = data.ASNote?.Replies?.Id + RepliesCollection = data.ASNote?.Replies?.Id, + CombinedAltText = combinedAltText != null ? string.Join(' ', combinedAltText) : null }; if (data.Poll != null) @@ -594,6 +596,10 @@ public class NoteService( { note.FileIds = fileIds; note.AttachedFileTypes = data.Attachments?.Select(p => p.Type).ToList() ?? []; + + var combinedAltText = data.Attachments?.Select(p => p.Comment).Where(c => c != null); + note.CombinedAltText = combinedAltText != null ? string.Join(' ', combinedAltText) : null; + } var isPollEdited = false; @@ -1607,4 +1613,4 @@ public class NoteService( if (dbNote == null) return; await RemoveReactionFromNoteAsync(dbNote, actor, name); } -} \ No newline at end of file +}