瀏覽代碼

Merge remote-tracking branch 'upstream/master' into api-stream-return

crobibero 4 年之前
父節點
當前提交
eab92a0b01
共有 100 個文件被更改,包括 759 次插入671 次删除
  1. 4 4
      .ci/azure-pipelines-main.yml
  2. 7 2
      .ci/azure-pipelines-test.yml
  3. 1 1
      .vscode/tasks.json
  4. 1 0
      CONTRIBUTORS.md
  5. 2 2
      Emby.Naming/Common/NamingOptions.cs
  6. 3 2
      Jellyfin.Api/Controllers/AlbumsController.cs
  7. 2 1
      Jellyfin.Api/Controllers/ArtistsController.cs
  8. 3 2
      Jellyfin.Api/Controllers/AudioController.cs
  9. 3 2
      Jellyfin.Api/Controllers/ChannelsController.cs
  10. 2 2
      Jellyfin.Api/Controllers/CollectionController.cs
  11. 2 2
      Jellyfin.Api/Controllers/ConfigurationController.cs
  12. 2 2
      Jellyfin.Api/Controllers/DisplayPreferencesController.cs
  13. 4 3
      Jellyfin.Api/Controllers/DlnaController.cs
  14. 10 9
      Jellyfin.Api/Controllers/DlnaServerController.cs
  15. 16 16
      Jellyfin.Api/Controllers/DynamicHlsController.cs
  16. 2 1
      Jellyfin.Api/Controllers/GenresController.cs
  17. 7 6
      Jellyfin.Api/Controllers/HlsSegmentController.cs
  18. 79 78
      Jellyfin.Api/Controllers/ImageController.cs
  19. 6 6
      Jellyfin.Api/Controllers/InstantMixController.cs
  20. 2 2
      Jellyfin.Api/Controllers/ItemLookupController.cs
  21. 2 1
      Jellyfin.Api/Controllers/ItemRefreshController.cs
  22. 3 3
      Jellyfin.Api/Controllers/ItemUpdateController.cs
  23. 3 2
      Jellyfin.Api/Controllers/ItemsController.cs
  24. 8 8
      Jellyfin.Api/Controllers/LibraryController.cs
  25. 14 13
      Jellyfin.Api/Controllers/LiveTvController.cs
  26. 2 2
      Jellyfin.Api/Controllers/MediaInfoController.cs
  27. 2 1
      Jellyfin.Api/Controllers/MusicGenresController.cs
  28. 3 3
      Jellyfin.Api/Controllers/PackageController.cs
  29. 2 1
      Jellyfin.Api/Controllers/PersonsController.cs
  30. 14 14
      Jellyfin.Api/Controllers/PlaylistsController.cs
  31. 10 9
      Jellyfin.Api/Controllers/PlaystateController.cs
  32. 5 5
      Jellyfin.Api/Controllers/PluginsController.cs
  33. 3 3
      Jellyfin.Api/Controllers/RemoteImageController.cs
  34. 1 1
      Jellyfin.Api/Controllers/ScheduledTasksController.cs
  35. 3 3
      Jellyfin.Api/Controllers/SessionController.cs
  36. 2 1
      Jellyfin.Api/Controllers/StudiosController.cs
  37. 8 8
      Jellyfin.Api/Controllers/SubtitleController.cs
  38. 2 1
      Jellyfin.Api/Controllers/SuggestionsController.cs
  39. 3 2
      Jellyfin.Api/Controllers/UniversalAudioController.cs
  40. 7 7
      Jellyfin.Api/Controllers/UserController.cs
  41. 11 10
      Jellyfin.Api/Controllers/UserLibraryController.cs
  42. 3 2
      Jellyfin.Api/Controllers/UserViewsController.cs
  43. 2 1
      Jellyfin.Api/Controllers/VideoHlsController.cs
  44. 4 4
      Jellyfin.Api/Controllers/VideosController.cs
  45. 2 1
      Jellyfin.Api/Controllers/YearsController.cs
  46. 1 1
      Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs
  47. 2 1
      Jellyfin.Api/Helpers/StreamingHelpers.cs
  48. 31 47
      Jellyfin.Data/Entities/ActivityLog.cs
  49. 3 1
      Jellyfin.Data/Entities/DisplayPreferences.cs
  50. 16 45
      Jellyfin.Data/Entities/Group.cs
  51. 39 6
      Jellyfin.Data/Entities/ImageInfo.cs
  52. 4 3
      Jellyfin.Data/Entities/ItemDisplayPreferences.cs
  53. 2 0
      Jellyfin.Data/Entities/Libraries/Artwork.cs
  54. 2 0
      Jellyfin.Data/Entities/Libraries/Book.cs
  55. 3 1
      Jellyfin.Data/Entities/Libraries/BookMetadata.cs
  56. 2 0
      Jellyfin.Data/Entities/Libraries/Chapter.cs
  57. 2 0
      Jellyfin.Data/Entities/Libraries/Collection.cs
  58. 2 2
      Jellyfin.Data/Entities/Libraries/CollectionItem.cs
  59. 2 0
      Jellyfin.Data/Entities/Libraries/Company.cs
  60. 1 1
      Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs
  61. 2 0
      Jellyfin.Data/Entities/Libraries/CustomItem.cs
  62. 1 1
      Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs
  63. 2 0
      Jellyfin.Data/Entities/Libraries/Episode.cs
  64. 1 1
      Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs
  65. 5 5
      Jellyfin.Data/Entities/Libraries/Genre.cs
  66. 7 5
      Jellyfin.Data/Entities/Libraries/ItemMetadata.cs
  67. 2 0
      Jellyfin.Data/Entities/Libraries/MediaFile.cs
  68. 5 5
      Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs
  69. 2 0
      Jellyfin.Data/Entities/Libraries/Movie.cs
  70. 3 1
      Jellyfin.Data/Entities/Libraries/MovieMetadata.cs
  71. 2 0
      Jellyfin.Data/Entities/Libraries/MusicAlbum.cs
  72. 3 1
      Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs
  73. 2 0
      Jellyfin.Data/Entities/Libraries/Person.cs
  74. 7 5
      Jellyfin.Data/Entities/Libraries/PersonRole.cs
  75. 2 0
      Jellyfin.Data/Entities/Libraries/Photo.cs
  76. 1 1
      Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs
  77. 5 5
      Jellyfin.Data/Entities/Libraries/Rating.cs
  78. 2 0
      Jellyfin.Data/Entities/Libraries/Release.cs
  79. 2 0
      Jellyfin.Data/Entities/Libraries/Season.cs
  80. 1 1
      Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs
  81. 2 0
      Jellyfin.Data/Entities/Libraries/Series.cs
  82. 3 1
      Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs
  83. 2 0
      Jellyfin.Data/Entities/Libraries/Track.cs
  84. 1 1
      Jellyfin.Data/Entities/Libraries/TrackMetadata.cs
  85. 2 34
      Jellyfin.Data/Entities/Permission.cs
  86. 1 25
      Jellyfin.Data/Entities/Preference.cs
  87. 0 129
      Jellyfin.Data/Entities/ProviderMapping.cs
  88. 5 44
      Jellyfin.Data/Entities/User.cs
  89. 27 7
      Jellyfin.Data/Enums/ArtKind.cs
  90. 42 2
      Jellyfin.Data/Enums/DynamicDayOfWeek.cs
  91. 4 3
      Jellyfin.Data/Enums/IndexingKind.cs
  92. 27 7
      Jellyfin.Data/Enums/MediaFileKind.cs
  93. 62 14
      Jellyfin.Data/Enums/PersonRoleType.cs
  94. 23 3
      Jellyfin.Data/Enums/SubtitlePlaybackMode.cs
  95. 47 11
      Jellyfin.Data/Enums/UnratedItem.cs
  96. 5 1
      Jellyfin.Data/Jellyfin.Data.csproj
  97. 4 1
      Jellyfin.Server/Jellyfin.Server.csproj
  98. 34 0
      Jellyfin.Server/wwwroot/api-docs/banner-dark.svg
  99. 15 0
      Jellyfin.Server/wwwroot/api-docs/swagger/custom.css
  100. 7 0
      MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs

+ 4 - 4
.ci/azure-pipelines-main.yml

@@ -64,28 +64,28 @@ jobs:
           arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
           arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)'
           zipAfterPublish: false
           zipAfterPublish: false
 
 
-      - task: PublishPipelineArtifact@0
+      - task: PublishPipelineArtifact@1
         displayName: 'Publish Artifact Naming'
         displayName: 'Publish Artifact Naming'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
         inputs:
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/Emby.Naming.dll'
           artifactName: 'Jellyfin.Naming'
           artifactName: 'Jellyfin.Naming'
 
 
-      - task: PublishPipelineArtifact@0
+      - task: PublishPipelineArtifact@1
         displayName: 'Publish Artifact Controller'
         displayName: 'Publish Artifact Controller'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
         inputs:
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Controller.dll'
           artifactName: 'Jellyfin.Controller'
           artifactName: 'Jellyfin.Controller'
 
 
-      - task: PublishPipelineArtifact@0
+      - task: PublishPipelineArtifact@1
         displayName: 'Publish Artifact Model'
         displayName: 'Publish Artifact Model'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
         inputs:
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
           targetPath: '$(build.ArtifactStagingDirectory)/Jellyfin.Server/MediaBrowser.Model.dll'
           artifactName: 'Jellyfin.Model'
           artifactName: 'Jellyfin.Model'
 
 
-      - task: PublishPipelineArtifact@0
+      - task: PublishPipelineArtifact@1
         displayName: 'Publish Artifact Common'
         displayName: 'Publish Artifact Common'
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         condition: and(succeeded(), eq(variables['BuildConfiguration'], 'Release'))
         inputs:
         inputs:

+ 7 - 2
.ci/azure-pipelines-test.yml

@@ -74,7 +74,6 @@ jobs:
       - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
       - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
         displayName: 'Run ReportGenerator'
         displayName: 'Run ReportGenerator'
-        enabled: false
         inputs:
         inputs:
           reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
           reports: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
           targetdir: "$(Agent.TempDirectory)/merged/"
           targetdir: "$(Agent.TempDirectory)/merged/"
@@ -84,10 +83,16 @@ jobs:
       - task: PublishCodeCoverageResults@1
       - task: PublishCodeCoverageResults@1
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
         condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) # !! THIS is for V1 only V2 will/should support merging
         displayName: 'Publish Code Coverage'
         displayName: 'Publish Code Coverage'
-        enabled: false
         inputs:
         inputs:
           codeCoverageTool: "cobertura"
           codeCoverageTool: "cobertura"
           #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
           #summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml' # !!THIS IS FOR V2
           summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
           summaryFileLocation: "$(Agent.TempDirectory)/merged/**.xml"
           pathToSources: $(Build.SourcesDirectory)
           pathToSources: $(Build.SourcesDirectory)
           failIfCoverageEmpty: true
           failIfCoverageEmpty: true
+
+      - task: PublishPipelineArtifact@1
+        displayName: 'Publish OpenAPI Artifact'
+        condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
+        inputs:
+          targetPath: "tests/Jellyfin.Api.Tests/bin/Release/netcoreapp3.1/openapi.json"
+          artifactName: 'OpenAPI Spec'

+ 1 - 1
.vscode/tasks.json

@@ -17,7 +17,7 @@
             "type": "process",
             "type": "process",
             "args": [
             "args": [
                 "test",
                 "test",
-                "${workspaceFolder}/tests/MediaBrowser.Api.Tests/MediaBrowser.Api.Tests.csproj"
+                "${workspaceFolder}/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj"
             ],
             ],
             "problemMatcher": "$msCompile"
             "problemMatcher": "$msCompile"
         }
         }

+ 1 - 0
CONTRIBUTORS.md

