diff --git a/API.Tests/Helpers/SmartFilterHelperTests.cs b/API.Tests/Helpers/SmartFilterHelperTests.cs index 731125b33..276bbf942 100644 --- a/API.Tests/Helpers/SmartFilterHelperTests.cs +++ b/API.Tests/Helpers/SmartFilterHelperTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using API.Data.ManualMigrations; using API.DTOs.Filtering; using API.DTOs.Filtering.v2; using API.Entities.Enums; @@ -102,6 +103,24 @@ public class SmartFilterHelperTests Assert.False(decoded.SortOptions.IsAscending); } + [Theory] + [InlineData("name=DC%20-%20On%20Deck&stmts=comparison%3D1%26field%3D20%26value%3D0,comparison%3D9%26field%3D20%26value%3D100,comparison%3D0%26field%3D19%26value%3D274&sortOptions=sortField%3D1&isAscending=True&limitTo=0&combination=1")] + [InlineData("name=Manga%20-%20On%20Deck&stmts=comparison%253D1%252Cfield%253D20%252Cvalue%253D0,comparison%253D3%252Cfield%253D20%252Cvalue%253D100,comparison%253D0%252Cfield%253D19%252Cvalue%253D2&sortOptions=sortField%3D1,isAscending%3DTrue&limitTo=0&combination=1")] + [InlineData("name=English%20In%20Progress&stmts=comparison%253D8%252Cfield%253D7%252Cvalue%253D4%25252C3,comparison%253D3%252Cfield%253D20%252Cvalue%253D100,comparison%253D8%252Cfield%253D3%252Cvalue%253Dja,comparison%253D1%252Cfield%253D20%252Cvalue%253D0&sortOptions=sortField%3D7,isAscending%3DFalse&limitTo=0&combination=1")] + public void MigrationWorks(string filter) + { + try + { + var updatedFilter = MigrateSmartFilterEncoding.EncodeFix(filter); + Assert.NotNull(updatedFilter); + } + catch (Exception ex) + { + Assert.Fail("Exception thrown: " + ex.Message); + } + + } + private static void AssertStatementSame(FilterStatementDto statement, FilterStatementDto statement2) { Assert.Equal(statement.Field, statement2.Field); diff --git a/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs b/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs index fdabd72c3..f3ac617e4 100644 --- a/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs +++ b/API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs @@ -13,42 +13,24 @@ namespace API.Data.ManualMigrations; /// public static class MigrateSmartFilterEncoding { + private static readonly Regex StatementsRegex = new Regex("stmts=(?.*?)&"); + private const string ValueRegex = @"value=(?\d+)"; + private const string FieldRegex = @"field=(?\d+)"; + private const string ComparisonRegex = @"comparison=(?\d+)"; + private const string SortOptionsRegex = @"sortField=(.*?),isAscending=(.*?)"; + public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger logger) { logger.LogCritical("Running MigrateSmartFilterEncoding migration - Please be patient, this may take some time. This is not an error"); - var statementsRegex = new Regex("stmts=(?.*?)&"); - const string valueRegex = @"value=(?\d+)"; - const string fieldRegex = @"field=(?\d+)"; - const string comparisonRegex = @"comparison=(?\d+)"; + var smartFilters = dataContext.AppUserSmartFilter.ToList(); foreach (var filter in smartFilters) { if (filter.Filter.Contains(SmartFilterHelper.StatementSeparator)) continue; - var statements = statementsRegex.Matches(filter.Filter) - .Select(match => match.Groups["Statements"]) - .FirstOrDefault(group => group.Success && group != Match.Empty)?.Value; - if (string.IsNullOrEmpty(statements)) continue; - - - // We have statements. Let's remove the statements and generate a filter dto - var noStmt = statementsRegex.Replace(filter.Filter, string.Empty).Replace("stmts=", string.Empty); - var filterDto = SmartFilterHelper.Decode(noStmt); - - // Now we just parse each individual stmt into the core components and add to statements - - var individualParts = Uri.UnescapeDataString(statements).Split(',').Select(Uri.UnescapeDataString); - foreach (var part in individualParts) - { - filterDto.Statements.Add(new FilterStatementDto() - { - Value = Regex.Match(part, valueRegex).Groups["value"].Value, - Field = Enum.Parse(Regex.Match(part, fieldRegex).Groups["value"].Value), - Comparison = Enum.Parse(Regex.Match(part, comparisonRegex).Groups["value"].Value), - }); - } - - filter.Filter = SmartFilterHelper.Encode(filterDto); + var decode = EncodeFix(filter.Filter); + if (string.IsNullOrEmpty(decode)) continue; + filter.Filter = decode; } if (unitOfWork.HasChanges()) @@ -58,4 +40,47 @@ public static class MigrateSmartFilterEncoding logger.LogCritical("Running MigrateSmartFilterEncoding migration - Completed. This is not an error"); } + + public static string EncodeFix(string encodedFilter) + { + var statements = StatementsRegex.Matches(encodedFilter) + .Select(match => match.Groups["Statements"]) + .FirstOrDefault(group => group.Success && group != Match.Empty)?.Value; + if (string.IsNullOrEmpty(statements)) return encodedFilter; + + + // We have statements. Let's remove the statements and generate a filter dto + var noStmt = StatementsRegex.Replace(encodedFilter, string.Empty).Replace("stmts=", string.Empty); + + // Pre-v0.7.10 filters could be extra escaped + if (!noStmt.Contains("sortField=")) + { + noStmt = Uri.UnescapeDataString(noStmt); + } + + // We need to replace sort options portion with a properly encoded + noStmt = Regex.Replace(noStmt, SortOptionsRegex, match => + { + var sortFieldValue = match.Groups[1].Value; + var isAscendingValue = match.Groups[2].Value; + + return $"sortField={sortFieldValue}{SmartFilterHelper.InnerStatementSeparator}isAscending={isAscendingValue}"; + }); + + var filterDto = SmartFilterHelper.Decode(noStmt); + + // Now we just parse each individual stmt into the core components and add to statements + + var individualParts = Uri.UnescapeDataString(statements).Split(',').Select(Uri.UnescapeDataString); + foreach (var part in individualParts) + { + filterDto.Statements.Add(new FilterStatementDto() + { + Value = Regex.Match(part, ValueRegex).Groups["value"].Value, + Field = Enum.Parse(Regex.Match(part, FieldRegex).Groups["value"].Value), + Comparison = Enum.Parse(Regex.Match(part, ComparisonRegex).Groups["value"].Value), + }); + } + return SmartFilterHelper.Encode(filterDto); + } }