< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.ItemsController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/ItemsController.cs
Line coverage
32%
Covered lines: 85
Uncovered lines: 174
Coverable lines: 259
Total lines: 1060
Line coverage: 32.8%
Branch coverage
26%
Covered branches: 32
Total branches: 120
Branch coverage: 26.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 31% (82/264) Branch coverage: 23.9% (29/121) Total lines: 10723/14/2026 - 12:13:58 AM Line coverage: 33.1% (82/247) Branch coverage: 26.8% (29/108) Total lines: 10404/6/2026 - 12:13:55 AM Line coverage: 33.3% (83/249) Branch coverage: 27.2% (30/110) Total lines: 10434/27/2026 - 12:15:04 AM Line coverage: 33.3% (83/249) Branch coverage: 27.2% (30/110) Total lines: 10445/4/2026 - 12:15:16 AM Line coverage: 32.8% (85/259) Branch coverage: 26.6% (32/120) Total lines: 1060 1/23/2026 - 12:11:06 AM Line coverage: 31% (82/264) Branch coverage: 23.9% (29/121) Total lines: 10723/14/2026 - 12:13:58 AM Line coverage: 33.1% (82/247) Branch coverage: 26.8% (29/108) Total lines: 10404/6/2026 - 12:13:55 AM Line coverage: 33.3% (83/249) Branch coverage: 27.2% (30/110) Total lines: 10434/27/2026 - 12:15:04 AM Line coverage: 33.3% (83/249) Branch coverage: 27.2% (30/110) Total lines: 10445/4/2026 - 12:15:16 AM Line coverage: 32.8% (85/259) Branch coverage: 26.6% (32/120) Total lines: 1060

Coverage delta

Coverage delta 3 -3

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetItems()25.51%52709818.64%
GetResumeItems(...)70%111080%
GetItemUserData(...)0%4260%
UpdateItemUserData(...)0%4260%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Controllers/ItemsController.cs

