Use 'includeSubResources' to include sub resources in responses

This commit is contained in:
Mark McDowall 2025-12-23 12:06:04 -08:00
parent 8ee10adbd4
commit dbb841f027
15 changed files with 114 additions and 19 deletions

View file

@ -28,7 +28,7 @@ public CalendarController(IBroadcastSignalRMessage signalR,
[HttpGet]
[Produces("application/json")]
public List<EpisodeResource> GetCalendar(DateTime? start, DateTime? end, bool includeUnmonitored = false, bool includeSpecials = true, string tags = "", bool includeSeries = false, bool includeEpisodeFile = false, bool includeEpisodeImages = false)
public List<EpisodeResource> GetCalendar(DateTime? start, DateTime? end, bool includeUnmonitored = false, bool includeSpecials = true, string tags = "", [FromQuery] CalendarSubresource[]? includeSubresources = null)
{
var startUse = start ?? DateTime.Today;
var endUse = end ?? DateTime.Today.AddDays(2);
@ -59,6 +59,10 @@ public List<EpisodeResource> GetCalendar(DateTime? start, DateTime? end, bool in
result.Add(episode);
}
var includeSeries = includeSubresources.Contains(CalendarSubresource.Series);
var includeEpisodeFile = includeSubresources.Contains(CalendarSubresource.EpisodeFile);
var includeEpisodeImages = includeSubresources.Contains(CalendarSubresource.Images);
var resources = MapToResource(result, includeSeries, includeEpisodeFile, includeEpisodeImages);
return resources.OrderBy(e => e.AirDateUtc).ToList();

View file

@ -0,0 +1,8 @@
namespace Sonarr.Api.V5.Calendar;
public enum CalendarSubresource
{
Series,
EpisodeFile,
Images
}

View file

@ -23,8 +23,12 @@ public EpisodeController(ISeriesService seriesService,
[HttpGet]
[Produces("application/json")]
public List<EpisodeResource> GetEpisodes(int? seriesId, int? seasonNumber, [FromQuery]List<int> episodeIds, int? episodeFileId, bool includeSeries = false, bool includeEpisodeFile = false, bool includeImages = false)
public List<EpisodeResource> GetEpisodes(int? seriesId, int? seasonNumber, [FromQuery]List<int> episodeIds, int? episodeFileId, [FromQuery] EpisodeSubresource[]? includeSubresources = null)
{
var includeSeries = includeSubresources.Contains(EpisodeSubresource.Series);
var includeEpisodeFile = includeSubresources.Contains(EpisodeSubresource.EpisodeFile);
var includeImages = includeSubresources.Contains(EpisodeSubresource.Images);
if (seriesId.HasValue)
{
if (seasonNumber.HasValue)
@ -59,8 +63,10 @@ public ActionResult<EpisodeResource> SetEpisodeMonitored([FromRoute] int id, [Fr
[HttpPut("monitor")]
[Consumes("application/json")]
public IActionResult SetEpisodesMonitored([FromBody] EpisodesMonitoredResource resource, [FromQuery] bool includeImages = false)
public IActionResult SetEpisodesMonitored([FromBody] EpisodesMonitoredResource resource, [FromQuery] EpisodeSubresource[]? includeSubresources = null)
{
var includeImages = includeSubresources.Contains(EpisodeSubresource.Images);
if (resource.EpisodeIds.Count == 1)
{
_episodeService.SetEpisodeMonitored(resource.EpisodeIds.First(), resource.Monitored);

View file

@ -0,0 +1,8 @@
namespace Sonarr.Api.V5.Episodes;
public enum EpisodeSubresource
{
Series,
EpisodeFile,
Images
}

View file

@ -62,7 +62,7 @@ protected HistoryResource MapToResource(EpisodeHistory model, bool includeSeries
[HttpGet]
[Produces("application/json")]
public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, bool includeSeries, bool includeEpisode, [FromQuery(Name = "eventType")] int[]? eventTypes, int? episodeId, string? downloadId, [FromQuery] int[]? seriesIds = null, [FromQuery] int[]? languages = null, [FromQuery] int[]? quality = null)
public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResource paging, [FromQuery(Name = "eventType")] int[]? eventTypes, int? episodeId, string? downloadId, [FromQuery] int[]? seriesIds = null, [FromQuery] int[]? languages = null, [FromQuery] int[]? quality = null, [FromQuery] HistorySubresource[]? includeSubresources = null)
{
var pagingResource = new PagingResource<HistoryResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, EpisodeHistory>(
@ -94,21 +94,29 @@ public PagingResource<HistoryResource> GetHistory([FromQuery] PagingRequestResou
pagingSpec.FilterExpressions.Add(h => seriesIds.Contains(h.SeriesId));
}
var includeSeries = includeSubresources.Contains(HistorySubresource.Series);
var includeEpisode = includeSubresources.Contains(HistorySubresource.Episode);
return pagingSpec.ApplyToPage(h => _historyService.Paged(pagingSpec, languages, quality), h => MapToResource(h, includeSeries, includeEpisode));
}
[HttpGet("since")]
[Produces("application/json")]
public List<HistoryResource> GetHistorySince(DateTime date, EpisodeHistoryEventType? eventType = null, bool includeSeries = false, bool includeEpisode = false)
public List<HistoryResource> GetHistorySince(DateTime date, EpisodeHistoryEventType? eventType = null, [FromQuery] HistorySubresource[]? includeSubresources = null)
{
var includeSeries = includeSubresources.Contains(HistorySubresource.Series);
var includeEpisode = includeSubresources.Contains(HistorySubresource.Episode);
return _historyService.Since(date, eventType).Select(h => MapToResource(h, includeSeries, includeEpisode)).ToList();
}
[HttpGet("series")]
[Produces("application/json")]
public List<HistoryResource> GetSeriesHistory(int seriesId, EpisodeHistoryEventType? eventType = null, bool includeSeries = false, bool includeEpisode = false)
public List<HistoryResource> GetSeriesHistory(int seriesId, EpisodeHistoryEventType? eventType = null, [FromQuery] HistorySubresource[]? includeSubresources = null)
{
var series = _seriesService.GetSeries(seriesId);
var includeSeries = includeSubresources.Contains(HistorySubresource.Series);
var includeEpisode = includeSubresources.Contains(HistorySubresource.Episode);
return _historyService.GetBySeries(seriesId, eventType).Select(h =>
{
@ -120,9 +128,11 @@ public List<HistoryResource> GetSeriesHistory(int seriesId, EpisodeHistoryEventT
[HttpGet("season")]
[Produces("application/json")]
public List<HistoryResource> GetSeasonHistory(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType = null, bool includeSeries = false, bool includeEpisode = false)
public List<HistoryResource> GetSeasonHistory(int seriesId, int seasonNumber, EpisodeHistoryEventType? eventType = null, [FromQuery] HistorySubresource[]? includeSubresources = null)
{
var series = _seriesService.GetSeries(seriesId);
var includeSeries = includeSubresources.Contains(HistorySubresource.Series);
var includeEpisode = includeSubresources.Contains(HistorySubresource.Episode);
return _historyService.GetBySeason(seriesId, seasonNumber, eventType).Select(h =>
{
@ -134,10 +144,12 @@ public List<HistoryResource> GetSeasonHistory(int seriesId, int seasonNumber, Ep
[HttpGet("episode")]
[Produces("application/json")]
public List<HistoryResource> GetEpisodeHistory(int episodeId, EpisodeHistoryEventType? eventType = null, bool includeSeries = false, bool includeEpisode = false)
public List<HistoryResource> GetEpisodeHistory(int episodeId, EpisodeHistoryEventType? eventType = null, [FromQuery] HistorySubresource[]? includeSubresources = null)
{
var episode = _episodeService.GetEpisode(episodeId);
var series = _seriesService.GetSeries(episode.SeriesId);
var includeSeries = includeSubresources.Contains(HistorySubresource.Series);
var includeEpisode = includeSubresources.Contains(HistorySubresource.Episode);
return _historyService.GetByEpisode(episodeId, eventType)
.Select(h =>

View file

@ -0,0 +1,7 @@
namespace Sonarr.Api.V5.History;
public enum HistorySubresource
{
Series,
Episode
}

View file

@ -135,7 +135,7 @@ public object RemoveMany([FromBody] QueueBulkResource resource, [FromQuery] stri
[HttpGet]
[Produces("application/json")]
public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownSeriesItems = false, bool includeSeries = false, bool includeEpisodes = false, [FromQuery] int[]? seriesIds = null, DownloadProtocol? protocol = null, [FromQuery] int[]? languages = null, [FromQuery] int[]? quality = null, [FromQuery] QueueStatus[]? status = null)
public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource paging, bool includeUnknownSeriesItems = false, [FromQuery] int[]? seriesIds = null, DownloadProtocol? protocol = null, [FromQuery] int[]? languages = null, [FromQuery] int[]? quality = null, [FromQuery] QueueStatus[]? status = null, [FromQuery] QueueSubresource[]? includeSubresources = null)
{
var pagingResource = new PagingResource<QueueResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<QueueResource, NzbDrone.Core.Queue.Queue>(
@ -164,6 +164,9 @@ public PagingResource<QueueResource> GetQueue([FromQuery] PagingRequestResource
"timeleft",
SortDirection.Ascending);
var includeSeries = includeSubresources.Contains(QueueSubresource.Series);
var includeEpisodes = includeSubresources.Contains(QueueSubresource.Episodes);
return pagingSpec.ApplyToPage((spec) => GetQueue(spec, seriesIds?.ToHashSet() ?? [], protocol, languages?.ToHashSet() ?? [], quality?.ToHashSet() ?? [], status?.ToHashSet() ?? [], includeUnknownSeriesItems), (q) => MapToResource(q, includeSeries, includeEpisodes));
}

View file

@ -37,11 +37,13 @@ protected override QueueResource GetResourceById(int id)
[HttpGet]
[Produces("application/json")]
public List<QueueResource> GetQueue(int? seriesId, [FromQuery]List<int> episodeIds, bool includeSeries = false, bool includeEpisodes = false)
public List<QueueResource> GetQueue(int? seriesId, [FromQuery]List<int> episodeIds, [FromQuery] QueueSubresource[]? includeSubresources = null)
{
var queue = _queueService.GetQueue();
var pending = _pendingReleaseService.GetPendingQueue();
var fullQueue = queue.Concat(pending);
var includeSeries = includeSubresources.Contains(QueueSubresource.Series);
var includeEpisodes = includeSubresources.Contains(QueueSubresource.Episodes);
if (seriesId.HasValue)
{

View file

@ -0,0 +1,7 @@
namespace Sonarr.Api.V5.Queue;
public enum QueueSubresource
{
Series,
Episodes
}

View file

@ -19,7 +19,6 @@
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
using Sonarr.Http;
using Sonarr.Http.Extensions;
using Sonarr.Http.REST;
using Sonarr.Http.REST.Attributes;
@ -108,10 +107,11 @@ public SeriesController(IBroadcastSignalRMessage signalRBroadcaster,
[HttpGet]
[Produces("application/json")]
public List<SeriesResource> AllSeries(int? tvdbId, bool includeSeasonImages = false)
public List<SeriesResource> AllSeries(int? tvdbId, [FromQuery] SeriesSubresource[]? includeSubresources = null)
{
var seriesStats = _seriesStatisticsService.SeriesStatistics();
var seriesResources = new List<SeriesResource>();
var includeSeasonImages = includeSubresources.Contains(SeriesSubresource.SeasonImages);
if (tvdbId.HasValue)
{
@ -138,8 +138,10 @@ public override ActionResult<SeriesResource> GetResourceByIdWithErrorHandler(int
[RestGetById]
[Produces("application/json")]
public ActionResult<SeriesResource> GetResourceByIdWithErrorHandler(int id, [FromQuery]bool includeSeasonImages = false)
public ActionResult<SeriesResource> GetResourceByIdWithErrorHandler(int id, [FromQuery] SeriesSubresource[]? includeSubresources = null)
{
var includeSeasonImages = includeSubresources.Contains(SeriesSubresource.SeasonImages);
try
{
var series = GetSeriesResourceById(id, includeSeasonImages);
@ -154,17 +156,25 @@ public ActionResult<SeriesResource> GetResourceByIdWithErrorHandler(int id, [Fro
protected override SeriesResource? GetResourceById(int id)
{
var includeSeasonImages = Request?.GetBooleanQueryParameter("includeSeasonImages", false) ?? false;
var includeSubresources = Request.Query["includeSubresources"].Select(v =>
{
if (Enum.TryParse<SeriesSubresource>(v, true, out var enumValue))
{
return enumValue;
}
throw new BadRequestException($"The value '{v}' is not valid.");
});
var includeSeasonImages = includeSubresources.Contains(SeriesSubresource.SeasonImages);
// Parse IncludeImages and use it
return GetSeriesResourceById(id, includeSeasonImages);
}
private SeriesResource? GetSeriesResourceById(int id, bool includeSeasonImages = false)
private SeriesResource? GetSeriesResourceById(int id, bool includeSeasonImages)
{
var series = _seriesService.GetSeries(id);
// Parse IncludeImages and use it
return GetSeriesResource(series, includeSeasonImages);
}

View file

@ -0,0 +1,6 @@
namespace Sonarr.Api.V5.Series;
public enum SeriesSubresource
{
SeasonImages
}

View file

@ -28,7 +28,7 @@ public CutoffController(IEpisodeCutoffService episodeCutoffService,
[HttpGet]
[Produces("application/json")]
public PagingResource<EpisodeResource> GetCutoffUnmetEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeEpisodeFile = false, bool includeImages = false, bool monitored = true)
public PagingResource<EpisodeResource> GetCutoffUnmetEpisodes([FromQuery] PagingRequestResource paging, bool monitored = true, [FromQuery] CutoffSubresource[]? includeSubresources = null)
{
var pagingResource = new PagingResource<EpisodeResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<EpisodeResource, Episode>(
@ -50,6 +50,10 @@ public PagingResource<EpisodeResource> GetCutoffUnmetEpisodes([FromQuery] Paging
pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false);
}
var includeSeries = includeSubresources.Contains(CutoffSubresource.Series);
var includeEpisodeFile = includeSubresources.Contains(CutoffSubresource.EpisodeFile);
var includeImages = includeSubresources.Contains(CutoffSubresource.Images);
var resource = pagingSpec.ApplyToPage(_episodeCutoffService.EpisodesWhereCutoffUnmet, v => MapToResource(v, includeSeries, includeEpisodeFile, includeImages));
return resource;

View file

@ -0,0 +1,8 @@
namespace Sonarr.Api.V5.Wanted;
public enum CutoffSubresource
{
Series,
EpisodeFile,
Images
}

View file

@ -24,7 +24,7 @@ public MissingController(IEpisodeService episodeService,
[HttpGet]
[Produces("application/json")]
public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool includeSeries = false, bool includeImages = false, bool monitored = true)
public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequestResource paging, bool monitored = true, [FromQuery] MissingSubresource[]? includeSubresources = null)
{
var pagingResource = new PagingResource<EpisodeResource>(paging);
var pagingSpec = pagingResource.MapToPagingSpec<EpisodeResource, Episode>(
@ -46,6 +46,9 @@ public PagingResource<EpisodeResource> GetMissingEpisodes([FromQuery] PagingRequ
pagingSpec.FilterExpressions.Add(v => v.Monitored == false || v.Series.Monitored == false);
}
var includeSeries = includeSubresources.Contains(MissingSubresource.Series);
var includeImages = includeSubresources.Contains(MissingSubresource.Images);
var resource = pagingSpec.ApplyToPage(_episodeService.EpisodesWithoutFiles, v => MapToResource(v, includeSeries, false, includeImages));
return resource;

View file

@ -0,0 +1,7 @@
namespace Sonarr.Api.V5.Wanted;
public enum MissingSubresource
{
Series,
Images
}