diff --git a/src/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index b14dc14b4..1fef2ab9a 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -158,6 +158,8 @@ namespace NzbDrone.Host { { apikeyQuery, Array.Empty() } }); + + c.DescribeAllParametersInCamelCase(); }); services diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs index 1e80d83ce..faa80430c 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs @@ -86,7 +86,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests var author = EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", false); EnsureBookFile(author, 1, "43765115", Quality.MOBI); - var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc", "monitored", "false"); + var result = WantedCutoffUnmet.GetPaged(0, 15, "releaseDate", "desc", "monitored", false); result.Records.Should().NotBeEmpty(); } diff --git a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs index d0cd688b7..801fd8197 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs @@ -86,7 +86,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests { EnsureAuthor("14586394", "43765115", "Andrew Hunter Murray", false); - var result = WantedMissing.GetPaged(0, 15, "releaseDate", "desc", "monitored", "false"); + var result = WantedMissing.GetPaged(0, 15, "releaseDate", "desc", "monitored", false); result.Records.Should().NotBeEmpty(); } diff --git a/src/NzbDrone.Integration.Test/Client/ClientBase.cs b/src/NzbDrone.Integration.Test/Client/ClientBase.cs index 756a09cf8..274c39007 100644 --- a/src/NzbDrone.Integration.Test/Client/ClientBase.cs +++ b/src/NzbDrone.Integration.Test/Client/ClientBase.cs @@ -93,7 +93,7 @@ namespace NzbDrone.Integration.Test.Client return Get>(request); } - public PagingResource GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, string filterValue = null) + public PagingResource GetPaged(int pageNumber, int pageSize, string sortKey, string sortDir, string filterKey = null, object filterValue = null) { var request = BuildRequest(); request.AddParameter("page", pageNumber); @@ -103,8 +103,7 @@ namespace NzbDrone.Integration.Test.Client if (filterKey != null && filterValue != null) { - request.AddParameter("filterKey", filterKey); - request.AddParameter("filterValue", filterValue); + request.AddParameter(filterKey, filterValue); } return Get>(request); diff --git a/src/Readarr.Api.V1/Blocklist/BlocklistController.cs b/src/Readarr.Api.V1/Blocklist/BlocklistController.cs index c3f2f7a7d..8e6dc44e4 100644 --- a/src/Readarr.Api.V1/Blocklist/BlocklistController.cs +++ b/src/Readarr.Api.V1/Blocklist/BlocklistController.cs @@ -22,9 +22,10 @@ namespace Readarr.Api.V1.Blocklist } [HttpGet] - public PagingResource GetBlocklist() + [Produces("application/json")] + public PagingResource GetBlocklist([FromQuery] PagingRequestResource paging) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = pagingResource.MapToPagingSpec("date", SortDirection.Descending); return pagingSpec.ApplyToPage(_blocklistService.Paged, model => BlocklistResourceMapper.MapToResource(model, _formatCalculator)); diff --git a/src/Readarr.Api.V1/History/HistoryController.cs b/src/Readarr.Api.V1/History/HistoryController.cs index dfe2fd579..edc2ed87d 100644 --- a/src/Readarr.Api.V1/History/HistoryController.cs +++ b/src/Readarr.Api.V1/History/HistoryController.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Books; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; @@ -60,30 +61,25 @@ namespace Readarr.Api.V1.History } [HttpGet] - public PagingResource GetHistory(bool includeAuthor = false, bool includeBook = false) + [Produces("application/json")] + public PagingResource GetHistory([FromQuery] PagingRequestResource paging, bool includeAuthor, bool includeBook, [FromQuery(Name = "eventType")] int[] eventTypes, int? bookId, string downloadId) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = pagingResource.MapToPagingSpec("date", SortDirection.Descending); - var eventTypeFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "eventType"); - var bookIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "bookId"); - var downloadIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "downloadId"); - - if (eventTypeFilter != null) + if (eventTypes != null && eventTypes.Any()) { - var filterValue = (EntityHistoryEventType)Convert.ToInt32(eventTypeFilter.Value); - pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue); + var filterValues = eventTypes.Cast().ToArray(); + pagingSpec.FilterExpressions.Add(v => filterValues.Contains(v.EventType)); } - if (bookIdFilter != null) + if (bookId.HasValue) { - var bookId = Convert.ToInt32(bookIdFilter.Value); pagingSpec.FilterExpressions.Add(h => h.BookId == bookId); } - if (downloadIdFilter != null) + if (downloadId.IsNotNullOrWhiteSpace()) { - var downloadId = downloadIdFilter.Value; pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId); } diff --git a/src/Readarr.Api.V1/Logs/LogController.cs b/src/Readarr.Api.V1/Logs/LogController.cs index 7020e2a6b..cfb0f8735 100644 --- a/src/Readarr.Api.V1/Logs/LogController.cs +++ b/src/Readarr.Api.V1/Logs/LogController.cs @@ -1,5 +1,5 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Instrumentation; using Readarr.Http; using Readarr.Http.Extensions; @@ -17,9 +17,10 @@ namespace Readarr.Api.V1.Logs } [HttpGet] - public PagingResource GetLogs() + [Produces("application/json")] + public PagingResource GetLogs([FromQuery] PagingRequestResource paging, string level) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pageSpec = pagingResource.MapToPagingSpec(); if (pageSpec.SortKey == "time") @@ -27,11 +28,9 @@ namespace Readarr.Api.V1.Logs pageSpec.SortKey = "id"; } - var levelFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "level"); - - if (levelFilter != null) + if (level.IsNotNullOrWhiteSpace()) { - switch (levelFilter.Value) + switch (level) { case "fatal": pageSpec.FilterExpressions.Add(h => h.Level == "Fatal"); diff --git a/src/Readarr.Api.V1/Queue/QueueController.cs b/src/Readarr.Api.V1/Queue/QueueController.cs index 87606ef6d..a3fb4312a 100644 --- a/src/Readarr.Api.V1/Queue/QueueController.cs +++ b/src/Readarr.Api.V1/Queue/QueueController.cs @@ -100,9 +100,10 @@ namespace Readarr.Api.V1.Queue } [HttpGet] - public PagingResource GetQueue(bool includeUnknownAuthorItems = false, bool includeAuthor = false, bool includeBook = false) + [Produces("application/json")] + public PagingResource GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownAuthorItems = false, bool includeAuthor = false, bool includeBook = false) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = pagingResource.MapToPagingSpec("timeleft", SortDirection.Ascending); return pagingSpec.ApplyToPage((spec) => GetQueue(spec, includeUnknownAuthorItems), (q) => MapToResource(q, includeAuthor, includeBook)); diff --git a/src/Readarr.Api.V1/Wanted/CutoffController.cs b/src/Readarr.Api.V1/Wanted/CutoffController.cs index 3d9907537..16590e77d 100644 --- a/src/Readarr.Api.V1/Wanted/CutoffController.cs +++ b/src/Readarr.Api.V1/Wanted/CutoffController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.AuthorStats; using NzbDrone.Core.Books; @@ -30,9 +29,9 @@ namespace Readarr.Api.V1.Wanted } [HttpGet] - public PagingResource GetCutoffUnmetBooks(bool includeAuthor = false) + public PagingResource GetCutoffUnmetBooks([FromQuery] PagingRequestResource paging, bool includeAuthor = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = new PagingSpec { Page = pagingResource.Page, @@ -41,9 +40,7 @@ namespace Readarr.Api.V1.Wanted SortDirection = pagingResource.SortDirection }; - var filter = pagingResource.Filters.FirstOrDefault(f => f.Key == "monitored"); - - if (filter != null && filter.Value == "false") + if (monitored) { pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Author.Value.Monitored == false); } diff --git a/src/Readarr.Api.V1/Wanted/MissingController.cs b/src/Readarr.Api.V1/Wanted/MissingController.cs index 809d3fef3..214eb7055 100644 --- a/src/Readarr.Api.V1/Wanted/MissingController.cs +++ b/src/Readarr.Api.V1/Wanted/MissingController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Microsoft.AspNetCore.Mvc; using NzbDrone.Core.AuthorStats; using NzbDrone.Core.Books; @@ -26,9 +25,9 @@ namespace Readarr.Api.V1.Wanted } [HttpGet] - public PagingResource GetMissingBooks(bool includeAuthor = false) + public PagingResource GetMissingBooks([FromQuery] PagingRequestResource paging, bool includeAuthor = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = new PagingSpec { Page = pagingResource.Page, @@ -37,9 +36,7 @@ namespace Readarr.Api.V1.Wanted SortDirection = pagingResource.SortDirection }; - var monitoredFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "monitored"); - - if (monitoredFilter != null && monitoredFilter.Value == "false") + if (monitored) { pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Author.Value.Monitored == false); } diff --git a/src/Readarr.Http/Extensions/RequestExtensions.cs b/src/Readarr.Http/Extensions/RequestExtensions.cs index 0b084d0f0..b0483ea79 100644 --- a/src/Readarr.Http/Extensions/RequestExtensions.cs +++ b/src/Readarr.Http/Extensions/RequestExtensions.cs @@ -4,7 +4,6 @@ using System.Linq; using Microsoft.AspNetCore.Http; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Core.Datastore; -using NzbDrone.Core.Exceptions; namespace Readarr.Http.Extensions { @@ -52,80 +51,6 @@ namespace Readarr.Http.Extensions return defaultValue; } - public static PagingResource ReadPagingResourceFromRequest(this HttpRequest request) - { - if (!int.TryParse(request.Query["PageSize"].ToString(), out var pageSize)) - { - pageSize = 10; - } - - if (!int.TryParse(request.Query["Page"].ToString(), out var page)) - { - page = 1; - } - - var pagingResource = new PagingResource - { - PageSize = pageSize, - Page = page, - Filters = new List() - }; - - if (request.Query["SortKey"].Any()) - { - var sortKey = request.Query["SortKey"].ToString(); - - if (!VALID_SORT_KEYS.Contains(sortKey) && - !TableMapping.Mapper.IsValidSortKey(sortKey)) - { - throw new BadRequestException($"Invalid sort key {sortKey}"); - } - - pagingResource.SortKey = sortKey; - - if (request.Query["SortDirection"].Any()) - { - pagingResource.SortDirection = request.Query["SortDirection"].ToString() - .Equals("ascending", StringComparison.InvariantCultureIgnoreCase) - ? SortDirection.Ascending - : SortDirection.Descending; - } - } - - // For backwards compatibility with v2 - if (request.Query["FilterKey"].Any()) - { - var filter = new PagingResourceFilter - { - Key = request.Query["FilterKey"].ToString() - }; - - if (request.Query["FilterValue"].Any()) - { - filter.Value = request.Query["FilterValue"].ToString(); - } - - pagingResource.Filters.Add(filter); - } - - // v3 uses filters in key=value format - foreach (var pair in request.Query) - { - if (EXCLUDED_KEYS.Contains(pair.Key)) - { - continue; - } - - pagingResource.Filters.Add(new PagingResourceFilter - { - Key = pair.Key, - Value = pair.Value.ToString() - }); - } - - return pagingResource; - } - public static PagingResource ApplyToPage(this PagingSpec pagingSpec, Func, PagingSpec> function, Converter mapper) { pagingSpec = function(pagingSpec); diff --git a/src/Readarr.Http/PagingResource.cs b/src/Readarr.Http/PagingResource.cs index 40b3f7a71..088517145 100644 --- a/src/Readarr.Http/PagingResource.cs +++ b/src/Readarr.Http/PagingResource.cs @@ -1,17 +1,39 @@ using System.Collections.Generic; +using System.ComponentModel; using NzbDrone.Core.Datastore; namespace Readarr.Http { + public class PagingRequestResource + { + [DefaultValue(1)] + public int? Page { get; set; } + [DefaultValue(10)] + public int? PageSize { get; set; } + public string SortKey { get; set; } + public SortDirection? SortDirection { get; set; } + } + public class PagingResource { public int Page { get; set; } public int PageSize { get; set; } public string SortKey { get; set; } public SortDirection SortDirection { get; set; } - public List Filters { get; set; } public int TotalRecords { get; set; } public List Records { get; set; } + + public PagingResource() + { + } + + public PagingResource(PagingRequestResource requestResource) + { + Page = requestResource.Page ?? 1; + PageSize = requestResource.PageSize ?? 10; + SortKey = requestResource.SortKey; + SortDirection = requestResource.SortDirection ?? SortDirection.Descending; + } } public static class PagingResourceMapper diff --git a/src/Readarr.Http/PagingResourceFilter.cs b/src/Readarr.Http/PagingResourceFilter.cs deleted file mode 100644 index e88e308ea..000000000 --- a/src/Readarr.Http/PagingResourceFilter.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Readarr.Http -{ - public class PagingResourceFilter - { - public string Key { get; set; } - public string Value { get; set; } - } -}