#LineLine coverage
 1using System;
 2using System.ComponentModel.DataAnnotations;
 3using System.Linq;
 4using System.Threading.Tasks;
 5using Jellyfin.Api.Extensions;
 6using Jellyfin.Api.Helpers;
 7using Jellyfin.Api.ModelBinders;
 8using Jellyfin.Data;
 9using Jellyfin.Data.Enums;
 10using Jellyfin.Database.Implementations.Enums;
 11using Jellyfin.Extensions;
 12using MediaBrowser.Common.Extensions;
 13using MediaBrowser.Controller.Dto;
 14using MediaBrowser.Controller.Entities;
 15using MediaBrowser.Controller.Entities.Movies;
 16using MediaBrowser.Controller.Library;
 17using MediaBrowser.Controller.Session;
 18using MediaBrowser.Model.Dto;
 19using MediaBrowser.Model.Entities;
 20using MediaBrowser.Model.Globalization;
 21using MediaBrowser.Model.Querying;
 22using Microsoft.AspNetCore.Authorization;
 23using Microsoft.AspNetCore.Http;
 24using Microsoft.AspNetCore.Mvc;
 25using Microsoft.Extensions.Logging;
 26
 27namespace Jellyfin.Api.Controllers;
 28
 29/// <summary>
 30/// The items controller.
 31/// </summary>
 32[Route("")]
 33[Authorize]
 34[Tags("Item")]
 35public class ItemsController : BaseJellyfinApiController
 36{
 37    private readonly IUserManager _userManager;
 38    private readonly ILibraryManager _libraryManager;
 39    private readonly ILocalizationManager _localization;
 40    private readonly IDtoService _dtoService;
 41    private readonly ILogger<ItemsController> _logger;
 42    private readonly ISessionManager _sessionManager;
 43    private readonly IUserDataManager _userDataRepository;
 44
 45    /// <summary>
 46    /// Initializes a new instance of the <see cref="ItemsController"/> class.
 47    /// </summary>
 48    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 49    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 50    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 51    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 52    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 53    /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
 54    /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
 655    public ItemsController(
 656        IUserManager userManager,
 657        ILibraryManager libraryManager,
 658        ILocalizationManager localization,
 659        IDtoService dtoService,
 660        ILogger<ItemsController> logger,
 661        ISessionManager sessionManager,
 662        IUserDataManager userDataRepository)
 63    {
 664        _userManager = userManager;
 665        _libraryManager = libraryManager;
 666        _localization = localization;
 667        _dtoService = dtoService;
 668        _logger = logger;
 669        _sessionManager = sessionManager;
 670        _userDataRepository = userDataRepository;
 671    }
 72
 73    /// <summary>
 74    /// Gets items based on a query.
 75    /// </summary>
 76    /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param
 77    /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
 78    /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
 79    /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
 80    /// <param name="hasSubtitles">Optional filter by items with subtitles.</param>
 81    /// <param name="hasSpecialFeature">Optional filter by items with special features.</param>
 82    /// <param name="hasTrailer">Optional filter by items with trailers.</param>
 83    /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 84    /// <param name="indexNumber">Optional filter by index number.</param>
 85    /// <param name="parentIndexNumber">Optional filter by parent index number.</param>
 86    /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param>
 87    /// <param name="isHd">Optional filter by items that are HD or not.</param>
 88    /// <param name="is4K">Optional filter by items that are 4K or not.</param>
 89    /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows 
 90    /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. T
 91    /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
 92    /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
 93    /// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
 94    /// <param name="minCriticRating">Optional filter by minimum critic rating.</param>
 95    /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param>
 96    /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param>
 97    /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.<
 98    /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param>
 99    /// <param name="hasOverview">Optional filter by items that have an overview or not.</param>
 100    /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param>
 101    /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param>
 102    /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param>
 103    /// <param name="isMovie">Optional filter for live tv movies.</param>
 104    /// <param name="isSeries">Optional filter for live tv series.</param>
 105    /// <param name="isNews">Optional filter for live tv news.</param>
 106    /// <param name="isKids">Optional filter for live tv kids.</param>
 107    /// <param name="isSports">Optional filter for live tv sports.</param>
 108    /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows
 109    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 110    /// <param name="limit">Optional. The maximum number of records to return.</param>
 111    /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursi
 112    /// <param name="searchTerm">Optional. Filter based on a search term.</param>
 113    /// <param name="sortOrder">Sort Order - Ascending, Descending.</param>
 114    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 115    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 116    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 117    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 118    /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Opti
 119    /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 120    /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 121    /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types.
 122    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 123    /// <param name="isPlayed">Optional filter by items that are played, or not.</param>
 124    /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe
 125    /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This all
 126    /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe del
 127    /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multi
 128    /// <param name="enableUserData">Optional, include user data.</param>
 129    /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 130    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 131    /// <param name="person">Optional. If specified, results will be filtered to include only those containing the speci
 132    /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the sp
 133    /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only th
 134    /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pi
 135    /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, p
 136    /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows 
 137    /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the sp
 138    /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing t
 139    /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those conta
 140    /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe
 141    /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple,
 142    /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows m
 143    /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma deli
 144    /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
 145    /// <param name="isLocked">Optional filter by items that are locked.</param>
 146    /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
 147    /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param>
 148    /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param>
 149    /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param>
 150    /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param>
 151    /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param>
 152    /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param>
 153    /// <param name="is3D">Optional filter by items that are 3D, or not.</param>
 154    /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
 155    /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a gi
 156    /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</p
 157    /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</
 158    /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multipl
 159    /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple,
 160    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 161    /// <param name="enableImages">Optional, include image information in output.</param>
 162    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
 163    [HttpGet("Items")]
 164    [ProducesResponseType(StatusCodes.Status200OK)]
 165    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItems(
 166        [FromQuery] Guid? userId,
 167        [FromQuery] string? maxOfficialRating,
 168        [FromQuery] bool? hasThemeSong,
 169        [FromQuery] bool? hasThemeVideo,
 170        [FromQuery] bool? hasSubtitles,
 171        [FromQuery] bool? hasSpecialFeature,
 172        [FromQuery] bool? hasTrailer,
 173        [FromQuery] Guid? adjacentTo,
 174        [FromQuery] int? indexNumber,
 175        [FromQuery] int? parentIndexNumber,
 176        [FromQuery] bool? hasParentalRating,
 177        [FromQuery] bool? isHd,
 178        [FromQuery] bool? is4K,
 179        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
 180        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
 181        [FromQuery] bool? isMissing,
 182        [FromQuery] bool? isUnaired,
 183        [FromQuery] double? minCommunityRating,
 184        [FromQuery] double? minCriticRating,
 185        [FromQuery] DateTime? minPremiereDate,
 186        [FromQuery] DateTime? minDateLastSaved,
 187        [FromQuery] DateTime? minDateLastSavedForUser,
 188        [FromQuery] DateTime? maxPremiereDate,
 189        [FromQuery] bool? hasOverview,
 190        [FromQuery] bool? hasImdbId,
 191        [FromQuery] bool? hasTmdbId,
 192        [FromQuery] bool? hasTvdbId,
 193        [FromQuery] bool? isMovie,
 194        [FromQuery] bool? isSeries,
 195        [FromQuery] bool? isNews,
 196        [FromQuery] bool? isKids,
 197        [FromQuery] bool? isSports,
 198        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
 199        [FromQuery] int? startIndex,
 200        [FromQuery] int? limit,
 201        [FromQuery] bool? recursive,
 202        [FromQuery] string? searchTerm,
 203        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
 204        [FromQuery] Guid? parentId,
 205        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 206        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 207        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 208        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
 209        [FromQuery] bool? isFavorite,
 210        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 211        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
 212        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 213        [FromQuery] bool? isPlayed,
 214        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
 215        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
 216        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
 217        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
 218        [FromQuery] bool? enableUserData,
 219        [FromQuery] int? imageTypeLimit,
 220        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 221        [FromQuery] string? person,
 222        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
 223        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
 224        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
 225        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
 226        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
 227        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
 228        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
 229        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
 230        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
 231        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
 232        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
 233        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
 234        [FromQuery] string? minOfficialRating,
 235        [FromQuery] bool? isLocked,
 236        [FromQuery] bool? isPlaceHolder,
 237        [FromQuery] bool? hasOfficialRating,
 238        [FromQuery] bool? collapseBoxSetItems,
 239        [FromQuery] int? minWidth,
 240        [FromQuery] int? minHeight,
 241        [FromQuery] int? maxWidth,
 242        [FromQuery] int? maxHeight,
 243        [FromQuery] bool? is3D,
 244        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
 245        [FromQuery] string? nameStartsWithOrGreater,
 246        [FromQuery] string? nameStartsWith,
 247        [FromQuery] string? nameLessThan,
 248        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
 249        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 250        [FromQuery] bool enableTotalRecordCount = true,
 251        [FromQuery] bool? enableImages = true)
 252    {
 4253        var isApiKey = User.GetIsApiKey();
 254        // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
 4255        userId = RequestHelpers.GetUserId(User, userId);
 4256        var user = userId.IsNullOrEmpty()
 4257            ? null
 4258            : _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException();
 259
 260        // beyond this point, we're either using an api key or we have a valid user
 3261        if (!isApiKey && user is null)
 262        {
 0263            return BadRequest("userId is required");
 264        }
 265
 3266        if (user is not null
 3267            && user.GetPreference(PreferenceKind.AllowedTags).Length != 0
 3268            && !fields.Contains(ItemFields.Tags))
 269        {
 0270            fields = [..fields, ItemFields.Tags];
 271        }
 272
 3273        var dtoOptions = new DtoOptions { Fields = fields }
 3274            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 275
 3276        var item = _libraryManager.GetParentItem(parentId, userId);
 277        QueryResult<BaseItem> result;
 278
 3279        if (includeItemTypes.Length == 1
 3280            && includeItemTypes[0] == BaseItemKind.BoxSet
 3281            && item is not BoxSet)
 282        {
 0283            parentId = null;
 0284            item = _libraryManager.GetUserRootFolder();
 285        }
 286
 3287        if (item is not Folder folder)
 288        {
 0289            folder = _libraryManager.GetUserRootFolder();
 290        }
 291
 3292        CollectionType? collectionType = null;
 3293        if (folder is IHasCollectionType hasCollectionType)
 294        {
 0295            collectionType = hasCollectionType.CollectionType;
 296        }
 297
 3298        if (collectionType == CollectionType.playlists)
 299        {
 0300            recursive = true;
 0301            includeItemTypes = new[] { BaseItemKind.Playlist };
 302        }
 3303        else if (folder is ICollectionFolder)
 304        {
 305            // When the client doesn't specify recursive/includeItemTypes, force the query
 306            // through the database path where all filters (IsHD, genres, etc.) are applied.
 0307            recursive ??= true;
 0308            if (includeItemTypes.Length == 0)
 309            {
 0310                includeItemTypes = collectionType switch
 0311                {
 0312                    CollectionType.boxsets => [BaseItemKind.BoxSet],
 0313                    null => [BaseItemKind.Movie, BaseItemKind.Series],
 0314                    _ => []
 0315                };
 316            }
 317        }
 318
 3319        if (item is not UserRootFolder
 3320            // api keys can always access all folders
 3321            && !isApiKey
 3322            // check the item is visible for the user
 3323            && !item.IsVisible(user))
 324        {
 0325            _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name);
 0326            return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
 327        }
 328
 3329        if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder)
 330        {
 0331            var query = new InternalItemsQuery(user)
 0332            {
 0333                IsPlayed = isPlayed,
 0334                MediaTypes = mediaTypes,
 0335                IncludeItemTypes = includeItemTypes,
 0336                ExcludeItemTypes = excludeItemTypes,
 0337                Recursive = recursive ?? false,
 0338                OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
 0339                IsFavorite = isFavorite,
 0340                Limit = limit,
 0341                StartIndex = startIndex,
 0342                IsMissing = isMissing,
 0343                IsUnaired = isUnaired,
 0344                CollapseBoxSetItems = collapseBoxSetItems,
 0345                NameLessThan = nameLessThan,
 0346                NameStartsWith = nameStartsWith,
 0347                NameStartsWithOrGreater = nameStartsWithOrGreater,
 0348                HasImdbId = hasImdbId,
 0349                IsPlaceHolder = isPlaceHolder,
 0350                IsLocked = isLocked,
 0351                MinWidth = minWidth,
 0352                MinHeight = minHeight,
 0353                MaxWidth = maxWidth,
 0354                MaxHeight = maxHeight,
 0355                Is3D = is3D,
 0356                HasTvdbId = hasTvdbId,
 0357                HasTmdbId = hasTmdbId,
 0358                IsMovie = isMovie,
 0359                IsSeries = isSeries,
 0360                IsNews = isNews,
 0361                IsKids = isKids,
 0362                IsSports = isSports,
 0363                HasOverview = hasOverview,
 0364                HasOfficialRating = hasOfficialRating,
 0365                HasParentalRating = hasParentalRating,
 0366                HasSpecialFeature = hasSpecialFeature,
 0367                HasSubtitles = hasSubtitles,
 0368                HasThemeSong = hasThemeSong,
 0369                HasThemeVideo = hasThemeVideo,
 0370                HasTrailer = hasTrailer,
 0371                IsHD = isHd,
 0372                Is4K = is4K,
 0373                Tags = tags,
 0374                OfficialRatings = officialRatings,
 0375                Genres = genres,
 0376                ArtistIds = artistIds,
 0377                AlbumArtistIds = albumArtistIds,
 0378                ContributingArtistIds = contributingArtistIds,
 0379                GenreIds = genreIds,
 0380                StudioIds = studioIds,
 0381                Person = person,
 0382                PersonIds = personIds,
 0383                PersonTypes = personTypes,
 0384                Years = years,
 0385                ImageTypes = imageTypes,
 0386                VideoTypes = videoTypes,
 0387                AdjacentTo = adjacentTo,
 0388                ItemIds = ids,
 0389                MinCommunityRating = minCommunityRating,
 0390                MinCriticRating = minCriticRating,
 0391                ParentId = parentId ?? Guid.Empty,
 0392                IndexNumber = indexNumber,
 0393                ParentIndexNumber = parentIndexNumber,
 0394                EnableTotalRecordCount = enableTotalRecordCount,
 0395                ExcludeItemIds = excludeItemIds,
 0396                DtoOptions = dtoOptions,
 0397                SearchTerm = searchTerm,
 0398                MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
 0399                MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
 0400                MinPremiereDate = minPremiereDate?.ToUniversalTime(),
 0401                MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
 0402            };
 403
 0404            if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
 405            {
 0406                query.CollapseBoxSetItems = false;
 407            }
 408
 0409            query.ApplyFilters(filters);
 410
 411            // Filter by Series Status
 0412            if (seriesStatus.Length != 0)
 413            {
 0414                query.SeriesStatuses = seriesStatus;
 415            }
 416
 417            // Exclude Blocked Unrated Items
 0418            var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
 0419            if (blockedUnratedItems is not null)
 420            {
 0421                query.BlockUnratedItems = blockedUnratedItems;
 422            }
 423
 424            // ExcludeLocationTypes
 0425            if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
 426            {
 0427                query.IsVirtualItem = false;
 428            }
 429
 0430            if (locationTypes.Length > 0 && locationTypes.Length < 4)
 431            {
 0432                query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual);
 433            }
 434
 435            // Min official rating
 0436            if (!string.IsNullOrWhiteSpace(minOfficialRating))
 437            {
 0438                query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
 439            }
 440
 441            // Max official rating
 0442            if (!string.IsNullOrWhiteSpace(maxOfficialRating))
 443            {
 0444                query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
 445            }
 446
 447            // Artists
 0448            if (artists.Length != 0)
 449            {
 0450                query.ArtistIds = artists.Select(i =>
 0451                {
 0452                    try
 0453                    {
 0454                        return _libraryManager.GetArtist(i, new DtoOptions(false));
 0455                    }
 0456                    catch
 0457                    {
 0458                        return null;
 0459                    }
 0460                }).Where(i => i is not null).Select(i => i!.Id).ToArray();
 461            }
 462
 463            // ExcludeArtistIds
 0464            if (excludeArtistIds.Length != 0)
 465            {
 0466                query.ExcludeArtistIds = excludeArtistIds;
 467            }
 468
 0469            if (albumIds.Length != 0)
 470            {
 0471                query.AlbumIds = albumIds;
 472            }
 473
 474            // Albums
 0475            if (albums.Length != 0)
 476            {
 0477                query.AlbumIds = albums.SelectMany(i =>
 0478                {
 0479                    return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.M
 0480                }).ToArray();
 481            }
 482
 483            // Studios
 0484            if (studios.Length != 0)
 485            {
 0486                query.StudioIds = studios.Select(i =>
 0487                {
 0488                    try
 0489                    {
 0490                        return _libraryManager.GetStudio(i);
 0491                    }
 0492                    catch
 0493                    {
 0494                        return null;
 0495                    }
 0496                }).Where(i => i is not null).Select(i => i!.Id).ToArray();
 497            }
 498
 499            // Apply default sorting if none requested
 0500            if (query.OrderBy.Count == 0)
 501            {
 502                // Albums by artist
 0503                if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == Bas
 504                {
 0505                    query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, Sor
 506                }
 507            }
 508
 0509            query.Parent = null;
 0510            result = folder.GetItems(query);
 511        }
 512        else
 513        {
 3514            var itemsArray = folder.GetChildren(user, true);
 3515            result = new QueryResult<BaseItem>(itemsArray);
 516        }
 517
 3518        return new QueryResult<BaseItemDto>(
 3519            startIndex,
 3520            result.TotalRecordCount,
 3521            _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user, skipVisibilityCheck: true));
 3522    }
 523
 524    /// <summary>
 525    /// Gets items based on a query.
 526    /// </summary>
 527    /// <param name="userId">The user id supplied as query parameter.</param>
 528    /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
 529    /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
 530    /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
 531    /// <param name="hasSubtitles">Optional filter by items with subtitles.</param>
 532    /// <param name="hasSpecialFeature">Optional filter by items with special features.</param>
 533    /// <param name="hasTrailer">Optional filter by items with trailers.</param>
 534    /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 535    /// <param name="parentIndexNumber">Optional filter by parent index number.</param>
 536    /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param>
 537    /// <param name="isHd">Optional filter by items that are HD or not.</param>
 538    /// <param name="is4K">Optional filter by items that are 4K or not.</param>
 539    /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows 
 540    /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. T
 541    /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
 542    /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
 543    /// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
 544    /// <param name="minCriticRating">Optional filter by minimum critic rating.</param>
 545    /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param>
 546    /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param>
 547    /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.<
 548    /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param>
 549    /// <param name="hasOverview">Optional filter by items that have an overview or not.</param>
 550    /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param>
 551    /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param>
 552    /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param>
 553    /// <param name="isMovie">Optional filter for live tv movies.</param>
 554    /// <param name="isSeries">Optional filter for live tv series.</param>
 555    /// <param name="isNews">Optional filter for live tv news.</param>
 556    /// <param name="isKids">Optional filter for live tv kids.</param>
 557    /// <param name="isSports">Optional filter for live tv sports.</param>
 558    /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows
 559    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 560    /// <param name="limit">Optional. The maximum number of records to return.</param>
 561    /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursi
 562    /// <param name="searchTerm">Optional. Filter based on a search term.</param>
 563    /// <param name="sortOrder">Sort Order - Ascending, Descending.</param>
 564    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 565    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 566    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 567    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 568    /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Opti
 569    /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 570    /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 571    /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types.
 572    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 573    /// <param name="isPlayed">Optional filter by items that are played, or not.</param>
 574    /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe
 575    /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This all
 576    /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe del
 577    /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multi
 578    /// <param name="enableUserData">Optional, include user data.</param>
 579    /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 580    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 581    /// <param name="person">Optional. If specified, results will be filtered to include only those containing the speci
 582    /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the sp
 583    /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only th
 584    /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pi
 585    /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, p
 586    /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows 
 587    /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the sp
 588    /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing t
 589    /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those conta
 590    /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe
 591    /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple,
 592    /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows m
 593    /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma deli
 594    /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
 595    /// <param name="isLocked">Optional filter by items that are locked.</param>
 596    /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
 597    /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param>
 598    /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param>
 599    /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param>
 600    /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param>
 601    /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param>
 602    /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param>
 603    /// <param name="is3D">Optional filter by items that are 3D, or not.</param>
 604    /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
 605    /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a gi
 606    /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</p
 607    /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</
 608    /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multipl
 609    /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple,
 610    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 611    /// <param name="enableImages">Optional, include image information in output.</param>
 612    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
 613    [HttpGet("Users/{userId}/Items")]
 614    [Obsolete("Kept for backwards compatibility")]
 615    [ApiExplorerSettings(IgnoreApi = true)]
 616    [ProducesResponseType(StatusCodes.Status200OK)]
 617    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItemsByUserIdLegacy(
 618        [FromRoute] Guid userId,
 619        [FromQuery] string? maxOfficialRating,
 620        [FromQuery] bool? hasThemeSong,
 621        [FromQuery] bool? hasThemeVideo,
 622        [FromQuery] bool? hasSubtitles,
 623        [FromQuery] bool? hasSpecialFeature,
 624        [FromQuery] bool? hasTrailer,
 625        [FromQuery] Guid? adjacentTo,
 626        [FromQuery] int? parentIndexNumber,
 627        [FromQuery] bool? hasParentalRating,
 628        [FromQuery] bool? isHd,
 629        [FromQuery] bool? is4K,
 630        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
 631        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
 632        [FromQuery] bool? isMissing,
 633        [FromQuery] bool? isUnaired,
 634        [FromQuery] double? minCommunityRating,
 635        [FromQuery] double? minCriticRating,
 636        [FromQuery] DateTime? minPremiereDate,
 637        [FromQuery] DateTime? minDateLastSaved,
 638        [FromQuery] DateTime? minDateLastSavedForUser,
 639        [FromQuery] DateTime? maxPremiereDate,
 640        [FromQuery] bool? hasOverview,
 641        [FromQuery] bool? hasImdbId,
 642        [FromQuery] bool? hasTmdbId,
 643        [FromQuery] bool? hasTvdbId,
 644        [FromQuery] bool? isMovie,
 645        [FromQuery] bool? isSeries,
 646        [FromQuery] bool? isNews,
 647        [FromQuery] bool? isKids,
 648        [FromQuery] bool? isSports,
 649        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
 650        [FromQuery] int? startIndex,
 651        [FromQuery] int? limit,
 652        [FromQuery] bool? recursive,
 653        [FromQuery] string? searchTerm,
 654        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
 655        [FromQuery] Guid? parentId,
 656        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 657        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 658        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 659        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
 660        [FromQuery] bool? isFavorite,
 661        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 662        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
 663        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 664        [FromQuery] bool? isPlayed,
 665        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
 666        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
 667        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
 668        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
 669        [FromQuery] bool? enableUserData,
 670        [FromQuery] int? imageTypeLimit,
 671        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 672        [FromQuery] string? person,
 673        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
 674        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
 675        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
 676        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
 677        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
 678        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
 679        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
 680        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
 681        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
 682        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
 683        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
 684        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
 685        [FromQuery] string? minOfficialRating,
 686        [FromQuery] bool? isLocked,
 687        [FromQuery] bool? isPlaceHolder,
 688        [FromQuery] bool? hasOfficialRating,
 689        [FromQuery] bool? collapseBoxSetItems,
 690        [FromQuery] int? minWidth,
 691        [FromQuery] int? minHeight,
 692        [FromQuery] int? maxWidth,
 693        [FromQuery] int? maxHeight,
 694        [FromQuery] bool? is3D,
 695        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
 696        [FromQuery] string? nameStartsWithOrGreater,
 697        [FromQuery] string? nameStartsWith,
 698        [FromQuery] string? nameLessThan,
 699        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
 700        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 701        [FromQuery] bool enableTotalRecordCount = true,
 702        [FromQuery] bool? enableImages = true)
 703        => await GetItems(
 704            userId,
 705            maxOfficialRating,
 706            hasThemeSong,
 707            hasThemeVideo,
 708            hasSubtitles,
 709            hasSpecialFeature,
 710            hasTrailer,
 711            adjacentTo,
 712            null,
 713            parentIndexNumber,
 714            hasParentalRating,
 715            isHd,
 716            is4K,
 717            locationTypes,
 718            excludeLocationTypes,
 719            isMissing,
 720            isUnaired,
 721            minCommunityRating,
 722            minCriticRating,
 723            minPremiereDate,
 724            minDateLastSaved,
 725            minDateLastSavedForUser,
 726            maxPremiereDate,
 727            hasOverview,
 728            hasImdbId,
 729            hasTmdbId,
 730            hasTvdbId,
 731            isMovie,
 732            isSeries,
 733            isNews,
 734            isKids,
 735            isSports,
 736            excludeItemIds,
 737            startIndex,
 738            limit,
 739            recursive,
 740            searchTerm,
 741            sortOrder,
 742            parentId,
 743            fields,
 744            excludeItemTypes,
 745            includeItemTypes,
 746            filters,
 747            isFavorite,
 748            mediaTypes,
 749            imageTypes,
 750            sortBy,
 751            isPlayed,
 752            genres,
 753            officialRatings,
 754            tags,
 755            years,
 756            enableUserData,
 757            imageTypeLimit,
 758            enableImageTypes,
 759            person,
 760            personIds,
 761            personTypes,
 762            studios,
 763            artists,
 764            excludeArtistIds,
 765            artistIds,
 766            albumArtistIds,
 767            contributingArtistIds,
 768            albums,
 769            albumIds,
 770            ids,
 771            videoTypes,
 772            minOfficialRating,
 773            isLocked,
 774            isPlaceHolder,
 775            hasOfficialRating,
 776            collapseBoxSetItems,
 777            minWidth,
 778            minHeight,
 779            maxWidth,
 780            maxHeight,
 781            is3D,
 782            seriesStatus,
 783            nameStartsWithOrGreater,
 784            nameStartsWith,
 785            nameLessThan,
 786            studioIds,
 787            genreIds,
 788            enableTotalRecordCount,
 789            enableImages).ConfigureAwait(false);
 790
 791    /// <summary>
 792    /// Gets items based on a query.
 793    /// </summary>
 794    /// <param name="userId">The user id.</param>
 795    /// <param name="startIndex">The start index.</param>
 796    /// <param name="limit">The item limit.</param>
 797    /// <param name="searchTerm">The search term.</param>
 798    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 799    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 800    /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 801    /// <param name="enableUserData">Optional. Include user data.</param>
 802    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 803    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 804    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 805    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 806    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 807    /// <param name="enableImages">Optional. Include image information in output.</param>
 808    /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param>
 809    /// <response code="200">Items returned.</response>
 810    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
 811    [HttpGet("UserItems/Resume")]
 812    [ProducesResponseType(StatusCodes.Status200OK)]
 813    public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
 814        [FromQuery] Guid? userId,
 815        [FromQuery] int? startIndex,
 816        [FromQuery] int? limit,
 817        [FromQuery] string? searchTerm,
 818        [FromQuery] Guid? parentId,
 819        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 820        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 821        [FromQuery] bool? enableUserData,
 822        [FromQuery] int? imageTypeLimit,
 823        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 824        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 825        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 826        [FromQuery] bool enableTotalRecordCount = true,
 827        [FromQuery] bool? enableImages = true,
 828        [FromQuery] bool excludeActiveSessions = false)
 829    {
 2830        var requestUserId = RequestHelpers.GetUserId(User, userId);
 2831        var user = _userManager.GetUserById(requestUserId);
 2832        if (user is null)
 833        {
 1834            return NotFound();
 835        }
 836
 1837        var parentIdGuid = parentId ?? Guid.Empty;
 1838        var dtoOptions = new DtoOptions { Fields = fields }
 1839            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 840
 1841        var ancestorIds = Array.Empty<Guid>();
 842
 1843        var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
 1844        if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0)
 845        {
 0846            ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 0847                .Where(i => i is Folder)
 0848                .Where(i => !excludeFolderIds.Contains(i.Id))
 0849                .Select(i => i.Id)
 0850                .ToArray();
 851        }
 852
 1853        var excludeItemIds = Array.Empty<Guid>();
 1854        if (excludeActiveSessions)
 855        {
 0856            excludeItemIds = _sessionManager.Sessions
 0857                .Where(s => s.UserId.Equals(requestUserId) && s.NowPlayingItem is not null)
 0858                .Select(s => s.NowPlayingItem.Id)
 0859                .ToArray();
 860        }
 861
 1862        var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
 1863        {
 1864            OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
 1865            IsResumable = true,
 1866            StartIndex = startIndex,
 1867            Limit = limit,
 1868            ParentId = parentIdGuid,
 1869            Recursive = true,
 1870            DtoOptions = dtoOptions,
 1871            MediaTypes = mediaTypes,
 1872            IsVirtualItem = false,
 1873            CollapseBoxSetItems = false,
 1874            EnableTotalRecordCount = enableTotalRecordCount,
 1875            AncestorIds = ancestorIds,
 1876            IncludeItemTypes = includeItemTypes,
 1877            ExcludeItemTypes = excludeItemTypes,
 1878            SearchTerm = searchTerm,
 1879            ExcludeItemIds = excludeItemIds
 1880        });
 881
 1882        var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user);
 883
 1884        return new QueryResult<BaseItemDto>(
 1885            startIndex,
 1886            itemsResult.TotalRecordCount,
 1887            returnItems);
 888    }
 889
 890    /// <summary>
 891    /// Gets items based on a query.
 892    /// </summary>
 893    /// <param name="userId">The user id.</param>
 894    /// <param name="startIndex">The start index.</param>
 895    /// <param name="limit">The item limit.</param>
 896    /// <param name="searchTerm">The search term.</param>
 897    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 898    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 899    /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 900    /// <param name="enableUserData">Optional. Include user data.</param>
 901    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 902    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 903    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 904    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 905    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 906    /// <param name="enableImages">Optional. Include image information in output.</param>
 907    /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param>
 908    /// <response code="200">Items returned.</response>
 909    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
 910    [HttpGet("Users/{userId}/Items/Resume")]
 911    [Obsolete("Kept for backwards compatibility")]
 912    [ApiExplorerSettings(IgnoreApi = true)]
 913    [ProducesResponseType(StatusCodes.Status200OK)]
 914    public ActionResult<QueryResult<BaseItemDto>> GetResumeItemsLegacy(
 915        [FromRoute, Required] Guid userId,
 916        [FromQuery] int? startIndex,
 917        [FromQuery] int? limit,
 918        [FromQuery] string? searchTerm,
 919        [FromQuery] Guid? parentId,
 920        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 921        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 922        [FromQuery] bool? enableUserData,
 923        [FromQuery] int? imageTypeLimit,
 924        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 925        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 926        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 927        [FromQuery] bool enableTotalRecordCount = true,
 928        [FromQuery] bool? enableImages = true,
 929        [FromQuery] bool excludeActiveSessions = false)
 930    => GetResumeItems(
 931        userId,
 932        startIndex,
 933        limit,
 934        searchTerm,
 935        parentId,
 936        fields,
 937        mediaTypes,
 938        enableUserData,
 939        imageTypeLimit,
 940        enableImageTypes,
 941        excludeItemTypes,
 942        includeItemTypes,
 943        enableTotalRecordCount,
 944        enableImages,
 945        excludeActiveSessions);
 946
 947    /// <summary>
 948    /// Get Item User Data.
 949    /// </summary>
 950    /// <param name="userId">The user id.</param>
 951    /// <param name="itemId">The item id.</param>
 952    /// <response code="200">return item user data.</response>
 953    /// <response code="404">Item is not found.</response>
 954    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 955    [HttpGet("UserItems/{itemId}/UserData")]
 956    [ProducesResponseType(StatusCodes.Status200OK)]
 957    [ProducesResponseType(StatusCodes.Status404NotFound)]
 958    public ActionResult<UserItemDataDto?> GetItemUserData(
 959        [FromQuery] Guid? userId,
 960        [FromRoute, Required] Guid itemId)
 961    {
 0962        var requestUserId = RequestHelpers.GetUserId(User, userId);
 0963        var user = _userManager.GetUserById(requestUserId);
 0964        if (user is null)
 965        {
 0966            return NotFound();
 967        }
 968
 0969        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 970        {
 0971            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data.");
 972        }
 973
 0974        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 0975        if (item is null)
 976        {
 0977            return NotFound();
 978        }
 979
 0980        return _userDataRepository.GetUserDataDto(item, user);
 981    }
 982
 983    /// <summary>
 984    /// Get Item User Data.
 985    /// </summary>
 986    /// <param name="userId">The user id.</param>
 987    /// <param name="itemId">The item id.</param>
 988    /// <response code="200">return item user data.</response>
 989    /// <response code="404">Item is not found.</response>
 990    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 991    [HttpGet("Users/{userId}/Items/{itemId}/UserData")]
 992    [ProducesResponseType(StatusCodes.Status200OK)]
 993    [ProducesResponseType(StatusCodes.Status404NotFound)]
 994    [Obsolete("Kept for backwards compatibility")]
 995    [ApiExplorerSettings(IgnoreApi = true)]
 996    public ActionResult<UserItemDataDto?> GetItemUserDataLegacy(
 997        [FromRoute, Required] Guid userId,
 998        [FromRoute, Required] Guid itemId)
 999        => GetItemUserData(userId, itemId);
 1000
 1001    /// <summary>
 1002    /// Update Item User Data.
 1003    /// </summary>
 1004    /// <param name="userId">The user id.</param>
 1005    /// <param name="itemId">The item id.</param>
 1006    /// <param name="userDataDto">New user data object.</param>
 1007    /// <response code="200">return updated user item data.</response>
 1008    /// <response code="404">Item is not found.</response>
 1009    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1010    [HttpPost("UserItems/{itemId}/UserData")]
 1011    [ProducesResponseType(StatusCodes.Status200OK)]
 1012    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1013    public ActionResult<UserItemDataDto?> UpdateItemUserData(
 1014        [FromQuery] Guid? userId,
 1015        [FromRoute, Required] Guid itemId,
 1016        [FromBody, Required] UpdateUserItemDataDto userDataDto)
 1017    {
 01018        var requestUserId = RequestHelpers.GetUserId(User, userId);
 01019        var user = _userManager.GetUserById(requestUserId);
 01020        if (user is null)
 1021        {
 01022            return NotFound();
 1023        }
 1024
 01025        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 1026        {
 01027            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data.");
 1028        }
 1029
 01030        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 01031        if (item is null)
 1032        {
 01033            return NotFound();
 1034        }
 1035
 01036        _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData);
 1037
 01038        return _userDataRepository.GetUserDataDto(item, user);
 1039    }
 1040
 1041    /// <summary>
 1042    /// Update Item User Data.
 1043    /// </summary>
 1044    /// <param name="userId">The user id.</param>
 1045    /// <param name="itemId">The item id.</param>
 1046    /// <param name="userDataDto">New user data object.</param>
 1047    /// <response code="200">return updated user item data.</response>
 1048    /// <response code="404">Item is not found.</response>
 1049    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1050    [HttpPost("Users/{userId}/Items/{itemId}/UserData")]
 1051    [ProducesResponseType(StatusCodes.Status200OK)]
 1052    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1053    [Obsolete("Kept for backwards compatibility")]
 1054    [ApiExplorerSettings(IgnoreApi = true)]
 1055    public ActionResult<UserItemDataDto?> UpdateItemUserDataLegacy(
 1056        [FromRoute, Required] Guid userId,
 1057        [FromRoute, Required] Guid itemId,
 1058        [FromBody, Required] UpdateUserItemDataDto userDataDto)
 1059        => UpdateItemUserData(userId, itemId, userDataDto);
 1060}