aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api/Controllers')
-rw-r--r--Jellyfin.Api/Controllers/AlbumsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/AudioController.cs5
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs4
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs102
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs8
-rw-r--r--Jellyfin.Api/Controllers/DlnaController.cs7
-rw-r--r--Jellyfin.Api/Controllers/DlnaServerController.cs39
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs39
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs3
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs13
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs157
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs12
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs4
-rw-r--r--Jellyfin.Api/Controllers/ItemRefreshController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemUpdateController.cs6
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs16
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs30
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs4
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs3
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs6
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs28
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs19
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs9
-rw-r--r--Jellyfin.Api/Controllers/ScheduledTasksController.cs2
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs6
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs3
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs16
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs5
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs14
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs21
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs5
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs11
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs3
41 files changed, 288 insertions, 356 deletions
diff --git a/Jellyfin.Api/Controllers/AlbumsController.cs b/Jellyfin.Api/Controllers/AlbumsController.cs
index 190d4bd07..357f646a2 100644
--- a/Jellyfin.Api/Controllers/AlbumsController.cs
+++ b/Jellyfin.Api/Controllers/AlbumsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Albums/{albumId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
- [FromRoute] string albumId,
+ [FromRoute, Required] string albumId,
[FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
@@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Artists/{artistId}/Similar")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
- [FromRoute] string artistId,
+ [FromRoute, Required] string artistId,
[FromQuery] Guid? userId,
[FromQuery] string? excludeArtistIds,
[FromQuery] int? limit)
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 3f72830cd..d38214116 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -469,7 +470,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the artist.</returns>
[HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BaseItemDto> GetArtistByName([FromRoute] string name, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs
index 802cd026e..3bec43720 100644
--- a/Jellyfin.Api/Controllers/AudioController.cs
+++ b/Jellyfin.Api/Controllers/AudioController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
@@ -89,8 +90,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream", Name = "HeadAudioStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetAudioStream(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index bdd7dfd96..33a969f85 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Channel features returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
[HttpGet("{channelId}/Features")]
- public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute] string channelId)
+ public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] string channelId)
{
return _channelManager.GetChannelFeatures(channelId);
}
@@ -114,7 +115,7 @@ namespace Jellyfin.Api.Controllers
/// </returns>
[HttpGet("{channelId}/Items")]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
- [FromRoute] Guid channelId,
+ [FromRoute, Required] Guid channelId,
[FromQuery] Guid? folderId,
[FromQuery] Guid? userId,
[FromQuery] int? startIndex,
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index c5910d6e8..f78690b06 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> AddToCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+ public async Task<ActionResult> AddToCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{
await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
return NoContent();
@@ -103,7 +103,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("{collectionId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> RemoveFromCollection([FromRoute] Guid collectionId, [FromQuery, Required] string? itemIds)
+ public async Task<ActionResult> RemoveFromCollection([FromRoute, Required] Guid collectionId, [FromQuery, Required] string? itemIds)
{
await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index 20fb0ec87..5fd4c712a 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -73,7 +73,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Configuration.</returns>
[HttpGet("Configuration/{key}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<object> GetNamedConfiguration([FromRoute] string? key)
+ public ActionResult<object> GetNamedConfiguration([FromRoute, Required] string? key)
{
return _configurationManager.GetConfiguration(key);
}
@@ -87,7 +87,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Configuration/{key}")]
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> UpdateNamedConfiguration([FromRoute] string? key)
+ public async Task<ActionResult> UpdateNamedConfiguration([FromRoute, Required] string? key)
{
var configurationType = _configurationManager.GetConfigurationType(key);
var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index 33abe3ccd..3f0fc2e91 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
namespace Jellyfin.Api.Controllers
{
@@ -26,39 +27,21 @@ namespace Jellyfin.Api.Controllers
{
private readonly ILogger<DashboardController> _logger;
private readonly IServerApplicationHost _appHost;
- private readonly IConfiguration _appConfig;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly IResourceFileManager _resourceFileManager;
/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary>
/// <param name="logger">Instance of <see cref="ILogger{DashboardController}"/> interface.</param>
/// <param name="appHost">Instance of <see cref="IServerApplicationHost"/> interface.</param>
- /// <param name="appConfig">Instance of <see cref="IConfiguration"/> interface.</param>
- /// <param name="resourceFileManager">Instance of <see cref="IResourceFileManager"/> interface.</param>
- /// <param name="serverConfigurationManager">Instance of <see cref="IServerConfigurationManager"/> interface.</param>
public DashboardController(
ILogger<DashboardController> logger,
- IServerApplicationHost appHost,
- IConfiguration appConfig,
- IResourceFileManager resourceFileManager,
- IServerConfigurationManager serverConfigurationManager)
+ IServerApplicationHost appHost)
{
_logger = logger;
_appHost = appHost;
- _appConfig = appConfig;
- _resourceFileManager = resourceFileManager;
- _serverConfigurationManager = serverConfigurationManager;
}
/// <summary>
- /// Gets the path of the directory containing the static web interface content, or null if the server is not
- /// hosting the web client.
- /// </summary>
- private string? WebClientUiPath => GetWebClientUiPath(_appConfig, _serverConfigurationManager);
-
- /// <summary>
/// Gets the configuration pages.
/// </summary>
/// <param name="enableInMainMenu">Whether to enable in the main menu.</param>
@@ -169,87 +152,6 @@ namespace Jellyfin.Api.Controllers
return NotFound();
}
- /// <summary>
- /// Gets the robots.txt.
- /// </summary>
- /// <response code="200">Robots.txt returned.</response>
- /// <returns>The robots.txt.</returns>
- [HttpGet("robots.txt")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetRobotsTxt()
- {
- return GetWebClientResource("robots.txt");
- }
-
- /// <summary>
- /// Gets a resource from the web client.
- /// </summary>
- /// <param name="resourceName">The resource name.</param>
- /// <response code="200">Web client returned.</response>
- /// <response code="404">Server does not host a web client.</response>
- /// <returns>The resource.</returns>
- [HttpGet("web/{*resourceName}")]
- [ApiExplorerSettings(IgnoreApi = true)]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetWebClientResource([FromRoute] string resourceName)
- {
- if (!_appConfig.HostWebClient() || WebClientUiPath == null)
- {
- return NotFound("Server does not host a web client.");
- }
-
- var path = resourceName;
- var basePath = WebClientUiPath;
-
- var requestPathAndQuery = Request.GetEncodedPathAndQuery();
- // Bounce them to the startup wizard if it hasn't been completed yet
- if (!_serverConfigurationManager.Configuration.IsStartupWizardCompleted
- && !requestPathAndQuery.Contains("wizard", StringComparison.OrdinalIgnoreCase)
- && requestPathAndQuery.Contains("index", StringComparison.OrdinalIgnoreCase))
- {
- return Redirect("index.html?start=wizard#!/wizardstart.html");
- }
-
- var stream = new FileStream(_resourceFileManager.GetResourcePath(basePath, path), FileMode.Open, FileAccess.Read);
- return File(stream, MimeTypes.GetMimeType(path));
- }
-
- /// <summary>
- /// Gets the favicon.
- /// </summary>
- /// <response code="200">Favicon.ico returned.</response>
- /// <returns>The favicon.</returns>
- [HttpGet("favicon.ico")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ApiExplorerSettings(IgnoreApi = true)]
- public ActionResult GetFavIcon()
- {
- return GetWebClientResource("favicon.ico");
- }
-
- /// <summary>
- /// Gets the path of the directory containing the static web interface content.
- /// </summary>
- /// <param name="appConfig">The app configuration.</param>
- /// <param name="serverConfigManager">The server configuration manager.</param>
- /// <returns>The directory path, or null if the server is not hosting the web client.</returns>
- public static string? GetWebClientUiPath(IConfiguration appConfig, IServerConfigurationManager serverConfigManager)
- {
- if (!appConfig.HostWebClient())
- {
- return null;
- }
-
- if (!string.IsNullOrEmpty(serverConfigManager.Configuration.DashboardSourcePath))
- {
- return serverConfigManager.Configuration.DashboardSourcePath;
- }
-
- return serverConfigManager.ApplicationPaths.WebPath;
- }
-
private IEnumerable<ConfigurationPageInfo> GetConfigPages(IPlugin plugin)
{
return GetPluginPages(plugin).Select(i => new ConfigurationPageInfo(plugin, i.Item1));
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index c547d0cde..6bb7b1910 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -43,7 +43,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
- [FromRoute] string? displayPreferencesId,
+ [FromRoute, Required] string? displayPreferencesId,
[FromQuery] [Required] Guid userId,
[FromQuery] [Required] string? client)
{
@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
public ActionResult UpdateDisplayPreferences(
- [FromRoute] string? displayPreferencesId,
+ [FromRoute, Required] string? displayPreferencesId,
[FromQuery, Required] Guid userId,
[FromQuery, Required] string? client,
[FromBody, Required] DisplayPreferencesDto displayPreferences)
@@ -153,7 +153,6 @@ namespace Jellyfin.Api.Controllers
{
var itemPreferences = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Parse(key.Substring("landing-".Length)), existingDisplayPreferences.Client);
itemPreferences.ViewType = Enum.Parse<ViewType>(displayPreferences.ViewType);
- _displayPreferencesManager.SaveChanges(itemPreferences);
}
var itemPrefs = _displayPreferencesManager.GetItemDisplayPreferences(existingDisplayPreferences.UserId, Guid.Empty, existingDisplayPreferences.Client);
@@ -167,8 +166,7 @@ namespace Jellyfin.Api.Controllers
itemPrefs.ViewType = viewType;
}
- _displayPreferencesManager.SaveChanges(existingDisplayPreferences);
- _displayPreferencesManager.SaveChanges(itemPrefs);
+ _displayPreferencesManager.SaveChanges();
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/DlnaController.cs b/Jellyfin.Api/Controllers/DlnaController.cs
index 397299a73..052a6aff2 100644
--- a/Jellyfin.Api/Controllers/DlnaController.cs
+++ b/Jellyfin.Api/Controllers/DlnaController.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Dlna;
using MediaBrowser.Model.Dlna;
@@ -59,7 +60,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<DeviceProfile> GetProfile([FromRoute] string profileId)
+ public ActionResult<DeviceProfile> GetProfile([FromRoute, Required] string profileId)
{
var profile = _dlnaManager.GetProfile(profileId);
if (profile == null)
@@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteProfile([FromRoute] string profileId)
+ public ActionResult DeleteProfile([FromRoute, Required] string profileId)
{
var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
if (existingDeviceProfile == null)
@@ -117,7 +118,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Profiles/{profileId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateProfile([FromRoute] string profileId, [FromBody] DeviceProfile deviceProfile)
+ public ActionResult UpdateProfile([FromRoute, Required] string profileId, [FromBody] DeviceProfile deviceProfile)
{
var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
if (existingDeviceProfile == null)
diff --git a/Jellyfin.Api/Controllers/DlnaServerController.cs b/Jellyfin.Api/Controllers/DlnaServerController.cs
index ee87d917b..8cdea4367 100644
--- a/Jellyfin.Api/Controllers/DlnaServerController.cs
+++ b/Jellyfin.Api/Controllers/DlnaServerController.cs
@@ -1,6 +1,8 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Net.Mime;
using System.Threading.Tasks;
using Emby.Dlna;
using Emby.Dlna.Main;
@@ -17,8 +19,6 @@ namespace Jellyfin.Api.Controllers
[Route("Dlna")]
public class DlnaServerController : BaseJellyfinApiController
{
- private const string XMLContentType = "text/xml; charset=UTF-8";
-
private readonly IDlnaManager _dlnaManager;
private readonly IContentDirectory _contentDirectory;
private readonly IConnectionManager _connectionManager;
@@ -44,9 +44,9 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the description xml.</returns>
[HttpGet("{serverId}/description")]
[HttpGet("{serverId}/description.xml", Name = "GetDescriptionXml_2")]
- [Produces(XMLContentType)]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult GetDescriptionXml([FromRoute] string serverId)
+ public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
{
var url = GetAbsoluteUri();
var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
@@ -61,11 +61,12 @@ namespace Jellyfin.Api.Controllers
/// <response code="200">Dlna content directory returned.</response>
/// <returns>An <see cref="OkResult"/> containing the dlna content directory xml.</returns>
[HttpGet("{serverId}/ContentDirectory")]
- [HttpGet("{serverId}/ContentDirectory.xml", Name = "GetContentDirectory_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/ContentDirectory/ContentDirectory", Name = "GetContentDirectory_2")]
+ [HttpGet("{serverId}/ContentDirectory/ContentDirectory.xml", Name = "GetContentDirectory_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetContentDirectory([FromRoute] string serverId)
+ public ActionResult GetContentDirectory([FromRoute, Required] string serverId)
{
return Ok(_contentDirectory.GetServiceXml());
}
@@ -76,11 +77,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/MediaReceiverRegistrar")]
- [HttpGet("{serverId}/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar", Name = "GetMediaReceiverRegistrar_2")]
+ [HttpGet("{serverId}/MediaReceiverRegistrar/MediaReceiverRegistrar.xml", Name = "GetMediaReceiverRegistrar_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetMediaReceiverRegistrar([FromRoute] string serverId)
+ public ActionResult GetMediaReceiverRegistrar([FromRoute, Required] string serverId)
{
return Ok(_mediaReceiverRegistrar.GetServiceXml());
}
@@ -91,11 +93,12 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Dlna media receiver registrar xml.</returns>
[HttpGet("{serverId}/ConnectionManager")]
- [HttpGet("{serverId}/ConnectionManager.xml", Name = "GetConnectionManager_2")]
- [Produces(XMLContentType)]
+ [HttpGet("{serverId}/ConnectionManager/ConnectionManager", Name = "GetConnectionManager_2")]
+ [HttpGet("{serverId}/ConnectionManager/ConnectionManager.xml", Name = "GetConnectionManager_3")]
+ [Produces(MediaTypeNames.Text.Xml)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetConnectionManager([FromRoute] string serverId)
+ public ActionResult GetConnectionManager([FromRoute, Required] string serverId)
{
return Ok(_connectionManager.GetServiceXml());
}
@@ -106,7 +109,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ContentDirectory/Control")]
- public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute] string serverId)
+ public async Task<ActionResult<ControlResponse>> ProcessContentDirectoryControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
}
@@ -117,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/ConnectionManager/Control")]
- public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute] string serverId)
+ public async Task<ActionResult<ControlResponse>> ProcessConnectionManagerControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
}
@@ -128,7 +131,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="serverId">Server UUID.</param>
/// <returns>Control response.</returns>
[HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
- public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute] string serverId)
+ public async Task<ActionResult<ControlResponse>> ProcessMediaReceiverRegistrarControlRequest([FromRoute, Required] string serverId)
{
return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
}
@@ -183,7 +186,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Icon stream.</returns>
[HttpGet("{serverId}/icons/{fileName}")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
- public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName)
+ public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
{
return GetIconInternal(fileName);
}
@@ -194,7 +197,7 @@ namespace Jellyfin.Api.Controllers
/// <param name="fileName">The icon filename.</param>
/// <returns>Icon stream.</returns>
[HttpGet("icons/{fileName}")]
- public ActionResult GetIcon([FromRoute] string fileName)
+ public ActionResult GetIcon([FromRoute, Required] string fileName)
{
return GetIconInternal(fileName);
}
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b115ac6cd..d81c7996e 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -167,8 +167,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsVideoPlaylist(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -334,8 +334,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetMasterHlsAudioPlaylist(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -499,8 +499,8 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Videos/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsVideoPlaylist(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -664,8 +664,8 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Audio/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVariantHlsAudioPlaylist(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -832,10 +832,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsVideoSegment(
- [FromRoute] Guid itemId,
- [FromRoute] string playlistId,
- [FromRoute] int segmentId,
- [FromRoute] string container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string playlistId,
+ [FromRoute, Required] int segmentId,
+ [FromRoute, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -1001,10 +1001,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetHlsAudioSegment(
- [FromRoute] Guid itemId,
- [FromRoute] string playlistId,
- [FromRoute] int segmentId,
- [FromRoute] string container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string playlistId,
+ [FromRoute, Required] int segmentId,
+ [FromRoute, Required] string container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -1354,15 +1354,20 @@ namespace Jellyfin.Api.Controllers
segmentFormat = "mpegts";
}
+ var maxMuxingQueueSize = encodingOptions.MaxMuxingQueueSize > 128
+ ? encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
+ : "128";
+
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size 2048 -f hls -max_delay 5000000 -hls_time {6} -individual_header_trailer 0 -hls_segment_type {7} -start_number {8} -hls_segment_filename \"{9}\" -hls_playlist_type vod -hls_list_size 0 -y \"{10}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -individual_header_trailer 0 -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, encodingOptions),
threads,
mapArgs,
GetVideoArguments(state, encodingOptions, startNumber),
GetAudioArguments(state, encodingOptions),
+ maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
segmentFormat,
startNumberParam,
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 55ad71200..de6aa86c9 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -260,7 +261,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the genre.</returns>
[HttpGet("{genreName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BaseItemDto> GetGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions()
.AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 816252f80..e96df83fa 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -55,7 +56,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Audio/{itemId}/hls/{segmentId}/stream.aac", Name = "GetHlsAudioSegmentLegacyAac")]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
- public ActionResult GetHlsAudioSegmentLegacy([FromRoute] string itemId, [FromRoute] string segmentId)
+ public ActionResult GetHlsAudioSegmentLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string segmentId)
{
// TODO: Deprecate with new iOS app
var file = segmentId + Path.GetExtension(Request.Path);
@@ -75,7 +76,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
- public ActionResult GetHlsPlaylistLegacy([FromRoute] string itemId, [FromRoute] string playlistId)
+ public ActionResult GetHlsPlaylistLegacy([FromRoute, Required] string itemId, [FromRoute, Required] string playlistId)
{
var file = playlistId + Path.GetExtension(Request.Path);
file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
@@ -114,10 +115,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
public ActionResult GetHlsVideoSegmentLegacy(
- [FromRoute] string itemId,
- [FromRoute] string playlistId,
- [FromRoute] string segmentId,
- [FromRoute] string segmentContainer)
+ [FromRoute, Required] string itemId,
+ [FromRoute, Required] string playlistId,
+ [FromRoute, Required] string segmentId,
+ [FromRoute, Required] string segmentContainer)
{
var file = segmentId + Path.GetExtension(Request.Path);
var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index a204fe35c..453da5711 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
@@ -90,9 +91,9 @@ namespace Jellyfin.Api.Controllers
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> PostUserImage(
- [FromRoute] Guid userId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? index = null)
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? index = null)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
{
@@ -137,9 +138,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult DeleteUserImage(
- [FromRoute] Guid userId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? index = null)
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? index = null)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
{
@@ -175,9 +176,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteItemImage(
- [FromRoute] Guid itemId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -205,9 +206,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> SetItemImage(
- [FromRoute] Guid itemId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -238,9 +239,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateItemImageIndex(
- [FromRoute] Guid itemId,
- [FromRoute] ImageType imageType,
- [FromRoute] int imageIndex,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int imageIndex,
[FromQuery] int newIndex)
{
var item = _libraryManager.GetItemById(itemId);
@@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute] Guid itemId)
+ public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -352,10 +353,10 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage(
- [FromRoute] Guid itemId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -368,7 +369,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -430,23 +431,23 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetItemImage2(
- [FromRoute] Guid itemId,
- [FromRoute] ImageType imageType,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
- [FromRoute] string tag,
+ [FromRoute, Required] string tag,
[FromQuery] bool? cropWhitespace,
- [FromRoute] string format,
+ [FromRoute, Required] string format,
[FromQuery] bool? addPlayedIndicator,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -508,14 +509,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetArtistImage(
- [FromRoute] string name,
- [FromRoute] ImageType imageType,
- [FromRoute] string tag,
- [FromRoute] string format,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] string name,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] string tag,
+ [FromRoute, Required] string format,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -524,7 +525,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetArtist(name);
if (item == null)
@@ -586,14 +587,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetGenreImage(
- [FromRoute] string name,
- [FromRoute] ImageType imageType,
- [FromRoute] string tag,
- [FromRoute] string format,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] string name,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] string tag,
+ [FromRoute, Required] string format,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -602,7 +603,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetGenre(name);
if (item == null)
@@ -664,14 +665,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetMusicGenreImage(
- [FromRoute] string name,
- [FromRoute] ImageType imageType,
- [FromRoute] string tag,
- [FromRoute] string format,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] string name,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] string tag,
+ [FromRoute, Required] string format,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -680,7 +681,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetMusicGenre(name);
if (item == null)
@@ -742,14 +743,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetPersonImage(
- [FromRoute] string name,
- [FromRoute] ImageType imageType,
- [FromRoute] string tag,
- [FromRoute] string format,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] string name,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] string tag,
+ [FromRoute, Required] string format,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -758,7 +759,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetPerson(name);
if (item == null)
@@ -820,14 +821,14 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetStudioImage(
- [FromRoute] string name,
- [FromRoute] ImageType imageType,
- [FromRoute] string tag,
- [FromRoute] string format,
- [FromRoute] int? maxWidth,
- [FromRoute] int? maxHeight,
- [FromRoute] double? percentPlayed,
- [FromRoute] int? unplayedCount,
+ [FromRoute, Required] string name,
+ [FromRoute, Required] ImageType imageType,
+ [FromRoute, Required] string tag,
+ [FromRoute, Required] string format,
+ [FromRoute, Required] int? maxWidth,
+ [FromRoute, Required] int? maxHeight,
+ [FromRoute, Required] double? percentPlayed,
+ [FromRoute, Required] int? unplayedCount,
[FromQuery] int? width,
[FromQuery] int? height,
[FromQuery] int? quality,
@@ -836,7 +837,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var item = _libraryManager.GetStudio(name);
if (item == null)
@@ -898,8 +899,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> GetUserImage(
- [FromRoute] Guid userId,
- [FromRoute] ImageType imageType,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] ImageType imageType,
[FromQuery] string? tag,
[FromQuery] string? format,
[FromQuery] int? maxWidth,
@@ -914,7 +915,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] int? blur,
[FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer,
- [FromRoute] int? imageIndex = null)
+ [FromRoute, Required] int? imageIndex = null)
{
var user = _userManager.GetUserById(userId);
if (user == null)
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 73bd30c4d..01bfbba4e 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Songs/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Albums/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Playlists/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
@@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items/{id}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
- [FromRoute] Guid id,
+ [FromRoute, Required] Guid id,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
[FromQuery] string? fields,
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index afde4a433..f7b515cec 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute] Guid itemId)
+ public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -294,7 +294,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/RemoteSearch/Apply/{id}")]
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> ApplySearchCriteria(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromBody, Required] RemoteSearchResult searchResult,
[FromQuery] bool replaceAllImages = true)
{
diff --git a/Jellyfin.Api/Controllers/ItemRefreshController.cs b/Jellyfin.Api/Controllers/ItemRefreshController.cs
index 3f5d305c1..49865eb5e 100644
--- a/Jellyfin.Api/Controllers/ItemRefreshController.cs
+++ b/Jellyfin.Api/Controllers/ItemRefreshController.cs
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult Post(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
[FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
[FromQuery] bool replaceAllMetadata = false,
diff --git a/Jellyfin.Api/Controllers/ItemUpdateController.cs b/Jellyfin.Api/Controllers/ItemUpdateController.cs
index ec52f4996..4308a434d 100644
--- a/Jellyfin.Api/Controllers/ItemUpdateController.cs
+++ b/Jellyfin.Api/Controllers/ItemUpdateController.cs
@@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> UpdateItem([FromRoute] Guid itemId, [FromBody, Required] BaseItemDto request)
+ public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Items/{itemId}/MetadataEditor")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute] Guid itemId)
+ public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/ContentType")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UpdateItemContentType([FromRoute] Guid itemId, [FromQuery, Required] string? contentType)
+ public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery, Required] string? contentType)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index f9273bad6..06ab176b2 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -144,7 +145,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetItems(
- [FromRoute] Guid? uId,
+ [FromRoute, Required] Guid? uId,
[FromQuery] Guid? userId,
[FromQuery] string? maxOfficialRating,
[FromQuery] bool? hasThemeSong,
@@ -529,7 +530,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Items/Resume")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
[FromQuery] string? searchTerm,
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index a30873e9e..f1f52961d 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/Jellyfin.Api/Controllers/LibraryController.cs
@@ -104,7 +104,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult GetFile([FromRoute] Guid itemId)
+ public ActionResult GetFile([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -144,7 +144,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeSongs(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -210,7 +210,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeVideos(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -275,7 +275,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] bool inheritFromParent = false)
{
@@ -438,7 +438,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+ public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
var item = _libraryManager.GetItemById(itemId);
@@ -555,7 +555,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Library/Movies/Updated")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public ActionResult PostUpdatedMovies([FromRoute] string? tmdbId, [FromRoute] string? imdbId)
+ public ActionResult PostUpdatedMovies([FromRoute, Required] string? tmdbId, [FromRoute, Required] string? imdbId)
{
var movies = _libraryManager.GetItemList(new InternalItemsQuery
{
@@ -618,7 +618,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.Download)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
+ public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -687,7 +687,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] string? excludeArtistIds,
[FromQuery] Guid? userId,
[FromQuery] int? limit,
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 3ccfc826d..8678844d2 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -16,6 +17,7 @@ using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
@@ -208,7 +210,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)]
- public ActionResult<BaseItemDto> GetChannel([FromRoute] Guid channelId, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
@@ -405,7 +407,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[Authorize(Policy = Policies.DefaultAuthorization)]
- public ActionResult<BaseItemDto> GetRecording([FromRoute] Guid recordingId, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
@@ -427,7 +429,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[Authorize(Policy = Policies.DefaultAuthorization)]
- public ActionResult ResetTuner([FromRoute] string tunerId)
+ public ActionResult ResetTuner([FromRoute, Required] string tunerId)
{
AssertUserCanManageLiveTv();
_liveTvManager.ResetTuner(tunerId, CancellationToken.None);
@@ -743,7 +745,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetProgram(
- [FromRoute] string programId,
+ [FromRoute, Required] string programId,
[FromQuery] Guid? userId)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -764,7 +766,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteRecording([FromRoute] Guid recordingId)
+ public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{
AssertUserCanManageLiveTv();
@@ -791,7 +793,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("Timers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> CancelTimer([FromRoute] string timerId)
+ public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
{
AssertUserCanManageLiveTv();
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
@@ -809,7 +811,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
- public async Task<ActionResult> UpdateTimer([FromRoute] string timerId, [FromBody] TimerInfoDto timerInfo)
+ public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{
AssertUserCanManageLiveTv();
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -843,7 +845,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute] string timerId)
+ public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
{
var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
if (timer == null)
@@ -883,7 +885,7 @@ namespace Jellyfin.Api.Controllers
[HttpDelete("SeriesTimers/{timerId}")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> CancelSeriesTimer([FromRoute] string timerId)
+ public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
{
AssertUserCanManageLiveTv();
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
@@ -901,7 +903,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
- public async Task<ActionResult> UpdateSeriesTimer([FromRoute] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
+ public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
AssertUserCanManageLiveTv();
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -933,7 +935,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")]
- public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute] Guid? groupId)
+ public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid? groupId)
{
return NotFound();
}
@@ -1069,7 +1071,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetSchedulesDirectCountries()
{
- var client = _httpClientFactory.CreateClient();
+ var client = _httpClientFactory.CreateClient(NamedClient.Default);
// https://json.schedulesdirect.org/20141201/available/countries
// Can't dispose the response as it's required up the call chain.
var response = await client.GetAsync("https://json.schedulesdirect.org/20141201/available/countries")
@@ -1175,7 +1177,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("LiveRecordings/{recordingId}/stream")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> GetLiveRecordingFile([FromRoute] string recordingId)
+ public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
{
var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
@@ -1205,7 +1207,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> GetLiveStreamFile([FromRoute] string streamId, [FromRoute] string container)
+ public async Task<ActionResult> GetLiveStreamFile([FromRoute, Required] string streamId, [FromRoute, Required] string container)
{
var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
if (liveStreamInfo == null)
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index 1e154a039..cc6eba4ae 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
[HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute] Guid itemId, [FromQuery, Required] Guid? userId)
+ public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery, Required] Guid? userId)
{
return await _mediaInfoHelper.GetPlaybackInfo(
itemId,
@@ -100,7 +100,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId,
[FromQuery] long? maxStreamingBitrate,
[FromQuery] long? startTimeTicks,
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 0d319137a..570ae8fdc 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -258,7 +259,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
[HttpGet("{genreName}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BaseItemDto> GetMusicGenre([FromRoute] string genreName, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index ae3d0081f..9509f8708 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -44,7 +44,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Packages/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<PackageInfo>> GetPackageInfo(
- [FromRoute] [Required] string? name,
+ [FromRoute, Required] string? name,
[FromQuery] string? assemblyGuid)
{
var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
@@ -85,7 +85,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.RequiresElevation)]
public async Task<ActionResult> InstallPackage(
- [FromRoute] [Required] string? name,
+ [FromRoute, Required] string? name,
[FromQuery] string? assemblyGuid,
[FromQuery] string? version)
{
@@ -117,7 +117,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult CancelPackageInstallation(
- [FromRoute] [Required] Guid packageId)
+ [FromRoute, Required] Guid packageId)
{
_installationManager.CancelInstallation(packageId);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index b6ccec666..8bd610dad 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Constants;
@@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<BaseItemDto> GetPerson([FromRoute] string name, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions()
.AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index f4c6a9253..69e0b8e07 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> AddToPlaylist(
- [FromRoute] Guid playlistId,
+ [FromRoute, Required] Guid playlistId,
[FromQuery] string? ids,
[FromQuery] Guid? userId)
{
@@ -103,9 +103,9 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> MoveItem(
- [FromRoute] string? playlistId,
- [FromRoute] string? itemId,
- [FromRoute] int newIndex)
+ [FromRoute, Required] string? playlistId,
+ [FromRoute, Required] string? itemId,
+ [FromRoute, Required] int newIndex)
{
await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
return NoContent();
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="NoContentResult"/> on success.</returns>
[HttpDelete("{playlistId}/Items")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- public async Task<ActionResult> RemoveFromPlaylist([FromRoute] string? playlistId, [FromQuery] string? entryIds)
+ public async Task<ActionResult> RemoveFromPlaylist([FromRoute, Required] string? playlistId, [FromQuery] string? entryIds)
{
await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
return NoContent();
@@ -143,15 +143,15 @@ namespace Jellyfin.Api.Controllers
/// <returns>The original playlist items.</returns>
[HttpGet("{playlistId}/Items")]
public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
- [FromRoute] Guid playlistId,
- [FromRoute] Guid userId,
- [FromRoute] int? startIndex,
- [FromRoute] int? limit,
- [FromRoute] string? fields,
- [FromRoute] bool? enableImages,
- [FromRoute] bool? enableUserData,
- [FromRoute] int? imageTypeLimit,
- [FromRoute] string? enableImageTypes)
+ [FromRoute, Required] Guid playlistId,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] int? startIndex,
+ [FromRoute, Required] int? limit,
+ [FromRoute, Required] string? fields,
+ [FromRoute, Required] bool? enableImages,
+ [FromRoute, Required] bool? enableUserData,
+ [FromRoute, Required] int? imageTypeLimit,
+ [FromRoute, Required] string? enableImageTypes)
{
var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
if (playlist == null)
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 22f2ca5c3..5c15e9a0d 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
@@ -71,8 +72,8 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<UserItemDataDto> MarkPlayedItem(
- [FromRoute] Guid userId,
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] DateTime? datePlayed)
{
var user = _userManager.GetUserById(userId);
@@ -96,7 +97,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<UserItemDataDto> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -195,8 +196,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStart(
- [FromRoute] Guid userId,
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
[FromQuery] int? audioStreamIndex,
[FromQuery] int? subtitleStreamIndex,
@@ -245,8 +246,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackProgress(
- [FromRoute] Guid userId,
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
[FromQuery] long? positionTicks,
[FromQuery] int? audioStreamIndex,
@@ -297,8 +298,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
public async Task<ActionResult> OnPlaybackStopped(
- [FromRoute] Guid userId,
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid userId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] string? mediaSourceId,
[FromQuery] string? nextMediaType,
[FromQuery] long? positionTicks,
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index a82f2621a..342b0328d 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult UninstallPlugin([FromRoute] Guid pluginId)
+ public ActionResult UninstallPlugin([FromRoute, Required] Guid pluginId)
{
var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
if (plugin == null)
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute] Guid pluginId)
+ public ActionResult<BasePluginConfiguration> GetPluginConfiguration([FromRoute, Required] Guid pluginId)
{
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("{pluginId}/Configuration")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> UpdatePluginConfiguration([FromRoute] Guid pluginId)
+ public async Task<ActionResult> UpdatePluginConfiguration([FromRoute, Required] Guid pluginId)
{
if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
{
@@ -172,7 +172,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("This endpoint should not be used.")]
[HttpPost("RegistrationRecords/{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name)
+ public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute, Required] string? name)
{
return new MBRegistrationRecord
{
@@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
[Obsolete("Paid plugins are not supported")]
[HttpGet("Registrations/{name}")]
[ProducesResponseType(StatusCodes.Status501NotImplemented)]
- public ActionResult GetRegistration([FromRoute] string? name)
+ public ActionResult GetRegistration([FromRoute, Required] string? name)
{
// TODO Once we have proper apps and plugins and decide to break compatibility with paid plugins,
// delete all these registration endpoints. They are only kept for compatibility.
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 30a4f73fc..bdc817126 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers;
@@ -69,7 +70,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] ImageType? type,
[FromQuery] int? startIndex,
[FromQuery] int? limit,
@@ -132,7 +133,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute] Guid itemId)
+ public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
{
var item = _libraryManager.GetItemById(itemId);
if (item == null)
@@ -208,7 +209,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DownloadRemoteImage(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery, Required] ImageType type,
[FromQuery] string? imageUrl)
{
@@ -244,7 +245,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>Task.</returns>
private async Task DownloadImage(string url, Guid urlHash, string pointerCachePath)
{
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
using var response = await httpClient.GetAsync(url).ConfigureAwait(false);
var ext = response.Content.Headers.ContentType.MediaType.Split('/').Last();
var fullCachePath = GetFullCachePath(urlHash + "." + ext);
diff --git a/Jellyfin.Api/Controllers/ScheduledTasksController.cs b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
index e672070c0..3206f2734 100644
--- a/Jellyfin.Api/Controllers/ScheduledTasksController.cs
+++ b/Jellyfin.Api/Controllers/ScheduledTasksController.cs
@@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
[HttpPost("Running/{taskId}")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult StartTask([FromRoute] string? taskId)
+ public ActionResult StartTask([FromRoute, Required] string? taskId)
{
var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index ba8d51598..cff7c1501 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession(
[FromRoute, Required] string? sessionId,
- [FromRoute] Guid userId)
+ [FromRoute, Required] Guid userId)
{
_sessionManager.AddAdditionalUser(sessionId, userId);
return NoContent();
@@ -353,8 +353,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession(
- [FromRoute] string? sessionId,
- [FromRoute] Guid userId)
+ [FromRoute, Required] string? sessionId,
+ [FromRoute, Required] Guid userId)
{
_sessionManager.RemoveAdditionalUser(sessionId, userId);
return NoContent();
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 6f2787d93..cdd5f958e 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -259,7 +260,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the studio.</returns>
[HttpGet("{name}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BaseItemDto> GetStudio([FromRoute] string name, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
{
var dtoOptions = new DtoOptions().AddClientFields(Request);
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index 988acccc3..2c82b5423 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -86,8 +86,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Task> DeleteSubtitle(
- [FromRoute] Guid itemId,
- [FromRoute] int index)
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] int index)
{
var item = _libraryManager.GetItemById(itemId);
@@ -112,7 +112,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromRoute, Required] string? language,
[FromQuery] bool? isPerfectMatch)
{
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromRoute, Required] string? subtitleId)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -193,7 +193,7 @@ namespace Jellyfin.Api.Controllers
[FromQuery] long? endPositionTicks,
[FromQuery] bool copyTimestamps = false,
[FromQuery] bool addVttTimeMap = false,
- [FromRoute] long startPositionTicks = 0)
+ [FromRoute, Required] long startPositionTicks = 0)
{
if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
{
@@ -253,9 +253,9 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetSubtitlePlaylist(
- [FromRoute] Guid itemId,
- [FromRoute] int index,
- [FromRoute] string? mediaSourceId,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] int index,
+ [FromRoute, Required] string? mediaSourceId,
[FromQuery, Required] int segmentLength)
{
var item = (Video)_libraryManager.GetItemById(itemId);
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index 42db6b6a1..d7c81a3ab 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
@@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Suggestions")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromQuery] string? mediaType,
[FromQuery] string? type,
[FromQuery] int? startIndex,
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index b13cf9fa5..f7f2d0174 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
@@ -92,8 +93,8 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)]
public async Task<ActionResult> GetUniversalAudioStream(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] string? mediaSourceId,
[FromQuery] string? deviceId,
[FromQuery] Guid? userId,
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index d67f82219..95067bc17 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.IgnoreParentalControl)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
+ public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult DeleteUser([FromRoute] Guid userId)
+ public ActionResult DeleteUser([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
_sessionManager.RevokeUserTokens(user.Id, null);
@@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateUserPassword(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromBody] UpdateUserPassword request)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateUserEasyPassword(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromBody] UpdateUserEasyPassword request)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult> UpdateUser(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromBody] UserDto updateUser)
{
if (updateUser == null)
@@ -409,7 +409,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult UpdateUserPolicy(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromBody] UserPolicy newPolicy)
{
if (newPolicy == null)
@@ -464,7 +464,7 @@ namespace Jellyfin.Api.Controllers
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public ActionResult UpdateUserConfiguration(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromBody] UserConfiguration userConfig)
{
if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index f55ff6f3d..48262f062 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -70,7 +71,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the d item.</returns>
[HttpGet("Users/{userId}/Items/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
@@ -93,7 +94,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
[HttpGet("Users/{userId}/Items/Root")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
+ public ActionResult<BaseItemDto> GetRootFolder([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
var item = _libraryManager.GetUserRootFolder();
@@ -110,7 +111,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/Intros")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
@@ -138,7 +139,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<UserItemDataDto> MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
return MarkFavorite(userId, itemId, true);
}
@@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<UserItemDataDto> UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
return MarkFavorite(userId, itemId, false);
}
@@ -166,7 +167,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<UserItemDataDto> DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
return UpdateUserItemRatingInternal(userId, itemId, null);
}
@@ -181,7 +182,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
[HttpPost("Users/{userId}/Items/{itemId}/Rating")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute] Guid userId, [FromRoute] Guid itemId, [FromQuery] bool? likes)
+ public ActionResult<UserItemDataDto> UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes)
{
return UpdateUserItemRatingInternal(userId, itemId, likes);
}
@@ -195,7 +196,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>The items local trailers.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
@@ -230,7 +231,7 @@ namespace Jellyfin.Api.Controllers
/// <returns>An <see cref="OkResult"/> containing the special features.</returns>
[HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute] Guid userId, [FromRoute] Guid itemId)
+ public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
@@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Items/Latest")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromQuery] Guid? parentId,
[FromQuery] string? fields,
[FromQuery] string? includeItemTypes,
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index 6df7cc779..d575bfc3b 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Jellyfin.Api.Extensions;
@@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/Views")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
- [FromRoute] Guid userId,
+ [FromRoute, Required] Guid userId,
[FromQuery] bool? includeExternalContent,
[FromQuery] string? presetViews,
[FromQuery] bool includeHidden = false)
@@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Users/{userId}/GroupingOptions")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute] Guid userId)
+ public ActionResult<IEnumerable<SpecialViewOptionDto>> GetGroupingOptions([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
if (user == null)
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
index 76188f46d..dabf04dee 100644
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ b/Jellyfin.Api/Controllers/VideoHlsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Threading;
@@ -162,7 +163,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("Videos/{itemId}/live.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetLiveHlsStream(
- [FromRoute] Guid itemId,
+ [FromRoute, Required] Guid itemId,
[FromQuery] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 0978d44e9..5c65399cb 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -11,6 +11,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.StreamingDtos;
using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -114,7 +115,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{itemId}/AdditionalParts")]
[Authorize(Policy = Policies.DefaultAuthorization)]
[ProducesResponseType(StatusCodes.Status200OK)]
- public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute] Guid itemId, [FromQuery] Guid? userId)
+ public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{
var user = userId.HasValue && !userId.Equals(Guid.Empty)
? _userManager.GetUserById(userId.Value)
@@ -161,7 +162,7 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> DeleteAlternateSources([FromRoute] Guid itemId)
+ public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId)
{
var video = (Video)_libraryManager.GetItemById(itemId);
@@ -330,8 +331,8 @@ namespace Jellyfin.Api.Controllers
[HttpHead("{itemId}/stream", Name = "HeadVideoStream")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult> GetVideoStream(
- [FromRoute] Guid itemId,
- [FromRoute] string? container,
+ [FromRoute, Required] Guid itemId,
+ [FromRoute, Required] string? container,
[FromQuery] bool? @static,
[FromQuery] string? @params,
[FromQuery] string? tag,
@@ -473,7 +474,7 @@ namespace Jellyfin.Api.Controllers
{
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, startTimeTicks, Request, _dlnaManager);
- var httpClient = _httpClientFactory.CreateClient();
+ var httpClient = _httpClientFactory.CreateClient(NamedClient.Default);
return await FileStreamResponseHelpers.GetStaticRemoteStreamResult(state, isHeadRequest, httpClient, HttpContext).ConfigureAwait(false);
}
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index eb91ac23e..4ecf0407b 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
using System.Linq;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
@@ -179,7 +180,7 @@ namespace Jellyfin.Api.Controllers
[HttpGet("{year}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public ActionResult<BaseItemDto> GetYear([FromRoute] int year, [FromQuery] Guid? userId)
+ public ActionResult<BaseItemDto> GetYear([FromRoute, Required] int year, [FromQuery] Guid? userId)
{
var item = _libraryManager.GetYear(year);
if (item == null)