mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-31 14:33:50 -04:00
Smart Filter Encoding Fix (#2387)
This commit is contained in:
parent
b6d4938e22
commit
9894a2623c
@ -10,8 +10,8 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.9" />
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
|
||||
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.10" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -6,12 +6,12 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.69" />
|
||||
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" Version="19.2.69" />
|
||||
<PackageReference Include="xunit" Version="2.5.2" />
|
||||
<PackageReference Include="xunit" Version="2.6.0" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -5,7 +5,6 @@ using API.DTOs.Filtering;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.Entities.Enums;
|
||||
using API.Helpers;
|
||||
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Helpers;
|
||||
@ -15,25 +14,25 @@ public class SmartFilterHelperTests
|
||||
[Fact]
|
||||
public void Test_Decode()
|
||||
{
|
||||
var encoded = """
|
||||
stmts=comparison%3D5%26field%3D18%26value%3D95%2Ccomparison%3D0%26field%3D4%26value%3D0%2Ccomparison%3D7%26field%3D1%26value%3Da&sortOptions=sortField=2&isAscending=false&limitTo=10&combination=1
|
||||
""";
|
||||
const string encoded = """
|
||||
name=Test&stmts=comparison%253D0%25C2%25A6field%253D18%25C2%25A6value%253D95<EFBFBD>comparison%253D0%25C2%25A6field%253D4%25C2%25A6value%253D0<EFBFBD>comparison%253D7%25C2%25A6field%253D1%25C2%25A6value%253Da&sortOptions=sortField%3D2¦isAscending%3DFalse&limitTo=10&combination=1
|
||||
""";
|
||||
|
||||
var filter = SmartFilterHelper.Decode(encoded);
|
||||
|
||||
Assert.Equal(10, filter.LimitTo);
|
||||
Assert.Equal(SortField.CreatedDate, filter.SortOptions.SortField);
|
||||
Assert.False(filter.SortOptions.IsAscending);
|
||||
Assert.Null(filter.Name);
|
||||
Assert.Equal("Test" , filter.Name);
|
||||
|
||||
var list = filter.Statements.ToList();
|
||||
AssertStatementSame(list[2], FilterField.SeriesName, FilterComparison.Matches, "a");
|
||||
AssertStatementSame(list[1], FilterField.AgeRating, FilterComparison.Equal, (int) AgeRating.Unknown + string.Empty);
|
||||
AssertStatementSame(list[0], FilterField.Genres, FilterComparison.Contains, "95");
|
||||
AssertStatementSame(list[0], FilterField.Genres, FilterComparison.Equal, "95");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Test_Encode()
|
||||
public void Test_EncodeDecode()
|
||||
{
|
||||
var filter = new FilterV2Dto()
|
||||
{
|
||||
@ -56,10 +55,61 @@ public class SmartFilterHelperTests
|
||||
};
|
||||
|
||||
var encodedFilter = SmartFilterHelper.Encode(filter);
|
||||
Assert.Equal("name=Test&stmts=comparison%253D0%252Cfield%253D4%252Cvalue%253D0&sortOptions=sortField%3D2%2CisAscending%3DFalse&limitTo=10&combination=1", encodedFilter);
|
||||
|
||||
var decoded = SmartFilterHelper.Decode(encodedFilter);
|
||||
Assert.Single(decoded.Statements);
|
||||
AssertStatementSame(decoded.Statements.First(), filter.Statements.First());
|
||||
Assert.Equal("Test", decoded.Name);
|
||||
Assert.Equal(10, decoded.LimitTo);
|
||||
Assert.Equal(SortField.CreatedDate, decoded.SortOptions.SortField);
|
||||
Assert.False(decoded.SortOptions.IsAscending);
|
||||
}
|
||||
|
||||
private void AssertStatementSame(FilterStatementDto statement, FilterField field, FilterComparison combination, string value)
|
||||
[Fact]
|
||||
public void Test_EncodeDecode_MultipleValues_Contains()
|
||||
{
|
||||
var filter = new FilterV2Dto()
|
||||
{
|
||||
Name = "Test",
|
||||
SortOptions = new SortOptions() {
|
||||
IsAscending = false,
|
||||
SortField = SortField.CreatedDate
|
||||
},
|
||||
LimitTo = 10,
|
||||
Combination = FilterCombination.And,
|
||||
Statements = new List<FilterStatementDto>()
|
||||
{
|
||||
new FilterStatementDto()
|
||||
{
|
||||
Comparison = FilterComparison.Equal,
|
||||
Field = FilterField.AgeRating,
|
||||
Value = $"{(int) AgeRating.Unknown + string.Empty},{(int) AgeRating.G + string.Empty}"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var encodedFilter = SmartFilterHelper.Encode(filter);
|
||||
var decoded = SmartFilterHelper.Decode(encodedFilter);
|
||||
|
||||
Assert.Single(decoded.Statements);
|
||||
AssertStatementSame(decoded.Statements.First(), filter.Statements.First());
|
||||
|
||||
Assert.Equal(2, decoded.Statements.First().Value.Split(",").Length);
|
||||
|
||||
Assert.Equal("Test", decoded.Name);
|
||||
Assert.Equal(10, decoded.LimitTo);
|
||||
Assert.Equal(SortField.CreatedDate, decoded.SortOptions.SortField);
|
||||
Assert.False(decoded.SortOptions.IsAscending);
|
||||
}
|
||||
|
||||
private static void AssertStatementSame(FilterStatementDto statement, FilterStatementDto statement2)
|
||||
{
|
||||
Assert.Equal(statement.Field, statement2.Field);
|
||||
Assert.Equal(statement.Comparison, statement2.Comparison);
|
||||
Assert.Equal(statement.Value, statement2.Value);
|
||||
}
|
||||
|
||||
private static void AssertStatementSame(FilterStatementDto statement, FilterField field, FilterComparison combination, string value)
|
||||
{
|
||||
Assert.Equal(statement.Field, field);
|
||||
Assert.Equal(statement.Comparison, combination);
|
||||
|
@ -23,7 +23,7 @@ public abstract class SiteThemeServiceTest : AbstractDbTest
|
||||
private readonly IEventHub _messageHub = Substitute.For<IEventHub>();
|
||||
|
||||
|
||||
protected SiteThemeServiceTest(ITestOutputHelper testOutputHelper) : base()
|
||||
protected SiteThemeServiceTest(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
}
|
||||
@ -56,7 +56,7 @@ public abstract class SiteThemeServiceTest : AbstractDbTest
|
||||
});
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
var ex = await Assert.ThrowsAsync<KavitaException>(async () => await siteThemeService.UpdateDefault(10));
|
||||
var ex = await Assert.ThrowsAsync<KavitaException>(() => siteThemeService.UpdateDefault(10));
|
||||
Assert.Equal("Theme file missing or invalid", ex.Message);
|
||||
|
||||
}
|
||||
|
@ -55,27 +55,27 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
|
||||
<PackageReference Include="Docnet.Core" Version="2.6.0" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.1" />
|
||||
<PackageReference Include="ExCSS" Version="4.2.2" />
|
||||
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
|
||||
<PackageReference Include="ExCSS" Version="4.2.4" />
|
||||
<PackageReference Include="Flurl" Version="3.0.7" />
|
||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.5" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" />
|
||||
<PackageReference Include="Hangfire" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.53" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.54" />
|
||||
<PackageReference Include="MarkdownDeep.NET.Core" Version="1.5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.12">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.13">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.12" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace API.Comparators;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Sorts chapters based on their Number. Uses natural ordering of doubles.
|
||||
/// </summary>
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace API.Comparators;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class NumericComparer : IComparer
|
||||
{
|
||||
|
||||
|
@ -6,6 +6,7 @@ using static System.Char;
|
||||
|
||||
namespace API.Comparators;
|
||||
|
||||
|
||||
public static class StringLogicalComparer
|
||||
{
|
||||
public static int Compare(string s1, string s2)
|
||||
|
@ -29,6 +29,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// All Account matters
|
||||
/// </summary>
|
||||
|
@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class AdminController : BaseApiController
|
||||
{
|
||||
private readonly UserManager<AppUser> _userManager;
|
||||
|
@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
|
@ -14,6 +14,8 @@ using VersOne.Epub;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class BookController : BaseApiController
|
||||
{
|
||||
private readonly IBookService _bookService;
|
||||
|
@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for the CBL import flow
|
||||
/// </summary>
|
||||
|
@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// APIs for Collections
|
||||
/// </summary>
|
||||
|
@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible interacting and creating Devices
|
||||
/// </summary>
|
||||
|
@ -16,6 +16,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// All APIs related to downloading entities from the system. Requires Download Role or Admin Role.
|
||||
/// </summary>
|
||||
|
@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[AllowAnonymous]
|
||||
public class FallbackController : Controller
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Constants;
|
||||
using API.Data;
|
||||
using API.Data.Repositories;
|
||||
using API.DTOs.Dashboard;
|
||||
@ -10,23 +9,22 @@ using API.DTOs.Filtering.v2;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using EasyCaching.Core;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// This is responsible for Filter caching
|
||||
/// </summary>
|
||||
public class FilterController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IEasyCachingProviderFactory _cacheFactory;
|
||||
|
||||
public FilterController(IUnitOfWork unitOfWork, IEasyCachingProviderFactory cacheFactory)
|
||||
public FilterController(IUnitOfWork unitOfWork)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_cacheFactory = cacheFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -93,4 +91,26 @@ public class FilterController : BaseApiController
|
||||
await _unitOfWork.CommitAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encode the Filter
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("encode")]
|
||||
public ActionResult<string> EncodeFilter(FilterV2Dto dto)
|
||||
{
|
||||
return Ok(SmartFilterHelper.Encode(dto));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decodes the Filter
|
||||
/// </summary>
|
||||
/// <param name="dto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("decode")]
|
||||
public ActionResult<FilterV2Dto> DecodeFilter(DecodeFilterDto dto)
|
||||
{
|
||||
return Ok(SmartFilterHelper.Decode(dto.EncodedFilter));
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[AllowAnonymous]
|
||||
public class HealthController : BaseApiController
|
||||
{
|
||||
|
@ -13,6 +13,8 @@ using MimeTypes;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for servicing up images stored in Kavita for entities
|
||||
/// </summary>
|
||||
|
@ -27,6 +27,8 @@ using TaskScheduler = API.Services.TaskScheduler;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[Authorize]
|
||||
public class LibraryController : BaseApiController
|
||||
{
|
||||
|
@ -15,6 +15,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class LicenseController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class LocaleController : BaseApiController
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class MetadataController : BaseApiController
|
||||
{
|
||||
|
@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// For the Panels app explicitly
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
@ -11,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class PluginController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
@ -43,7 +46,7 @@ public class PluginController : BaseApiController
|
||||
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
|
||||
if (userId <= 0)
|
||||
{
|
||||
_logger.LogInformation("A Plugin ({PluginName}) tried to authenticate with an apiKey that doesn't match. Information {Information}", pluginName, new
|
||||
_logger.LogInformation("A Plugin ({PluginName}) tried to authenticate with an apiKey that doesn't match. Information {@Information}", Uri.EscapeDataString(pluginName), new
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
UserAgent = userAgent,
|
||||
@ -52,7 +55,7 @@ public class PluginController : BaseApiController
|
||||
throw new KavitaUnauthenticatedUserException();
|
||||
}
|
||||
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId);
|
||||
_logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", pluginName, user!.UserName, userId);
|
||||
_logger.LogInformation("Plugin {PluginName} has authenticated with {UserName} ({UserId})'s API Key", Uri.EscapeDataString(pluginName), user!.UserName, userId);
|
||||
return new UserDto
|
||||
{
|
||||
Username = user.UserName!,
|
||||
|
@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for providing external ratings for Series
|
||||
/// </summary>
|
||||
|
@ -26,6 +26,8 @@ using MimeTypes;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// For all things regarding reading, mainly focusing on non-Book related entities
|
||||
/// </summary>
|
||||
|
@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[Authorize]
|
||||
public class ReadingListController : BaseApiController
|
||||
{
|
||||
|
@ -17,6 +17,8 @@ using Newtonsoft.Json;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class RecommendedController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
@ -18,6 +18,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class ReviewController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<ReviewController> _logger;
|
||||
|
@ -21,6 +21,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class ScrobblingController : BaseApiController
|
||||
{
|
||||
|
@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for the Search interface from the UI
|
||||
/// </summary>
|
||||
|
@ -28,6 +28,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class SeriesController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<SeriesController> _logger;
|
||||
|
@ -20,13 +20,14 @@ using Hangfire.Storage;
|
||||
using Kavita.Common;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MimeTypes;
|
||||
using TaskScheduler = API.Services.TaskScheduler;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
public class ServerController : BaseApiController
|
||||
{
|
||||
@ -286,8 +287,6 @@ public class ServerController : BaseApiController
|
||||
if (emailServiceUrl.Equals(EmailService.DefaultApiUrl)) return Ok(null);
|
||||
|
||||
return Ok(await _emailService.GetVersion(emailServiceUrl));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class SettingsController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<SettingsController> _logger;
|
||||
|
@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class StatsController : BaseApiController
|
||||
{
|
||||
private readonly IStatisticService _statService;
|
||||
|
@ -10,6 +10,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for anything that deals with Streams (SmartFilters, ExternalSource, DashboardStream, SideNavStream)
|
||||
/// </summary>
|
||||
|
@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any
|
||||
/// other purposes.
|
||||
|
@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public class ThemeController : BaseApiController
|
||||
{
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
|
@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
[Authorize]
|
||||
public class UsersController : BaseApiController
|
||||
{
|
||||
|
@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace API.Controllers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for all things Want To Read
|
||||
/// </summary>
|
||||
@ -42,7 +44,7 @@ public class WantToReadController : BaseApiController
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Obsolete("use v2 instead")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToRead([FromQuery] UserParams userParams, FilterDto filterDto)
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToRead([FromQuery] UserParams? userParams, FilterDto filterDto)
|
||||
{
|
||||
userParams ??= new UserParams();
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(User.GetUserId(), userParams, filterDto);
|
||||
@ -60,7 +62,7 @@ public class WantToReadController : BaseApiController
|
||||
/// <param name="filterDto"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("v2")]
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToReadV2([FromQuery] UserParams userParams, FilterV2Dto filterDto)
|
||||
public async Task<ActionResult<PagedList<SeriesDto>>> GetWantToReadV2([FromQuery] UserParams? userParams, FilterV2Dto filterDto)
|
||||
{
|
||||
userParams ??= new UserParams();
|
||||
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserV2Async(User.GetUserId(), userParams, filterDto);
|
||||
|
9
API/DTOs/Filtering/v2/DecodeFilterDto.cs
Normal file
9
API/DTOs/Filtering/v2/DecodeFilterDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace API.DTOs.Filtering.v2;
|
||||
|
||||
/// <summary>
|
||||
/// For requesting an encoded filter to be decoded
|
||||
/// </summary>
|
||||
public class DecodeFilterDto
|
||||
{
|
||||
public string EncodedFilter { get; set; }
|
||||
}
|
61
API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs
Normal file
61
API/Data/ManualMigrations/MigrateSmartFilterEncoding.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Data.ManualMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// v0.7.10.2 introduced a bad encoding, this will migrate those bad smart filters
|
||||
/// </summary>
|
||||
public static class MigrateSmartFilterEncoding
|
||||
{
|
||||
public static async Task Migrate(IUnitOfWork unitOfWork, DataContext dataContext, ILogger<Program> logger)
|
||||
{
|
||||
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Please be patient, this may take some time. This is not an error");
|
||||
|
||||
var statementsRegex = new Regex("stmts=(?<Statements>.*?)&");
|
||||
const string valueRegex = @"value=(?<value>\d+)";
|
||||
const string fieldRegex = @"field=(?<value>\d+)";
|
||||
const string comparisonRegex = @"comparison=(?<value>\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<FilterField>(Regex.Match(part, fieldRegex).Groups["value"].Value),
|
||||
Comparison = Enum.Parse<FilterComparison>(Regex.Match(part, comparisonRegex).Groups["value"].Value),
|
||||
});
|
||||
}
|
||||
|
||||
filter.Filter = SmartFilterHelper.Encode(filterDto);
|
||||
}
|
||||
|
||||
if (unitOfWork.HasChanges())
|
||||
{
|
||||
await unitOfWork.CommitAsync();
|
||||
}
|
||||
|
||||
logger.LogCritical("Running MigrateSmartFilterEncoding migration - Completed. This is not an error");
|
||||
}
|
||||
}
|
@ -627,7 +627,6 @@ public class UserRepository : IUserRepository
|
||||
return await ApplyLimit(filterSeriesQuery
|
||||
.Sort(filter.SortOptions)
|
||||
.AsSplitQuery(), filter.LimitTo)
|
||||
.Select(o => o.Bookmark)
|
||||
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ using API.Entities;
|
||||
using API.Helpers;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class AppUserExtensions
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace API.Extensions;
|
||||
|
||||
|
||||
public static class ApplicationServiceExtensions
|
||||
{
|
||||
public static void AddApplicationServices(this IServiceCollection services, IConfiguration config, IWebHostEnvironment env)
|
||||
|
@ -5,6 +5,7 @@ using API.Helpers;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class ChapterListExtensions
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using Kavita.Common;
|
||||
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class ClaimsPrincipalExtensions
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class DateTimeExtensions
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class EncodeFormatExtensions
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using API.Data.Misc;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class FileInfoExtensions
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using API.DTOs.Filtering;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class FilterDtoExtensions
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class HttpExtensions
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class IdentityServiceExtensions
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class ParserInfoListExtensions
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
using System.IO;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class PathExtensions
|
||||
{
|
||||
|
@ -1,14 +1,14 @@
|
||||
using System.Linq;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities;
|
||||
using API.Extensions.QueryExtensions;
|
||||
|
||||
namespace API.Extensions.QueryExtensions.Filtering;
|
||||
#nullable enable
|
||||
|
||||
public class BookmarkSeriesPair
|
||||
{
|
||||
public AppUserBookmark Bookmark { get; set; }
|
||||
public Series Series { get; set; }
|
||||
public AppUserBookmark Bookmark { get; init; } = null!;
|
||||
public Series Series { get; init; } = null!;
|
||||
}
|
||||
|
||||
public static class BookmarkSort
|
||||
|
@ -9,7 +9,6 @@ using Kavita.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Extensions.QueryExtensions.Filtering;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class SeriesFilter
|
||||
|
@ -1,7 +1,9 @@
|
||||
using System.Linq;
|
||||
using API.DTOs.Filtering;
|
||||
using API.Entities;
|
||||
using API.Extensions.QueryExtensions;
|
||||
|
||||
namespace API.Extensions.QueryExtensions.Filtering;
|
||||
#nullable enable
|
||||
|
||||
public static class SeriesSort
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using API.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Extensions.QueryExtensions;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// All extensions against IQueryable that enables the dynamic including based on bitwise flag pattern
|
||||
|
@ -12,6 +12,7 @@ using API.Entities.Scrobble;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Extensions.QueryExtensions;
|
||||
#nullable enable
|
||||
|
||||
public static class QueryableExtensions
|
||||
{
|
||||
|
@ -4,6 +4,7 @@ using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Extensions.QueryExtensions;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Responsible for restricting Entities based on an AgeRestriction
|
||||
|
@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
@ -7,6 +6,7 @@ using API.Entities;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class SeriesExtensions
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class StringExtensions
|
||||
{
|
||||
|
@ -5,6 +5,7 @@ using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class VolumeListExtensions
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using System.IO.Compression;
|
||||
using System.Linq;
|
||||
|
||||
namespace API.Extensions;
|
||||
#nullable enable
|
||||
|
||||
public static class ZipArchiveExtensions
|
||||
{
|
||||
|
@ -1,24 +0,0 @@
|
||||
using API.DTOs.Filtering.v2;
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Helpers.Builders;
|
||||
|
||||
public class SmartFilterBuilder : IEntityBuilder<AppUserSmartFilter>
|
||||
{
|
||||
private AppUserSmartFilter _smartFilter;
|
||||
public AppUserSmartFilter Build() => _smartFilter;
|
||||
|
||||
public SmartFilterBuilder(FilterV2Dto filter)
|
||||
{
|
||||
_smartFilter = new AppUserSmartFilter()
|
||||
{
|
||||
Name = filter.Name,
|
||||
Filter = SmartFilterHelper.Encode(filter)
|
||||
};
|
||||
}
|
||||
|
||||
// public SmartFilterBuilder WithName(string name)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
}
|
@ -4,6 +4,7 @@ using API.Entities.Interfaces;
|
||||
using API.Services;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public interface ICacheHelper
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Hangfire;
|
||||
|
||||
namespace API.Helpers.Converters;
|
||||
#nullable enable
|
||||
|
||||
public static class CronConverter
|
||||
{
|
||||
|
@ -7,6 +7,7 @@ using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
|
||||
namespace API.Helpers.Converters;
|
||||
#nullable enable
|
||||
|
||||
public static class FilterFieldValueConverter
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using API.Entities.Enums;
|
||||
using AutoMapper;
|
||||
|
||||
namespace API.Helpers.Converters;
|
||||
#nullable enable
|
||||
|
||||
public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>, ServerSettingDto>
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using API.DTOs.Scrobbling;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class LibraryTypeHelper
|
||||
{
|
||||
|
@ -1,4 +1,5 @@
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class NumberHelper
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
using API.Entities;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class OrderableHelper
|
||||
{
|
||||
|
@ -5,10 +5,11 @@ using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public class PagedList<T> : List<T>
|
||||
{
|
||||
public PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
|
||||
private PagedList(IEnumerable<T> items, int count, int pageNumber, int pageSize)
|
||||
{
|
||||
CurrentPage = pageNumber;
|
||||
TotalPages = (int) Math.Ceiling(count / (double) pageSize);
|
||||
|
@ -1,4 +1,5 @@
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public class PaginationHeader
|
||||
{
|
||||
|
@ -6,6 +6,7 @@ using API.Services.Tasks.Scanner;
|
||||
using API.Services.Tasks.Scanner.Parser;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class ParserInfoHelpers
|
||||
{
|
||||
|
@ -8,6 +8,7 @@ using API.Extensions;
|
||||
using API.Helpers.Builders;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class PersonHelper
|
||||
{
|
||||
@ -29,6 +30,7 @@ public static class PersonHelper
|
||||
foreach (var name in names)
|
||||
{
|
||||
var normalizedName = name.ToNormalized();
|
||||
// BUG: Doesn't this create a duplicate entry because allPeopleTypeRoles is a different instance?
|
||||
var person = allPeopleTypeRole.Find(p =>
|
||||
p.NormalizedName != null && p.NormalizedName.Equals(normalizedName));
|
||||
if (person == null)
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Helpers;
|
||||
|
||||
public static class SqlHelper
|
||||
{
|
||||
public static List<T> RawSqlQuery<T>(DbContext context, string query, Func<DbDataReader, T> map)
|
||||
{
|
||||
using var command = context.Database.GetDbConnection().CreateCommand();
|
||||
command.CommandText = query;
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
context.Database.OpenConnection();
|
||||
|
||||
using var result = command.ExecuteReader();
|
||||
var entities = new List<T>();
|
||||
|
||||
while (result.Read())
|
||||
{
|
||||
entities.Add(map(result));
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using API.Extensions;
|
||||
using API.Services.Tasks.Scanner;
|
||||
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public static class SeriesHelper
|
||||
{
|
||||
|
@ -5,14 +5,24 @@ using System.Web;
|
||||
using API.DTOs.Filtering;
|
||||
using API.DTOs.Filtering.v2;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace API.Helpers;
|
||||
|
||||
public static class SmartFilterHelper
|
||||
{
|
||||
private const string SortOptionsKey = "sortOptions=";
|
||||
private const string NameKey = "name=";
|
||||
private const string SortFieldKey = "sortField=";
|
||||
private const string IsAscendingKey = "isAscending=";
|
||||
private const string StatementsKey = "stmts=";
|
||||
private const string LimitToKey = "limitTo=";
|
||||
private const string CombinationKey = "combination=";
|
||||
private const string StatementComparisonKey = "comparison=";
|
||||
private const string StatementFieldKey = "field=";
|
||||
private const string StatementValueKey = "value=";
|
||||
public const string StatementSeparator = "\ufffd";
|
||||
public const string InnerStatementSeparator = "¦";
|
||||
|
||||
public static FilterV2Dto Decode(string? encodedFilter)
|
||||
{
|
||||
@ -21,7 +31,7 @@ public static class SmartFilterHelper
|
||||
return new FilterV2Dto(); // Create a default filter if the input is empty
|
||||
}
|
||||
|
||||
string[] parts = encodedFilter.Split('&');
|
||||
var parts = encodedFilter.Split('&');
|
||||
var filter = new FilterV2Dto();
|
||||
|
||||
foreach (var part in parts)
|
||||
@ -42,7 +52,7 @@ public static class SmartFilterHelper
|
||||
{
|
||||
filter.Statements = DecodeFilterStatementDtos(part.Substring(StatementsKey.Length));
|
||||
}
|
||||
else if (part.StartsWith("name="))
|
||||
else if (part.StartsWith(NameKey))
|
||||
{
|
||||
filter.Name = HttpUtility.UrlDecode(part.Substring(5));
|
||||
}
|
||||
@ -51,7 +61,7 @@ public static class SmartFilterHelper
|
||||
return filter;
|
||||
}
|
||||
|
||||
public static string Encode(FilterV2Dto filter)
|
||||
public static string Encode(FilterV2Dto? filter)
|
||||
{
|
||||
if (filter == null)
|
||||
return string.Empty;
|
||||
@ -59,50 +69,50 @@ public static class SmartFilterHelper
|
||||
var encodedStatements = EncodeFilterStatementDtos(filter.Statements);
|
||||
var encodedSortOptions = filter.SortOptions != null
|
||||
? $"{SortOptionsKey}{EncodeSortOptions(filter.SortOptions)}"
|
||||
: "";
|
||||
: string.Empty;
|
||||
var encodedLimitTo = $"{LimitToKey}{filter.LimitTo}";
|
||||
|
||||
return $"{EncodeName(filter.Name)}{encodedStatements}&{encodedSortOptions}&{encodedLimitTo}&{CombinationKey}{(int) filter.Combination}";
|
||||
}
|
||||
|
||||
private static string EncodeName(string name)
|
||||
private static string EncodeName(string? name)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(name) ? string.Empty : $"name={HttpUtility.UrlEncode(name)}&";
|
||||
return string.IsNullOrWhiteSpace(name) ? string.Empty : $"{NameKey}{Uri.EscapeDataString(name)}&";
|
||||
}
|
||||
|
||||
private static string EncodeSortOptions(SortOptions sortOptions)
|
||||
{
|
||||
return Uri.EscapeDataString($"sortField={(int) sortOptions.SortField},isAscending={sortOptions.IsAscending}");
|
||||
return Uri.EscapeDataString($"{SortFieldKey}{(int) sortOptions.SortField}{InnerStatementSeparator}{IsAscendingKey}{sortOptions.IsAscending}");
|
||||
}
|
||||
|
||||
private static string EncodeFilterStatementDtos(ICollection<FilterStatementDto> statements)
|
||||
private static string EncodeFilterStatementDtos(ICollection<FilterStatementDto>? statements)
|
||||
{
|
||||
if (statements == null || statements.Count == 0)
|
||||
return string.Empty;
|
||||
|
||||
var encodedStatements = StatementsKey + Uri.EscapeDataString(string.Join(",", statements.Select(EncodeFilterStatementDto)));
|
||||
var encodedStatements = StatementsKey + Uri.EscapeDataString(string.Join(StatementSeparator, statements.Select(EncodeFilterStatementDto)));
|
||||
return encodedStatements;
|
||||
}
|
||||
|
||||
private static string EncodeFilterStatementDto(FilterStatementDto statement)
|
||||
{
|
||||
var encodedComparison = $"comparison={(int) statement.Comparison}";
|
||||
var encodedField = $"field={(int) statement.Field}";
|
||||
var encodedValue = $"value={Uri.EscapeDataString(statement.Value)}";
|
||||
|
||||
return Uri.EscapeDataString($"{encodedComparison},{encodedField},{encodedValue}");
|
||||
var encodedComparison = $"{StatementComparisonKey}{(int) statement.Comparison}";
|
||||
var encodedField = $"{StatementFieldKey}{(int) statement.Field}";
|
||||
var encodedValue = $"{StatementValueKey}{Uri.EscapeDataString(statement.Value)}";
|
||||
|
||||
return Uri.EscapeDataString($"{encodedComparison}{InnerStatementSeparator}{encodedField}{InnerStatementSeparator}{encodedValue}");
|
||||
}
|
||||
|
||||
private static List<FilterStatementDto> DecodeFilterStatementDtos(string encodedStatements)
|
||||
{
|
||||
encodedStatements = HttpUtility.UrlDecode(encodedStatements);
|
||||
string[] statementStrings = encodedStatements.Split(',');
|
||||
var statementStrings = Uri.UnescapeDataString(encodedStatements).Split(StatementSeparator);
|
||||
|
||||
var statements = new List<FilterStatementDto>();
|
||||
|
||||
foreach (var statementString in statementStrings)
|
||||
{
|
||||
var parts = statementString.Split('&');
|
||||
var parts = Uri.UnescapeDataString(statementString).Split(InnerStatementSeparator);
|
||||
if (parts.Length < 3)
|
||||
continue;
|
||||
|
||||
@ -110,7 +120,7 @@ public static class SmartFilterHelper
|
||||
{
|
||||
Comparison = Enum.Parse<FilterComparison>(parts[0].Split("=")[1]),
|
||||
Field = Enum.Parse<FilterField>(parts[1].Split("=")[1]),
|
||||
Value = HttpUtility.UrlDecode(parts[2].Split("=")[1])
|
||||
Value = Uri.UnescapeDataString(parts[2].Split("=")[1])
|
||||
});
|
||||
}
|
||||
|
||||
@ -119,22 +129,22 @@ public static class SmartFilterHelper
|
||||
|
||||
private static SortOptions DecodeSortOptions(string encodedSortOptions)
|
||||
{
|
||||
string[] parts = encodedSortOptions.Split(',');
|
||||
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith("sortField="));
|
||||
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith("isAscending="));
|
||||
var parts = Uri.UnescapeDataString(encodedSortOptions).Split(InnerStatementSeparator);
|
||||
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith(SortFieldKey));
|
||||
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith(IsAscendingKey));
|
||||
|
||||
var isAscending = isAscendingPart?.Substring(11).Equals("true", StringComparison.OrdinalIgnoreCase) ?? false;
|
||||
if (sortFieldPart != null)
|
||||
if (sortFieldPart == null)
|
||||
{
|
||||
var sortField = Enum.Parse<SortField>(sortFieldPart.Split("=")[1]);
|
||||
|
||||
return new SortOptions
|
||||
{
|
||||
SortField = sortField,
|
||||
IsAscending = isAscending
|
||||
};
|
||||
return new SortOptions();
|
||||
}
|
||||
|
||||
return null;
|
||||
var sortField = Enum.Parse<SortField>(sortFieldPart.Split("=")[1]);
|
||||
|
||||
return new SortOptions
|
||||
{
|
||||
SortField = sortField,
|
||||
IsAscending = isAscending
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ using API.Extensions;
|
||||
using API.Helpers.Builders;
|
||||
|
||||
namespace API.Helpers;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class TagHelper
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -1,4 +1,5 @@
|
||||
namespace API.Helpers;
|
||||
#nullable enable
|
||||
|
||||
public class UserParams
|
||||
{
|
||||
@ -15,7 +16,7 @@ public class UserParams
|
||||
init => _pageSize = (value == 0) ? MaxPageSize : value;
|
||||
}
|
||||
|
||||
public static readonly UserParams Default = new UserParams()
|
||||
public static readonly UserParams Default = new()
|
||||
{
|
||||
PageSize = 20,
|
||||
PageNumber = 1
|
||||
|
@ -16,6 +16,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public interface IAccountService
|
||||
{
|
||||
Task<IEnumerable<ApiException>> ChangeUserPassword(AppUser user, string newPassword);
|
||||
|
@ -18,6 +18,8 @@ using SharpCompress.Common;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public interface IArchiveService
|
||||
{
|
||||
void ExtractArchive(string archivePath, string extractPath);
|
||||
|
@ -29,6 +29,7 @@ using VersOne.Epub.Options;
|
||||
using VersOne.Epub.Schema;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public interface IBookService
|
||||
|
@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public interface IBookmarkService
|
||||
{
|
||||
Task DeleteBookmarkFiles(IEnumerable<AppUserBookmark> bookmarks);
|
||||
|
@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace API.Services.HostedServices;
|
||||
#nullable enable
|
||||
|
||||
public class StartupTasksHostedService : IHostedService
|
||||
{
|
||||
|
@ -14,6 +14,7 @@ using Kavita.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Plus;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Used for matching and fetching metadata on a series
|
||||
|
@ -13,6 +13,7 @@ using Kavita.Common.EnvironmentInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Plus;
|
||||
#nullable enable
|
||||
|
||||
internal class RegisterLicenseResponseDto
|
||||
{
|
||||
|
@ -17,6 +17,7 @@ using Kavita.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Plus;
|
||||
#nullable enable
|
||||
|
||||
public interface IRatingService
|
||||
{
|
||||
|
@ -18,6 +18,7 @@ using Kavita.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Plus;
|
||||
#nullable enable
|
||||
|
||||
public record PlusSeriesDto
|
||||
{
|
||||
|
@ -22,6 +22,7 @@ using Kavita.Common.Helpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Plus;
|
||||
#nullable enable
|
||||
|
||||
/// <summary>
|
||||
/// Misleading name but is the source of data (like a review coming from AniList)
|
||||
|
@ -12,6 +12,7 @@ using Hangfire;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Tasks;
|
||||
#nullable enable
|
||||
|
||||
public interface IBackupService
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using Hangfire;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace API.Services.Tasks;
|
||||
#nullable enable
|
||||
|
||||
public interface ICleanupService
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
using VersOne.Epub;
|
||||
|
||||
namespace API.Services.Tasks.Metadata;
|
||||
#nullable enable
|
||||
|
||||
public interface IWordCountAnalyzerService
|
||||
{
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user