@@ -57,6 +57,7 @@
  - [Larvitar](https://github.com/Larvitar)
  - [Larvitar](https://github.com/Larvitar)
  - [LeoVerto](https://github.com/LeoVerto)
  - [LeoVerto](https://github.com/LeoVerto)
  - [Liggy](https://github.com/Liggy)
  - [Liggy](https://github.com/Liggy)
+ - [lmaonator](https://github.com/lmaonator)
  - [LogicalPhallacy](https://github.com/LogicalPhallacy)
  - [LogicalPhallacy](https://github.com/LogicalPhallacy)
  - [loli10K](https://github.com/loli10K)
  - [loli10K](https://github.com/loli10K)
  - [lostmypillow](https://github.com/lostmypillow)
  - [lostmypillow](https://github.com/lostmypillow)

+ 2 - 2
Emby.Naming/Common/NamingOptions.cs

@@ -136,8 +136,8 @@ namespace Emby.Naming.Common
 
 
             CleanDateTimes = new[]
             CleanDateTimes = new[]
             {
             {
-                @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
-                @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
+                @"(.+[^_\,\.\(\)\[\]\-])[_\.\(\)\[\]\-](19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*",
+                @"(.+[^_\,\.\(\)\[\]\-])[ _\.\(\)\[\]\-]+(19[0-9]{2}|20[0-9]{2})(?![0-9]+|\W[0-9]{2}\W[0-9]{2})([ _\,\.\(\)\[\]\-][^0-9]|).*(19[0-9]{2}|20[0-9]{2})*"
             };
             };
 
 
             CleanStrings = new[]
             CleanStrings = new[]

+ 3 - 2
Jellyfin.Api/Controllers/AlbumsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Helpers;
@@ -52,7 +53,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Albums/{albumId}/Similar")]
         [HttpGet("Albums/{albumId}/Similar")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarAlbums(
-            [FromRoute] string albumId,
+            [FromRoute, Required] string albumId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
             [FromQuery] int? limit)
@@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Artists/{artistId}/Similar")]
         [HttpGet("Artists/{artistId}/Similar")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarArtists(
-            [FromRoute] string artistId,
+            [FromRoute, Required] string artistId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] int? limit)
             [FromQuery] int? limit)

+ 2 - 1
Jellyfin.Api/Controllers/ArtistsController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
@@ -469,7 +470,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
         /// <returns>An <see cref="OkResult"/> containing the artist.</returns>
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

+ 3 - 2
Jellyfin.Api/Controllers/AudioController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Helpers;
@@ -91,8 +92,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesAudioFile]
         [ProducesAudioFile]
         public async Task<ActionResult> GetAudioStream(
         public async Task<ActionResult> GetAudioStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

+ 3 - 2
Jellyfin.Api/Controllers/ChannelsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -90,7 +91,7 @@ namespace Jellyfin.Api.Controllers
         /// <response code="200">Channel features returned.</response>
         /// <response code="200">Channel features returned.</response>
         /// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
         /// <returns>An <see cref="OkResult"/> containing the channel features.</returns>
         [HttpGet("{channelId}/Features")]
         [HttpGet("{channelId}/Features")]
-        public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute] string channelId)
+        public ActionResult<ChannelFeatures> GetChannelFeatures([FromRoute, Required] string channelId)
         {
         {
             return _channelManager.GetChannelFeatures(channelId);
             return _channelManager.GetChannelFeatures(channelId);
         }
         }
@@ -114,7 +115,7 @@ namespace Jellyfin.Api.Controllers
         /// </returns>
         /// </returns>
         [HttpGet("{channelId}/Items")]
         [HttpGet("{channelId}/Items")]
         public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
         public async Task<ActionResult<QueryResult<BaseItemDto>>> GetChannelItems(
-            [FromRoute] Guid channelId,
+            [FromRoute, Required] Guid channelId,
             [FromQuery] Guid? folderId,
             [FromQuery] Guid? folderId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,

+ 2 - 2
Jellyfin.Api/Controllers/CollectionController.cs

@@ -88,7 +88,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("{collectionId}/Items")]
         [HttpPost("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [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);
             await _collectionManager.AddToCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(true);
             return NoContent();
             return NoContent();
@@ -103,7 +103,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpDelete("{collectionId}/Items")]
         [HttpDelete("{collectionId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [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);
             await _collectionManager.RemoveFromCollectionAsync(collectionId, RequestHelpers.GetGuids(itemIds)).ConfigureAwait(false);
             return NoContent();
             return NoContent();

+ 2 - 2
Jellyfin.Api/Controllers/ConfigurationController.cs

@@ -76,7 +76,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Configuration/{key}")]
         [HttpGet("Configuration/{key}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesFile(MediaTypeNames.Application.Json)]
         [ProducesFile(MediaTypeNames.Application.Json)]
-        public ActionResult<object> GetNamedConfiguration([FromRoute] string? key)
+        public ActionResult<object> GetNamedConfiguration([FromRoute, Required] string? key)
         {
         {
             return _configurationManager.GetConfiguration(key);
             return _configurationManager.GetConfiguration(key);
         }
         }
@@ -90,7 +90,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Configuration/{key}")]
         [HttpPost("Configuration/{key}")]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [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 configurationType = _configurationManager.GetConfigurationType(key);
             var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);
             var configuration = await JsonSerializer.DeserializeAsync(Request.Body, configurationType, _serializerOptions).ConfigureAwait(false);

+ 2 - 2
Jellyfin.Api/Controllers/DisplayPreferencesController.cs

@@ -43,7 +43,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
         public ActionResult<DisplayPreferencesDto> GetDisplayPreferences(
-            [FromRoute] string? displayPreferencesId,
+            [FromRoute, Required] string? displayPreferencesId,
             [FromQuery] [Required] Guid userId,
             [FromQuery] [Required] Guid userId,
             [FromQuery] [Required] string? client)
             [FromQuery] [Required] string? client)
         {
         {
@@ -97,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "displayPreferencesId", Justification = "Imported from ServiceStack")]
         public ActionResult UpdateDisplayPreferences(
         public ActionResult UpdateDisplayPreferences(
-            [FromRoute] string? displayPreferencesId,
+            [FromRoute, Required] string? displayPreferencesId,
             [FromQuery, Required] Guid userId,
             [FromQuery, Required] Guid userId,
             [FromQuery, Required] string? client,
             [FromQuery, Required] string? client,
             [FromBody, Required] DisplayPreferencesDto displayPreferences)
             [FromBody, Required] DisplayPreferencesDto displayPreferences)

+ 4 - 3
Jellyfin.Api/Controllers/DlnaController.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
@@ -59,7 +60,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Profiles/{profileId}")]
         [HttpGet("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<DeviceProfile> GetProfile([FromRoute] string profileId)
+        public ActionResult<DeviceProfile> GetProfile([FromRoute, Required] string profileId)
         {
         {
             var profile = _dlnaManager.GetProfile(profileId);
             var profile = _dlnaManager.GetProfile(profileId);
             if (profile == null)
             if (profile == null)
@@ -80,7 +81,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("Profiles/{profileId}")]
         [HttpDelete("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteProfile([FromRoute] string profileId)
+        public ActionResult DeleteProfile([FromRoute, Required] string profileId)
         {
         {
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             if (existingDeviceProfile == null)
             if (existingDeviceProfile == null)
@@ -117,7 +118,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Profiles/{profileId}")]
         [HttpPost("Profiles/{profileId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var existingDeviceProfile = _dlnaManager.GetProfile(profileId);
             if (existingDeviceProfile == null)
             if (existingDeviceProfile == null)

+ 10 - 9
Jellyfin.Api/Controllers/DlnaServerController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
 using System.Net.Mime;
 using System.Net.Mime;
@@ -46,7 +47,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
-        public ActionResult GetDescriptionXml([FromRoute] string serverId)
+        public ActionResult GetDescriptionXml([FromRoute, Required] string serverId)
         {
         {
             var url = GetAbsoluteUri();
             var url = GetAbsoluteUri();
             var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
             var serverAddress = url.Substring(0, url.IndexOf("/dlna/", StringComparison.OrdinalIgnoreCase));
@@ -67,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [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());
             return Ok(_contentDirectory.GetServiceXml());
         }
         }
@@ -84,7 +85,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [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());
             return Ok(_mediaReceiverRegistrar.GetServiceXml());
         }
         }
@@ -101,7 +102,7 @@ namespace Jellyfin.Api.Controllers
         [Produces(MediaTypeNames.Text.Xml)]
         [Produces(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [ProducesFile(MediaTypeNames.Text.Xml)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [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());
             return Ok(_connectionManager.GetServiceXml());
         }
         }
@@ -112,7 +113,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/ContentDirectory/Control")]
         [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);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _contentDirectory).ConfigureAwait(false);
         }
         }
@@ -123,7 +124,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/ConnectionManager/Control")]
         [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);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _connectionManager).ConfigureAwait(false);
         }
         }
@@ -134,7 +135,7 @@ namespace Jellyfin.Api.Controllers
         /// <param name="serverId">Server UUID.</param>
         /// <param name="serverId">Server UUID.</param>
         /// <returns>Control response.</returns>
         /// <returns>Control response.</returns>
         [HttpPost("{serverId}/MediaReceiverRegistrar/Control")]
         [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);
             return await ProcessControlRequestInternalAsync(serverId, Request.Body, _mediaReceiverRegistrar).ConfigureAwait(false);
         }
         }
@@ -191,7 +192,7 @@ namespace Jellyfin.Api.Controllers
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "serverId", Justification = "Required for DLNA")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesImageFile]
         [ProducesImageFile]
-        public ActionResult GetIconId([FromRoute] string serverId, [FromRoute] string fileName)
+        public ActionResult GetIconId([FromRoute, Required] string serverId, [FromRoute, Required] string fileName)
         {
         {
             return GetIconInternal(fileName);
             return GetIconInternal(fileName);
         }
         }
@@ -203,7 +204,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>Icon stream.</returns>
         /// <returns>Icon stream.</returns>
         [HttpGet("icons/{fileName}")]
         [HttpGet("icons/{fileName}")]
         [ProducesImageFile]
         [ProducesImageFile]
-        public ActionResult GetIcon([FromRoute] string fileName)
+        public ActionResult GetIcon([FromRoute, Required] string fileName)
         {
         {
             return GetIconInternal(fileName);
             return GetIconInternal(fileName);
         }
         }

+ 16 - 16
Jellyfin.Api/Controllers/DynamicHlsController.cs

@@ -169,8 +169,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         public async Task<ActionResult> GetMasterHlsVideoPlaylist(
         public async Task<ActionResult> GetMasterHlsVideoPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -337,8 +337,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         public async Task<ActionResult> GetMasterHlsAudioPlaylist(
         public async Task<ActionResult> GetMasterHlsAudioPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -503,8 +503,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         public async Task<ActionResult> GetVariantHlsVideoPlaylist(
         public async Task<ActionResult> GetVariantHlsVideoPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -669,8 +669,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         public async Task<ActionResult> GetVariantHlsAudioPlaylist(
         public async Task<ActionResult> GetVariantHlsAudioPlaylist(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -838,10 +838,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesVideoFile]
         [ProducesVideoFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetHlsVideoSegment(
         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] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
@@ -1008,10 +1008,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesAudioFile]
         [ProducesAudioFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetHlsAudioSegment(
         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] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

+ 2 - 1
Jellyfin.Api/Controllers/GenresController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
@@ -260,7 +261,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
         /// <returns>An <see cref="OkResult"/> containing the genre.</returns>
         [HttpGet("{genreName}")]
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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()
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
                 .AddClientFields(Request);

+ 7 - 6
Jellyfin.Api/Controllers/HlsSegmentController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -57,7 +58,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesAudioFile]
         [ProducesAudioFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [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
             // TODO: Deprecate with new iOS app
             var file = segmentId + Path.GetExtension(Request.Path);
             var file = segmentId + Path.GetExtension(Request.Path);
@@ -78,7 +79,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [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);
             var file = playlistId + Path.GetExtension(Request.Path);
             file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
             file = Path.Combine(_serverConfigurationManager.GetTranscodePath(), file);
@@ -118,10 +119,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesVideoFile]
         [ProducesVideoFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
         public ActionResult GetHlsVideoSegmentLegacy(
         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 file = segmentId + Path.GetExtension(Request.Path);
             var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();
             var transcodeFolderPath = _serverConfigurationManager.GetTranscodePath();

+ 79 - 78
Jellyfin.Api/Controllers/ImageController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
@@ -91,9 +92,9 @@ namespace Jellyfin.Api.Controllers
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> PostUserImage(
         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))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             {
             {
@@ -138,9 +139,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult DeleteUserImage(
         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))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             {
             {
@@ -176,9 +177,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> DeleteItemImage(
         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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -206,9 +207,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> SetItemImage(
         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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -239,9 +240,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> UpdateItemImageIndex(
         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)
             [FromQuery] int newIndex)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
@@ -265,7 +266,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -354,10 +355,10 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetItemImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -370,7 +371,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -433,23 +434,23 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetItemImage2(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
-            [FromRoute] string tag,
+            [FromRoute, Required] string tag,
             [FromQuery] bool? cropWhitespace,
             [FromQuery] bool? cropWhitespace,
-            [FromRoute] string format,
+            [FromRoute, Required] string format,
             [FromQuery] bool? addPlayedIndicator,
             [FromQuery] bool? addPlayedIndicator,
-            [FromRoute] double? percentPlayed,
-            [FromRoute] int? unplayedCount,
+            [FromRoute, Required] double? percentPlayed,
+            [FromRoute, Required] int? unplayedCount,
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -512,14 +513,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetArtistImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -528,7 +529,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetArtist(name);
             var item = _libraryManager.GetArtist(name);
             if (item == null)
             if (item == null)
@@ -591,14 +592,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetGenreImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -607,7 +608,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetGenre(name);
             var item = _libraryManager.GetGenre(name);
             if (item == null)
             if (item == null)
@@ -670,14 +671,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetMusicGenreImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -686,7 +687,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetMusicGenre(name);
             var item = _libraryManager.GetMusicGenre(name);
             if (item == null)
             if (item == null)
@@ -749,14 +750,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetPersonImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -765,7 +766,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetPerson(name);
             var item = _libraryManager.GetPerson(name);
             if (item == null)
             if (item == null)
@@ -828,14 +829,14 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetStudioImage(
         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? width,
             [FromQuery] int? height,
             [FromQuery] int? height,
             [FromQuery] int? quality,
             [FromQuery] int? quality,
@@ -844,7 +845,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var item = _libraryManager.GetStudio(name);
             var item = _libraryManager.GetStudio(name);
             if (item == null)
             if (item == null)
@@ -907,8 +908,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesImageFile]
         [ProducesImageFile]
         public async Task<ActionResult> GetUserImage(
         public async Task<ActionResult> GetUserImage(
-            [FromRoute] Guid userId,
-            [FromRoute] ImageType imageType,
+            [FromRoute, Required] Guid userId,
+            [FromRoute, Required] ImageType imageType,
             [FromQuery] string? tag,
             [FromQuery] string? tag,
             [FromQuery] string? format,
             [FromQuery] string? format,
             [FromQuery] int? maxWidth,
             [FromQuery] int? maxWidth,
@@ -923,7 +924,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] int? blur,
             [FromQuery] int? blur,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? backgroundColor,
             [FromQuery] string? foregroundLayer,
             [FromQuery] string? foregroundLayer,
-            [FromRoute] int? imageIndex = null)
+            [FromRoute, Required] int? imageIndex = null)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             if (user == null)
             if (user == null)

+ 6 - 6
Jellyfin.Api/Controllers/InstantMixController.cs

@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Songs/{id}/InstantMix")]
         [HttpGet("Songs/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Albums/{id}/InstantMix")]
         [HttpGet("Albums/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -138,7 +138,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Playlists/{id}/InstantMix")]
         [HttpGet("Playlists/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Artists/InstantMix")]
         [HttpGet("Artists/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -248,7 +248,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("MusicGenres/InstantMix")]
         [HttpGet("MusicGenres/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenres(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
@@ -285,7 +285,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Items/{id}/InstantMix")]
         [HttpGet("Items/{id}/InstantMix")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
         public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
-            [FromRoute] Guid id,
+            [FromRoute, Required] Guid id,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? fields,
             [FromQuery] string? fields,

+ 2 - 2
Jellyfin.Api/Controllers/ItemLookupController.cs

@@ -74,7 +74,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -296,7 +296,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> ApplySearchCriteria(
         public async Task<ActionResult> ApplySearchCriteria(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromBody, Required] RemoteSearchResult searchResult,
             [FromBody, Required] RemoteSearchResult searchResult,
             [FromQuery] bool replaceAllImages = true)
             [FromQuery] bool replaceAllImages = true)
         {
         {

+ 2 - 1
Jellyfin.Api/Controllers/ItemRefreshController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.ComponentModel;
 using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
@@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult Post(
         public ActionResult Post(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode metadataRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
             [FromQuery] MetadataRefreshMode imageRefreshMode = MetadataRefreshMode.None,
             [FromQuery] bool replaceAllMetadata = false,
             [FromQuery] bool replaceAllMetadata = false,

+ 3 - 3
Jellyfin.Api/Controllers/ItemUpdateController.cs

@@ -68,7 +68,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}")]
         [HttpPost("Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -141,7 +141,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Items/{itemId}/MetadataEditor")]
         [HttpGet("Items/{itemId}/MetadataEditor")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute] Guid itemId)
+        public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -195,7 +195,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}/ContentType")]
         [HttpPost("Items/{itemId}/ContentType")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)

+ 3 - 2
Jellyfin.Api/Controllers/ItemsController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
@@ -144,7 +145,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
         [HttpGet("Users/{uId}/Items", Name = "GetItems_2")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
         public ActionResult<QueryResult<BaseItemDto>> GetItems(
-            [FromRoute] Guid? uId,
+            [FromRoute, Required] Guid? uId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] string? maxOfficialRating,
             [FromQuery] bool? hasThemeSong,
             [FromQuery] bool? hasThemeSong,
@@ -529,7 +530,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Items/Resume")]
         [HttpGet("Users/{userId}/Items/Resume")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
         public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
             [FromQuery] string? searchTerm,
             [FromQuery] string? searchTerm,

+ 8 - 8
Jellyfin.Api/Controllers/LibraryController.cs

@@ -106,7 +106,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesFile("video/*", "audio/*")]
         [ProducesFile("video/*", "audio/*")]
-        public ActionResult GetFile([FromRoute] Guid itemId)
+        public ActionResult GetFile([FromRoute, Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -145,7 +145,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeSongs(
         public ActionResult<ThemeMediaResult> GetThemeSongs(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<ThemeMediaResult> GetThemeVideos(
         public ActionResult<ThemeMediaResult> GetThemeVideos(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -276,7 +276,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
         public ActionResult<AllThemeMediaResult> GetThemeMedia(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] bool inheritFromParent = false)
             [FromQuery] bool inheritFromParent = false)
         {
         {
@@ -439,7 +439,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -556,7 +556,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Library/Movies/Updated")]
         [HttpPost("Library/Movies/Updated")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [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
             var movies = _libraryManager.GetItemList(new InternalItemsQuery
             {
             {
@@ -620,7 +620,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesFile("video/*", "audio/*")]
         [ProducesFile("video/*", "audio/*")]
-        public async Task<ActionResult> GetDownload([FromRoute] Guid itemId)
+        public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -689,7 +689,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
         public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] string? excludeArtistIds,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] int? limit,
             [FromQuery] int? limit,

+ 14 - 13
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -210,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Channels/{channelId}")]
         [HttpGet("Channels/{channelId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [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)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -407,7 +408,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Recordings/{recordingId}")]
         [HttpGet("Recordings/{recordingId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [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)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -429,7 +430,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Tuners/{tunerId}/Reset")]
         [HttpPost("Tuners/{tunerId}/Reset")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
-        public ActionResult ResetTuner([FromRoute] string tunerId)
+        public ActionResult ResetTuner([FromRoute, Required] string tunerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
             _liveTvManager.ResetTuner(tunerId, CancellationToken.None);
@@ -745,7 +746,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<BaseItemDto>> GetProgram(
         public async Task<ActionResult<BaseItemDto>> GetProgram(
-            [FromRoute] string programId,
+            [FromRoute, Required] string programId,
             [FromQuery] Guid? userId)
             [FromQuery] Guid? userId)
         {
         {
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
@@ -766,7 +767,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteRecording([FromRoute] Guid recordingId)
+        public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
 
 
@@ -793,7 +794,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("Timers/{timerId}")]
         [HttpDelete("Timers/{timerId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> CancelTimer([FromRoute] string timerId)
+        public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
             await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
@@ -811,7 +812,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
         [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();
             AssertUserCanManageLiveTv();
             await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
             await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -845,7 +846,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var timer = await _liveTvManager.GetSeriesTimer(timerId, CancellationToken.None).ConfigureAwait(false);
             if (timer == null)
             if (timer == null)
@@ -885,7 +886,7 @@ namespace Jellyfin.Api.Controllers
         [HttpDelete("SeriesTimers/{timerId}")]
         [HttpDelete("SeriesTimers/{timerId}")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
-        public async Task<ActionResult> CancelSeriesTimer([FromRoute] string timerId)
+        public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
         {
         {
             AssertUserCanManageLiveTv();
             AssertUserCanManageLiveTv();
             await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
             await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
@@ -903,7 +904,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
         [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();
             AssertUserCanManageLiveTv();
             await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
             await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
@@ -935,7 +936,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Obsolete("This endpoint is obsolete.")]
         [Obsolete("This endpoint is obsolete.")]
-        public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute] Guid? groupId)
+        public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid? groupId)
         {
         {
             return NotFound();
             return NotFound();
         }
         }
@@ -1179,7 +1180,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesVideoFile]
         [ProducesVideoFile]
-        public async Task<ActionResult> GetLiveRecordingFile([FromRoute] string recordingId)
+        public async Task<ActionResult> GetLiveRecordingFile([FromRoute, Required] string recordingId)
         {
         {
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
             var path = _liveTvManager.GetEmbyTvActiveRecordingPath(recordingId);
 
 
@@ -1210,7 +1211,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesVideoFile]
         [ProducesVideoFile]
-        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);
             var liveStreamInfo = await _mediaSourceManager.GetDirectStreamProviderByUniqueId(streamId, CancellationToken.None).ConfigureAwait(false);
             if (liveStreamInfo == null)
             if (liveStreamInfo == null)

+ 2 - 2
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -69,7 +69,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
         /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
         [HttpGet("Items/{itemId}/PlaybackInfo")]
         [HttpGet("Items/{itemId}/PlaybackInfo")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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(
             return await _mediaInfoHelper.GetPlaybackInfo(
                     itemId,
                     itemId,
@@ -101,7 +101,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Items/{itemId}/PlaybackInfo")]
         [HttpPost("Items/{itemId}/PlaybackInfo")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
         public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? maxStreamingBitrate,
             [FromQuery] long? startTimeTicks,
             [FromQuery] long? startTimeTicks,

+ 2 - 1
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 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>
         /// <returns>An <see cref="OkResult"/> containing a <see cref="BaseItemDto"/> with the music genre.</returns>
         [HttpGet("{genreName}")]
         [HttpGet("{genreName}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

+ 3 - 3
Jellyfin.Api/Controllers/PackageController.cs

@@ -44,7 +44,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Packages/{name}")]
         [HttpGet("Packages/{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<PackageInfo>> GetPackageInfo(
         public async Task<ActionResult<PackageInfo>> GetPackageInfo(
-            [FromRoute] [Required] string? name,
+            [FromRoute, Required] string? name,
             [FromQuery] string? assemblyGuid)
             [FromQuery] string? assemblyGuid)
         {
         {
             var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
             var packages = await _installationManager.GetAvailablePackages().ConfigureAwait(false);
@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         public async Task<ActionResult> InstallPackage(
         public async Task<ActionResult> InstallPackage(
-            [FromRoute] [Required] string? name,
+            [FromRoute, Required] string? name,
             [FromQuery] string? assemblyGuid,
             [FromQuery] string? assemblyGuid,
             [FromQuery] string? version)
             [FromQuery] string? version)
         {
         {
@@ -115,7 +115,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult CancelPackageInstallation(
         public ActionResult CancelPackageInstallation(
-            [FromRoute] [Required] Guid packageId)
+            [FromRoute, Required] Guid packageId)
         {
         {
             _installationManager.CancelInstallation(packageId);
             _installationManager.CancelInstallation(packageId);
             return NoContent();
             return NoContent();

+ 2 - 1
Jellyfin.Api/Controllers/PersonsController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
@@ -262,7 +263,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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()
             var dtoOptions = new DtoOptions()
                 .AddClientFields(Request);
                 .AddClientFields(Request);

+ 14 - 14
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -84,7 +84,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{playlistId}/Items")]
         [HttpPost("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> AddToPlaylist(
         public async Task<ActionResult> AddToPlaylist(
-            [FromRoute] Guid playlistId,
+            [FromRoute, Required] Guid playlistId,
             [FromQuery] string? ids,
             [FromQuery] string? ids,
             [FromQuery] Guid? userId)
             [FromQuery] Guid? userId)
         {
         {
@@ -103,9 +103,9 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> MoveItem(
         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);
             await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false);
             return NoContent();
             return NoContent();
@@ -120,7 +120,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         /// <returns>An <see cref="NoContentResult"/> on success.</returns>
         [HttpDelete("{playlistId}/Items")]
         [HttpDelete("{playlistId}/Items")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [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);
             await _playlistManager.RemoveFromPlaylistAsync(playlistId, RequestHelpers.Split(entryIds, ',', true)).ConfigureAwait(false);
             return NoContent();
             return NoContent();
@@ -143,15 +143,15 @@ namespace Jellyfin.Api.Controllers
         /// <returns>The original playlist items.</returns>
         /// <returns>The original playlist items.</returns>
         [HttpGet("{playlistId}/Items")]
         [HttpGet("{playlistId}/Items")]
         public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
         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);
             var playlist = (Playlist)_libraryManager.GetItemById(playlistId);
             if (playlist == null)
             if (playlist == null)

+ 10 - 9
Jellyfin.Api/Controllers/PlaystateController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Diagnostics.CodeAnalysis;
 using System.Diagnostics.CodeAnalysis;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
@@ -71,8 +72,8 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Users/{userId}/PlayedItems/{itemId}")]
         [HttpPost("Users/{userId}/PlayedItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<UserItemDataDto> MarkPlayedItem(
         public ActionResult<UserItemDataDto> MarkPlayedItem(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid userId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] DateTime? datePlayed)
             [FromQuery] DateTime? datePlayed)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
@@ -96,7 +97,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>A <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
         [HttpDelete("Users/{userId}/PlayedItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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 user = _userManager.GetUserById(userId);
             var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
             var session = RequestHelpers.GetSession(_sessionManager, _authContext, Request);
@@ -195,8 +196,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackStart(
         public async Task<ActionResult> OnPlaybackStart(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid userId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
             [FromQuery] int? subtitleStreamIndex,
@@ -245,8 +246,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackProgress(
         public async Task<ActionResult> OnPlaybackProgress(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid userId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] long? positionTicks,
             [FromQuery] long? positionTicks,
             [FromQuery] int? audioStreamIndex,
             [FromQuery] int? audioStreamIndex,
@@ -297,8 +298,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Required for ServiceStack")]
         public async Task<ActionResult> OnPlaybackStopped(
         public async Task<ActionResult> OnPlaybackStopped(
-            [FromRoute] Guid userId,
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid userId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? nextMediaType,
             [FromQuery] string? nextMediaType,
             [FromQuery] long? positionTicks,
             [FromQuery] long? positionTicks,

+ 5 - 5
Jellyfin.Api/Controllers/PluginsController.cs

@@ -64,7 +64,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var plugin = _appHost.Plugins.FirstOrDefault(p => p.Id == pluginId);
             if (plugin == null)
             if (plugin == null)
@@ -86,7 +86,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{pluginId}/Configuration")]
         [HttpGet("{pluginId}/Configuration")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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))
             if (!(_appHost.Plugins.FirstOrDefault(p => p.Id == pluginId) is IHasPluginConfiguration plugin))
             {
             {
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("{pluginId}/Configuration")]
         [HttpPost("{pluginId}/Configuration")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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))
             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.")]
         [Obsolete("This endpoint should not be used.")]
         [HttpPost("RegistrationRecords/{name}")]
         [HttpPost("RegistrationRecords/{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute] string? name)
+        public ActionResult<MBRegistrationRecord> GetRegistrationStatus([FromRoute, Required] string? name)
         {
         {
             return new MBRegistrationRecord
             return new MBRegistrationRecord
             {
             {
@@ -194,7 +194,7 @@ namespace Jellyfin.Api.Controllers
         [Obsolete("Paid plugins are not supported")]
         [Obsolete("Paid plugins are not supported")]
         [HttpGet("Registrations/{name}")]
         [HttpGet("Registrations/{name}")]
         [ProducesResponseType(StatusCodes.Status501NotImplemented)]
         [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,
             // 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.
             // delete all these registration endpoints. They are only kept for compatibility.

+ 3 - 3
Jellyfin.Api/Controllers/RemoteImageController.cs

@@ -71,7 +71,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
         public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] ImageType? type,
             [FromQuery] ImageType? type,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,
             [FromQuery] int? limit,
             [FromQuery] int? limit,
@@ -134,7 +134,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetItemById(itemId);
             if (item == null)
             if (item == null)
@@ -211,7 +211,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> DownloadRemoteImage(
         public async Task<ActionResult> DownloadRemoteImage(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery, Required] ImageType type,
             [FromQuery, Required] ImageType type,
             [FromQuery] string? imageUrl)
             [FromQuery] string? imageUrl)
         {
         {

+ 1 - 1
Jellyfin.Api/Controllers/ScheduledTasksController.cs

@@ -94,7 +94,7 @@ namespace Jellyfin.Api.Controllers
         [HttpPost("Running/{taskId}")]
         [HttpPost("Running/{taskId}")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult StartTask([FromRoute] string? taskId)
+        public ActionResult StartTask([FromRoute, Required] string? taskId)
         {
         {
             var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
             var task = _taskManager.ScheduledTasks.FirstOrDefault(o =>
                 o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));
                 o.Id.Equals(taskId, StringComparison.OrdinalIgnoreCase));

+ 3 - 3
Jellyfin.Api/Controllers/SessionController.cs

@@ -336,7 +336,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult AddUserToSession(
         public ActionResult AddUserToSession(
             [FromRoute, Required] string? sessionId,
             [FromRoute, Required] string? sessionId,
-            [FromRoute] Guid userId)
+            [FromRoute, Required] Guid userId)
         {
         {
             _sessionManager.AddAdditionalUser(sessionId, userId);
             _sessionManager.AddAdditionalUser(sessionId, userId);
             return NoContent();
             return NoContent();
@@ -353,8 +353,8 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public ActionResult RemoveUserFromSession(
         public ActionResult RemoveUserFromSession(
-            [FromRoute] string? sessionId,
-            [FromRoute] Guid userId)
+            [FromRoute, Required] string? sessionId,
+            [FromRoute, Required] Guid userId)
         {
         {
             _sessionManager.RemoveAdditionalUser(sessionId, userId);
             _sessionManager.RemoveAdditionalUser(sessionId, userId);
             return NoContent();
             return NoContent();

+ 2 - 1
Jellyfin.Api/Controllers/StudiosController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
@@ -259,7 +260,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
         /// <returns>An <see cref="OkResult"/> containing the studio.</returns>
         [HttpGet("{name}")]
         [HttpGet("{name}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var dtoOptions = new DtoOptions().AddClientFields(Request);
 
 

+ 8 - 8
Jellyfin.Api/Controllers/SubtitleController.cs

@@ -87,8 +87,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult<Task> DeleteSubtitle(
         public ActionResult<Task> DeleteSubtitle(
-            [FromRoute] Guid itemId,
-            [FromRoute] int index)
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] int index)
         {
         {
             var item = _libraryManager.GetItemById(itemId);
             var item = _libraryManager.GetItemById(itemId);
 
 
@@ -113,7 +113,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
         public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromRoute, Required] string? language,
             [FromRoute, Required] string? language,
             [FromQuery] bool? isPerfectMatch)
             [FromQuery] bool? isPerfectMatch)
         {
         {
@@ -133,7 +133,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         public async Task<ActionResult> DownloadRemoteSubtitles(
         public async Task<ActionResult> DownloadRemoteSubtitles(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromRoute, Required] string? subtitleId)
             [FromRoute, Required] string? subtitleId)
         {
         {
             var video = (Video)_libraryManager.GetItemById(itemId);
             var video = (Video)_libraryManager.GetItemById(itemId);
@@ -196,7 +196,7 @@ namespace Jellyfin.Api.Controllers
             [FromQuery] long? endPositionTicks,
             [FromQuery] long? endPositionTicks,
             [FromQuery] bool copyTimestamps = false,
             [FromQuery] bool copyTimestamps = false,
             [FromQuery] bool addVttTimeMap = false,
             [FromQuery] bool addVttTimeMap = false,
-            [FromRoute] long startPositionTicks = 0)
+            [FromRoute, Required] long startPositionTicks = 0)
         {
         {
             if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
             if (string.Equals(format, "js", StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -256,9 +256,9 @@ namespace Jellyfin.Api.Controllers
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
         public async Task<ActionResult> GetSubtitlePlaylist(
         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)
             [FromQuery, Required] int segmentLength)
         {
         {
             var item = (Video)_libraryManager.GetItemById(itemId);
             var item = (Video)_libraryManager.GetItemById(itemId);

+ 2 - 1
Jellyfin.Api/Controllers/SuggestionsController.cs

@@ -1,4 +1,5 @@
 using System;
 using System;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Api.Helpers;
@@ -53,7 +54,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Suggestions")]
         [HttpGet("Users/{userId}/Suggestions")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
         public ActionResult<QueryResult<BaseItemDto>> GetSuggestions(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromQuery] string? mediaType,
             [FromQuery] string? mediaType,
             [FromQuery] string? type,
             [FromQuery] string? type,
             [FromQuery] int? startIndex,
             [FromQuery] int? startIndex,

+ 3 - 2
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -94,8 +95,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status302Found)]
         [ProducesResponseType(StatusCodes.Status302Found)]
         [ProducesAudioFile]
         [ProducesAudioFile]
         public async Task<ActionResult> GetUniversalAudioStream(
         public async Task<ActionResult> GetUniversalAudioStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? mediaSourceId,
             [FromQuery] string? deviceId,
             [FromQuery] string? deviceId,
             [FromQuery] Guid? userId,
             [FromQuery] Guid? userId,

+ 7 - 7
Jellyfin.Api/Controllers/UserController.cs

@@ -108,7 +108,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.IgnoreParentalControl)]
         [Authorize(Policy = Policies.IgnoreParentalControl)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult<UserDto> GetUserById([FromRoute] Guid userId)
+        public ActionResult<UserDto> GetUserById([FromRoute, Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
 
 
@@ -132,7 +132,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
-        public ActionResult DeleteUser([FromRoute] Guid userId)
+        public ActionResult DeleteUser([FromRoute, Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             _sessionManager.RevokeUserTokens(user.Id, null);
             _sessionManager.RevokeUserTokens(user.Id, null);
@@ -265,7 +265,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public async Task<ActionResult> UpdateUserPassword(
         public async Task<ActionResult> UpdateUserPassword(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromBody] UpdateUserPassword request)
             [FromBody] UpdateUserPassword request)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -323,7 +323,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         public ActionResult UpdateUserEasyPassword(
         public ActionResult UpdateUserEasyPassword(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromBody] UpdateUserEasyPassword request)
             [FromBody] UpdateUserEasyPassword request)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, true))
@@ -365,7 +365,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public async Task<ActionResult> UpdateUser(
         public async Task<ActionResult> UpdateUser(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromBody] UserDto updateUser)
             [FromBody] UserDto updateUser)
         {
         {
             if (updateUser == null)
             if (updateUser == null)
@@ -409,7 +409,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status400BadRequest)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult UpdateUserPolicy(
         public ActionResult UpdateUserPolicy(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromBody] UserPolicy newPolicy)
             [FromBody] UserPolicy newPolicy)
         {
         {
             if (newPolicy == null)
             if (newPolicy == null)
@@ -464,7 +464,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         [ProducesResponseType(StatusCodes.Status403Forbidden)]
         public ActionResult UpdateUserConfiguration(
         public ActionResult UpdateUserConfiguration(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromBody] UserConfiguration userConfig)
             [FromBody] UserConfiguration userConfig)
         {
         {
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))
             if (!RequestHelpers.AssertCanUpdateUser(_authContext, HttpContext.Request, userId, false))

+ 11 - 10
Jellyfin.Api/Controllers/UserLibraryController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -70,7 +71,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
         /// <returns>An <see cref="OkResult"/> containing the d item.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}")]
         [HttpGet("Users/{userId}/Items/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             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>
         /// <returns>An <see cref="OkResult"/> containing the user's root folder.</returns>
         [HttpGet("Users/{userId}/Items/Root")]
         [HttpGet("Users/{userId}/Items/Root")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
-        public ActionResult<BaseItemDto> GetRootFolder([FromRoute] Guid userId)
+        public ActionResult<BaseItemDto> GetRootFolder([FromRoute, Required] Guid userId)
         {
         {
             var user = _userManager.GetUserById(userId);
             var user = _userManager.GetUserById(userId);
             var item = _libraryManager.GetUserRootFolder();
             var item = _libraryManager.GetUserRootFolder();
@@ -110,7 +111,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
         /// <returns>An <see cref="OkResult"/> containing the intros to play.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/Intros")]
         [HttpGet("Users/{userId}/Items/{itemId}/Intros")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var user = _userManager.GetUserById(userId);
 
 
@@ -138,7 +139,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
         [HttpPost("Users/{userId}/FavoriteItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             return MarkFavorite(userId, itemId, true);
         }
         }
@@ -152,7 +153,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
         [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             return MarkFavorite(userId, itemId, false);
         }
         }
@@ -166,7 +167,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
         [HttpDelete("Users/{userId}/Items/{itemId}/Rating")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             return UpdateUserItemRatingInternal(userId, itemId, null);
         }
         }
@@ -181,7 +182,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         /// <returns>An <see cref="OkResult"/> containing the <see cref="UserItemDataDto"/>.</returns>
         [HttpPost("Users/{userId}/Items/{itemId}/Rating")]
         [HttpPost("Users/{userId}/Items/{itemId}/Rating")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             return UpdateUserItemRatingInternal(userId, itemId, likes);
         }
         }
@@ -195,7 +196,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>The items local trailers.</returns>
         /// <returns>The items local trailers.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
         [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var user = _userManager.GetUserById(userId);
 
 
@@ -230,7 +231,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
         /// <returns>An <see cref="OkResult"/> containing the special features.</returns>
         [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
         [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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);
             var user = _userManager.GetUserById(userId);
 
 
@@ -264,7 +265,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Items/Latest")]
         [HttpGet("Users/{userId}/Items/Latest")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
         public ActionResult<IEnumerable<BaseItemDto>> GetLatestMedia(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromQuery] Guid? parentId,
             [FromQuery] Guid? parentId,
             [FromQuery] string? fields,
             [FromQuery] string? fields,
             [FromQuery] string? includeItemTypes,
             [FromQuery] string? includeItemTypes,

+ 3 - 2
Jellyfin.Api/Controllers/UserViewsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
@@ -64,7 +65,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/Views")]
         [HttpGet("Users/{userId}/Views")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
         public ActionResult<QueryResult<BaseItemDto>> GetUserViews(
-            [FromRoute] Guid userId,
+            [FromRoute, Required] Guid userId,
             [FromQuery] bool? includeExternalContent,
             [FromQuery] bool? includeExternalContent,
             [FromQuery] string? presetViews,
             [FromQuery] string? presetViews,
             [FromQuery] bool includeHidden = false)
             [FromQuery] bool includeHidden = false)
@@ -126,7 +127,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("Users/{userId}/GroupingOptions")]
         [HttpGet("Users/{userId}/GroupingOptions")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var user = _userManager.GetUserById(userId);
             if (user == null)
             if (user == null)

+ 2 - 1
Jellyfin.Api/Controllers/VideoHlsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
@@ -164,7 +165,7 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesPlaylistFile]
         [ProducesPlaylistFile]
         public async Task<ActionResult> GetLiveHlsStream(
         public async Task<ActionResult> GetLiveHlsStream(
-            [FromRoute] Guid itemId,
+            [FromRoute, Required] Guid itemId,
             [FromQuery] string? container,
             [FromQuery] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,

+ 4 - 4
Jellyfin.Api/Controllers/VideosController.cs

@@ -116,7 +116,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{itemId}/AdditionalParts")]
         [HttpGet("{itemId}/AdditionalParts")]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [Authorize(Policy = Policies.DefaultAuthorization)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [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)
             var user = userId.HasValue && !userId.Equals(Guid.Empty)
                 ? _userManager.GetUserById(userId.Value)
                 ? _userManager.GetUserById(userId.Value)
@@ -163,7 +163,7 @@ namespace Jellyfin.Api.Controllers
         [Authorize(Policy = Policies.RequiresElevation)]
         [Authorize(Policy = Policies.RequiresElevation)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var video = (Video)_libraryManager.GetItemById(itemId);
 
 
@@ -333,8 +333,8 @@ namespace Jellyfin.Api.Controllers
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesVideoFile]
         [ProducesVideoFile]
         public async Task<ActionResult> GetVideoStream(
         public async Task<ActionResult> GetVideoStream(
-            [FromRoute] Guid itemId,
-            [FromRoute] string? container,
+            [FromRoute, Required] Guid itemId,
+            [FromRoute, Required] string? container,
             [FromQuery] bool? @static,
             [FromQuery] bool? @static,
             [FromQuery] string? @params,
             [FromQuery] string? @params,
             [FromQuery] string? tag,
             [FromQuery] string? tag,

+ 2 - 1
Jellyfin.Api/Controllers/YearsController.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
 using Jellyfin.Api.Extensions;
@@ -179,7 +180,7 @@ namespace Jellyfin.Api.Controllers
         [HttpGet("{year}")]
         [HttpGet("{year}")]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status200OK)]
         [ProducesResponseType(StatusCodes.Status404NotFound)]
         [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);
             var item = _libraryManager.GetYear(year);
             if (item == null)
             if (item == null)

+ 1 - 1
Jellyfin.Api/Helpers/FileStreamResponseHelpers.cs

@@ -72,7 +72,7 @@ namespace Jellyfin.Api.Helpers
                 return new NoContentResult();
                 return new NoContentResult();
             }
             }
 
 
-            return new PhysicalFileResult(path, contentType);
+            return new PhysicalFileResult(path, contentType) { EnableRangeProcessing = true };
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
Jellyfin.Api/Helpers/StreamingHelpers.cs

@@ -1,4 +1,4 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
@@ -92,6 +92,7 @@ namespace Jellyfin.Api.Helpers
             }
             }
 
 
             var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
             var enableDlnaHeaders = !string.IsNullOrWhiteSpace(streamingRequest.Params) ||
+                                    streamingRequest.StreamOptions.ContainsKey("dlnaheaders") ||
                                     string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
                                     string.Equals(httpRequest.Headers["GetContentFeatures.DLNA.ORG"], "1", StringComparison.OrdinalIgnoreCase);
 
 
             var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)
             var state = new StreamState(mediaSourceManager, transcodingJobType, transcodingJobHelper)

+ 31 - 47
Jellyfin.Data/Entities/ActivityLog.cs

@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
@@ -11,7 +9,7 @@ namespace Jellyfin.Data.Entities
     /// <summary>
     /// <summary>
     /// An entity referencing an activity log entry.
     /// An entity referencing an activity log entry.
     /// </summary>
     /// </summary>
-    public partial class ActivityLog : IHasConcurrencyToken
+    public class ActivityLog : IHasConcurrencyToken
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ActivityLog"/> class.
         /// Initializes a new instance of the <see cref="ActivityLog"/> class.
@@ -32,13 +30,11 @@ namespace Jellyfin.Data.Entities
                 throw new ArgumentNullException(nameof(type));
                 throw new ArgumentNullException(nameof(type));
             }
             }
 
 
-            this.Name = name;
-            this.Type = type;
-            this.UserId = userId;
-            this.DateCreated = DateTime.UtcNow;
-            this.LogSeverity = LogLevel.Trace;
-
-            Init();
+            Name = name;
+            Type = type;
+            UserId = userId;
+            DateCreated = DateTime.UtcNow;
+            LogSeverity = LogLevel.Trace;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -47,38 +43,21 @@ namespace Jellyfin.Data.Entities
         /// </summary>
         /// </summary>
         protected ActivityLog()
         protected ActivityLog()
         {
         {
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="name">The name.</param>
-        /// <param name="type">The type.</param>
-        /// <param name="userId">The user's id.</param>
-        /// <returns>The new <see cref="ActivityLog"/> instance.</returns>
-        public static ActivityLog Create(string name, string type, Guid userId)
-        {
-            return new ActivityLog(name, type, userId);
         }
         }
 
 
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the identity of this instance.
         /// Gets or sets the identity of this instance.
         /// This is the key in the backing database.
         /// This is the key in the backing database.
         /// </summary>
         /// </summary>
-        [Key]
-        [Required]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         public int Id { get; protected set; }
         public int Id { get; protected set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
-        /// Required, Max length = 512.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Required, Max length = 512.
+        /// </remarks>
         [Required]
         [Required]
         [MaxLength(512)]
         [MaxLength(512)]
         [StringLength(512)]
         [StringLength(512)]
@@ -86,24 +65,30 @@ namespace Jellyfin.Data.Entities
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the overview.
         /// Gets or sets the overview.
-        /// Max length = 512.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Max length = 512.
+        /// </remarks>
         [MaxLength(512)]
         [MaxLength(512)]
         [StringLength(512)]
         [StringLength(512)]
         public string Overview { get; set; }
         public string Overview { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the short overview.
         /// Gets or sets the short overview.
-        /// Max length = 512.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Max length = 512.
+        /// </remarks>
         [MaxLength(512)]
         [MaxLength(512)]
         [StringLength(512)]
         [StringLength(512)]
         public string ShortOverview { get; set; }
         public string ShortOverview { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the type.
         /// Gets or sets the type.
-        /// Required, Max length = 256.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Required, Max length = 256.
+        /// </remarks>
         [Required]
         [Required]
         [MaxLength(256)]
         [MaxLength(256)]
         [StringLength(256)]
         [StringLength(256)]
@@ -111,43 +96,42 @@ namespace Jellyfin.Data.Entities
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the user id.
         /// Gets or sets the user id.
-        /// Required.
         /// </summary>
         /// </summary>
-        [Required]
+        /// <remarks>
+        /// Required.
+        /// </remarks>
         public Guid UserId { get; set; }
         public Guid UserId { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the item id.
         /// Gets or sets the item id.
-        /// Max length = 256.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Max length = 256.
+        /// </remarks>
         [MaxLength(256)]
         [MaxLength(256)]
         [StringLength(256)]
         [StringLength(256)]
         public string ItemId { get; set; }
         public string ItemId { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the date created. This should be in UTC.
         /// Gets or sets the date created. This should be in UTC.
-        /// Required.
         /// </summary>
         /// </summary>
-        [Required]
+        /// <remarks>
+        /// Required.
+        /// </remarks>
         public DateTime DateCreated { get; set; }
         public DateTime DateCreated { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
         /// Gets or sets the log severity. Default is <see cref="LogLevel.Trace"/>.
-        /// Required.
         /// </summary>
         /// </summary>
-        [Required]
+        /// <remarks>
+        /// Required.
+        /// </remarks>
         public LogLevel LogSeverity { get; set; }
         public LogLevel LogSeverity { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the row version.
-        /// Required, ConcurrencyToken.
-        /// </summary>
+        /// <inheritdoc />
         [ConcurrencyCheck]
         [ConcurrencyCheck]
-        [Required]
         public uint RowVersion { get; set; }
         public uint RowVersion { get; set; }
 
 
-        partial void Init();
-
         /// <inheritdoc />
         /// <inheritdoc />
         public void OnSavingChanges()
         public void OnSavingChanges()
         {
         {

+ 3 - 1
Jellyfin.Data/Entities/DisplayPreferences.cs

@@ -1,4 +1,6 @@
-using System;
+#pragma warning disable CA2227
+
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;

+ 16 - 45
Jellyfin.Data/Entities/Group.cs

@@ -1,9 +1,8 @@
-#pragma warning disable CS1591
+#pragma warning disable CA2227
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
 using System.Linq;
 using System.Linq;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;
@@ -13,11 +12,10 @@ namespace Jellyfin.Data.Entities
     /// <summary>
     /// <summary>
     /// An entity representing a group.
     /// An entity representing a group.
     /// </summary>
     /// </summary>
-    public partial class Group : IHasPermissions, IHasConcurrencyToken
+    public class Group : IHasPermissions, IHasConcurrencyToken
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Group"/> class.
         /// Initializes a new instance of the <see cref="Group"/> class.
-        /// Public constructor with required data.
         /// </summary>
         /// </summary>
         /// <param name="name">The name of the group.</param>
         /// <param name="name">The name of the group.</param>
         public Group(string name)
         public Group(string name)
@@ -31,33 +29,25 @@ namespace Jellyfin.Data.Entities
             Id = Guid.NewGuid();
             Id = Guid.NewGuid();
 
 
             Permissions = new HashSet<Permission>();
             Permissions = new HashSet<Permission>();
-            ProviderMappings = new HashSet<ProviderMapping>();
             Preferences = new HashSet<Preference>();
             Preferences = new HashSet<Preference>();
-
-            Init();
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Group"/> class.
         /// Initializes a new instance of the <see cref="Group"/> class.
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
         /// </summary>
         /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
         protected Group()
         protected Group()
         {
         {
-            Init();
         }
         }
 
 
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the id of this group.
         /// Gets or sets the id of this group.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Identity, Indexed, Required.
         /// Identity, Indexed, Required.
         /// </remarks>
         /// </remarks>
-        [Key]
-        [Required]
         public Guid Id { get; protected set; }
         public Guid Id { get; protected set; }
 
 
         /// <summary>
         /// <summary>
@@ -71,42 +61,19 @@ namespace Jellyfin.Data.Entities
         [StringLength(255)]
         [StringLength(255)]
         public string Name { get; set; }
         public string Name { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the row version.
-        /// </summary>
-        /// <remarks>
-        /// Required, Concurrency Token.
-        /// </remarks>
+        /// <inheritdoc />
         [ConcurrencyCheck]
         [ConcurrencyCheck]
-        [Required]
         public uint RowVersion { get; set; }
         public uint RowVersion { get; set; }
 
 
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-
-        [ForeignKey("Permission_GroupPermissions_Id")]
+        /// <summary>
+        /// Gets or sets a collection containing the group's permissions.
+        /// </summary>
         public virtual ICollection<Permission> Permissions { get; protected set; }
         public virtual ICollection<Permission> Permissions { get; protected set; }
 
 
-        [ForeignKey("ProviderMapping_ProviderMappings_Id")]
-        public virtual ICollection<ProviderMapping> ProviderMappings { get; protected set; }
-
-        [ForeignKey("Preference_Preferences_Id")]
-        public virtual ICollection<Preference> Preferences { get; protected set; }
-
         /// <summary>
         /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
+        /// Gets or sets a collection containing the group's preferences.
         /// </summary>
         /// </summary>
-        /// <param name="name">The name of this group.</param>
-        public static Group Create(string name)
-        {
-            return new Group(name);
-        }
+        public virtual ICollection<Preference> Preferences { get; protected set; }
 
 
         /// <inheritdoc/>
         /// <inheritdoc/>
         public bool HasPermission(PermissionKind kind)
         public bool HasPermission(PermissionKind kind)
@@ -120,6 +87,10 @@ namespace Jellyfin.Data.Entities
             Permissions.First(p => p.Kind == kind).Value = value;
             Permissions.First(p => p.Kind == kind).Value = value;
         }
         }
 
 
-        partial void Init();
+        /// <inheritdoc />
+        public void OnSavingChanges()
+        {
+            RowVersion++;
+        }
     }
     }
 }
 }

+ 39 - 6
Jellyfin.Data/Entities/ImageInfo.cs

@@ -1,32 +1,65 @@
-#pragma warning disable CS1591
-
-using System;
+using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
 
 
 namespace Jellyfin.Data.Entities
 namespace Jellyfin.Data.Entities
 {
 {
+    /// <summary>
+    /// An entity representing an image.
+    /// </summary>
     public class ImageInfo
     public class ImageInfo
     {
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImageInfo"/> class.
+        /// </summary>
+        /// <param name="path">The path.</param>
         public ImageInfo(string path)
         public ImageInfo(string path)
         {
         {
             Path = path;
             Path = path;
             LastModified = DateTime.UtcNow;
             LastModified = DateTime.UtcNow;
         }
         }
 
 
-        [Key]
-        [Required]
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImageInfo"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Default constructor. Protected due to required properties, but present because EF needs it.
+        /// </remarks>
+        protected ImageInfo()
+        {
+        }
+
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <remarks>
+        /// Identity, Indexed, Required.
+        /// </remarks>
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         public int Id { get; protected set; }
         public int Id { get; protected set; }
 
 
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
         public Guid? UserId { get; protected set; }
         public Guid? UserId { get; protected set; }
 
 
+        /// <summary>
+        /// Gets or sets the path of the image.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
         [Required]
         [Required]
         [MaxLength(512)]
         [MaxLength(512)]
         [StringLength(512)]
         [StringLength(512)]
         public string Path { get; set; }
         public string Path { get; set; }
 
 
-        [Required]
+        /// <summary>
+        /// Gets or sets the date last modified.
+        /// </summary>
+        /// <remarks>
+        /// Required.
+        /// </remarks>
         public DateTime LastModified { get; set; }
         public DateTime LastModified { get; set; }
     }
     }
 }
 }

+ 4 - 3
Jellyfin.Data/Entities/ItemDisplayPreferences.cs

@@ -1,12 +1,13 @@
-#pragma warning disable CS1591
-
-using System;
+using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
 
 
 namespace Jellyfin.Data.Entities
 namespace Jellyfin.Data.Entities
 {
 {
+    /// <summary>
+    /// An entity that represents a user's display preferences for a specific item.
+    /// </summary>
     public class ItemDisplayPreferences
     public class ItemDisplayPreferences
     {
     {
         /// <summary>
         /// <summary>

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Artwork.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Book.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;
 
 

+ 3 - 1
Jellyfin.Data/Entities/Libraries/BookMetadata.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
@@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity containing metadata for a book.
     /// An entity containing metadata for a book.
     /// </summary>
     /// </summary>
-    public class BookMetadata : Metadata, IHasCompanies
+    public class BookMetadata : ItemMetadata, IHasCompanies
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BookMetadata"/> class.
         /// Initializes a new instance of the <see cref="BookMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Chapter.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Collection.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;

+ 2 - 2
Jellyfin.Data/Entities/Libraries/CollectionItem.cs

@@ -73,7 +73,7 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Gets or sets the next item in the collection.
         /// Gets or sets the next item in the collection.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
-        /// TODO check if this properly updated dependant and has the proper principal relationship
+        /// TODO check if this properly updated dependant and has the proper principal relationship.
         /// </remarks>
         /// </remarks>
         public virtual CollectionItem Next { get; set; }
         public virtual CollectionItem Next { get; set; }
 
 
@@ -81,7 +81,7 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Gets or sets the previous item in the collection.
         /// Gets or sets the previous item in the collection.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
-        /// TODO check if this properly updated dependant and has the proper principal relationship
+        /// TODO check if this properly updated dependant and has the proper principal relationship.
         /// </remarks>
         /// </remarks>
         public virtual CollectionItem Previous { get; set; }
         public virtual CollectionItem Previous { get; set; }
 
 

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Company.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;

+ 1 - 1
Jellyfin.Data/Entities/Libraries/CompanyMetadata.cs

@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity holding metadata for a <see cref="Company"/>.
     /// An entity holding metadata for a <see cref="Company"/>.
     /// </summary>
     /// </summary>
-    public class CompanyMetadata : Metadata
+    public class CompanyMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="CompanyMetadata"/> class.
         /// Initializes a new instance of the <see cref="CompanyMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/CustomItem.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;
 
 

+ 1 - 1
Jellyfin.Data/Entities/Libraries/CustomItemMetadata.cs

@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity containing metadata for a custom item.
     /// An entity containing metadata for a custom item.
     /// </summary>
     /// </summary>
-    public class CustomItemMetadata : Metadata
+    public class CustomItemMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.
         /// Initializes a new instance of the <see cref="CustomItemMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Episode.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;

+ 1 - 1
Jellyfin.Data/Entities/Libraries/EpisodeMetadata.cs

@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity containing metadata for an <see cref="Episode"/>.
     /// An entity containing metadata for an <see cref="Episode"/>.
     /// </summary>
     /// </summary>
-    public class EpisodeMetadata : Metadata
+    public class EpisodeMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.
         /// Initializes a new instance of the <see cref="EpisodeMetadata"/> class.

+ 5 - 5
Jellyfin.Data/Entities/Libraries/Genre.cs

@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Initializes a new instance of the <see cref="Genre"/> class.
         /// Initializes a new instance of the <see cref="Genre"/> class.
         /// </summary>
         /// </summary>
         /// <param name="name">The name.</param>
         /// <param name="name">The name.</param>
-        /// <param name="metadata">The metadata.</param>
-        public Genre(string name, Metadata metadata)
+        /// <param name="itemMetadata">The metadata.</param>
+        public Genre(string name, ItemMetadata itemMetadata)
         {
         {
             if (string.IsNullOrEmpty(name))
             if (string.IsNullOrEmpty(name))
             {
             {
@@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
 
 
             Name = name;
             Name = name;
 
 
-            if (metadata == null)
+            if (itemMetadata == null)
             {
             {
-                throw new ArgumentNullException(nameof(metadata));
+                throw new ArgumentNullException(nameof(itemMetadata));
             }
             }
 
 
-            metadata.Genres.Add(this);
+            itemMetadata.Genres.Add(this);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 7 - 5
Jellyfin.Data/Entities/Libraries/Metadata.cs → Jellyfin.Data/Entities/Libraries/ItemMetadata.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
@@ -9,14 +11,14 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An abstract class that holds metadata.
     /// An abstract class that holds metadata.
     /// </summary>
     /// </summary>
-    public abstract class Metadata : IHasArtwork, IHasConcurrencyToken
+    public abstract class ItemMetadata : IHasArtwork, IHasConcurrencyToken
     {
     {
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="Metadata"/> class.
+        /// Initializes a new instance of the <see cref="ItemMetadata"/> class.
         /// </summary>
         /// </summary>
         /// <param name="title">The title or name of the object.</param>
         /// <param name="title">The title or name of the object.</param>
         /// <param name="language">ISO-639-3 3-character language codes.</param>
         /// <param name="language">ISO-639-3 3-character language codes.</param>
-        protected Metadata(string title, string language)
+        protected ItemMetadata(string title, string language)
         {
         {
             if (string.IsNullOrEmpty(title))
             if (string.IsNullOrEmpty(title))
             {
             {
@@ -41,12 +43,12 @@ namespace Jellyfin.Data.Entities.Libraries
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Initializes a new instance of the <see cref="Metadata"/> class.
+        /// Initializes a new instance of the <see cref="ItemMetadata"/> class.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Default constructor. Protected due to being abstract.
         /// Default constructor. Protected due to being abstract.
         /// </remarks>
         /// </remarks>
-        protected Metadata()
+        protected ItemMetadata()
         {
         {
         }
         }
 
 

+ 2 - 0
Jellyfin.Data/Entities/Libraries/MediaFile.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;

+ 5 - 5
Jellyfin.Data/Entities/Libraries/MetadataProviderId.cs

@@ -14,8 +14,8 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
         /// Initializes a new instance of the <see cref="MetadataProviderId"/> class.
         /// </summary>
         /// </summary>
         /// <param name="providerId">The provider id.</param>
         /// <param name="providerId">The provider id.</param>
-        /// <param name="metadata">The metadata entity.</param>
-        public MetadataProviderId(string providerId, Metadata metadata)
+        /// <param name="itemMetadata">The metadata entity.</param>
+        public MetadataProviderId(string providerId, ItemMetadata itemMetadata)
         {
         {
             if (string.IsNullOrEmpty(providerId))
             if (string.IsNullOrEmpty(providerId))
             {
             {
@@ -24,12 +24,12 @@ namespace Jellyfin.Data.Entities.Libraries
 
 
             ProviderId = providerId;
             ProviderId = providerId;
 
 
-            if (metadata == null)
+            if (itemMetadata == null)
             {
             {
-                throw new ArgumentNullException(nameof(metadata));
+                throw new ArgumentNullException(nameof(itemMetadata));
             }
             }
 
 
-            metadata.Sources.Add(this);
+            itemMetadata.Sources.Add(this);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Movie.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;
 
 

+ 3 - 1
Jellyfin.Data/Entities/Libraries/MovieMetadata.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
@@ -8,7 +10,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity holding the metadata for a movie.
     /// An entity holding the metadata for a movie.
     /// </summary>
     /// </summary>
-    public class MovieMetadata : Metadata, IHasCompanies
+    public class MovieMetadata : ItemMetadata, IHasCompanies
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="MovieMetadata"/> class.
         /// Initializes a new instance of the <see cref="MovieMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/MusicAlbum.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 
 namespace Jellyfin.Data.Entities.Libraries
 namespace Jellyfin.Data.Entities.Libraries

+ 3 - 1
Jellyfin.Data/Entities/Libraries/MusicAlbumMetadata.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 
 
@@ -6,7 +8,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity holding the metadata for a music album.
     /// An entity holding the metadata for a music album.
     /// </summary>
     /// </summary>
-    public class MusicAlbumMetadata : Metadata
+    public class MusicAlbumMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.
         /// Initializes a new instance of the <see cref="MusicAlbumMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Person.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;

+ 7 - 5
Jellyfin.Data/Entities/Libraries/PersonRole.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
@@ -16,17 +18,17 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Initializes a new instance of the <see cref="PersonRole"/> class.
         /// Initializes a new instance of the <see cref="PersonRole"/> class.
         /// </summary>
         /// </summary>
         /// <param name="type">The role type.</param>
         /// <param name="type">The role type.</param>
-        /// <param name="metadata">The metadata.</param>
-        public PersonRole(PersonRoleType type, Metadata metadata)
+        /// <param name="itemMetadata">The metadata.</param>
+        public PersonRole(PersonRoleType type, ItemMetadata itemMetadata)
         {
         {
             Type = type;
             Type = type;
 
 
-            if (metadata == null)
+            if (itemMetadata == null)
             {
             {
-                throw new ArgumentNullException(nameof(metadata));
+                throw new ArgumentNullException(nameof(itemMetadata));
             }
             }
 
 
-            metadata.PersonRoles.Add(this);
+            itemMetadata.PersonRoles.Add(this);
 
 
             Sources = new HashSet<MetadataProviderId>();
             Sources = new HashSet<MetadataProviderId>();
         }
         }

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Photo.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;
 
 

+ 1 - 1
Jellyfin.Data/Entities/Libraries/PhotoMetadata.cs

@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity that holds metadata for a photo.
     /// An entity that holds metadata for a photo.
     /// </summary>
     /// </summary>
-    public class PhotoMetadata : Metadata
+    public class PhotoMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="PhotoMetadata"/> class.
         /// Initializes a new instance of the <see cref="PhotoMetadata"/> class.

+ 5 - 5
Jellyfin.Data/Entities/Libraries/Rating.cs

@@ -14,17 +14,17 @@ namespace Jellyfin.Data.Entities.Libraries
         /// Initializes a new instance of the <see cref="Rating"/> class.
         /// Initializes a new instance of the <see cref="Rating"/> class.
         /// </summary>
         /// </summary>
         /// <param name="value">The value.</param>
         /// <param name="value">The value.</param>
-        /// <param name="metadata">The metadata.</param>
-        public Rating(double value, Metadata metadata)
+        /// <param name="itemMetadata">The metadata.</param>
+        public Rating(double value, ItemMetadata itemMetadata)
         {
         {
             Value = value;
             Value = value;
 
 
-            if (metadata == null)
+            if (itemMetadata == null)
             {
             {
-                throw new ArgumentNullException(nameof(metadata));
+                throw new ArgumentNullException(nameof(itemMetadata));
             }
             }
 
 
-            metadata.Ratings.Add(this);
+            itemMetadata.Ratings.Add(this);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Release.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Season.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 

+ 1 - 1
Jellyfin.Data/Entities/Libraries/SeasonMetadata.cs

@@ -6,7 +6,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity that holds metadata for seasons.
     /// An entity that holds metadata for seasons.
     /// </summary>
     /// </summary>
-    public class SeasonMetadata : Metadata
+    public class SeasonMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SeasonMetadata"/> class.
         /// Initializes a new instance of the <see cref="SeasonMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Series.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 
 

+ 3 - 1
Jellyfin.Data/Entities/Libraries/SeriesMetadata.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
@@ -9,7 +11,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity representing series metadata.
     /// An entity representing series metadata.
     /// </summary>
     /// </summary>
-    public class SeriesMetadata : Metadata, IHasCompanies
+    public class SeriesMetadata : ItemMetadata, IHasCompanies
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SeriesMetadata"/> class.
         /// Initializes a new instance of the <see cref="SeriesMetadata"/> class.

+ 2 - 0
Jellyfin.Data/Entities/Libraries/Track.cs

@@ -1,3 +1,5 @@
+#pragma warning disable CA2227
+
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using Jellyfin.Data.Interfaces;
 using Jellyfin.Data.Interfaces;

+ 1 - 1
Jellyfin.Data/Entities/Libraries/TrackMetadata.cs

@@ -5,7 +5,7 @@ namespace Jellyfin.Data.Entities.Libraries
     /// <summary>
     /// <summary>
     /// An entity holding metadata for a track.
     /// An entity holding metadata for a track.
     /// </summary>
     /// </summary>
-    public class TrackMetadata : Metadata
+    public class TrackMetadata : ItemMetadata
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="TrackMetadata"/> class.
         /// Initializes a new instance of the <see cref="TrackMetadata"/> class.

+ 2 - 34
Jellyfin.Data/Entities/Permission.cs

@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations;
 using System.ComponentModel.DataAnnotations.Schema;
 using System.ComponentModel.DataAnnotations.Schema;
 using Jellyfin.Data.Enums;
 using Jellyfin.Data.Enums;
@@ -10,7 +8,7 @@ namespace Jellyfin.Data.Entities
     /// <summary>
     /// <summary>
     /// An entity representing whether the associated user has a specific permission.
     /// An entity representing whether the associated user has a specific permission.
     /// </summary>
     /// </summary>
-    public partial class Permission : IHasConcurrencyToken
+    public class Permission : IHasConcurrencyToken
     {
     {
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="Permission"/> class.
         /// Initializes a new instance of the <see cref="Permission"/> class.
@@ -22,8 +20,6 @@ namespace Jellyfin.Data.Entities
         {
         {
             Kind = kind;
             Kind = kind;
             Value = value;
             Value = value;
-
-            Init();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -32,21 +28,14 @@ namespace Jellyfin.Data.Entities
         /// </summary>
         /// </summary>
         protected Permission()
         protected Permission()
         {
         {
-            Init();
         }
         }
 
 
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the id of this permission.
         /// Gets or sets the id of this permission.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Identity, Indexed, Required.
         /// Identity, Indexed, Required.
         /// </remarks>
         /// </remarks>
-        [Key]
-        [Required]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         public int Id { get; protected set; }
         public int Id { get; protected set; }
 
 
@@ -56,7 +45,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public PermissionKind Kind { get; protected set; }
         public PermissionKind Kind { get; protected set; }
 
 
         /// <summary>
         /// <summary>
@@ -65,36 +53,16 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool Value { get; set; }
         public bool Value { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the row version.
-        /// </summary>
-        /// <remarks>
-        /// Required, ConcurrencyToken.
-        /// </remarks>
+        /// <inheritdoc />
         [ConcurrencyCheck]
         [ConcurrencyCheck]
-        [Required]
         public uint RowVersion { get; set; }
         public uint RowVersion { get; set; }
 
 
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="kind">The permission kind.</param>
-        /// <param name="value">The value of this permission.</param>
-        /// <returns>The newly created instance.</returns>
-        public static Permission Create(PermissionKind kind, bool value)
-        {
-            return new Permission(kind, value);
-        }
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void OnSavingChanges()
         public void OnSavingChanges()
         {
         {
             RowVersion++;
             RowVersion++;
         }
         }
-
-        partial void Init();
     }
     }
 }
 }

+ 1 - 25
Jellyfin.Data/Entities/Preference.cs

@@ -31,18 +31,12 @@ namespace Jellyfin.Data.Entities
         {
         {
         }
         }
 
 
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the id of this preference.
         /// Gets or sets the id of this preference.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Identity, Indexed, Required.
         /// Identity, Indexed, Required.
         /// </remarks>
         /// </remarks>
-        [Key]
-        [Required]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
         public int Id { get; protected set; }
         public int Id { get; protected set; }
 
 
@@ -52,7 +46,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public PreferenceKind Kind { get; protected set; }
         public PreferenceKind Kind { get; protected set; }
 
 
         /// <summary>
         /// <summary>
@@ -66,27 +59,10 @@ namespace Jellyfin.Data.Entities
         [StringLength(65535)]
         [StringLength(65535)]
         public string Value { get; set; }
         public string Value { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the row version.
-        /// </summary>
-        /// <remarks>
-        /// Required, ConcurrencyToken.
-        /// </remarks>
+        /// <inheritdoc/>
         [ConcurrencyCheck]
         [ConcurrencyCheck]
-        [Required]
         public uint RowVersion { get; set; }
         public uint RowVersion { get; set; }
 
 
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="kind">The preference kind.</param>
-        /// <param name="value">The value.</param>
-        /// <returns>The new instance.</returns>
-        public static Preference Create(PreferenceKind kind, string value)
-        {
-            return new Preference(kind, value);
-        }
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void OnSavingChanges()
         public void OnSavingChanges()
         {
         {

+ 0 - 129
Jellyfin.Data/Entities/ProviderMapping.cs

@@ -1,129 +0,0 @@
-#pragma warning disable CS1591
-
-using System;
-using System.ComponentModel.DataAnnotations;
-using System.ComponentModel.DataAnnotations.Schema;
-
-namespace Jellyfin.Data.Entities
-{
-    public partial class ProviderMapping
-    {
-        partial void Init();
-
-        /// <summary>
-        /// Default constructor. Protected due to required properties, but present because EF needs it.
-        /// </summary>
-        protected ProviderMapping()
-        {
-            Init();
-        }
-
-        /// <summary>
-        /// Replaces default constructor, since it's protected. Caller assumes responsibility for setting all required values before saving.
-        /// </summary>
-        public static ProviderMapping CreateProviderMappingUnsafe()
-        {
-            return new ProviderMapping();
-        }
-
-        /// <summary>
-        /// Public constructor with required data.
-        /// </summary>
-        /// <param name="providername"></param>
-        /// <param name="providersecrets"></param>
-        /// <param name="providerdata"></param>
-        /// <param name="_user0"></param>
-        /// <param name="_group1"></param>
-        public ProviderMapping(string providername, string providersecrets, string providerdata, User _user0, Group _group1)
-        {
-            if (string.IsNullOrEmpty(providername))
-            {
-                throw new ArgumentNullException(nameof(providername));
-            }
-
-            this.ProviderName = providername;
-
-            if (string.IsNullOrEmpty(providersecrets))
-            {
-                throw new ArgumentNullException(nameof(providersecrets));
-            }
-
-            this.ProviderSecrets = providersecrets;
-
-            if (string.IsNullOrEmpty(providerdata))
-            {
-                throw new ArgumentNullException(nameof(providerdata));
-            }
-
-            this.ProviderData = providerdata;
-
-            Init();
-        }
-
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="providername"></param>
-        /// <param name="providersecrets"></param>
-        /// <param name="providerdata"></param>
-        /// <param name="_user0"></param>
-        /// <param name="_group1"></param>
-        public static ProviderMapping Create(string providername, string providersecrets, string providerdata, User _user0, Group _group1)
-        {
-            return new ProviderMapping(providername, providersecrets, providerdata, _user0, _group1);
-        }
-
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
-        /// <summary>
-        /// Identity, Indexed, Required.
-        /// </summary>
-        [Key]
-        [Required]
-        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
-        public int Id { get; protected set; }
-
-        /// <summary>
-        /// Required, Max length = 255
-        /// </summary>
-        [Required]
-        [MaxLength(255)]
-        [StringLength(255)]
-        public string ProviderName { get; set; }
-
-        /// <summary>
-        /// Required, Max length = 65535
-        /// </summary>
-        [Required]
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string ProviderSecrets { get; set; }
-
-        /// <summary>
-        /// Required, Max length = 65535
-        /// </summary>
-        [Required]
-        [MaxLength(65535)]
-        [StringLength(65535)]
-        public string ProviderData { get; set; }
-
-        /// <summary>
-        /// Required, ConcurrenyToken.
-        /// </summary>
-        [ConcurrencyCheck]
-        [Required]
-        public uint RowVersion { get; set; }
-
-        public void OnSavingChanges()
-        {
-            RowVersion++;
-        }
-
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-    }
-}
-

+ 5 - 44
Jellyfin.Data/Entities/User.cs

@@ -1,4 +1,4 @@
-#pragma warning disable CS1591
+#pragma warning disable CA2227
 
 
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -15,7 +15,7 @@ namespace Jellyfin.Data.Entities
     /// <summary>
     /// <summary>
     /// An entity representing a user.
     /// An entity representing a user.
     /// </summary>
     /// </summary>
-    public partial class User : IHasPermissions, IHasConcurrencyToken
+    public class User : IHasPermissions, IHasConcurrencyToken
     {
     {
         /// <summary>
         /// <summary>
         /// The values being delimited here are Guids, so commas work as they do not appear in Guids.
         /// The values being delimited here are Guids, so commas work as they do not appear in Guids.
@@ -75,7 +75,6 @@ namespace Jellyfin.Data.Entities
 
 
             AddDefaultPermissions();
             AddDefaultPermissions();
             AddDefaultPreferences();
             AddDefaultPreferences();
-            Init();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -84,21 +83,14 @@ namespace Jellyfin.Data.Entities
         /// </summary>
         /// </summary>
         protected User()
         protected User()
         {
         {
-            Init();
         }
         }
 
 
-        /*************************************************************************
-         * Properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the Id of the user.
         /// Gets or sets the Id of the user.
         /// </summary>
         /// </summary>
         /// <remarks>
         /// <remarks>
         /// Identity, Indexed, Required.
         /// Identity, Indexed, Required.
         /// </remarks>
         /// </remarks>
-        [Key]
-        [Required]
         [JsonIgnore]
         [JsonIgnore]
         public Guid Id { get; set; }
         public Guid Id { get; set; }
 
 
@@ -139,7 +131,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool MustUpdatePassword { get; set; }
         public bool MustUpdatePassword { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -180,7 +171,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public int InvalidLoginAttemptCount { get; set; }
         public int InvalidLoginAttemptCount { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -204,7 +194,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public SubtitlePlaybackMode SubtitleMode { get; set; }
         public SubtitlePlaybackMode SubtitleMode { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -213,7 +202,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool PlayDefaultAudioTrack { get; set; }
         public bool PlayDefaultAudioTrack { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -232,7 +220,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool DisplayMissingEpisodes { get; set; }
         public bool DisplayMissingEpisodes { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -241,7 +228,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool DisplayCollectionsView { get; set; }
         public bool DisplayCollectionsView { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -250,7 +236,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool EnableLocalPassword { get; set; }
         public bool EnableLocalPassword { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -259,7 +244,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool HidePlayedInLatest { get; set; }
         public bool HidePlayedInLatest { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -268,7 +252,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool RememberAudioSelections { get; set; }
         public bool RememberAudioSelections { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -277,7 +260,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool RememberSubtitleSelections { get; set; }
         public bool RememberSubtitleSelections { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -286,7 +268,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool EnableNextEpisodeAutoPlay { get; set; }
         public bool EnableNextEpisodeAutoPlay { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -295,7 +276,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool EnableAutoLogin { get; set; }
         public bool EnableAutoLogin { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -304,7 +284,6 @@ namespace Jellyfin.Data.Entities
         /// <remarks>
         /// <remarks>
         /// Required.
         /// Required.
         /// </remarks>
         /// </remarks>
-        [Required]
         public bool EnableUserPreferenceAccess { get; set; }
         public bool EnableUserPreferenceAccess { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -322,7 +301,6 @@ namespace Jellyfin.Data.Entities
         /// This is a temporary stopgap for until the library db is migrated.
         /// This is a temporary stopgap for until the library db is migrated.
         /// This corresponds to the value of the index of this user in the library db.
         /// This corresponds to the value of the index of this user in the library db.
         /// </summary>
         /// </summary>
-        [Required]
         public long InternalId { get; set; }
         public long InternalId { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -340,7 +318,9 @@ namespace Jellyfin.Data.Entities
         [Required]
         [Required]
         public virtual DisplayPreferences DisplayPreferences { get; set; }
         public virtual DisplayPreferences DisplayPreferences { get; set; }
 
 
-        [Required]
+        /// <summary>
+        /// Gets or sets the level of sync play permissions this user has.
+        /// </summary>
         public SyncPlayAccess SyncPlayAccess { get; set; }
         public SyncPlayAccess SyncPlayAccess { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -350,13 +330,8 @@ namespace Jellyfin.Data.Entities
         /// Required, Concurrency Token.
         /// Required, Concurrency Token.
         /// </remarks>
         /// </remarks>
         [ConcurrencyCheck]
         [ConcurrencyCheck]
-        [Required]
         public uint RowVersion { get; set; }
         public uint RowVersion { get; set; }
 
 
-        /*************************************************************************
-         * Navigation properties
-         *************************************************************************/
-
         /// <summary>
         /// <summary>
         /// Gets or sets the list of access schedules this user has.
         /// Gets or sets the list of access schedules this user has.
         /// </summary>
         /// </summary>
@@ -395,18 +370,6 @@ namespace Jellyfin.Data.Entities
         [ForeignKey("Preference_Preferences_Guid")]
         [ForeignKey("Preference_Preferences_Guid")]
         public virtual ICollection<Preference> Preferences { get; protected set; }
         public virtual ICollection<Preference> Preferences { get; protected set; }
 
 
-        /// <summary>
-        /// Static create function (for use in LINQ queries, etc.)
-        /// </summary>
-        /// <param name="username">The username for the created user.</param>
-        /// <param name="authenticationProviderId">The Id of the user's authentication provider.</param>
-        /// <param name="passwordResetProviderId">The Id of the user's password reset provider.</param>
-        /// <returns>The created instance.</returns>
-        public static User Create(string username, string authenticationProviderId, string passwordResetProviderId)
-        {
-            return new User(username, authenticationProviderId, passwordResetProviderId);
-        }
-
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void OnSavingChanges()
         public void OnSavingChanges()
         {
         {
@@ -519,7 +482,5 @@ namespace Jellyfin.Data.Entities
                 Preferences.Add(new Preference(val, string.Empty));
                 Preferences.Add(new Preference(val, string.Empty));
             }
             }
         }
         }
-
-        partial void Init();
     }
     }
 }
 }

+ 27 - 7
Jellyfin.Data/Enums/ArtKind.cs

@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
 namespace Jellyfin.Data.Enums
 namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing types of art.
+    /// </summary>
     public enum ArtKind
     public enum ArtKind
     {
     {
-        Other,
-        Poster,
-        Banner,
-        Thumbnail,
-        Logo
+        /// <summary>
+        /// Another type of art, not covered by the other members.
+        /// </summary>
+        Other = 0,
+
+        /// <summary>
+        /// A poster.
+        /// </summary>
+        Poster = 1,
+
+        /// <summary>
+        /// A banner.
+        /// </summary>
+        Banner = 2,
+
+        /// <summary>
+        /// A thumbnail.
+        /// </summary>
+        Thumbnail = 3,
+
+        /// <summary>
+        /// A logo.
+        /// </summary>
+        Logo = 4
     }
     }
 }
 }

+ 42 - 2
Jellyfin.Data/Enums/DynamicDayOfWeek.cs

@@ -1,18 +1,58 @@
-#pragma warning disable CS1591
-
 namespace Jellyfin.Data.Enums
 namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum that represents a day of the week, weekdays, weekends, or all days.
+    /// </summary>
     public enum DynamicDayOfWeek
     public enum DynamicDayOfWeek
     {
     {
+        /// <summary>
+        /// Sunday.
+        /// </summary>
         Sunday = 0,
         Sunday = 0,
+
+        /// <summary>
+        /// Monday.
+        /// </summary>
         Monday = 1,
         Monday = 1,
+
+        /// <summary>
+        /// Tuesday.
+        /// </summary>
         Tuesday = 2,
         Tuesday = 2,
+
+        /// <summary>
+        /// Wednesday.
+        /// </summary>
         Wednesday = 3,
         Wednesday = 3,
+
+        /// <summary>
+        /// Thursday.
+        /// </summary>
         Thursday = 4,
         Thursday = 4,
+
+        /// <summary>
+        /// Friday.
+        /// </summary>
         Friday = 5,
         Friday = 5,
+
+        /// <summary>
+        /// Saturday.
+        /// </summary>
         Saturday = 6,
         Saturday = 6,
+
+        /// <summary>
+        /// All days of the week.
+        /// </summary>
         Everyday = 7,
         Everyday = 7,
+
+        /// <summary>
+        /// A week day, or Monday-Friday.
+        /// </summary>
         Weekday = 8,
         Weekday = 8,
+
+        /// <summary>
+        /// Saturday and Sunday.
+        /// </summary>
         Weekend = 9
         Weekend = 9
     }
     }
 }
 }

+ 4 - 3
Jellyfin.Data/Enums/IndexingKind.cs

@@ -1,7 +1,8 @@
-#pragma warning disable CS1591
-
-namespace Jellyfin.Data.Enums
+namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing a type of indexing in a user's display preferences.
+    /// </summary>
     public enum IndexingKind
     public enum IndexingKind
     {
     {
         /// <summary>
         /// <summary>

+ 27 - 7
Jellyfin.Data/Enums/MediaFileKind.cs

@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
 namespace Jellyfin.Data.Enums
 namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing the type of media file.
+    /// </summary>
     public enum MediaFileKind
     public enum MediaFileKind
     {
     {
-        Main,
-        Sidecar,
-        AdditionalPart,
-        AlternativeFormat,
-        AdditionalStream
+        /// <summary>
+        /// The main file.
+        /// </summary>
+        Main = 0,
+
+        /// <summary>
+        /// A sidecar file.
+        /// </summary>
+        Sidecar = 1,
+
+        /// <summary>
+        /// An additional part to the main file.
+        /// </summary>
+        AdditionalPart = 2,
+
+        /// <summary>
+        /// An alternative format to the main file.
+        /// </summary>
+        AlternativeFormat = 3,
+
+        /// <summary>
+        /// An additional stream for the main file.
+        /// </summary>
+        AdditionalStream = 4
     }
     }
 }
 }

+ 62 - 14
Jellyfin.Data/Enums/PersonRoleType.cs

@@ -1,20 +1,68 @@
-#pragma warning disable CS1591
-
 namespace Jellyfin.Data.Enums
 namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing a person's role in a specific media item.
+    /// </summary>
     public enum PersonRoleType
     public enum PersonRoleType
     {
     {
-        Other,
-        Director,
-        Artist,
-        OriginalArtist,
-        Actor,
-        VoiceActor,
-        Producer,
-        Remixer,
-        Conductor,
-        Composer,
-        Author,
-        Editor
+        /// <summary>
+        /// Another role, not covered by the other types.
+        /// </summary>
+        Other = 0,
+
+        /// <summary>
+        /// The director of the media.
+        /// </summary>
+        Director = 1,
+
+        /// <summary>
+        /// An artist.
+        /// </summary>
+        Artist = 2,
+
+        /// <summary>
+        /// The original artist.
+        /// </summary>
+        OriginalArtist = 3,
+
+        /// <summary>
+        /// An actor.
+        /// </summary>
+        Actor = 4,
+
+        /// <summary>
+        /// A voice actor.
+        /// </summary>
+        VoiceActor = 5,
+
+        /// <summary>
+        /// A producer.
+        /// </summary>
+        Producer = 6,
+
+        /// <summary>
+        /// A remixer.
+        /// </summary>
+        Remixer = 7,
+
+        /// <summary>
+        /// A conductor.
+        /// </summary>
+        Conductor = 8,
+
+        /// <summary>
+        /// A composer.
+        /// </summary>
+        Composer = 9,
+
+        /// <summary>
+        /// An author.
+        /// </summary>
+        Author = 10,
+
+        /// <summary>
+        /// An editor.
+        /// </summary>
+        Editor = 11
     }
     }
 }
 }

+ 23 - 3
Jellyfin.Data/Enums/SubtitlePlaybackMode.cs

@@ -1,13 +1,33 @@
-#pragma warning disable CS1591
-
-namespace Jellyfin.Data.Enums
+namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing a subtitle playback mode.
+    /// </summary>
     public enum SubtitlePlaybackMode
     public enum SubtitlePlaybackMode
     {
     {
+        /// <summary>
+        /// The default subtitle playback mode.
+        /// </summary>
         Default = 0,
         Default = 0,
+
+        /// <summary>
+        /// Always show subtitles.
+        /// </summary>
         Always = 1,
         Always = 1,
+
+        /// <summary>
+        /// Only show forced subtitles.
+        /// </summary>
         OnlyForced = 2,
         OnlyForced = 2,
+
+        /// <summary>
+        /// Don't show subtitles.
+        /// </summary>
         None = 3,
         None = 3,
+
+        /// <summary>
+        /// Only show subtitles when the current audio stream is in a different language.
+        /// </summary>
         Smart = 4
         Smart = 4
     }
     }
 }
 }

+ 47 - 11
Jellyfin.Data/Enums/UnratedItem.cs

@@ -1,17 +1,53 @@
-#pragma warning disable CS1591
-
 namespace Jellyfin.Data.Enums
 namespace Jellyfin.Data.Enums
 {
 {
+    /// <summary>
+    /// An enum representing an unrated item.
+    /// </summary>
     public enum UnratedItem
     public enum UnratedItem
     {
     {
-        Movie,
-        Trailer,
-        Series,
-        Music,
-        Book,
-        LiveTvChannel,
-        LiveTvProgram,
-        ChannelContent,
-        Other
+        /// <summary>
+        /// A movie.
+        /// </summary>
+        Movie = 0,
+
+        /// <summary>
+        /// A trailer.
+        /// </summary>
+        Trailer = 1,
+
+        /// <summary>
+        /// A series.
+        /// </summary>
+        Series = 2,
+
+        /// <summary>
+        /// Music.
+        /// </summary>
+        Music = 3,
+
+        /// <summary>
+        /// A book.
+        /// </summary>
+        Book = 4,
+
+        /// <summary>
+        /// A live TV channel
+        /// </summary>
+        LiveTvChannel = 5,
+
+        /// <summary>
+        /// A live TV program.
+        /// </summary>
+        LiveTvProgram = 6,
+
+        /// <summary>
+        /// Channel content.
+        /// </summary>
+        ChannelContent = 7,
+
+        /// <summary>
+        /// Another type, not covered by the other fields.
+        /// </summary>
+        Other = 8
     }
     }
 }
 }

+ 5 - 1
Jellyfin.Data/Jellyfin.Data.csproj

@@ -4,7 +4,7 @@
     <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
     <TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
     <GenerateDocumentationFile>true</GenerateDocumentationFile>
-    <TreatWarningsAsErrors Condition=" '$(Configuration)' == 'Release' ">true</TreatWarningsAsErrors>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>
     <PublishRepositoryUrl>true</PublishRepositoryUrl>
     <EmbedUntrackedSources>true</EmbedUntrackedSources>
     <EmbedUntrackedSources>true</EmbedUntrackedSources>
     <IncludeSymbols>true</IncludeSymbols>
     <IncludeSymbols>true</IncludeSymbols>
@@ -45,4 +45,8 @@
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
     <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.7" />
   </ItemGroup>
   </ItemGroup>
 
 
+  <ItemGroup>
+    <Compile Include="..\SharedVersion.cs" />
+  </ItemGroup>
+
 </Project>
 </Project>

+ 4 - 1
Jellyfin.Server/Jellyfin.Server.csproj

@@ -66,10 +66,13 @@
   </ItemGroup>
   </ItemGroup>
 
 
   <ItemGroup>
   <ItemGroup>
+    <None Update="wwwroot\api-docs\redoc\custom.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <None Update="wwwroot\api-docs\swagger\custom.css">
     <None Update="wwwroot\api-docs\swagger\custom.css">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     </None>
-    <None Update="wwwroot\api-docs\redoc\custom.css">
+    <None Update="wwwroot\api-docs\banner-dark.svg">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     </None>
   </ItemGroup>
   </ItemGroup>

+ 34 - 0
Jellyfin.Server/wwwroot/api-docs/banner-dark.svg

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+  - Part of the Jellyfin project (https://jellyfin.media)
+  - 
+  - All copyright belongs to the Jellyfin contributors; a full list can
+  - be found in the file CONTRIBUTORS.md
+  - 
+  - This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License.
+  - To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/.
+- ***** END LICENSE BLOCK ***** -->
+<svg id="banner-dark" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1536 512">
+    <defs>
+        <linearGradient id="linear-gradient" x1="110.25" y1="213.3" x2="496.14" y2="436.09" gradientUnits="userSpaceOnUse">
+            <stop offset="0" stop-color="#aa5cc3"/>
+            <stop offset="1" stop-color="#00a4dc"/>
+        </linearGradient>
+    </defs>
+    <title>banner-dark</title>
+    <g id="banner-dark">
+        <g id="banner-dark-icon">
+            <path id="inner-shape" d="M261.42,201.62c-20.44,0-86.24,119.29-76.2,139.43s142.48,19.92,152.4,0S281.86,201.63,261.42,201.62Z" fill="url(#linear-gradient)"/>
+            <path id="outer-shape" d="M261.42,23.3C199.83,23.3,1.57,382.73,31.8,443.43s429.34,60,459.24,0S323,23.3,261.42,23.3ZM411.9,390.76c-19.59,39.33-281.08,39.77-300.9,0S221.1,115.48,261.45,115.48,431.49,351.42,411.9,390.76Z" fill="url(#linear-gradient)"/>
+        </g>
+        <g id="jellyfin-light-outlines" style="isolation:isolate" transform="translate(43.8)">
+            <path d="M556.64,350.75a67,67,0,0,1-22.87-27.47,8.91,8.91,0,0,1-1.49-4.75,7.42,7.42,0,0,1,2.83-5.94,9.25,9.25,0,0,1,6.09-2.38c3.16,0,5.94,1.69,8.31,5.05a48.09,48.09,0,0,0,16.34,20.34,40.59,40.59,0,0,0,24,7.58q20.51,0,33.27-12.62t12.77-33.12V159a8.44,8.44,0,0,1,2.67-6.39,9.56,9.56,0,0,1,6.83-2.52,9,9,0,0,1,6.68,2.52,8.7,8.7,0,0,1,2.53,6.39v138.4a64.7,64.7,0,0,1-8.32,32.67,59,59,0,0,1-23,22.72Q608.62,361,589.9,361A57.21,57.21,0,0,1,556.64,350.75Z" fill="#fff"/>
+            <path d="M831.66,279.47a8.77,8.77,0,0,1-6.24,2.53H713.16q0,17.82,7.27,31.92a54.91,54.91,0,0,0,20.79,22.28q13.51,8.18,31.93,8.17a54,54,0,0,0,25.54-5.94,52.7,52.7,0,0,0,18.12-15.15,10,10,0,0,1,6.24-2.67,8.14,8.14,0,0,1,7.72,7.72,8.81,8.81,0,0,1-3,6.24,74.7,74.7,0,0,1-23.91,19A65.56,65.56,0,0,1,773.45,361q-22.87,0-40.4-9.8a69.51,69.51,0,0,1-27.32-27.48q-9.79-17.66-9.8-40.83,0-24.36,9.65-42.62t25.69-27.92a65.2,65.2,0,0,1,34.16-9.65A70,70,0,0,1,798.84,211a65.78,65.78,0,0,1,25.39,24.36q9.81,16,10.1,38A8.07,8.07,0,0,1,831.66,279.47ZM733.5,231.8Q718.8,243.68,714.64,266H815.92v-2.38A46.91,46.91,0,0,0,807,240.27a48.47,48.47,0,0,0-18.56-15.15,54,54,0,0,0-23-5.2Q748.2,219.92,733.5,231.8Z" fill="#fff"/>
+            <path d="M888.24,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,888.24,355.5Z" fill="#fff"/>
+            <path d="M956.55,355.5a8.92,8.92,0,0,1-15.3-6.38v-202a8.91,8.91,0,1,1,17.82,0v202A8.65,8.65,0,0,1,956.55,355.5Z" fill="#fff"/>
+            <path d="M1122.86,206.11a8.7,8.7,0,0,1,2.53,6.39v131q0,23.44-9.21,40.09a61.58,61.58,0,0,1-25.54,25.25q-16.34,8.61-36.83,8.61a96.73,96.73,0,0,1-23.31-2.68,61.72,61.72,0,0,1-18-7.12q-6.24-3.87-6.24-8.62a17.94,17.94,0,0,1,.6-3,8.06,8.06,0,0,1,3-4.45,7.49,7.49,0,0,1,4.45-1.49,7.91,7.91,0,0,1,3.56.89q19,10.39,36.24,10.4,24.65,0,39.06-15.44t14.4-42.18V333.38a54.37,54.37,0,0,1-21.38,20,62.55,62.55,0,0,1-30.3,7.58q-25.83,0-39.2-15.45t-13.37-41.87V212.5a8.91,8.91,0,1,1,17.82,0V301q0,21.39,9.36,32.38t29.25,11a48,48,0,0,0,23.32-6.09,49.88,49.88,0,0,0,17.82-16,37.44,37.44,0,0,0,6.68-21.24V212.5a9,9,0,0,1,15.29-6.39Z" fill="#fff"/>
+            <path d="M1210.18,161.41q-5.21,6.24-5.2,17.23v30.59h33.27a8.19,8.19,0,0,1,5.79,2.38,8.26,8.26,0,0,1,0,11.88,8.22,8.22,0,0,1-5.79,2.37H1205V349.12a8.91,8.91,0,1,1-17.82,0V225.86h-21.68a7.83,7.83,0,0,1-5.94-2.52,8.21,8.21,0,0,1-2.37-5.79,8,8,0,0,1,2.37-6.09,8.33,8.33,0,0,1,5.94-2.23h21.68V178.64q0-18.7,10.84-29t29-10.24a46.1,46.1,0,0,1,15.45,2.52q7.13,2.53,7.12,8.17a8.07,8.07,0,0,1-2.37,5.94,7.37,7.37,0,0,1-5.35,2.37,18.81,18.81,0,0,1-6.53-1.48,42,42,0,0,0-10.4-1.78Q1215.37,155.18,1210.18,161.41ZM1276,180.87c-2.19-1.88-3.27-4.61-3.27-8.17v-3q0-5.34,3.41-8.17t9.36-2.82q11.88,0,11.88,11v3c0,3.56-1,6.29-3.12,8.17s-5.1,2.82-9.06,2.82S1278.14,182.75,1276,180.87Zm15.59,174.63a8.92,8.92,0,0,1-15.3-6.38V212.5a8.91,8.91,0,1,1,17.82,0V349.12A8.65,8.65,0,0,1,1291.56,355.5Z" fill="#fff"/>
+            <path d="M1452.53,218.88q12.92,16.2,12.92,42.92v87.32a8.4,8.4,0,0,1-2.67,6.38,8.8,8.8,0,0,1-6.24,2.53,8.64,8.64,0,0,1-8.91-8.91V262.69q0-19.31-9.65-31.33t-29.85-12a53.28,53.28,0,0,0-42.77,21.83,36.24,36.24,0,0,0-7.13,21.53v86.43a8.91,8.91,0,1,1-17.82,0V216.06a8.91,8.91,0,1,1,17.82,0V232.4q8-12.77,23-21.24A61.84,61.84,0,0,1,1412,202.7Q1439.61,202.7,1452.53,218.88Z" fill="#fff"/>
+        </g>
+    </g>
+</svg>

+ 15 - 0
Jellyfin.Server/wwwroot/api-docs/swagger/custom.css

@@ -0,0 +1,15 @@
+/* logo */
+.topbar-wrapper img[alt="Swagger UI"], .topbar-wrapper span {
+    visibility: collapse;
+}
+
+.topbar-wrapper .link:after {
+    content: url(../banner-dark.svg);
+    display: block;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+    max-width: 100%;
+    max-height: 100%;
+    width: 150px;
+}
+/* end logo */

+ 7 - 0
MediaBrowser.Providers/Plugins/TheTvdb/TvdbEpisodeProvider.cs

@@ -154,6 +154,13 @@ namespace MediaBrowser.Providers.Plugins.TheTvdb
                 item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
                 item.IndexNumber = Convert.ToInt32(episode.DvdEpisodeNumber ?? episode.AiredEpisodeNumber);
                 item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
                 item.ParentIndexNumber = episode.DvdSeason ?? episode.AiredSeason;
             }
             }
+            else if (string.Equals(id.SeriesDisplayOrder, "absolute", StringComparison.OrdinalIgnoreCase))
+            {
+                if (episode.AbsoluteNumber.GetValueOrDefault() != 0)
+                {
+                    item.IndexNumber = episode.AbsoluteNumber;
+                }
+            }
             else if (episode.AiredEpisodeNumber.HasValue)
             else if (episode.AiredEpisodeNumber.HasValue)
             {
             {
                 item.IndexNumber = episode.AiredEpisodeNumber;
                 item.IndexNumber = episode.AiredEpisodeNumber;

部分文件因文件數量過多而無法顯示