From cb4ae26b60a08a083175810fdbe4e2c630fed674 Mon Sep 17 00:00:00 2001 From: PromKnight Date: Tue, 12 Nov 2024 09:30:35 +0000 Subject: [PATCH] fix: Ensure infohashes only 40 chars in results --- .github/workflows/conventional-commits.yaml | 15 + .github/workflows/release-please.yaml | 19 ++ .release-please-manifest.json | 3 + release-please-config.json | 25 ++ .../Functions/SearchImdbProcedure.cs | 149 ++++++++ .../20241112090934_v2search.Designer.cs | 323 ++++++++++++++++++ .../Migrations/20241112090934_v2search.cs | 24 ++ .../Services/TorrentInfoService.cs | 1 + 8 files changed, 559 insertions(+) create mode 100644 .github/workflows/conventional-commits.yaml create mode 100644 .github/workflows/release-please.yaml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json create mode 100644 src/Zilean.Database/Migrations/20241112090934_v2search.Designer.cs create mode 100644 src/Zilean.Database/Migrations/20241112090934_v2search.cs diff --git a/.github/workflows/conventional-commits.yaml b/.github/workflows/conventional-commits.yaml new file mode 100644 index 0000000..a0b4671 --- /dev/null +++ b/.github/workflows/conventional-commits.yaml @@ -0,0 +1,15 @@ +name: Conventional Commits + +on: + pull_request: + branches: [ main ] + +jobs: + build: + name: Conventional Commits + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.7 + - uses: webiny/action-conventional-commits@v1.3.0 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/release-please.yaml b/.github/workflows/release-please.yaml new file mode 100644 index 0000000..623e2bc --- /dev/null +++ b/.github/workflows/release-please.yaml @@ -0,0 +1,19 @@ +name: "Release Please" + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ secrets.RELEASE_PLEASE_TOKEN }} \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..b725ab0 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "2.0.1" +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..bd3bfce --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "changelog-path": "CHANGELOG.md", + "release-type": "simple", + "extra-files": ["Directory.Build.props"], + "changelog-sections": [ + { "type": "feat", "section": "πŸš€ New Features", "hidden": false }, + { "type": "feature", "section": "πŸš€ New Features", "hidden": false }, + { "type": "enhance", "section": "πŸ’… Enhancements", "hidden": false }, + { "type": "fix", "section": "πŸ”₯ Bug Fixes", "hidden": false }, + { "type": "perf", "section": "πŸƒ Performance Improvements", "hidden": false }, + { "type": "revert", "section": "↩️ Reverts", "hidden": true }, + { "type": "docs", "section": "πŸ“š Documentation", "hidden": false }, + { "type": "style", "section": "🎨 Code Style", "hidden": false }, + { "type": "chore", "section": "βš™οΈ Chores", "hidden": false }, + { "type": "refactor", "section": "⌨️ Code Refactoring", "hidden": false }, + { "type": "test", "section": "πŸ§ͺ Automated Testing", "hidden": false }, + { "type": "build", "section": "πŸ› οΈ Build System", "hidden": false }, + { "type": "ci", "section": "πŸ“¦ CI Improvements", "hidden": false } + ] + } + } +} diff --git a/src/Zilean.Database/Functions/SearchImdbProcedure.cs b/src/Zilean.Database/Functions/SearchImdbProcedure.cs index ba32826..390007e 100644 --- a/src/Zilean.Database/Functions/SearchImdbProcedure.cs +++ b/src/Zilean.Database/Functions/SearchImdbProcedure.cs @@ -168,6 +168,155 @@ ORDER BY $$ LANGUAGE plpgsql; """; + internal const string CreateTorrentProcedureV2 = + """ + CREATE OR REPLACE FUNCTION search_torrents_meta( + query TEXT DEFAULT NULL, + season INT DEFAULT NULL, + episode INT DEFAULT NULL, + year INT DEFAULT NULL, + language TEXT DEFAULT NULL, + resolution TEXT DEFAULT NULL, + imdbId TEXT DEFAULT NULL, + limit_param INT DEFAULT 20, + similarity_threshold REAL DEFAULT 0.85 + ) + RETURNS TABLE( + "InfoHash" TEXT, + "Resolution" TEXT, + "Year" INT, + "Remastered" BOOLEAN, + "Codec" TEXT, + "Audio" TEXT[], + "Quality" TEXT, + "Episodes" INT[], + "Seasons" INT[], + "Languages" TEXT[], + "ParsedTitle" TEXT, + "NormalizedTitle" TEXT, + "RawTitle" TEXT, + "Size" TEXT, + "Category" TEXT, + "Complete" BOOLEAN, + "Volumes" INT[], + "Hdr" TEXT[], + "Channels" TEXT[], + "Dubbed" BOOLEAN, + "Subbed" BOOLEAN, + "Edition" TEXT, + "BitDepth" TEXT, + "Bitrate" TEXT, + "Network" TEXT, + "Extended" BOOLEAN, + "Converted" BOOLEAN, + "Hardcoded" BOOLEAN, + "Region" TEXT, + "Ppv" BOOLEAN, + "Is3d" BOOLEAN, + "Site" TEXT, + "Proper" BOOLEAN, + "Repack" BOOLEAN, + "Retail" BOOLEAN, + "Upscaled" BOOLEAN, + "Unrated" BOOLEAN, + "Documentary" BOOLEAN, + "EpisodeCode" TEXT, + "Country" TEXT, + "Container" TEXT, + "Extension" TEXT, + "Torrent" BOOLEAN, + "Score" REAL, + "ImdbId" TEXT, + "ImdbCategory" TEXT, + "ImdbTitle" TEXT, + "ImdbYear" INT, + "ImdbAdult" BOOLEAN + ) AS $$ + BEGIN + EXECUTE format('SET pg_trgm.similarity_threshold = %L', similarity_threshold); + + RETURN QUERY + SELECT + t."InfoHash", + t."Resolution", + t."Year", + t."Remastered", + t."Codec", + t."Audio", + t."Quality", + t."Episodes", + t."Seasons", + t."Languages", + t."ParsedTitle", + t."NormalizedTitle", + t."RawTitle", + t."Size", + t."Category", + t."Complete", + t."Volumes", + t."Hdr", + t."Channels", + t."Dubbed", + t."Subbed", + t."Edition", + t."BitDepth", + t."Bitrate", + t."Network", + t."Extended", + t."Converted", + t."Hardcoded", + t."Region", + t."Ppv", + t."Is3d", + t."Site", + t."Proper", + t."Repack", + t."Retail", + t."Upscaled", + t."Unrated", + t."Documentary", + t."EpisodeCode", + t."Country", + t."Container", + t."Extension", + t."Torrent", + similarity(t."ParsedTitle", query) AS "Score", + t."ImdbId", + i."Category" AS "ImdbCategory", + i."Title" AS "ImdbTitle", + i."Year" AS "ImdbYear", + i."Adult" AS "ImdbAdult" + FROM + public."Torrents" t + LEFT JOIN + public."ImdbFiles" i ON t."ImdbId" = i."ImdbId" + WHERE + Length(t."InfoHash") = 40 + AND + (query IS NULL OR t."ParsedTitle" % query) + AND (season IS NULL OR season = ANY(t."Seasons")) + AND ( + (episode IS NULL AND season IS NOT NULL) + OR + ( + episode IS NOT NULL AND + season IS NOT NULL AND + (episode = ANY(t."Episodes") OR t."Episodes" IS NULL OR t."Episodes" = '{}') + ) + OR (season IS NULL AND episode IS NULL) + ) + AND (year IS NULL OR t."Year" BETWEEN year - 1 AND year + 1) + AND (language IS NULL OR language = ANY(t."Languages")) + AND (resolution IS NULL OR resolution = t."Resolution") + AND (imdbId IS NULL OR t."ImdbId" = imdbId) + ORDER BY + "Score" DESC + LIMIT + limit_param; + END; + $$ LANGUAGE plpgsql; + """; + internal const string RemoveImdbProcedure = "DROP FUNCTION IF EXISTS search_imdb_meta(TEXT, TEXT, INT, INT);"; internal const string RemoveTorrentProcedure = "DROP FUNCTION IF EXISTS search_torrents_meta(TEXT, INT, INT, INT, TEXT, TEXT, TEXT, INT, REAL);"; } diff --git a/src/Zilean.Database/Migrations/20241112090934_v2search.Designer.cs b/src/Zilean.Database/Migrations/20241112090934_v2search.Designer.cs new file mode 100644 index 0000000..4abb572 --- /dev/null +++ b/src/Zilean.Database/Migrations/20241112090934_v2search.Designer.cs @@ -0,0 +1,323 @@ +ο»Ώ// +using System; +using System.Text.Json; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using Zilean.Database; + +#nullable disable + +namespace Zilean.Database.Migrations +{ + [DbContext(typeof(ZileanDbContext))] + [Migration("20241112090934_v2search")] + partial class v2search + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.8") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Zilean.Shared.Features.Dmm.ParsedPages", b => + { + b.Property("Page") + .HasColumnType("text"); + + b.Property("EntryCount") + .HasColumnType("integer"); + + b.HasKey("Page"); + + b.ToTable("ParsedPages", (string)null); + }); + + modelBuilder.Entity("Zilean.Shared.Features.Dmm.TorrentInfo", b => + { + b.Property("InfoHash") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "info_hash"); + + b.Property("Audio") + .IsRequired() + .HasColumnType("text[]") + .HasAnnotation("Relational:JsonPropertyName", "audio"); + + b.Property("BitDepth") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "bit_depth"); + + b.Property("Bitrate") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "bitrate"); + + b.Property("Category") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "category"); + + b.Property("Channels") + .IsRequired() + .HasColumnType("text[]") + .HasAnnotation("Relational:JsonPropertyName", "channels"); + + b.Property("Codec") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "codec"); + + b.Property("Complete") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "complete"); + + b.Property("Container") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "container"); + + b.Property("Converted") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "converted"); + + b.Property("Country") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "country"); + + b.Property("Date") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "date"); + + b.Property("Documentary") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "documentary"); + + b.Property("Dubbed") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "dubbed"); + + b.Property("Edition") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "edition"); + + b.Property("EpisodeCode") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "episode_code"); + + b.Property("Episodes") + .IsRequired() + .HasColumnType("integer[]") + .HasAnnotation("Relational:JsonPropertyName", "episodes"); + + b.Property("Extended") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "extended"); + + b.Property("Extension") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "extension"); + + b.Property("Group") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "group"); + + b.Property("Hardcoded") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "hardcoded"); + + b.Property("Hdr") + .IsRequired() + .HasColumnType("text[]") + .HasAnnotation("Relational:JsonPropertyName", "hdr"); + + b.Property("ImdbId") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "imdb_id"); + + b.Property("Is3d") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "_3d"); + + b.Property("Languages") + .IsRequired() + .HasColumnType("text[]") + .HasAnnotation("Relational:JsonPropertyName", "languages"); + + b.Property("Network") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "network"); + + b.Property("NormalizedTitle") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "normalized_title"); + + b.Property("ParsedTitle") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "parsed_title"); + + b.Property("Ppv") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "ppv"); + + b.Property("Proper") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "proper"); + + b.Property("Quality") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "quality"); + + b.Property("RawTitle") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "raw_title"); + + b.Property("Region") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "region"); + + b.Property("Remastered") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "remastered"); + + b.Property("Repack") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "repack"); + + b.Property("Resolution") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "resolution"); + + b.Property("Retail") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "retail"); + + b.Property("Seasons") + .IsRequired() + .HasColumnType("integer[]") + .HasAnnotation("Relational:JsonPropertyName", "seasons"); + + b.Property("Site") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "site"); + + b.Property("Size") + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "size"); + + b.Property("Subbed") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "subbed"); + + b.Property("Torrent") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "torrent"); + + b.Property("Trash") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "trash"); + + b.Property("Unrated") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "unrated"); + + b.Property("Upscaled") + .IsRequired() + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "upscaled"); + + b.Property("Volumes") + .IsRequired() + .HasColumnType("integer[]") + .HasAnnotation("Relational:JsonPropertyName", "volumes"); + + b.Property("Year") + .HasColumnType("integer") + .HasAnnotation("Relational:JsonPropertyName", "year"); + + b.HasKey("InfoHash"); + + b.HasIndex("ImdbId"); + + b.HasIndex("InfoHash") + .IsUnique(); + + b.ToTable("Torrents", (string)null); + }); + + modelBuilder.Entity("Zilean.Shared.Features.Imdb.ImdbFile", b => + { + b.Property("ImdbId") + .HasColumnType("text"); + + b.Property("Adult") + .HasColumnType("boolean"); + + b.Property("Category") + .HasColumnType("text"); + + b.Property("Title") + .HasColumnType("text"); + + b.Property("Year") + .HasColumnType("integer"); + + b.HasKey("ImdbId"); + + b.HasIndex("ImdbId") + .IsUnique(); + + b.ToTable("ImdbFiles", (string)null); + + b.HasAnnotation("Relational:JsonPropertyName", "imdb"); + }); + + modelBuilder.Entity("Zilean.Shared.Features.Statistics.ImportMetadata", b => + { + b.Property("Key") + .HasColumnType("text"); + + b.Property("Value") + .IsRequired() + .HasColumnType("jsonb"); + + b.HasKey("Key"); + + b.ToTable("ImportMetadata"); + }); + + modelBuilder.Entity("Zilean.Shared.Features.Dmm.TorrentInfo", b => + { + b.HasOne("Zilean.Shared.Features.Imdb.ImdbFile", "Imdb") + .WithMany() + .HasForeignKey("ImdbId"); + + b.Navigation("Imdb"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Zilean.Database/Migrations/20241112090934_v2search.cs b/src/Zilean.Database/Migrations/20241112090934_v2search.cs new file mode 100644 index 0000000..bc88a02 --- /dev/null +++ b/src/Zilean.Database/Migrations/20241112090934_v2search.cs @@ -0,0 +1,24 @@ +ο»Ώusing Microsoft.EntityFrameworkCore.Migrations; +using Zilean.Database.Functions; + +#nullable disable + +namespace Zilean.Database.Migrations; + +/// +public partial class v2search : Migration +{ + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(SearchImdbProcedure.RemoveTorrentProcedure); + migrationBuilder.Sql(SearchImdbProcedure.CreateTorrentProcedureV2); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(SearchImdbProcedure.RemoveTorrentProcedure); + migrationBuilder.Sql(SearchImdbProcedure.CreateTorrentProcedure); + } +} diff --git a/src/Zilean.Database/Services/TorrentInfoService.cs b/src/Zilean.Database/Services/TorrentInfoService.cs index 15df62f..6e67b58 100644 --- a/src/Zilean.Database/Services/TorrentInfoService.cs +++ b/src/Zilean.Database/Services/TorrentInfoService.cs @@ -62,6 +62,7 @@ await ExecuteCommandAsync(async connection => * FROM "Torrents" WHERE "ParsedTitle" % @query + AND Length("InfoHash") = 40 LIMIT 100; """;