From 6e5ec99ea10557c141ed8d755e672cef628d35f0 Mon Sep 17 00:00:00 2001 From: Cody Robibero Date: Sun, 3 Mar 2024 13:51:31 -0700 Subject: Move userId in API from route to optional query parameter (#11074) * Move userId in API from route to optional query parameter * Standardize UserViewsController * Move userId to query in ImageController * Move userId to query in ItemsController * Move userId to query in PlaystateController * Move userId to query in SuggestionsController * Move userId from route to query in UserLibraryController * Clean up routes * Move userId to query in UserController * fix bad merge --------- Co-authored-by: Niels van Velzen --- Jellyfin.Api/Controllers/UserLibraryController.cs | 287 +++++++++++++++++++--- 1 file changed, 249 insertions(+), 38 deletions(-) (limited to 'Jellyfin.Api/Controllers/UserLibraryController.cs') diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs index e3bfd4ea9..c19ad33c8 100644 --- a/Jellyfin.Api/Controllers/UserLibraryController.cs +++ b/Jellyfin.Api/Controllers/UserLibraryController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Extensions; +using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Entities; using Jellyfin.Data.Enums; @@ -13,12 +14,10 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Lyrics; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; -using MediaBrowser.Model.Lyrics; using MediaBrowser.Model.Querying; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -39,7 +38,6 @@ public class UserLibraryController : BaseJellyfinApiController private readonly IDtoService _dtoService; private readonly IUserViewManager _userViewManager; private readonly IFileSystem _fileSystem; - private readonly ILyricManager _lyricManager; /// /// Initializes a new instance of the class. @@ -50,15 +48,13 @@ public class UserLibraryController : BaseJellyfinApiController /// Instance of the interface. /// Instance of the interface. /// Instance of the interface. - /// Instance of the interface. public UserLibraryController( IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IDtoService dtoService, IUserViewManager userViewManager, - IFileSystem fileSystem, - ILyricManager lyricManager) + IFileSystem fileSystem) { _userManager = userManager; _userDataRepository = userDataRepository; @@ -66,7 +62,6 @@ public class UserLibraryController : BaseJellyfinApiController _dtoService = dtoService; _userViewManager = userViewManager; _fileSystem = fileSystem; - _lyricManager = lyricManager; } /// @@ -76,11 +71,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Item returned. /// An containing the item. - [HttpGet("Users/{userId}/Items/{itemId}")] + [HttpGet("Items/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public async Task> GetItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -109,17 +107,34 @@ public class UserLibraryController : BaseJellyfinApiController return _dtoService.GetBaseItemDto(item, dtoOptions, user); } + /// + /// Gets an item from a user's library. + /// + /// User id. + /// Item id. + /// Item returned. + /// An containing the item. + [HttpGet("Users/{userId}/Items/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task> GetItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetItem(userId, itemId); + /// /// Gets the root folder from a user's library. /// /// User id. /// Root folder returned. /// An containing the user's root folder. - [HttpGet("Users/{userId}/Items/Root")] + [HttpGet("Items/Root")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult GetRootFolder([FromRoute, Required] Guid userId) + public ActionResult GetRootFolder([FromQuery] Guid? userId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -130,6 +145,20 @@ public class UserLibraryController : BaseJellyfinApiController return _dtoService.GetBaseItemDto(item, dtoOptions, user); } + /// + /// Gets the root folder from a user's library. + /// + /// User id. + /// Root folder returned. + /// An containing the user's root folder. + [HttpGet("Users/{userId}/Items/Root")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult GetRootFolderLegacy( + [FromRoute, Required] Guid userId) + => GetRootFolder(userId); + /// /// Gets intros to play before the main media item plays. /// @@ -137,11 +166,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Intros returned. /// An containing the intros to play. - [HttpGet("Users/{userId}/Items/{itemId}/Intros")] + [HttpGet("Items/{itemId}/Intros")] [ProducesResponseType(StatusCodes.Status200OK)] - public async Task>> GetIntros([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public async Task>> GetIntros( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -170,6 +202,22 @@ public class UserLibraryController : BaseJellyfinApiController return new QueryResult(dtos); } + /// + /// Gets intros to play before the main media item plays. + /// + /// User id. + /// Item id. + /// Intros returned. + /// An containing the intros to play. + [HttpGet("Users/{userId}/Items/{itemId}/Intros")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public Task>> GetIntrosLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetIntros(userId, itemId); + /// /// Marks an item as a favorite. /// @@ -177,11 +225,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Item marked as favorite. /// An containing the . - [HttpPost("Users/{userId}/FavoriteItems/{itemId}")] + [HttpPost("UserFavoriteItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult MarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public ActionResult MarkFavoriteItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -206,6 +257,22 @@ public class UserLibraryController : BaseJellyfinApiController return MarkFavorite(user, item, true); } + /// + /// Marks an item as a favorite. + /// + /// User id. + /// Item id. + /// Item marked as favorite. + /// An containing the . + [HttpPost("Users/{userId}/FavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult MarkFavoriteItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => MarkFavoriteItem(userId, itemId); + /// /// Unmarks item as a favorite. /// @@ -213,11 +280,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Item unmarked as favorite. /// An containing the . - [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")] + [HttpDelete("UserFavoriteItems/{itemId}")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult UnmarkFavoriteItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public ActionResult UnmarkFavoriteItem( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -242,6 +312,22 @@ public class UserLibraryController : BaseJellyfinApiController return MarkFavorite(user, item, false); } + /// + /// Unmarks item as a favorite. + /// + /// User id. + /// Item id. + /// Item unmarked as favorite. + /// An containing the . + [HttpDelete("Users/{userId}/FavoriteItems/{itemId}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult UnmarkFavoriteItemLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => UnmarkFavoriteItem(userId, itemId); + /// /// Deletes a user's saved personal rating for an item. /// @@ -249,11 +335,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Personal rating removed. /// An containing the . - [HttpDelete("Users/{userId}/Items/{itemId}/Rating")] + [HttpDelete("UserItems/{itemId}/Rating")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult DeleteUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public ActionResult DeleteUserItemRating( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -278,6 +367,22 @@ public class UserLibraryController : BaseJellyfinApiController return UpdateUserItemRatingInternal(user, item, null); } + /// + /// Deletes a user's saved personal rating for an item. + /// + /// User id. + /// Item id. + /// Personal rating removed. + /// An containing the . + [HttpDelete("Users/{userId}/Items/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult DeleteUserItemRatingLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => DeleteUserItemRating(userId, itemId); + /// /// Updates a user's rating for an item. /// @@ -286,11 +391,15 @@ public class UserLibraryController : BaseJellyfinApiController /// Whether this is likes. /// Item rating updated. /// An containing the . - [HttpPost("Users/{userId}/Items/{itemId}/Rating")] + [HttpPost("UserItems/{itemId}/Rating")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult UpdateUserItemRating([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId, [FromQuery] bool? likes) + public ActionResult UpdateUserItemRating( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId, + [FromQuery] bool? likes) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -315,6 +424,24 @@ public class UserLibraryController : BaseJellyfinApiController return UpdateUserItemRatingInternal(user, item, likes); } + /// + /// Updates a user's rating for an item. + /// + /// User id. + /// Item id. + /// Whether this is likes. + /// Item rating updated. + /// An containing the . + [HttpPost("Users/{userId}/Items/{itemId}/Rating")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult UpdateUserItemRatingLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId, + [FromQuery] bool? likes) + => UpdateUserItemRating(userId, itemId, likes); + /// /// Gets local trailers for an item. /// @@ -322,11 +449,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// An containing the item's local trailers. /// The items local trailers. - [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")] + [HttpGet("Items/{itemId}/LocalTrailers")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetLocalTrailers([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public ActionResult> GetLocalTrailers( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -360,6 +490,22 @@ public class UserLibraryController : BaseJellyfinApiController .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))); } + /// + /// Gets local trailers for an item. + /// + /// User id. + /// Item id. + /// An containing the item's local trailers. + /// The items local trailers. + [HttpGet("Users/{userId}/Items/{itemId}/LocalTrailers")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult> GetLocalTrailersLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetLocalTrailers(userId, itemId); + /// /// Gets special features for an item. /// @@ -367,11 +513,14 @@ public class UserLibraryController : BaseJellyfinApiController /// Item id. /// Special features returned. /// An containing the special features. - [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")] + [HttpGet("Items/{itemId}/SpecialFeatures")] [ProducesResponseType(StatusCodes.Status200OK)] - public ActionResult> GetSpecialFeatures([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId) + public ActionResult> GetSpecialFeatures( + [FromQuery] Guid? userId, + [FromRoute, Required] Guid itemId) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -401,6 +550,22 @@ public class UserLibraryController : BaseJellyfinApiController .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item))); } + /// + /// Gets special features for an item. + /// + /// User id. + /// Item id. + /// Special features returned. + /// An containing the special features. + [HttpGet("Users/{userId}/Items/{itemId}/SpecialFeatures")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult> GetSpecialFeaturesLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] Guid itemId) + => GetSpecialFeatures(userId, itemId); + /// /// Gets latest media. /// @@ -417,10 +582,10 @@ public class UserLibraryController : BaseJellyfinApiController /// Whether or not to group items into a parent container. /// Latest media returned. /// An containing the latest media. - [HttpGet("Users/{userId}/Items/Latest")] + [HttpGet("Items/Latest")] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetLatestMedia( - [FromRoute, Required] Guid userId, + [FromQuery] Guid? userId, [FromQuery] Guid? parentId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, @@ -432,7 +597,8 @@ public class UserLibraryController : BaseJellyfinApiController [FromQuery] int limit = 20, [FromQuery] bool groupItems = true) { - var user = _userManager.GetUserById(userId); + var requestUserId = RequestHelpers.GetUserId(User, userId); + var user = _userManager.GetUserById(requestUserId); if (user is null) { return NotFound(); @@ -458,7 +624,7 @@ public class UserLibraryController : BaseJellyfinApiController IsPlayed = isPlayed, Limit = limit, ParentId = parentId ?? Guid.Empty, - UserId = userId, + UserId = requestUserId, }, dtoOptions); @@ -483,6 +649,51 @@ public class UserLibraryController : BaseJellyfinApiController return Ok(dtos); } + /// + /// Gets latest media. + /// + /// User id. + /// Specify this to localize the search to a specific item or folder. Omit to use the root. + /// Optional. Specify additional fields of information to return in the output. + /// Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited. + /// Filter by items that are played, or not. + /// Optional. include image information in output. + /// Optional. the max number of images to return, per image type. + /// Optional. The image types to include in the output. + /// Optional. include user data. + /// Return item limit. + /// Whether or not to group items into a parent container. + /// Latest media returned. + /// An containing the latest media. + [HttpGet("Users/{userId}/Items/Latest")] + [ProducesResponseType(StatusCodes.Status200OK)] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + public ActionResult> GetLatestMediaLegacy( + [FromRoute, Required] Guid userId, + [FromQuery] Guid? parentId, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes, + [FromQuery] bool? isPlayed, + [FromQuery] bool? enableImages, + [FromQuery] int? imageTypeLimit, + [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes, + [FromQuery] bool? enableUserData, + [FromQuery] int limit = 20, + [FromQuery] bool groupItems = true) + => GetLatestMedia( + userId, + parentId, + fields, + includeItemTypes, + isPlayed, + enableImages, + imageTypeLimit, + enableImageTypes, + enableUserData, + limit, + groupItems); + private async Task RefreshItemOnDemandIfNeeded(BaseItem item) { if (item is Person) -- cgit v1.2.3