Smart Filter Encoding Fix (#2387)

This commit is contained in:
Joe Milazzo 2023-11-02 08:35:43 -05:00 committed by GitHub
parent b6d4938e22
commit 9894a2623c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
133 changed files with 677 additions and 471 deletions

View File

@ -10,8 +10,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" /> <PackageReference Include="BenchmarkDotNet" Version="0.13.10" />
<PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.9" /> <PackageReference Include="BenchmarkDotNet.Annotations" Version="0.13.10" />
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
</ItemGroup> </ItemGroup>

View File

@ -6,12 +6,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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="Microsoft.NET.Test.Sdk" Version="17.7.2" />
<PackageReference Include="NSubstitute" Version="5.1.0" /> <PackageReference Include="NSubstitute" Version="5.1.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.69" /> <PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="19.2.69" />
<PackageReference Include="TestableIO.System.IO.Abstractions.Wrappers" 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"> <PackageReference Include="xunit.runner.visualstudio" Version="2.5.3">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>

View File

@ -5,7 +5,6 @@ using API.DTOs.Filtering;
using API.DTOs.Filtering.v2; using API.DTOs.Filtering.v2;
using API.Entities.Enums; using API.Entities.Enums;
using API.Helpers; using API.Helpers;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.Utilities;
using Xunit; using Xunit;
namespace API.Tests.Helpers; namespace API.Tests.Helpers;
@ -15,25 +14,25 @@ public class SmartFilterHelperTests
[Fact] [Fact]
public void Test_Decode() public void Test_Decode()
{ {
var encoded = """ const string 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 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); var filter = SmartFilterHelper.Decode(encoded);
Assert.Equal(10, filter.LimitTo); Assert.Equal(10, filter.LimitTo);
Assert.Equal(SortField.CreatedDate, filter.SortOptions.SortField); Assert.Equal(SortField.CreatedDate, filter.SortOptions.SortField);
Assert.False(filter.SortOptions.IsAscending); Assert.False(filter.SortOptions.IsAscending);
Assert.Null(filter.Name); Assert.Equal("Test" , filter.Name);
var list = filter.Statements.ToList(); var list = filter.Statements.ToList();
AssertStatementSame(list[2], FilterField.SeriesName, FilterComparison.Matches, "a"); AssertStatementSame(list[2], FilterField.SeriesName, FilterComparison.Matches, "a");
AssertStatementSame(list[1], FilterField.AgeRating, FilterComparison.Equal, (int) AgeRating.Unknown + string.Empty); 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] [Fact]
public void Test_Encode() public void Test_EncodeDecode()
{ {
var filter = new FilterV2Dto() var filter = new FilterV2Dto()
{ {
@ -56,10 +55,61 @@ public class SmartFilterHelperTests
}; };
var encodedFilter = SmartFilterHelper.Encode(filter); 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.Field, field);
Assert.Equal(statement.Comparison, combination); Assert.Equal(statement.Comparison, combination);

View File

@ -23,7 +23,7 @@ public abstract class SiteThemeServiceTest : AbstractDbTest
private readonly IEventHub _messageHub = Substitute.For<IEventHub>(); private readonly IEventHub _messageHub = Substitute.For<IEventHub>();
protected SiteThemeServiceTest(ITestOutputHelper testOutputHelper) : base() protected SiteThemeServiceTest(ITestOutputHelper testOutputHelper)
{ {
_testOutputHelper = testOutputHelper; _testOutputHelper = testOutputHelper;
} }
@ -56,7 +56,7 @@ public abstract class SiteThemeServiceTest : AbstractDbTest
}); });
await _context.SaveChangesAsync(); 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); Assert.Equal("Theme file missing or invalid", ex.Message);
} }

View File

@ -55,27 +55,27 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Docnet.Core" Version="2.6.0" /> <PackageReference Include="Docnet.Core" Version="2.6.0" />
<PackageReference Include="EasyCaching.InMemory" Version="1.9.1" /> <PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
<PackageReference Include="ExCSS" Version="4.2.2" /> <PackageReference Include="ExCSS" Version="4.2.4" />
<PackageReference Include="Flurl" Version="3.0.7" /> <PackageReference Include="Flurl" Version="3.0.7" />
<PackageReference Include="Flurl.Http" Version="3.2.4" /> <PackageReference Include="Flurl.Http" Version="3.2.4" />
<PackageReference Include="Hangfire" Version="1.8.5" /> <PackageReference Include="Hangfire" Version="1.8.6" />
<PackageReference Include="Hangfire.AspNetCore" Version="1.8.5" /> <PackageReference Include="Hangfire.AspNetCore" Version="1.8.6" />
<PackageReference Include="Hangfire.InMemory" Version="0.5.1" /> <PackageReference Include="Hangfire.InMemory" Version="0.6.0" />
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" /> <PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" /> <PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
<PackageReference Include="Hangfire.Storage.SQLite" Version="0.3.4" /> <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="MarkdownDeep.NET.Core" Version="1.5.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.12" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.12" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.12" /> <PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.13" />
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.1.0" /> <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> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </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.Extensions.DependencyInjection" Version="7.0.0" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" /> <PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
<PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" /> <PackageReference Include="MimeTypeMapOfficial" Version="1.0.17" />

View File

@ -2,6 +2,8 @@
namespace API.Comparators; namespace API.Comparators;
#nullable enable
/// <summary> /// <summary>
/// Sorts chapters based on their Number. Uses natural ordering of doubles. /// Sorts chapters based on their Number. Uses natural ordering of doubles.
/// </summary> /// </summary>

View File

@ -2,6 +2,8 @@
namespace API.Comparators; namespace API.Comparators;
#nullable enable
public class NumericComparer : IComparer public class NumericComparer : IComparer
{ {

View File

@ -6,6 +6,7 @@ using static System.Char;
namespace API.Comparators; namespace API.Comparators;
public static class StringLogicalComparer public static class StringLogicalComparer
{ {
public static int Compare(string s1, string s2) public static int Compare(string s1, string s2)

View File

@ -29,6 +29,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// All Account matters /// All Account matters
/// </summary> /// </summary>

View File

@ -6,6 +6,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class AdminController : BaseApiController public class AdminController : BaseApiController
{ {
private readonly UserManager<AppUser> _userManager; private readonly UserManager<AppUser> _userManager;

View File

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
[Authorize] [Authorize]

View File

@ -14,6 +14,8 @@ using VersOne.Epub;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class BookController : BaseApiController public class BookController : BaseApiController
{ {
private readonly IBookService _bookService; private readonly IBookService _bookService;

View File

@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for the CBL import flow /// Responsible for the CBL import flow
/// </summary> /// </summary>

View File

@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// APIs for Collections /// APIs for Collections
/// </summary> /// </summary>

View File

@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible interacting and creating Devices /// Responsible interacting and creating Devices
/// </summary> /// </summary>

View File

@ -16,6 +16,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// All APIs related to downloading entities from the system. Requires Download Role or Admin Role. /// All APIs related to downloading entities from the system. Requires Download Role or Admin Role.
/// </summary> /// </summary>

View File

@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[AllowAnonymous] [AllowAnonymous]
public class FallbackController : Controller public class FallbackController : Controller
{ {

View File

@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Constants;
using API.Data; using API.Data;
using API.Data.Repositories; using API.Data.Repositories;
using API.DTOs.Dashboard; using API.DTOs.Dashboard;
@ -10,23 +9,22 @@ using API.DTOs.Filtering.v2;
using API.Entities; using API.Entities;
using API.Extensions; using API.Extensions;
using API.Helpers; using API.Helpers;
using EasyCaching.Core;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// This is responsible for Filter caching /// This is responsible for Filter caching
/// </summary> /// </summary>
public class FilterController : BaseApiController public class FilterController : BaseApiController
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
private readonly IEasyCachingProviderFactory _cacheFactory;
public FilterController(IUnitOfWork unitOfWork, IEasyCachingProviderFactory cacheFactory) public FilterController(IUnitOfWork unitOfWork)
{ {
_unitOfWork = unitOfWork; _unitOfWork = unitOfWork;
_cacheFactory = cacheFactory;
} }
/// <summary> /// <summary>
@ -93,4 +91,26 @@ public class FilterController : BaseApiController
await _unitOfWork.CommitAsync(); await _unitOfWork.CommitAsync();
return Ok(); 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));
}
} }

View File

@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[AllowAnonymous] [AllowAnonymous]
public class HealthController : BaseApiController public class HealthController : BaseApiController
{ {

View File

@ -13,6 +13,8 @@ using MimeTypes;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for servicing up images stored in Kavita for entities /// Responsible for servicing up images stored in Kavita for entities
/// </summary> /// </summary>

View File

@ -27,6 +27,8 @@ using TaskScheduler = API.Services.TaskScheduler;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[Authorize] [Authorize]
public class LibraryController : BaseApiController public class LibraryController : BaseApiController
{ {

View File

@ -15,6 +15,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class LicenseController : BaseApiController public class LicenseController : BaseApiController
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;

View File

@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class LocaleController : BaseApiController public class LocaleController : BaseApiController
{ {
private readonly ILocalizationService _localizationService; private readonly ILocalizationService _localizationService;

View File

@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class MetadataController : BaseApiController public class MetadataController : BaseApiController
{ {

View File

@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// For the Panels app explicitly /// For the Panels app explicitly
/// </summary> /// </summary>

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations; using System;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Data; using API.Data;
using API.DTOs; using API.DTOs;
@ -11,6 +12,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class PluginController : BaseApiController public class PluginController : BaseApiController
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;
@ -43,7 +46,7 @@ public class PluginController : BaseApiController
var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); var userId = await _unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey);
if (userId <= 0) 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, IpAddress = ipAddress,
UserAgent = userAgent, UserAgent = userAgent,
@ -52,7 +55,7 @@ public class PluginController : BaseApiController
throw new KavitaUnauthenticatedUserException(); throw new KavitaUnauthenticatedUserException();
} }
var user = await _unitOfWork.UserRepository.GetUserByIdAsync(userId); 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 return new UserDto
{ {
Username = user.UserName!, Username = user.UserName!,

View File

@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for providing external ratings for Series /// Responsible for providing external ratings for Series
/// </summary> /// </summary>

View File

@ -26,6 +26,8 @@ using MimeTypes;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// For all things regarding reading, mainly focusing on non-Book related entities /// For all things regarding reading, mainly focusing on non-Book related entities
/// </summary> /// </summary>

View File

@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[Authorize] [Authorize]
public class ReadingListController : BaseApiController public class ReadingListController : BaseApiController
{ {

View File

@ -17,6 +17,8 @@ using Newtonsoft.Json;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class RecommendedController : BaseApiController public class RecommendedController : BaseApiController
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;

View File

@ -18,6 +18,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class ReviewController : BaseApiController public class ReviewController : BaseApiController
{ {
private readonly ILogger<ReviewController> _logger; private readonly ILogger<ReviewController> _logger;

View File

@ -21,6 +21,7 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class ScrobblingController : BaseApiController public class ScrobblingController : BaseApiController
{ {

View File

@ -10,6 +10,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for the Search interface from the UI /// Responsible for the Search interface from the UI
/// </summary> /// </summary>

View File

@ -28,6 +28,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class SeriesController : BaseApiController public class SeriesController : BaseApiController
{ {
private readonly ILogger<SeriesController> _logger; private readonly ILogger<SeriesController> _logger;

View File

@ -20,13 +20,14 @@ using Hangfire.Storage;
using Kavita.Common; using Kavita.Common;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using MimeTypes; using MimeTypes;
using TaskScheduler = API.Services.TaskScheduler; using TaskScheduler = API.Services.TaskScheduler;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[Authorize(Policy = "RequireAdminRole")] [Authorize(Policy = "RequireAdminRole")]
public class ServerController : BaseApiController public class ServerController : BaseApiController
{ {
@ -286,8 +287,6 @@ public class ServerController : BaseApiController
if (emailServiceUrl.Equals(EmailService.DefaultApiUrl)) return Ok(null); if (emailServiceUrl.Equals(EmailService.DefaultApiUrl)) return Ok(null);
return Ok(await _emailService.GetVersion(emailServiceUrl)); return Ok(await _emailService.GetVersion(emailServiceUrl));
} }
} }

View File

@ -24,6 +24,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class SettingsController : BaseApiController public class SettingsController : BaseApiController
{ {
private readonly ILogger<SettingsController> _logger; private readonly ILogger<SettingsController> _logger;

View File

@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class StatsController : BaseApiController public class StatsController : BaseApiController
{ {
private readonly IStatisticService _statService; private readonly IStatisticService _statService;

View File

@ -10,6 +10,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for anything that deals with Streams (SmartFilters, ExternalSource, DashboardStream, SideNavStream) /// Responsible for anything that deals with Streams (SmartFilters, ExternalSource, DashboardStream, SideNavStream)
/// </summary> /// </summary>

View File

@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any /// All APIs are for Tachiyomi extension and app. They have hacks for our implementation and should not be used for any
/// other purposes. /// other purposes.

View File

@ -11,6 +11,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
public class ThemeController : BaseApiController public class ThemeController : BaseApiController
{ {
private readonly IUnitOfWork _unitOfWork; private readonly IUnitOfWork _unitOfWork;

View File

@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>

View File

@ -13,6 +13,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
[Authorize] [Authorize]
public class UsersController : BaseApiController public class UsersController : BaseApiController
{ {

View File

@ -16,6 +16,8 @@ using Microsoft.AspNetCore.Mvc;
namespace API.Controllers; namespace API.Controllers;
#nullable enable
/// <summary> /// <summary>
/// Responsible for all things Want To Read /// Responsible for all things Want To Read
/// </summary> /// </summary>
@ -42,7 +44,7 @@ public class WantToReadController : BaseApiController
/// <returns></returns> /// <returns></returns>
[HttpPost] [HttpPost]
[Obsolete("use v2 instead")] [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(); userParams ??= new UserParams();
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(User.GetUserId(), userParams, filterDto); var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(User.GetUserId(), userParams, filterDto);
@ -60,7 +62,7 @@ public class WantToReadController : BaseApiController
/// <param name="filterDto"></param> /// <param name="filterDto"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("v2")] [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(); userParams ??= new UserParams();
var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserV2Async(User.GetUserId(), userParams, filterDto); var pagedList = await _unitOfWork.SeriesRepository.GetWantToReadForUserV2Async(User.GetUserId(), userParams, filterDto);

View 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; }
}

View 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");
}
}

View File

@ -627,7 +627,6 @@ public class UserRepository : IUserRepository
return await ApplyLimit(filterSeriesQuery return await ApplyLimit(filterSeriesQuery
.Sort(filter.SortOptions) .Sort(filter.SortOptions)
.AsSplitQuery(), filter.LimitTo) .AsSplitQuery(), filter.LimitTo)
.Select(o => o.Bookmark)
.ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider) .ProjectTo<BookmarkDto>(_mapper.ConfigurationProvider)
.ToListAsync(); .ToListAsync();
} }

View File

@ -4,6 +4,7 @@ using API.Entities;
using API.Helpers; using API.Helpers;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class AppUserExtensions public static class AppUserExtensions
{ {

View File

@ -17,6 +17,7 @@ using Microsoft.Extensions.DependencyInjection;
namespace API.Extensions; namespace API.Extensions;
public static class ApplicationServiceExtensions public static class ApplicationServiceExtensions
{ {
public static void AddApplicationServices(this IServiceCollection services, IConfiguration config, IWebHostEnvironment env) public static void AddApplicationServices(this IServiceCollection services, IConfiguration config, IWebHostEnvironment env)

View File

@ -5,6 +5,7 @@ using API.Helpers;
using API.Services.Tasks.Scanner.Parser; using API.Services.Tasks.Scanner.Parser;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class ChapterListExtensions public static class ChapterListExtensions
{ {

View File

@ -3,6 +3,7 @@ using Kavita.Common;
using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames; using JwtRegisteredClaimNames = Microsoft.IdentityModel.JsonWebTokens.JwtRegisteredClaimNames;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class ClaimsPrincipalExtensions public static class ClaimsPrincipalExtensions
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class DateTimeExtensions public static class DateTimeExtensions
{ {

View File

@ -2,6 +2,7 @@
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class EncodeFormatExtensions public static class EncodeFormatExtensions
{ {

View File

@ -6,6 +6,7 @@ using API.Data.Misc;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class EnumerableExtensions public static class EnumerableExtensions
{ {

View File

@ -2,6 +2,7 @@
using System.IO; using System.IO;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class FileInfoExtensions public static class FileInfoExtensions
{ {

View File

@ -4,6 +4,7 @@ using API.DTOs.Filtering;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class FilterDtoExtensions public static class FilterDtoExtensions
{ {

View File

@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class HttpExtensions public static class HttpExtensions
{ {

View File

@ -11,6 +11,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class IdentityServiceExtensions public static class IdentityServiceExtensions
{ {

View File

@ -4,6 +4,7 @@ using API.Entities;
using API.Services.Tasks.Scanner.Parser; using API.Services.Tasks.Scanner.Parser;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class ParserInfoListExtensions public static class ParserInfoListExtensions
{ {

View File

@ -1,6 +1,7 @@
using System.IO; using System.IO;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class PathExtensions public static class PathExtensions
{ {

View File

@ -1,14 +1,14 @@
using System.Linq; using System.Linq;
using API.DTOs.Filtering; using API.DTOs.Filtering;
using API.Entities; using API.Entities;
using API.Extensions.QueryExtensions;
namespace API.Extensions.QueryExtensions.Filtering; namespace API.Extensions.QueryExtensions.Filtering;
#nullable enable
public class BookmarkSeriesPair public class BookmarkSeriesPair
{ {
public AppUserBookmark Bookmark { get; set; } public AppUserBookmark Bookmark { get; init; } = null!;
public Series Series { get; set; } public Series Series { get; init; } = null!;
} }
public static class BookmarkSort public static class BookmarkSort

View File

@ -9,7 +9,6 @@ using Kavita.Common;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace API.Extensions.QueryExtensions.Filtering; namespace API.Extensions.QueryExtensions.Filtering;
#nullable enable #nullable enable
public static class SeriesFilter public static class SeriesFilter

View File

@ -1,7 +1,9 @@
using System.Linq; using System.Linq;
using API.DTOs.Filtering; using API.DTOs.Filtering;
using API.Entities; using API.Entities;
using API.Extensions.QueryExtensions;
namespace API.Extensions.QueryExtensions.Filtering;
#nullable enable
public static class SeriesSort public static class SeriesSort
{ {

View File

@ -4,6 +4,7 @@ using API.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace API.Extensions.QueryExtensions; namespace API.Extensions.QueryExtensions;
#nullable enable
/// <summary> /// <summary>
/// All extensions against IQueryable that enables the dynamic including based on bitwise flag pattern /// All extensions against IQueryable that enables the dynamic including based on bitwise flag pattern

View File

@ -12,6 +12,7 @@ using API.Entities.Scrobble;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace API.Extensions.QueryExtensions; namespace API.Extensions.QueryExtensions;
#nullable enable
public static class QueryableExtensions public static class QueryableExtensions
{ {

View File

@ -4,6 +4,7 @@ using API.Entities;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Extensions.QueryExtensions; namespace API.Extensions.QueryExtensions;
#nullable enable
/// <summary> /// <summary>
/// Responsible for restricting Entities based on an AgeRestriction /// Responsible for restricting Entities based on an AgeRestriction

View File

@ -1,5 +1,4 @@
#nullable enable using System.Collections.Generic;
using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using API.Comparators; using API.Comparators;
@ -7,6 +6,7 @@ using API.Entities;
using API.Services.Tasks.Scanner.Parser; using API.Services.Tasks.Scanner.Parser;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class SeriesExtensions public static class SeriesExtensions
{ {

View File

@ -2,6 +2,7 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class StringExtensions public static class StringExtensions
{ {

View File

@ -5,6 +5,7 @@ using API.Entities;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class VolumeListExtensions public static class VolumeListExtensions
{ {

View File

@ -3,6 +3,7 @@ using System.IO.Compression;
using System.Linq; using System.Linq;
namespace API.Extensions; namespace API.Extensions;
#nullable enable
public static class ZipArchiveExtensions public static class ZipArchiveExtensions
{ {

View File

@ -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)
// {
//
// }
}

View File

@ -4,6 +4,7 @@ using API.Entities.Interfaces;
using API.Services; using API.Services;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public interface ICacheHelper public interface ICacheHelper
{ {

View File

@ -2,6 +2,7 @@
using Hangfire; using Hangfire;
namespace API.Helpers.Converters; namespace API.Helpers.Converters;
#nullable enable
public static class CronConverter public static class CronConverter
{ {

View File

@ -7,6 +7,7 @@ using API.Entities.Enums;
using API.Extensions; using API.Extensions;
namespace API.Helpers.Converters; namespace API.Helpers.Converters;
#nullable enable
public static class FilterFieldValueConverter public static class FilterFieldValueConverter
{ {

View File

@ -6,6 +6,7 @@ using API.Entities.Enums;
using AutoMapper; using AutoMapper;
namespace API.Helpers.Converters; namespace API.Helpers.Converters;
#nullable enable
public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>, ServerSettingDto> public class ServerSettingConverter : ITypeConverter<IEnumerable<ServerSetting>, ServerSettingDto>
{ {

View File

@ -3,6 +3,7 @@ using API.DTOs.Scrobbling;
using API.Entities.Enums; using API.Entities.Enums;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class LibraryTypeHelper public static class LibraryTypeHelper
{ {

View File

@ -1,4 +1,5 @@
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class NumberHelper public static class NumberHelper
{ {

View File

@ -2,6 +2,7 @@
using API.Entities; using API.Entities;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class OrderableHelper public static class OrderableHelper
{ {

View File

@ -5,10 +5,11 @@ using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public class PagedList<T> : List<T> 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; CurrentPage = pageNumber;
TotalPages = (int) Math.Ceiling(count / (double) pageSize); TotalPages = (int) Math.Ceiling(count / (double) pageSize);

View File

@ -1,4 +1,5 @@
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public class PaginationHeader public class PaginationHeader
{ {

View File

@ -6,6 +6,7 @@ using API.Services.Tasks.Scanner;
using API.Services.Tasks.Scanner.Parser; using API.Services.Tasks.Scanner.Parser;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class ParserInfoHelpers public static class ParserInfoHelpers
{ {

View File

@ -8,6 +8,7 @@ using API.Extensions;
using API.Helpers.Builders; using API.Helpers.Builders;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class PersonHelper public static class PersonHelper
{ {
@ -29,6 +30,7 @@ public static class PersonHelper
foreach (var name in names) foreach (var name in names)
{ {
var normalizedName = name.ToNormalized(); var normalizedName = name.ToNormalized();
// BUG: Doesn't this create a duplicate entry because allPeopleTypeRoles is a different instance?
var person = allPeopleTypeRole.Find(p => var person = allPeopleTypeRole.Find(p =>
p.NormalizedName != null && p.NormalizedName.Equals(normalizedName)); p.NormalizedName != null && p.NormalizedName.Equals(normalizedName));
if (person == null) if (person == null)

View File

@ -1 +0,0 @@


View File

@ -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;
}
}

View File

@ -6,6 +6,7 @@ using API.Extensions;
using API.Services.Tasks.Scanner; using API.Services.Tasks.Scanner;
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public static class SeriesHelper public static class SeriesHelper
{ {

View File

@ -5,14 +5,24 @@ using System.Web;
using API.DTOs.Filtering; using API.DTOs.Filtering;
using API.DTOs.Filtering.v2; using API.DTOs.Filtering.v2;
#nullable enable
namespace API.Helpers; namespace API.Helpers;
public static class SmartFilterHelper public static class SmartFilterHelper
{ {
private const string SortOptionsKey = "sortOptions="; 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 StatementsKey = "stmts=";
private const string LimitToKey = "limitTo="; private const string LimitToKey = "limitTo=";
private const string CombinationKey = "combination="; 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) 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 return new FilterV2Dto(); // Create a default filter if the input is empty
} }
string[] parts = encodedFilter.Split('&'); var parts = encodedFilter.Split('&');
var filter = new FilterV2Dto(); var filter = new FilterV2Dto();
foreach (var part in parts) foreach (var part in parts)
@ -42,7 +52,7 @@ public static class SmartFilterHelper
{ {
filter.Statements = DecodeFilterStatementDtos(part.Substring(StatementsKey.Length)); filter.Statements = DecodeFilterStatementDtos(part.Substring(StatementsKey.Length));
} }
else if (part.StartsWith("name=")) else if (part.StartsWith(NameKey))
{ {
filter.Name = HttpUtility.UrlDecode(part.Substring(5)); filter.Name = HttpUtility.UrlDecode(part.Substring(5));
} }
@ -51,7 +61,7 @@ public static class SmartFilterHelper
return filter; return filter;
} }
public static string Encode(FilterV2Dto filter) public static string Encode(FilterV2Dto? filter)
{ {
if (filter == null) if (filter == null)
return string.Empty; return string.Empty;
@ -59,50 +69,50 @@ public static class SmartFilterHelper
var encodedStatements = EncodeFilterStatementDtos(filter.Statements); var encodedStatements = EncodeFilterStatementDtos(filter.Statements);
var encodedSortOptions = filter.SortOptions != null var encodedSortOptions = filter.SortOptions != null
? $"{SortOptionsKey}{EncodeSortOptions(filter.SortOptions)}" ? $"{SortOptionsKey}{EncodeSortOptions(filter.SortOptions)}"
: ""; : string.Empty;
var encodedLimitTo = $"{LimitToKey}{filter.LimitTo}"; var encodedLimitTo = $"{LimitToKey}{filter.LimitTo}";
return $"{EncodeName(filter.Name)}{encodedStatements}&{encodedSortOptions}&{encodedLimitTo}&{CombinationKey}{(int) filter.Combination}"; 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) 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) if (statements == null || statements.Count == 0)
return string.Empty; 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; return encodedStatements;
} }
private static string EncodeFilterStatementDto(FilterStatementDto statement) 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) private static List<FilterStatementDto> DecodeFilterStatementDtos(string encodedStatements)
{ {
encodedStatements = HttpUtility.UrlDecode(encodedStatements); var statementStrings = Uri.UnescapeDataString(encodedStatements).Split(StatementSeparator);
string[] statementStrings = encodedStatements.Split(',');
var statements = new List<FilterStatementDto>(); var statements = new List<FilterStatementDto>();
foreach (var statementString in statementStrings) foreach (var statementString in statementStrings)
{ {
var parts = statementString.Split('&'); var parts = Uri.UnescapeDataString(statementString).Split(InnerStatementSeparator);
if (parts.Length < 3) if (parts.Length < 3)
continue; continue;
@ -110,7 +120,7 @@ public static class SmartFilterHelper
{ {
Comparison = Enum.Parse<FilterComparison>(parts[0].Split("=")[1]), Comparison = Enum.Parse<FilterComparison>(parts[0].Split("=")[1]),
Field = Enum.Parse<FilterField>(parts[1].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) private static SortOptions DecodeSortOptions(string encodedSortOptions)
{ {
string[] parts = encodedSortOptions.Split(','); var parts = Uri.UnescapeDataString(encodedSortOptions).Split(InnerStatementSeparator);
var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith("sortField=")); var sortFieldPart = parts.FirstOrDefault(part => part.StartsWith(SortFieldKey));
var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith("isAscending=")); var isAscendingPart = parts.FirstOrDefault(part => part.StartsWith(IsAscendingKey));
var isAscending = isAscendingPart?.Substring(11).Equals("true", StringComparison.OrdinalIgnoreCase) ?? false; 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();
return new SortOptions
{
SortField = sortField,
IsAscending = isAscending
};
} }
return null; var sortField = Enum.Parse<SortField>(sortFieldPart.Split("=")[1]);
return new SortOptions
{
SortField = sortField,
IsAscending = isAscending
};
} }
} }

View File

@ -9,8 +9,8 @@ using API.Extensions;
using API.Helpers.Builders; using API.Helpers.Builders;
namespace API.Helpers; namespace API.Helpers;
#nullable enable #nullable enable
public static class TagHelper public static class TagHelper
{ {
/// <summary> /// <summary>

View File

@ -1,4 +1,5 @@
namespace API.Helpers; namespace API.Helpers;
#nullable enable
public class UserParams public class UserParams
{ {
@ -15,7 +16,7 @@ public class UserParams
init => _pageSize = (value == 0) ? MaxPageSize : value; init => _pageSize = (value == 0) ? MaxPageSize : value;
} }
public static readonly UserParams Default = new UserParams() public static readonly UserParams Default = new()
{ {
PageSize = 20, PageSize = 20,
PageNumber = 1 PageNumber = 1

View File

@ -16,6 +16,8 @@ using Microsoft.Extensions.Logging;
namespace API.Services; namespace API.Services;
#nullable enable
public interface IAccountService public interface IAccountService
{ {
Task<IEnumerable<ApiException>> ChangeUserPassword(AppUser user, string newPassword); Task<IEnumerable<ApiException>> ChangeUserPassword(AppUser user, string newPassword);

View File

@ -18,6 +18,8 @@ using SharpCompress.Common;
namespace API.Services; namespace API.Services;
#nullable enable
public interface IArchiveService public interface IArchiveService
{ {
void ExtractArchive(string archivePath, string extractPath); void ExtractArchive(string archivePath, string extractPath);

View File

@ -29,6 +29,7 @@ using VersOne.Epub.Options;
using VersOne.Epub.Schema; using VersOne.Epub.Schema;
namespace API.Services; namespace API.Services;
#nullable enable #nullable enable
public interface IBookService public interface IBookService

View File

@ -12,6 +12,8 @@ using Microsoft.Extensions.Logging;
namespace API.Services; namespace API.Services;
#nullable enable
public interface IBookmarkService public interface IBookmarkService
{ {
Task DeleteBookmarkFiles(IEnumerable<AppUserBookmark> bookmarks); Task DeleteBookmarkFiles(IEnumerable<AppUserBookmark> bookmarks);

View File

@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
namespace API.Services.HostedServices; namespace API.Services.HostedServices;
#nullable enable
public class StartupTasksHostedService : IHostedService public class StartupTasksHostedService : IHostedService
{ {

View File

@ -14,6 +14,7 @@ using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Plus; namespace API.Services.Plus;
#nullable enable
/// <summary> /// <summary>
/// Used for matching and fetching metadata on a series /// Used for matching and fetching metadata on a series

View File

@ -13,6 +13,7 @@ using Kavita.Common.EnvironmentInfo;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Plus; namespace API.Services.Plus;
#nullable enable
internal class RegisterLicenseResponseDto internal class RegisterLicenseResponseDto
{ {

View File

@ -17,6 +17,7 @@ using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Plus; namespace API.Services.Plus;
#nullable enable
public interface IRatingService public interface IRatingService
{ {

View File

@ -18,6 +18,7 @@ using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Plus; namespace API.Services.Plus;
#nullable enable
public record PlusSeriesDto public record PlusSeriesDto
{ {

View File

@ -22,6 +22,7 @@ using Kavita.Common.Helpers;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Plus; namespace API.Services.Plus;
#nullable enable
/// <summary> /// <summary>
/// Misleading name but is the source of data (like a review coming from AniList) /// Misleading name but is the source of data (like a review coming from AniList)

View File

@ -12,6 +12,7 @@ using Hangfire;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Tasks; namespace API.Services.Tasks;
#nullable enable
public interface IBackupService public interface IBackupService
{ {

View File

@ -13,6 +13,7 @@ using Hangfire;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace API.Services.Tasks; namespace API.Services.Tasks;
#nullable enable
public interface ICleanupService public interface ICleanupService
{ {

View File

@ -13,6 +13,7 @@ using Microsoft.Extensions.Logging;
using VersOne.Epub; using VersOne.Epub;
namespace API.Services.Tasks.Metadata; namespace API.Services.Tasks.Metadata;
#nullable enable
public interface IWordCountAnalyzerService public interface IWordCountAnalyzerService
{ {

Some files were not shown because too many files have changed in this diff Show More