diff options
| author | Cody Robibero <cody@robibe.ro> | 2024-03-03 13:51:31 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-03 13:51:31 -0700 |
| commit | 6e5ec99ea10557c141ed8d755e672cef628d35f0 (patch) | |
| tree | dc3aff5d566811d7f52030c10b25fdb1a02b9b5e /Jellyfin.Api/Controllers/ImageController.cs | |
| parent | 8d40d431e8e5b067a535e564362b902480a13259 (diff) | |
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 <git@ndat.nl>
Diffstat (limited to 'Jellyfin.Api/Controllers/ImageController.cs')
| -rw-r--r-- | Jellyfin.Api/Controllers/ImageController.cs | 335 |
1 files changed, 168 insertions, 167 deletions
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs index 8368b846d..6b38fa7d3 100644 --- a/Jellyfin.Api/Controllers/ImageController.cs +++ b/Jellyfin.Api/Controllers/ImageController.cs @@ -11,7 +11,9 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; +using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; +using Jellyfin.Extensions; using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; @@ -86,31 +88,26 @@ public class ImageController : BaseJellyfinApiController /// Sets the user image. /// </summary> /// <param name="userId">User Id.</param> - /// <param name="imageType">(Unused) Image type.</param> - /// <param name="index">(Unused) Image index.</param> /// <response code="204">Image updated.</response> /// <response code="403">User does not have permission to delete the image.</response> /// <returns>A <see cref="NoContentResult"/>.</returns> - [HttpPost("Users/{userId}/Images/{imageType}")] + [HttpPost("UserImage")] [Authorize] [AcceptsImageFile] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] public async Task<ActionResult> PostUserImage( - [FromRoute, Required] Guid userId, - [FromRoute, Required] ImageType imageType, - [FromQuery] int? index = null) + [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(); } - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) + if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, requestUserId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); } @@ -147,87 +144,69 @@ public class ImageController : BaseJellyfinApiController /// </summary> /// <param name="userId">User Id.</param> /// <param name="imageType">(Unused) Image type.</param> + /// <response code="204">Image updated.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpPost("Users/{userId}/Images/{imageType}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [AcceptsImageFile] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + public Task<ActionResult> PostUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType) + => PostUserImage(userId); + + /// <summary> + /// Sets the user image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> /// <param name="index">(Unused) Image index.</param> /// <response code="204">Image updated.</response> /// <response code="403">User does not have permission to delete the image.</response> /// <returns>A <see cref="NoContentResult"/>.</returns> [HttpPost("Users/{userId}/Images/{imageType}/{index}")] [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] [AcceptsImageFile] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] - public async Task<ActionResult> PostUserImageByIndex( + public Task<ActionResult> PostUserImageByIndexLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromRoute] int index) - { - var user = _userManager.GetUserById(userId); - if (user is null) - { - return NotFound(); - } - - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) - { - return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image."); - } - - if (!TryGetImageExtensionFromContentType(Request.ContentType, out string? extension)) - { - return BadRequest("Incorrect ContentType."); - } - - var stream = GetFromBase64Stream(Request.Body); - await using (stream.ConfigureAwait(false)) - { - // Handle image/png; charset=utf-8 - var mimeType = Request.ContentType?.Split(';').FirstOrDefault(); - var userDataPath = Path.Combine(_serverConfigurationManager.ApplicationPaths.UserConfigurationDirectoryPath, user.Username); - if (user.ProfileImage is not null) - { - await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); - } - - user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension)); - - await _providerManager - .SaveImage(stream, mimeType, user.ProfileImage.Path) - .ConfigureAwait(false); - await _userManager.UpdateUserAsync(user).ConfigureAwait(false); - - return NoContent(); - } - } + => PostUserImage(userId); /// <summary> /// Delete the user's image. /// </summary> /// <param name="userId">User Id.</param> - /// <param name="imageType">(Unused) Image type.</param> - /// <param name="index">(Unused) Image index.</param> /// <response code="204">Image deleted.</response> /// <response code="403">User does not have permission to delete the image.</response> /// <returns>A <see cref="NoContentResult"/>.</returns> - [HttpDelete("Users/{userId}/Images/{imageType}")] + [HttpDelete("UserImage")] [Authorize] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] - [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task<ActionResult> DeleteUserImage( - [FromRoute, Required] Guid userId, - [FromRoute, Required] ImageType imageType, - [FromQuery] int? index = null) + [FromQuery] Guid? userId) { - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) + var requestUserId = RequestHelpers.GetUserId(User, userId); + if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, requestUserId, true)) { return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); } - var user = _userManager.GetUserById(userId); + var user = _userManager.GetUserById(requestUserId); if (user?.ProfileImage is null) { return NoContent(); @@ -255,40 +234,42 @@ public class ImageController : BaseJellyfinApiController /// <response code="204">Image deleted.</response> /// <response code="403">User does not have permission to delete the image.</response> /// <returns>A <see cref="NoContentResult"/>.</returns> + [HttpDelete("Users/{userId}/Images/{imageType}")] + [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] + [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + public Task<ActionResult> DeleteUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromQuery] int? index = null) + => DeleteUserImage(userId); + + /// <summary> + /// Delete the user's image. + /// </summary> + /// <param name="userId">User Id.</param> + /// <param name="imageType">(Unused) Image type.</param> + /// <param name="index">(Unused) Image index.</param> + /// <response code="204">Image deleted.</response> + /// <response code="403">User does not have permission to delete the image.</response> + /// <returns>A <see cref="NoContentResult"/>.</returns> [HttpDelete("Users/{userId}/Images/{imageType}/{index}")] [Authorize] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status403Forbidden)] - public async Task<ActionResult> DeleteUserImageByIndex( + public Task<ActionResult> DeleteUserImageByIndexLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromRoute] int index) - { - if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true)) - { - return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to delete the image."); - } - - var user = _userManager.GetUserById(userId); - if (user?.ProfileImage is null) - { - return NoContent(); - } - - try - { - System.IO.File.Delete(user.ProfileImage.Path); - } - catch (IOException e) - { - _logger.LogError(e, "Error deleting user profile image:"); - } - - await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false); - return NoContent(); - } + => DeleteUserImage(userId); /// <summary> /// Delete an item's image. @@ -541,7 +522,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param> /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> @@ -571,7 +551,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromQuery] string? tag, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] ImageFormat? format, [FromQuery] double? percentPlayed, [FromQuery] int? unplayedCount, @@ -622,7 +601,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="format">Optional. The <see cref="ImageFormat"/> of the returned image.</param> /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> @@ -652,7 +630,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromQuery] string? tag, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] ImageFormat? format, [FromQuery] double? percentPlayed, [FromQuery] int? unplayedCount, @@ -701,7 +678,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> @@ -731,7 +707,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, [FromRoute, Required] string tag, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromRoute, Required] ImageFormat format, [FromRoute, Required] double percentPlayed, [FromRoute, Required] int unplayedCount, @@ -784,7 +759,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -814,7 +788,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, @@ -864,7 +837,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -894,7 +866,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, @@ -945,7 +916,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -975,7 +945,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) @@ -1024,7 +993,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1054,7 +1022,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, @@ -1105,7 +1072,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1135,7 +1101,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) @@ -1184,7 +1149,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1214,7 +1178,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, @@ -1265,7 +1228,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1295,7 +1257,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) @@ -1344,7 +1305,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1374,7 +1334,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, @@ -1425,7 +1384,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1455,7 +1413,6 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) @@ -1492,7 +1449,6 @@ public class ImageController : BaseJellyfinApiController /// Get user profile image. /// </summary> /// <param name="userId">User id.</param> - /// <param name="imageType">Image type.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> /// <param name="maxWidth">The maximum image width to return.</param> @@ -1504,25 +1460,25 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> /// <param name="imageIndex">Image index.</param> /// <response code="200">Image stream returned.</response> + /// <response code="400">User id not provided.</response> /// <response code="404">Item not found.</response> /// <returns> /// A <see cref="FileStreamResult"/> containing the file stream on success, /// or a <see cref="NotFoundResult"/> if item not found. /// </returns> - [HttpGet("Users/{userId}/Images/{imageType}")] - [HttpHead("Users/{userId}/Images/{imageType}", Name = "HeadUserImage")] + [HttpGet("UserImage")] + [HttpHead("UserImage", Name = "HeadUserImage")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] public async Task<ActionResult> GetUserImage( - [FromRoute, Required] Guid userId, - [FromRoute, Required] ImageType imageType, + [FromQuery] Guid? userId, [FromQuery] string? tag, [FromQuery] ImageFormat? format, [FromQuery] int? maxWidth, @@ -1534,13 +1490,18 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer, [FromQuery] int? imageIndex) { - var user = _userManager.GetUserById(userId); + var requestUserId = userId ?? User.GetUserId(); + if (requestUserId.IsEmpty()) + { + return BadRequest("UserId is required if unauthenticated"); + } + + var user = _userManager.GetUserById(requestUserId); if (user?.ProfileImage is null) { return NotFound(); @@ -1565,7 +1526,7 @@ public class ImageController : BaseJellyfinApiController return await GetImageInternal( user.Id, - imageType, + ImageType.Profile, imageIndex, tag, format, @@ -1591,6 +1552,75 @@ public class ImageController : BaseJellyfinApiController /// </summary> /// <param name="userId">User id.</param> /// <param name="imageType">Image type.</param> + /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> + /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> + /// <param name="maxWidth">The maximum image width to return.</param> + /// <param name="maxHeight">The maximum image height to return.</param> + /// <param name="percentPlayed">Optional. Percent to render for the percent played overlay.</param> + /// <param name="unplayedCount">Optional. Unplayed count overlay to render.</param> + /// <param name="width">The fixed image width to return.</param> + /// <param name="height">The fixed image height to return.</param> + /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> + /// <param name="fillWidth">Width of box to fill.</param> + /// <param name="fillHeight">Height of box to fill.</param> + /// <param name="blur">Optional. Blur image.</param> + /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> + /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> + /// <param name="imageIndex">Image index.</param> + /// <response code="200">Image stream returned.</response> + /// <response code="404">Item not found.</response> + /// <returns> + /// A <see cref="FileStreamResult"/> containing the file stream on success, + /// or a <see cref="NotFoundResult"/> if item not found. + /// </returns> + [HttpGet("Users/{userId}/Images/{imageType}")] + [HttpHead("Users/{userId}/Images/{imageType}", Name = "HeadUserImageLegacy")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesImageFile] + public Task<ActionResult> GetUserImageLegacy( + [FromRoute, Required] Guid userId, + [FromRoute, Required] ImageType imageType, + [FromQuery] string? tag, + [FromQuery] ImageFormat? format, + [FromQuery] int? maxWidth, + [FromQuery] int? maxHeight, + [FromQuery] double? percentPlayed, + [FromQuery] int? unplayedCount, + [FromQuery] int? width, + [FromQuery] int? height, + [FromQuery] int? quality, + [FromQuery] int? fillWidth, + [FromQuery] int? fillHeight, + [FromQuery] int? blur, + [FromQuery] string? backgroundColor, + [FromQuery] string? foregroundLayer, + [FromQuery] int? imageIndex) + => GetUserImage( + userId, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + imageIndex); + + /// <summary> + /// Get user profile image. + /// </summary> + /// <param name="userId">User id.</param> + /// <param name="imageType">Image type.</param> /// <param name="imageIndex">Image index.</param> /// <param name="tag">Optional. Supply the cache tag from the item object to receive strong caching headers.</param> /// <param name="format">Determines the output format of the image - original,gif,jpg,png.</param> @@ -1603,7 +1633,6 @@ public class ImageController : BaseJellyfinApiController /// <param name="quality">Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.</param> /// <param name="fillWidth">Width of box to fill.</param> /// <param name="fillHeight">Height of box to fill.</param> - /// <param name="cropWhitespace">Optional. Specify if whitespace should be cropped out of the image. True/False. If unspecified, whitespace will be cropped from logos and clear art.</param> /// <param name="blur">Optional. Blur image.</param> /// <param name="backgroundColor">Optional. Apply a background color for transparent images.</param> /// <param name="foregroundLayer">Optional. Apply a foreground layer on top of the image.</param> @@ -1614,11 +1643,13 @@ public class ImageController : BaseJellyfinApiController /// or a <see cref="NotFoundResult"/> if item not found. /// </returns> [HttpGet("Users/{userId}/Images/{imageType}/{imageIndex}")] - [HttpHead("Users/{userId}/Images/{imageType}/{imageIndex}", Name = "HeadUserImageByIndex")] + [HttpHead("Users/{userId}/Images/{imageType}/{imageIndex}", Name = "HeadUserImageByIndexLegacy")] + [Obsolete("Kept for backwards compatibility")] + [ApiExplorerSettings(IgnoreApi = true)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesImageFile] - public async Task<ActionResult> GetUserImageByIndex( + public Task<ActionResult> GetUserImageByIndexLegacy( [FromRoute, Required] Guid userId, [FromRoute, Required] ImageType imageType, [FromRoute, Required] int imageIndex, @@ -1633,56 +1664,26 @@ public class ImageController : BaseJellyfinApiController [FromQuery] int? quality, [FromQuery] int? fillWidth, [FromQuery] int? fillHeight, - [FromQuery, ParameterObsolete] bool? cropWhitespace, [FromQuery] int? blur, [FromQuery] string? backgroundColor, [FromQuery] string? foregroundLayer) - { - var user = _userManager.GetUserById(userId); - if (user?.ProfileImage is null) - { - return NotFound(); - } - - var info = new ItemImageInfo - { - Path = user.ProfileImage.Path, - Type = ImageType.Profile, - DateModified = user.ProfileImage.LastModified - }; - - if (width.HasValue) - { - info.Width = width.Value; - } - - if (height.HasValue) - { - info.Height = height.Value; - } - - return await GetImageInternal( - user.Id, - imageType, - imageIndex, - tag, - format, - maxWidth, - maxHeight, - percentPlayed, - unplayedCount, - width, - height, - quality, - fillWidth, - fillHeight, - blur, - backgroundColor, - foregroundLayer, - null, - info) - .ConfigureAwait(false); - } + => GetUserImage( + userId, + tag, + format, + maxWidth, + maxHeight, + percentPlayed, + unplayedCount, + width, + height, + quality, + fillWidth, + fillHeight, + blur, + backgroundColor, + foregroundLayer, + imageIndex); /// <summary> /// Generates or gets the splashscreen. |
