From dcdc1409453a7294d2bba3162bc50d6c47d0b55b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Mon, 9 Oct 2023 20:37:31 -0700 Subject: [PATCH] Paging params in API docs (cherry picked from commit bfaa7291e14a8d3847ef2154a52c363944560803) Closes #4178 Closes #4196 --- .../Blocklist/BlocklistController.cs | 4 +- .../History/HistoryController.cs | 22 +++--- src/Lidarr.Api.V1/Logs/LogController.cs | 12 ++- src/Lidarr.Api.V1/Queue/QueueController.cs | 4 +- src/Lidarr.Api.V1/Wanted/CutoffController.cs | 13 ++-- src/Lidarr.Api.V1/Wanted/MissingController.cs | 13 ++-- .../Extensions/RequestExtensions.cs | 75 ------------------- src/Lidarr.Http/PagingResource.cs | 24 +++++- src/NzbDrone.Host/Startup.cs | 2 + .../WantedTests/CutoffUnmetFixture.cs | 2 +- .../ApiTests/WantedTests/MissingFixture.cs | 2 +- .../Client/ClientBase.cs | 5 +- 12 files changed, 58 insertions(+), 120 deletions(-) diff --git a/src/Lidarr.Api.V1/Blocklist/BlocklistController.cs b/src/Lidarr.Api.V1/Blocklist/BlocklistController.cs index 3a4b950e2..0d9627062 100644 --- a/src/Lidarr.Api.V1/Blocklist/BlocklistController.cs +++ b/src/Lidarr.Api.V1/Blocklist/BlocklistController.cs @@ -23,9 +23,9 @@ namespace Lidarr.Api.V1.Blocklist [HttpGet] [Produces("application/json")] - public PagingResource GetBlocklist() + 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/Lidarr.Api.V1/History/HistoryController.cs b/src/Lidarr.Api.V1/History/HistoryController.cs index e4517153b..7c8197209 100644 --- a/src/Lidarr.Api.V1/History/HistoryController.cs +++ b/src/Lidarr.Api.V1/History/HistoryController.cs @@ -7,6 +7,7 @@ using Lidarr.Api.V1.Tracks; using Lidarr.Http; using Lidarr.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.CustomFormats; using NzbDrone.Core.Datastore; using NzbDrone.Core.DecisionEngine.Specifications; @@ -66,30 +67,25 @@ namespace Lidarr.Api.V1.History } [HttpGet] - public PagingResource GetHistory(bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) + [Produces("application/json")] + public PagingResource GetHistory([FromQuery] PagingRequestResource paging, bool includeArtist, bool includeAlbum, bool includeTrack, int? eventType, int? albumId, 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 albumIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "albumId"); - var downloadIdFilter = pagingResource.Filters.FirstOrDefault(f => f.Key == "downloadId"); - - if (eventTypeFilter != null) + if (eventType.HasValue) { - var filterValue = (EntityHistoryEventType)Convert.ToInt32(eventTypeFilter.Value); + var filterValue = (EntityHistoryEventType)eventType.Value; pagingSpec.FilterExpressions.Add(v => v.EventType == filterValue); } - if (albumIdFilter != null) + if (albumId.HasValue) { - var albumId = Convert.ToInt32(albumIdFilter.Value); pagingSpec.FilterExpressions.Add(h => h.AlbumId == albumId); } - if (downloadIdFilter != null) + if (downloadId.IsNotNullOrWhiteSpace()) { - var downloadId = downloadIdFilter.Value; pagingSpec.FilterExpressions.Add(h => h.DownloadId == downloadId); } @@ -97,12 +93,14 @@ namespace Lidarr.Api.V1.History } [HttpGet("since")] + [Produces("application/json")] public List GetHistorySince(DateTime date, EntityHistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) { return _historyService.Since(date, eventType).Select(h => MapToResource(h, includeArtist, includeAlbum, includeTrack)).ToList(); } [HttpGet("artist")] + [Produces("application/json")] public List GetArtistHistory(int artistId, int? albumId = null, EntityHistoryEventType? eventType = null, bool includeArtist = false, bool includeAlbum = false, bool includeTrack = false) { var artist = _artistService.GetArtist(artistId); diff --git a/src/Lidarr.Api.V1/Logs/LogController.cs b/src/Lidarr.Api.V1/Logs/LogController.cs index 96f76e3a7..e08f56333 100644 --- a/src/Lidarr.Api.V1/Logs/LogController.cs +++ b/src/Lidarr.Api.V1/Logs/LogController.cs @@ -1,7 +1,7 @@ -using System.Linq; using Lidarr.Http; using Lidarr.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using NzbDrone.Common.Extensions; using NzbDrone.Core.Instrumentation; namespace Lidarr.Api.V1.Logs @@ -18,9 +18,9 @@ namespace Lidarr.Api.V1.Logs [HttpGet] [Produces("application/json")] - public PagingResource GetLogs() + 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") @@ -28,11 +28,9 @@ namespace Lidarr.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/Lidarr.Api.V1/Queue/QueueController.cs b/src/Lidarr.Api.V1/Queue/QueueController.cs index be061e8ad..32a68310d 100644 --- a/src/Lidarr.Api.V1/Queue/QueueController.cs +++ b/src/Lidarr.Api.V1/Queue/QueueController.cs @@ -129,9 +129,9 @@ namespace Lidarr.Api.V1.Queue [HttpGet] [Produces("application/json")] - public PagingResource GetQueue(bool includeUnknownArtistItems = false, bool includeArtist = false, bool includeAlbum = false) + public PagingResource GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownArtistItems = false, bool includeArtist = false, bool includeAlbum = false) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = pagingResource.MapToPagingSpec("timeleft", SortDirection.Ascending); return pagingSpec.ApplyToPage((spec) => GetQueue(spec, includeUnknownArtistItems), (q) => MapToResource(q, includeArtist, includeAlbum)); diff --git a/src/Lidarr.Api.V1/Wanted/CutoffController.cs b/src/Lidarr.Api.V1/Wanted/CutoffController.cs index 9b0bb6208..1f64f7276 100644 --- a/src/Lidarr.Api.V1/Wanted/CutoffController.cs +++ b/src/Lidarr.Api.V1/Wanted/CutoffController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Lidarr.Api.V1.Albums; using Lidarr.Http; using Lidarr.Http.Extensions; @@ -30,9 +29,9 @@ namespace Lidarr.Api.V1.Wanted [HttpGet] [Produces("application/json")] - public PagingResource GetCutoffUnmetAlbums(bool includeArtist = false) + public PagingResource GetCutoffUnmetAlbums([FromQuery] PagingRequestResource paging, bool includeArtist = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = new PagingSpec { Page = pagingResource.Page, @@ -41,15 +40,13 @@ namespace Lidarr.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.Artist.Value.Monitored == false); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); } else { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Value.Monitored == false); } return pagingSpec.ApplyToPage(_albumCutoffService.AlbumsWhereCutoffUnmet, v => MapToResource(v, includeArtist)); diff --git a/src/Lidarr.Api.V1/Wanted/MissingController.cs b/src/Lidarr.Api.V1/Wanted/MissingController.cs index fd7fa6984..50d3cd87a 100644 --- a/src/Lidarr.Api.V1/Wanted/MissingController.cs +++ b/src/Lidarr.Api.V1/Wanted/MissingController.cs @@ -1,4 +1,3 @@ -using System.Linq; using Lidarr.Api.V1.Albums; using Lidarr.Http; using Lidarr.Http.Extensions; @@ -26,20 +25,18 @@ namespace Lidarr.Api.V1.Wanted [HttpGet] [Produces("application/json")] - public PagingResource GetMissingAlbums(bool includeArtist = false) + public PagingResource GetMissingAlbums([FromQuery] PagingRequestResource paging, bool includeArtist = false, bool monitored = true) { - var pagingResource = Request.ReadPagingResourceFromRequest(); + var pagingResource = new PagingResource(paging); var pagingSpec = pagingResource.MapToPagingSpec("releaseDate", SortDirection.Descending); - 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.Artist.Value.Monitored == false); + pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); } else { - pagingSpec.FilterExpressions.Add(v => v.Monitored == true && v.Artist.Value.Monitored == true); + pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Artist.Value.Monitored == false); } return pagingSpec.ApplyToPage(_albumService.AlbumsWithoutFiles, v => MapToResource(v, includeArtist)); diff --git a/src/Lidarr.Http/Extensions/RequestExtensions.cs b/src/Lidarr.Http/Extensions/RequestExtensions.cs index 47e6c1542..73b974c31 100644 --- a/src/Lidarr.Http/Extensions/RequestExtensions.cs +++ b/src/Lidarr.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 Lidarr.Http.Extensions { @@ -52,80 +51,6 @@ namespace Lidarr.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/Lidarr.Http/PagingResource.cs b/src/Lidarr.Http/PagingResource.cs index ee6f0b95c..c5381f6d9 100644 --- a/src/Lidarr.Http/PagingResource.cs +++ b/src/Lidarr.Http/PagingResource.cs @@ -1,17 +1,39 @@ using System.Collections.Generic; +using System.ComponentModel; using NzbDrone.Core.Datastore; namespace Lidarr.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/NzbDrone.Host/Startup.cs b/src/NzbDrone.Host/Startup.cs index 9b82a692e..3f6fa35a0 100644 --- a/src/NzbDrone.Host/Startup.cs +++ b/src/NzbDrone.Host/Startup.cs @@ -159,6 +159,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 1ee2f441c..d612de8b5 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/CutoffUnmetFixture.cs @@ -72,7 +72,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests var artist = EnsureArtist("8ac6cc32-8ddf-43b1-9ac4-4b04f9053176", "Alien Ant Farm", false); EnsureTrackFile(artist, 1, 1, 1, Quality.MP3_192); - 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 e7cd01522..934543499 100644 --- a/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs +++ b/src/NzbDrone.Integration.Test/ApiTests/WantedTests/MissingFixture.cs @@ -74,7 +74,7 @@ namespace NzbDrone.Integration.Test.ApiTests.WantedTests { EnsureArtist("8ac6cc32-8ddf-43b1-9ac4-4b04f9053176", "Alien Ant Farm", 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 17dd96e3c..400972b60 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);