< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.ItemsController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/ItemsController.cs
Line coverage
30%
Covered lines: 87
Uncovered lines: 196
Coverable lines: 283
Total lines: 1110
Line coverage: 30.7%
Branch coverage
20%
Covered branches: 32
Total branches: 154
Branch coverage: 20.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 2/13/2026 - 12:11:21 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: 10605/13/2026 - 12:15:27 AM Line coverage: 32.8% (85/259) Branch coverage: 26.6% (32/120) Total lines: 10625/16/2026 - 12:15:55 AM Line coverage: 31% (85/274) Branch coverage: 22.8% (32/140) Total lines: 10975/20/2026 - 12:15:44 AM Line coverage: 31% (85/274) Branch coverage: 22.1% (31/140) Total lines: 10975/22/2026 - 12:15:17 AM Line coverage: 30.8% (86/279) Branch coverage: 20.9% (31/148) Total lines: 11055/24/2026 - 12:15:27 AM Line coverage: 30.7% (87/283) Branch coverage: 20.7% (32/154) Total lines: 1110 2/13/2026 - 12:11:21 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: 10605/13/2026 - 12:15:27 AM Line coverage: 32.8% (85/259) Branch coverage: 26.6% (32/120) Total lines: 10625/16/2026 - 12:15:55 AM Line coverage: 31% (85/274) Branch coverage: 22.8% (32/140) Total lines: 10975/20/2026 - 12:15:44 AM Line coverage: 31% (85/274) Branch coverage: 22.1% (31/140) Total lines: 10975/22/2026 - 12:15:17 AM Line coverage: 30.8% (86/279) Branch coverage: 20.9% (31/148) Total lines: 11055/24/2026 - 12:15:27 AM Line coverage: 30.7% (87/283) Branch coverage: 20.7% (32/154) Total lines: 1110

Coverage delta

Coverage delta 4 -4

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
GetItems()19.69%994813217.41%
GetResumeItems(...)60%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.Playlists;
 18using MediaBrowser.Controller.Session;
 19using MediaBrowser.Model.Dto;
 20using MediaBrowser.Model.Entities;
 21using MediaBrowser.Model.Globalization;
 22using MediaBrowser.Model.Querying;
 23using Microsoft.AspNetCore.Authorization;
 24using Microsoft.AspNetCore.Http;
 25using Microsoft.AspNetCore.Mvc;
 26using Microsoft.Extensions.Logging;
 27
 28namespace Jellyfin.Api.Controllers;
 29
 30/// <summary>
 31/// The items controller.
 32/// </summary>
 33[Route("")]
 34[Authorize]
 35[Tags("Library")]
 36public class ItemsController : BaseJellyfinApiController
 37{
 38    private readonly IUserManager _userManager;
 39    private readonly ILibraryManager _libraryManager;
 40    private readonly ILocalizationManager _localization;
 41    private readonly IDtoService _dtoService;
 42    private readonly ILogger<ItemsController> _logger;
 43    private readonly ISessionManager _sessionManager;
 44    private readonly IUserDataManager _userDataRepository;
 45
 46    /// <summary>
 47    /// Initializes a new instance of the <see cref="ItemsController"/> class.
 48    /// </summary>
 49    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 50    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 51    /// <param name="localization">Instance of the <see cref="ILocalizationManager"/> interface.</param>
 52    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 53    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 54    /// <param name="sessionManager">Instance of the <see cref="ISessionManager"/> interface.</param>
 55    /// <param name="userDataRepository">Instance of the <see cref="IUserDataManager"/> interface.</param>
 656    public ItemsController(
 657        IUserManager userManager,
 658        ILibraryManager libraryManager,
 659        ILocalizationManager localization,
 660        IDtoService dtoService,
 661        ILogger<ItemsController> logger,
 662        ISessionManager sessionManager,
 663        IUserDataManager userDataRepository)
 64    {
 665        _userManager = userManager;
 666        _libraryManager = libraryManager;
 667        _localization = localization;
 668        _dtoService = dtoService;
 669        _logger = logger;
 670        _sessionManager = sessionManager;
 671        _userDataRepository = userDataRepository;
 672    }
 73
 74    /// <summary>
 75    /// Gets items based on a query.
 76    /// </summary>
 77    /// <param name="userId">The user id supplied as query parameter; this is required when not using an API key.</param
 78    /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
 79    /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
 80    /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
 81    /// <param name="hasSubtitles">Optional filter by items with subtitles.</param>
 82    /// <param name="hasSpecialFeature">Optional filter by items with special features.</param>
 83    /// <param name="hasTrailer">Optional filter by items with trailers.</param>
 84    /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 85    /// <param name="indexNumber">Optional filter by index number.</param>
 86    /// <param name="parentIndexNumber">Optional filter by parent index number.</param>
 87    /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param>
 88    /// <param name="isHd">Optional filter by items that are HD or not.</param>
 89    /// <param name="is4K">Optional filter by items that are 4K or not.</param>
 90    /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows 
 91    /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. T
 92    /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
 93    /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
 94    /// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
 95    /// <param name="minCriticRating">Optional filter by minimum critic rating.</param>
 96    /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param>
 97    /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param>
 98    /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.<
 99    /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param>
 100    /// <param name="hasOverview">Optional filter by items that have an overview or not.</param>
 101    /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param>
 102    /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param>
 103    /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param>
 104    /// <param name="isMovie">Optional filter for live tv movies.</param>
 105    /// <param name="isSeries">Optional filter for live tv series.</param>
 106    /// <param name="isNews">Optional filter for live tv news.</param>
 107    /// <param name="isKids">Optional filter for live tv kids.</param>
 108    /// <param name="isSports">Optional filter for live tv sports.</param>
 109    /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows
 110    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 111    /// <param name="limit">Optional. The maximum number of records to return.</param>
 112    /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursi
 113    /// <param name="searchTerm">Optional. Filter based on a search term.</param>
 114    /// <param name="sortOrder">Sort Order - Ascending, Descending.</param>
 115    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 116    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 117    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 118    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 119    /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Opti
 120    /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 121    /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 122    /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types.
 123    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 124    /// <param name="isPlayed">Optional filter by items that are played, or not.</param>
 125    /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe
 126    /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This all
 127    /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe del
 128    /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multi
 129    /// <param name="enableUserData">Optional, include user data.</param>
 130    /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 131    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 132    /// <param name="person">Optional. If specified, results will be filtered to include only those containing the speci
 133    /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the sp
 134    /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only th
 135    /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pi
 136    /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, p
 137    /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows 
 138    /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the sp
 139    /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing t
 140    /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those conta
 141    /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe
 142    /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple,
 143    /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows m
 144    /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma deli
 145    /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
 146    /// <param name="isLocked">Optional filter by items that are locked.</param>
 147    /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
 148    /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param>
 149    /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param>
 150    /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param>
 151    /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param>
 152    /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param>
 153    /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param>
 154    /// <param name="is3D">Optional filter by items that are 3D, or not.</param>
 155    /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
 156    /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a gi
 157    /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</p
 158    /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</
 159    /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multipl
 160    /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple,
 161    /// <param name="audioLanguages">Optional. If specified, results will be filtered based on audio language. This allo
 162    /// <param name="subtitleLanguages">Optional. If specified, results will be filtered based on subtitle language. Thi
 163    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 164    /// <param name="enableImages">Optional, include image information in output.</param>
 165    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
 166    [HttpGet("Items")]
 167    [ProducesResponseType(StatusCodes.Status200OK)]
 168    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItems(
 169        [FromQuery] Guid? userId,
 170        [FromQuery] string? maxOfficialRating,
 171        [FromQuery] bool? hasThemeSong,
 172        [FromQuery] bool? hasThemeVideo,
 173        [FromQuery] bool? hasSubtitles,
 174        [FromQuery] bool? hasSpecialFeature,
 175        [FromQuery] bool? hasTrailer,
 176        [FromQuery] Guid? adjacentTo,
 177        [FromQuery] int? indexNumber,
 178        [FromQuery] int? parentIndexNumber,
 179        [FromQuery] bool? hasParentalRating,
 180        [FromQuery] bool? isHd,
 181        [FromQuery] bool? is4K,
 182        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
 183        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
 184        [FromQuery] bool? isMissing,
 185        [FromQuery] bool? isUnaired,
 186        [FromQuery] double? minCommunityRating,
 187        [FromQuery] double? minCriticRating,
 188        [FromQuery] DateTime? minPremiereDate,
 189        [FromQuery] DateTime? minDateLastSaved,
 190        [FromQuery] DateTime? minDateLastSavedForUser,
 191        [FromQuery] DateTime? maxPremiereDate,
 192        [FromQuery] bool? hasOverview,
 193        [FromQuery] bool? hasImdbId,
 194        [FromQuery] bool? hasTmdbId,
 195        [FromQuery] bool? hasTvdbId,
 196        [FromQuery] bool? isMovie,
 197        [FromQuery] bool? isSeries,
 198        [FromQuery] bool? isNews,
 199        [FromQuery] bool? isKids,
 200        [FromQuery] bool? isSports,
 201        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
 202        [FromQuery] int? startIndex,
 203        [FromQuery] int? limit,
 204        [FromQuery] bool? recursive,
 205        [FromQuery] string? searchTerm,
 206        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
 207        [FromQuery] Guid? parentId,
 208        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 209        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 210        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 211        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
 212        [FromQuery] bool? isFavorite,
 213        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 214        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
 215        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 216        [FromQuery] bool? isPlayed,
 217        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
 218        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
 219        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
 220        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
 221        [FromQuery] bool? enableUserData,
 222        [FromQuery] int? imageTypeLimit,
 223        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 224        [FromQuery] string? person,
 225        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
 226        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
 227        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
 228        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
 229        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
 230        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
 231        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
 232        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
 233        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
 234        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
 235        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
 236        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
 237        [FromQuery] string? minOfficialRating,
 238        [FromQuery] bool? isLocked,
 239        [FromQuery] bool? isPlaceHolder,
 240        [FromQuery] bool? hasOfficialRating,
 241        [FromQuery] bool? collapseBoxSetItems,
 242        [FromQuery] int? minWidth,
 243        [FromQuery] int? minHeight,
 244        [FromQuery] int? maxWidth,
 245        [FromQuery] int? maxHeight,
 246        [FromQuery] bool? is3D,
 247        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
 248        [FromQuery] string? nameStartsWithOrGreater,
 249        [FromQuery] string? nameStartsWith,
 250        [FromQuery] string? nameLessThan,
 251        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
 252        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 253        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] audioLanguages,
 254        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] subtitleLanguages,
 255        [FromQuery] bool enableTotalRecordCount = true,
 256        [FromQuery] bool? enableImages = true)
 257    {
 4258        var isApiKey = User.GetIsApiKey();
 259        // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
 4260        userId = RequestHelpers.GetUserId(User, userId);
 4261        var user = userId.IsNullOrEmpty()
 4262            ? null
 4263            : _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException();
 264
 265        // beyond this point, we're either using an api key or we have a valid user
 3266        if (!isApiKey && user is null)
 267        {
 0268            return BadRequest("userId is required");
 269        }
 270
 3271        if (user is not null
 3272            && user.GetPreference(PreferenceKind.AllowedTags).Length != 0
 3273            && !fields.Contains(ItemFields.Tags))
 274        {
 0275            fields = [.. fields, ItemFields.Tags];
 276        }
 277
 3278        var dtoOptions = new DtoOptions { Fields = fields }
 3279            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 280
 3281        var item = _libraryManager.GetParentItem(parentId, userId);
 282        QueryResult<BaseItem> result;
 283
 3284        Guid[] linkedChildAncestorIds = [];
 3285        if (includeItemTypes.Length == 1
 3286            && (includeItemTypes[0] == BaseItemKind.BoxSet || includeItemTypes[0] == BaseItemKind.Playlist)
 3287            && item is not BoxSet
 3288            && item is not Playlist)
 289        {
 0290            var itemCollectionType = item is IHasCollectionType hct ? hct.CollectionType : null;
 0291            var targetCollectionType = includeItemTypes[0] == BaseItemKind.BoxSet
 0292                ? CollectionType.boxsets
 0293                : CollectionType.playlists;
 0294            if (parentId.HasValue && item is not UserRootFolder && itemCollectionType != targetCollectionType)
 295            {
 0296                linkedChildAncestorIds = [parentId.Value];
 297            }
 298
 0299            parentId = null;
 0300            item = _libraryManager.GetUserRootFolder();
 301        }
 302
 3303        if (item is not Folder folder)
 304        {
 0305            folder = _libraryManager.GetUserRootFolder();
 306        }
 307
 3308        CollectionType? collectionType = null;
 3309        if (folder is IHasCollectionType hasCollectionType)
 310        {
 0311            collectionType = hasCollectionType.CollectionType;
 312        }
 313
 3314        if (collectionType == CollectionType.playlists)
 315        {
 0316            recursive = true;
 0317            includeItemTypes = new[] { BaseItemKind.Playlist };
 318        }
 3319        else if (folder is ICollectionFolder)
 320        {
 321            // When the client doesn't specify recursive/includeItemTypes, force the query
 322            // through the database path where all filters (IsHD, genres, etc.) are applied.
 0323            recursive ??= true;
 0324            if (includeItemTypes.Length == 0)
 325            {
 0326                includeItemTypes = collectionType switch
 0327                {
 0328                    CollectionType.boxsets => [BaseItemKind.BoxSet],
 0329                    null => [BaseItemKind.Movie, BaseItemKind.Series],
 0330                    _ => []
 0331                };
 332            }
 333        }
 334
 3335        if (item is not UserRootFolder
 3336            // api keys can always access all folders
 3337            && !isApiKey
 3338            // check the item is visible for the user
 3339            && !item.IsVisible(user))
 340        {
 0341            _logger.LogWarning("{UserName} is not permitted to access Library {ItemName}", user!.Username, item.Name);
 0342            return Unauthorized($"{user.Username} is not permitted to access Library {item.Name}.");
 343        }
 344
 3345        if ((recursive.HasValue && recursive.Value) || ids.Length != 0 || item is not UserRootFolder)
 346        {
 0347            var query = new InternalItemsQuery(user)
 0348            {
 0349                IsPlayed = isPlayed,
 0350                MediaTypes = mediaTypes,
 0351                IncludeItemTypes = includeItemTypes,
 0352                ExcludeItemTypes = excludeItemTypes,
 0353                Recursive = recursive ?? false,
 0354                OrderBy = RequestHelpers.GetOrderBy(sortBy, sortOrder),
 0355                IsFavorite = isFavorite,
 0356                Limit = limit,
 0357                StartIndex = startIndex,
 0358                IsMissing = isMissing,
 0359                IsUnaired = isUnaired,
 0360                CollapseBoxSetItems = collapseBoxSetItems,
 0361                NameLessThan = nameLessThan,
 0362                NameStartsWith = nameStartsWith,
 0363                NameStartsWithOrGreater = nameStartsWithOrGreater,
 0364                HasImdbId = hasImdbId,
 0365                IsPlaceHolder = isPlaceHolder,
 0366                IsLocked = isLocked,
 0367                MinWidth = minWidth,
 0368                MinHeight = minHeight,
 0369                MaxWidth = maxWidth,
 0370                MaxHeight = maxHeight,
 0371                Is3D = is3D,
 0372                HasTvdbId = hasTvdbId,
 0373                HasTmdbId = hasTmdbId,
 0374                IsMovie = isMovie,
 0375                IsSeries = isSeries,
 0376                IsNews = isNews,
 0377                IsKids = isKids,
 0378                IsSports = isSports,
 0379                HasOverview = hasOverview,
 0380                HasOfficialRating = hasOfficialRating,
 0381                HasParentalRating = hasParentalRating,
 0382                HasSpecialFeature = hasSpecialFeature,
 0383                HasSubtitles = hasSubtitles,
 0384                HasThemeSong = hasThemeSong,
 0385                HasThemeVideo = hasThemeVideo,
 0386                HasTrailer = hasTrailer,
 0387                IsHD = isHd,
 0388                Is4K = is4K,
 0389                Tags = tags,
 0390                OfficialRatings = officialRatings,
 0391                Genres = genres,
 0392                ArtistIds = artistIds,
 0393                AlbumArtistIds = albumArtistIds,
 0394                ContributingArtistIds = contributingArtistIds,
 0395                GenreIds = genreIds,
 0396                StudioIds = studioIds,
 0397                Person = person,
 0398                PersonIds = personIds,
 0399                PersonTypes = personTypes,
 0400                Years = years,
 0401                ImageTypes = imageTypes,
 0402                VideoTypes = videoTypes,
 0403                AdjacentTo = adjacentTo,
 0404                ItemIds = ids,
 0405                MinCommunityRating = minCommunityRating,
 0406                MinCriticRating = minCriticRating,
 0407                ParentId = parentId ?? Guid.Empty,
 0408                IndexNumber = indexNumber,
 0409                ParentIndexNumber = parentIndexNumber,
 0410                EnableTotalRecordCount = enableTotalRecordCount,
 0411                ExcludeItemIds = excludeItemIds,
 0412                DtoOptions = dtoOptions,
 0413                SearchTerm = searchTerm,
 0414                MinDateLastSaved = minDateLastSaved?.ToUniversalTime(),
 0415                MinDateLastSavedForUser = minDateLastSavedForUser?.ToUniversalTime(),
 0416                MinPremiereDate = minPremiereDate?.ToUniversalTime(),
 0417                MaxPremiereDate = maxPremiereDate?.ToUniversalTime(),
 0418                AudioLanguages = audioLanguages,
 0419                SubtitleLanguages = subtitleLanguages,
 0420                LinkedChildAncestorIds = linkedChildAncestorIds,
 0421            };
 422
 0423            if (ids.Length != 0 || !string.IsNullOrWhiteSpace(searchTerm))
 424            {
 0425                query.CollapseBoxSetItems = false;
 426            }
 427
 0428            if (query.SubtitleLanguages.Count > 0 && query.HasSubtitles.HasValue)
 429            {
 0430                if (query.HasSubtitles.Value)
 431                {
 432                    // if we check for specific subtitles we don't need a separate check for subtitle existence
 0433                    query.HasSubtitles = null;
 434                }
 435                else
 436                {
 437                    // if we search for items without subtitles, we don't need to check for subtitles of a specific lang
 0438                    query.SubtitleLanguages = [];
 439                }
 440            }
 441
 442            // for filter values that rely on media streams, we need to include alternative and linked versions
 0443            if (query.HasSubtitles.HasValue
 0444                || query.SubtitleLanguages.Count > 0
 0445                || query.AudioLanguages.Count > 0
 0446                || query.Is3D.HasValue
 0447                || query.IsHD.HasValue
 0448                || query.Is4K.HasValue
 0449                || query.VideoTypes.Length > 0
 0450            )
 451            {
 0452                query.IncludeOwnedItems = true;
 453            }
 454
 0455            query.ApplyFilters(filters);
 456
 457            // Filter by Series Status
 0458            if (seriesStatus.Length != 0)
 459            {
 0460                query.SeriesStatuses = seriesStatus;
 461            }
 462
 463            // Exclude Blocked Unrated Items
 0464            var blockedUnratedItems = user?.GetPreferenceValues<UnratedItem>(PreferenceKind.BlockUnratedItems);
 0465            if (blockedUnratedItems is not null)
 466            {
 0467                query.BlockUnratedItems = blockedUnratedItems;
 468            }
 469
 470            // ExcludeLocationTypes
 0471            if (excludeLocationTypes.Any(t => t == LocationType.Virtual))
 472            {
 0473                query.IsVirtualItem = false;
 474            }
 475
 0476            if (locationTypes.Length > 0 && locationTypes.Length < 4)
 477            {
 0478                query.IsVirtualItem = locationTypes.Contains(LocationType.Virtual);
 479            }
 480
 481            // Min official rating
 0482            if (!string.IsNullOrWhiteSpace(minOfficialRating))
 483            {
 0484                query.MinParentalRating = _localization.GetRatingScore(minOfficialRating);
 485            }
 486
 487            // Max official rating
 0488            if (!string.IsNullOrWhiteSpace(maxOfficialRating))
 489            {
 0490                query.MaxParentalRating = _localization.GetRatingScore(maxOfficialRating);
 491            }
 492
 493            // Artists
 0494            if (artists.Length != 0)
 495            {
 0496                query.ArtistIds = artists.Select(i =>
 0497                {
 0498                    try
 0499                    {
 0500                        return _libraryManager.GetArtist(i, new DtoOptions(false));
 0501                    }
 0502                    catch
 0503                    {
 0504                        return null;
 0505                    }
 0506                }).Where(i => i is not null).Select(i => i!.Id).ToArray();
 507            }
 508
 509            // ExcludeArtistIds
 0510            if (excludeArtistIds.Length != 0)
 511            {
 0512                query.ExcludeArtistIds = excludeArtistIds;
 513            }
 514
 0515            if (albumIds.Length != 0)
 516            {
 0517                query.AlbumIds = albumIds;
 518            }
 519
 520            // Albums
 0521            if (albums.Length != 0)
 522            {
 0523                query.AlbumIds = albums.SelectMany(i =>
 0524                {
 0525                    return _libraryManager.GetItemIds(new InternalItemsQuery { IncludeItemTypes = new[] { BaseItemKind.M
 0526                }).ToArray();
 527            }
 528
 529            // Studios
 0530            if (studios.Length != 0)
 531            {
 0532                query.StudioIds = studios.Select(i =>
 0533                {
 0534                    try
 0535                    {
 0536                        return _libraryManager.GetStudio(i);
 0537                    }
 0538                    catch
 0539                    {
 0540                        return null;
 0541                    }
 0542                }).Where(i => i is not null).Select(i => i!.Id).ToArray();
 543            }
 544
 545            // Apply default sorting if none requested
 0546            if (query.OrderBy.Count == 0)
 547            {
 548                // Albums by artist
 0549                if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == Bas
 550                {
 0551                    query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, Sor
 552                }
 553            }
 554
 0555            query.Parent = null;
 0556            result = folder.GetItems(query);
 557        }
 558        else
 559        {
 3560            var itemsArray = folder.GetChildren(user, true);
 3561            result = new QueryResult<BaseItem>(itemsArray);
 562        }
 563
 3564        return new QueryResult<BaseItemDto>(
 3565            startIndex,
 3566            result.TotalRecordCount,
 3567            _dtoService.GetBaseItemDtos(result.Items, dtoOptions, user, skipVisibilityCheck: true));
 3568    }
 569
 570    /// <summary>
 571    /// Gets items based on a query.
 572    /// </summary>
 573    /// <param name="userId">The user id supplied as query parameter.</param>
 574    /// <param name="maxOfficialRating">Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).</param>
 575    /// <param name="hasThemeSong">Optional filter by items with theme songs.</param>
 576    /// <param name="hasThemeVideo">Optional filter by items with theme videos.</param>
 577    /// <param name="hasSubtitles">Optional filter by items with subtitles.</param>
 578    /// <param name="hasSpecialFeature">Optional filter by items with special features.</param>
 579    /// <param name="hasTrailer">Optional filter by items with trailers.</param>
 580    /// <param name="adjacentTo">Optional. Return items that are siblings of a supplied item.</param>
 581    /// <param name="parentIndexNumber">Optional filter by parent index number.</param>
 582    /// <param name="hasParentalRating">Optional filter by items that have or do not have a parental rating.</param>
 583    /// <param name="isHd">Optional filter by items that are HD or not.</param>
 584    /// <param name="is4K">Optional filter by items that are 4K or not.</param>
 585    /// <param name="locationTypes">Optional. If specified, results will be filtered based on LocationType. This allows 
 586    /// <param name="excludeLocationTypes">Optional. If specified, results will be filtered based on the LocationType. T
 587    /// <param name="isMissing">Optional filter by items that are missing episodes or not.</param>
 588    /// <param name="isUnaired">Optional filter by items that are unaired episodes or not.</param>
 589    /// <param name="minCommunityRating">Optional filter by minimum community rating.</param>
 590    /// <param name="minCriticRating">Optional filter by minimum critic rating.</param>
 591    /// <param name="minPremiereDate">Optional. The minimum premiere date. Format = ISO.</param>
 592    /// <param name="minDateLastSaved">Optional. The minimum last saved date. Format = ISO.</param>
 593    /// <param name="minDateLastSavedForUser">Optional. The minimum last saved date for the current user. Format = ISO.<
 594    /// <param name="maxPremiereDate">Optional. The maximum premiere date. Format = ISO.</param>
 595    /// <param name="hasOverview">Optional filter by items that have an overview or not.</param>
 596    /// <param name="hasImdbId">Optional filter by items that have an IMDb id or not.</param>
 597    /// <param name="hasTmdbId">Optional filter by items that have a TMDb id or not.</param>
 598    /// <param name="hasTvdbId">Optional filter by items that have a TVDb id or not.</param>
 599    /// <param name="isMovie">Optional filter for live tv movies.</param>
 600    /// <param name="isSeries">Optional filter for live tv series.</param>
 601    /// <param name="isNews">Optional filter for live tv news.</param>
 602    /// <param name="isKids">Optional filter for live tv kids.</param>
 603    /// <param name="isSports">Optional filter for live tv sports.</param>
 604    /// <param name="excludeItemIds">Optional. If specified, results will be filtered by excluding item ids. This allows
 605    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 606    /// <param name="limit">Optional. The maximum number of records to return.</param>
 607    /// <param name="recursive">When searching within folders, this determines whether or not the search will be recursi
 608    /// <param name="searchTerm">Optional. Filter based on a search term.</param>
 609    /// <param name="sortOrder">Sort Order - Ascending, Descending.</param>
 610    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 611    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 612    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 613    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 614    /// <param name="filters">Optional. Specify additional filters to apply. This allows multiple, comma delimited. Opti
 615    /// <param name="isFavorite">Optional filter by items that are marked as favorite, or not.</param>
 616    /// <param name="mediaTypes">Optional filter by MediaType. Allows multiple, comma delimited.</param>
 617    /// <param name="imageTypes">Optional. If specified, results will be filtered based on those containing image types.
 618    /// <param name="sortBy">Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Ar
 619    /// <param name="isPlayed">Optional filter by items that are played, or not.</param>
 620    /// <param name="genres">Optional. If specified, results will be filtered based on genre. This allows multiple, pipe
 621    /// <param name="officialRatings">Optional. If specified, results will be filtered based on OfficialRating. This all
 622    /// <param name="tags">Optional. If specified, results will be filtered based on tag. This allows multiple, pipe del
 623    /// <param name="years">Optional. If specified, results will be filtered based on production year. This allows multi
 624    /// <param name="enableUserData">Optional, include user data.</param>
 625    /// <param name="imageTypeLimit">Optional, the max number of images to return, per image type.</param>
 626    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 627    /// <param name="person">Optional. If specified, results will be filtered to include only those containing the speci
 628    /// <param name="personIds">Optional. If specified, results will be filtered to include only those containing the sp
 629    /// <param name="personTypes">Optional. If specified, along with Person, results will be filtered to include only th
 630    /// <param name="studios">Optional. If specified, results will be filtered based on studio. This allows multiple, pi
 631    /// <param name="artists">Optional. If specified, results will be filtered based on artists. This allows multiple, p
 632    /// <param name="excludeArtistIds">Optional. If specified, results will be filtered based on artist id. This allows 
 633    /// <param name="artistIds">Optional. If specified, results will be filtered to include only those containing the sp
 634    /// <param name="albumArtistIds">Optional. If specified, results will be filtered to include only those containing t
 635    /// <param name="contributingArtistIds">Optional. If specified, results will be filtered to include only those conta
 636    /// <param name="albums">Optional. If specified, results will be filtered based on album. This allows multiple, pipe
 637    /// <param name="albumIds">Optional. If specified, results will be filtered based on album id. This allows multiple,
 638    /// <param name="ids">Optional. If specific items are needed, specify a list of item id's to retrieve. This allows m
 639    /// <param name="videoTypes">Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma deli
 640    /// <param name="minOfficialRating">Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).</param>
 641    /// <param name="isLocked">Optional filter by items that are locked.</param>
 642    /// <param name="isPlaceHolder">Optional filter by items that are placeholders.</param>
 643    /// <param name="hasOfficialRating">Optional filter by items that have official ratings.</param>
 644    /// <param name="collapseBoxSetItems">Whether or not to hide items behind their boxsets.</param>
 645    /// <param name="minWidth">Optional. Filter by the minimum width of the item.</param>
 646    /// <param name="minHeight">Optional. Filter by the minimum height of the item.</param>
 647    /// <param name="maxWidth">Optional. Filter by the maximum width of the item.</param>
 648    /// <param name="maxHeight">Optional. Filter by the maximum height of the item.</param>
 649    /// <param name="is3D">Optional filter by items that are 3D, or not.</param>
 650    /// <param name="seriesStatus">Optional filter by Series Status. Allows multiple, comma delimited.</param>
 651    /// <param name="nameStartsWithOrGreater">Optional filter by items whose name is sorted equally or greater than a gi
 652    /// <param name="nameStartsWith">Optional filter by items whose name is sorted equally than a given input string.</p
 653    /// <param name="nameLessThan">Optional filter by items whose name is equally or lesser than a given input string.</
 654    /// <param name="studioIds">Optional. If specified, results will be filtered based on studio id. This allows multipl
 655    /// <param name="genreIds">Optional. If specified, results will be filtered based on genre id. This allows multiple,
 656    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 657    /// <param name="enableImages">Optional, include image information in output.</param>
 658    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items.</returns>
 659    [HttpGet("Users/{userId}/Items")]
 660    [Obsolete("Kept for backwards compatibility")]
 661    [ApiExplorerSettings(IgnoreApi = true)]
 662    [ProducesResponseType(StatusCodes.Status200OK)]
 663    public async Task<ActionResult<QueryResult<BaseItemDto>>> GetItemsByUserIdLegacy(
 664        [FromRoute] Guid userId,
 665        [FromQuery] string? maxOfficialRating,
 666        [FromQuery] bool? hasThemeSong,
 667        [FromQuery] bool? hasThemeVideo,
 668        [FromQuery] bool? hasSubtitles,
 669        [FromQuery] bool? hasSpecialFeature,
 670        [FromQuery] bool? hasTrailer,
 671        [FromQuery] Guid? adjacentTo,
 672        [FromQuery] int? parentIndexNumber,
 673        [FromQuery] bool? hasParentalRating,
 674        [FromQuery] bool? isHd,
 675        [FromQuery] bool? is4K,
 676        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] locationTypes,
 677        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] LocationType[] excludeLocationTypes,
 678        [FromQuery] bool? isMissing,
 679        [FromQuery] bool? isUnaired,
 680        [FromQuery] double? minCommunityRating,
 681        [FromQuery] double? minCriticRating,
 682        [FromQuery] DateTime? minPremiereDate,
 683        [FromQuery] DateTime? minDateLastSaved,
 684        [FromQuery] DateTime? minDateLastSavedForUser,
 685        [FromQuery] DateTime? maxPremiereDate,
 686        [FromQuery] bool? hasOverview,
 687        [FromQuery] bool? hasImdbId,
 688        [FromQuery] bool? hasTmdbId,
 689        [FromQuery] bool? hasTvdbId,
 690        [FromQuery] bool? isMovie,
 691        [FromQuery] bool? isSeries,
 692        [FromQuery] bool? isNews,
 693        [FromQuery] bool? isKids,
 694        [FromQuery] bool? isSports,
 695        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeItemIds,
 696        [FromQuery] int? startIndex,
 697        [FromQuery] int? limit,
 698        [FromQuery] bool? recursive,
 699        [FromQuery] string? searchTerm,
 700        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SortOrder[] sortOrder,
 701        [FromQuery] Guid? parentId,
 702        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 703        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 704        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 705        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFilter[] filters,
 706        [FromQuery] bool? isFavorite,
 707        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 708        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] imageTypes,
 709        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemSortBy[] sortBy,
 710        [FromQuery] bool? isPlayed,
 711        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] genres,
 712        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] officialRatings,
 713        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] tags,
 714        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] int[] years,
 715        [FromQuery] bool? enableUserData,
 716        [FromQuery] int? imageTypeLimit,
 717        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 718        [FromQuery] string? person,
 719        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] personIds,
 720        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] personTypes,
 721        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] studios,
 722        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] artists,
 723        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] excludeArtistIds,
 724        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] artistIds,
 725        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumArtistIds,
 726        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] contributingArtistIds,
 727        [FromQuery, ModelBinder(typeof(PipeDelimitedCollectionModelBinder))] string[] albums,
 728        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] albumIds,
 729        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
 730        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] VideoType[] videoTypes,
 731        [FromQuery] string? minOfficialRating,
 732        [FromQuery] bool? isLocked,
 733        [FromQuery] bool? isPlaceHolder,
 734        [FromQuery] bool? hasOfficialRating,
 735        [FromQuery] bool? collapseBoxSetItems,
 736        [FromQuery] int? minWidth,
 737        [FromQuery] int? minHeight,
 738        [FromQuery] int? maxWidth,
 739        [FromQuery] int? maxHeight,
 740        [FromQuery] bool? is3D,
 741        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] SeriesStatus[] seriesStatus,
 742        [FromQuery] string? nameStartsWithOrGreater,
 743        [FromQuery] string? nameStartsWith,
 744        [FromQuery] string? nameLessThan,
 745        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] studioIds,
 746        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] genreIds,
 747        [FromQuery] bool enableTotalRecordCount = true,
 748        [FromQuery] bool? enableImages = true)
 749        => await GetItems(
 750            userId,
 751            maxOfficialRating,
 752            hasThemeSong,
 753            hasThemeVideo,
 754            hasSubtitles,
 755            hasSpecialFeature,
 756            hasTrailer,
 757            adjacentTo,
 758            null,
 759            parentIndexNumber,
 760            hasParentalRating,
 761            isHd,
 762            is4K,
 763            locationTypes,
 764            excludeLocationTypes,
 765            isMissing,
 766            isUnaired,
 767            minCommunityRating,
 768            minCriticRating,
 769            minPremiereDate,
 770            minDateLastSaved,
 771            minDateLastSavedForUser,
 772            maxPremiereDate,
 773            hasOverview,
 774            hasImdbId,
 775            hasTmdbId,
 776            hasTvdbId,
 777            isMovie,
 778            isSeries,
 779            isNews,
 780            isKids,
 781            isSports,
 782            excludeItemIds,
 783            startIndex,
 784            limit,
 785            recursive,
 786            searchTerm,
 787            sortOrder,
 788            parentId,
 789            fields,
 790            excludeItemTypes,
 791            includeItemTypes,
 792            filters,
 793            isFavorite,
 794            mediaTypes,
 795            imageTypes,
 796            sortBy,
 797            isPlayed,
 798            genres,
 799            officialRatings,
 800            tags,
 801            years,
 802            enableUserData,
 803            imageTypeLimit,
 804            enableImageTypes,
 805            person,
 806            personIds,
 807            personTypes,
 808            studios,
 809            artists,
 810            excludeArtistIds,
 811            artistIds,
 812            albumArtistIds,
 813            contributingArtistIds,
 814            albums,
 815            albumIds,
 816            ids,
 817            videoTypes,
 818            minOfficialRating,
 819            isLocked,
 820            isPlaceHolder,
 821            hasOfficialRating,
 822            collapseBoxSetItems,
 823            minWidth,
 824            minHeight,
 825            maxWidth,
 826            maxHeight,
 827            is3D,
 828            seriesStatus,
 829            nameStartsWithOrGreater,
 830            nameStartsWith,
 831            nameLessThan,
 832            studioIds,
 833            genreIds,
 834            [],
 835            [],
 836            enableTotalRecordCount,
 837            enableImages).ConfigureAwait(false);
 838
 839    /// <summary>
 840    /// Gets items based on a query.
 841    /// </summary>
 842    /// <param name="userId">The user id.</param>
 843    /// <param name="startIndex">The start index.</param>
 844    /// <param name="limit">The item limit.</param>
 845    /// <param name="searchTerm">The search term.</param>
 846    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 847    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 848    /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 849    /// <param name="enableUserData">Optional. Include user data.</param>
 850    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 851    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 852    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 853    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 854    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 855    /// <param name="enableImages">Optional. Include image information in output.</param>
 856    /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param>
 857    /// <response code="200">Items returned.</response>
 858    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
 859    [HttpGet("UserItems/Resume")]
 860    [ProducesResponseType(StatusCodes.Status200OK)]
 861    public ActionResult<QueryResult<BaseItemDto>> GetResumeItems(
 862        [FromQuery] Guid? userId,
 863        [FromQuery] int? startIndex,
 864        [FromQuery] int? limit,
 865        [FromQuery] string? searchTerm,
 866        [FromQuery] Guid? parentId,
 867        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 868        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 869        [FromQuery] bool? enableUserData,
 870        [FromQuery] int? imageTypeLimit,
 871        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 872        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 873        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 874        [FromQuery] bool enableTotalRecordCount = true,
 875        [FromQuery] bool? enableImages = true,
 876        [FromQuery] bool excludeActiveSessions = false)
 877    {
 2878        var requestUserId = RequestHelpers.GetUserId(User, userId);
 2879        var user = _userManager.GetUserById(requestUserId);
 2880        if (user is null)
 881        {
 1882            return NotFound();
 883        }
 884
 1885        var parentIdGuid = parentId ?? Guid.Empty;
 1886        var dtoOptions = new DtoOptions { Fields = fields }
 1887            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 888
 1889        var ancestorIds = Array.Empty<Guid>();
 890
 1891        var excludeFolderIds = user.GetPreferenceValues<Guid>(PreferenceKind.LatestItemExcludes);
 1892        if (parentIdGuid.IsEmpty() && excludeFolderIds.Length > 0)
 893        {
 0894            ancestorIds = _libraryManager.GetUserRootFolder().GetChildren(user, true)
 0895                .Where(i => i is Folder)
 0896                .Where(i => !excludeFolderIds.Contains(i.Id))
 0897                .Select(i => i.Id)
 0898                .ToArray();
 899        }
 900
 1901        var excludeItemIds = Array.Empty<Guid>();
 1902        if (excludeActiveSessions)
 903        {
 0904            excludeItemIds = _sessionManager.Sessions
 0905                .Where(s => s.UserId.Equals(requestUserId) && s.NowPlayingItem is not null)
 0906                .Select(s => s.NowPlayingItem.Id)
 0907                .ToArray();
 908        }
 909
 1910        var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
 1911        {
 1912            OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
 1913            IsResumable = true,
 1914            StartIndex = startIndex,
 1915            Limit = limit,
 1916            ParentId = parentIdGuid,
 1917            Recursive = true,
 1918            DtoOptions = dtoOptions,
 1919            MediaTypes = mediaTypes,
 1920            IsVirtualItem = false,
 1921            CollapseBoxSetItems = false,
 1922            EnableTotalRecordCount = enableTotalRecordCount,
 1923            AncestorIds = ancestorIds,
 1924            IncludeItemTypes = includeItemTypes,
 1925            ExcludeItemTypes = excludeItemTypes,
 1926            SearchTerm = searchTerm,
 1927            ExcludeItemIds = excludeItemIds
 1928        });
 929
 1930        var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, dtoOptions, user);
 931
 1932        return new QueryResult<BaseItemDto>(
 1933            startIndex,
 1934            itemsResult.TotalRecordCount,
 1935            returnItems);
 936    }
 937
 938    /// <summary>
 939    /// Gets items based on a query.
 940    /// </summary>
 941    /// <param name="userId">The user id.</param>
 942    /// <param name="startIndex">The start index.</param>
 943    /// <param name="limit">The item limit.</param>
 944    /// <param name="searchTerm">The search term.</param>
 945    /// <param name="parentId">Specify this to localize the search to a specific item or folder. Omit to use the root.</
 946    /// <param name="fields">Optional. Specify additional fields of information to return in the output. This allows mul
 947    /// <param name="mediaTypes">Optional. Filter by MediaType. Allows multiple, comma delimited.</param>
 948    /// <param name="enableUserData">Optional. Include user data.</param>
 949    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 950    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 951    /// <param name="excludeItemTypes">Optional. If specified, results will be filtered based on item type. This allows 
 952    /// <param name="includeItemTypes">Optional. If specified, results will be filtered based on the item type. This all
 953    /// <param name="enableTotalRecordCount">Optional. Enable the total record count.</param>
 954    /// <param name="enableImages">Optional. Include image information in output.</param>
 955    /// <param name="excludeActiveSessions">Optional. Whether to exclude the currently active sessions.</param>
 956    /// <response code="200">Items returned.</response>
 957    /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the items that are resumable.</returns>
 958    [HttpGet("Users/{userId}/Items/Resume")]
 959    [Obsolete("Kept for backwards compatibility")]
 960    [ApiExplorerSettings(IgnoreApi = true)]
 961    [ProducesResponseType(StatusCodes.Status200OK)]
 962    public ActionResult<QueryResult<BaseItemDto>> GetResumeItemsLegacy(
 963        [FromRoute, Required] Guid userId,
 964        [FromQuery] int? startIndex,
 965        [FromQuery] int? limit,
 966        [FromQuery] string? searchTerm,
 967        [FromQuery] Guid? parentId,
 968        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 969        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] MediaType[] mediaTypes,
 970        [FromQuery] bool? enableUserData,
 971        [FromQuery] int? imageTypeLimit,
 972        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes,
 973        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] excludeItemTypes,
 974        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] BaseItemKind[] includeItemTypes,
 975        [FromQuery] bool enableTotalRecordCount = true,
 976        [FromQuery] bool? enableImages = true,
 977        [FromQuery] bool excludeActiveSessions = false)
 978    => GetResumeItems(
 979        userId,
 980        startIndex,
 981        limit,
 982        searchTerm,
 983        parentId,
 984        fields,
 985        mediaTypes,
 986        enableUserData,
 987        imageTypeLimit,
 988        enableImageTypes,
 989        excludeItemTypes,
 990        includeItemTypes,
 991        enableTotalRecordCount,
 992        enableImages,
 993        excludeActiveSessions);
 994
 995    /// <summary>
 996    /// Get Item User Data.
 997    /// </summary>
 998    /// <param name="userId">The user id.</param>
 999    /// <param name="itemId">The item id.</param>
 1000    /// <response code="200">return item user data.</response>
 1001    /// <response code="404">Item is not found.</response>
 1002    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1003    [HttpGet("UserItems/{itemId}/UserData")]
 1004    [ProducesResponseType(StatusCodes.Status200OK)]
 1005    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1006    [Tags("UserData")]
 1007    public ActionResult<UserItemDataDto?> GetItemUserData(
 1008        [FromQuery] Guid? userId,
 1009        [FromRoute, Required] Guid itemId)
 1010    {
 01011        var requestUserId = RequestHelpers.GetUserId(User, userId);
 01012        var user = _userManager.GetUserById(requestUserId);
 01013        if (user is null)
 1014        {
 01015            return NotFound();
 1016        }
 1017
 01018        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 1019        {
 01020            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to view this item user data.");
 1021        }
 1022
 01023        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 01024        if (item is null)
 1025        {
 01026            return NotFound();
 1027        }
 1028
 01029        return _userDataRepository.GetUserDataDto(item, user);
 1030    }
 1031
 1032    /// <summary>
 1033    /// Get Item User Data.
 1034    /// </summary>
 1035    /// <param name="userId">The user id.</param>
 1036    /// <param name="itemId">The item id.</param>
 1037    /// <response code="200">return item user data.</response>
 1038    /// <response code="404">Item is not found.</response>
 1039    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1040    [HttpGet("Users/{userId}/Items/{itemId}/UserData")]
 1041    [ProducesResponseType(StatusCodes.Status200OK)]
 1042    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1043    [Obsolete("Kept for backwards compatibility")]
 1044    [ApiExplorerSettings(IgnoreApi = true)]
 1045    public ActionResult<UserItemDataDto?> GetItemUserDataLegacy(
 1046        [FromRoute, Required] Guid userId,
 1047        [FromRoute, Required] Guid itemId)
 1048        => GetItemUserData(userId, itemId);
 1049
 1050    /// <summary>
 1051    /// Update Item User Data.
 1052    /// </summary>
 1053    /// <param name="userId">The user id.</param>
 1054    /// <param name="itemId">The item id.</param>
 1055    /// <param name="userDataDto">New user data object.</param>
 1056    /// <response code="200">return updated user item data.</response>
 1057    /// <response code="404">Item is not found.</response>
 1058    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1059    [HttpPost("UserItems/{itemId}/UserData")]
 1060    [ProducesResponseType(StatusCodes.Status200OK)]
 1061    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1062    [Tags("UserData")]
 1063    public ActionResult<UserItemDataDto?> UpdateItemUserData(
 1064        [FromQuery] Guid? userId,
 1065        [FromRoute, Required] Guid itemId,
 1066        [FromBody, Required] UpdateUserItemDataDto userDataDto)
 1067    {
 01068        var requestUserId = RequestHelpers.GetUserId(User, userId);
 01069        var user = _userManager.GetUserById(requestUserId);
 01070        if (user is null)
 1071        {
 01072            return NotFound();
 1073        }
 1074
 01075        if (!RequestHelpers.AssertCanUpdateUser(User, user, true))
 1076        {
 01077            return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update this item user data.");
 1078        }
 1079
 01080        var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
 01081        if (item is null)
 1082        {
 01083            return NotFound();
 1084        }
 1085
 01086        _userDataRepository.SaveUserData(user, item, userDataDto, UserDataSaveReason.UpdateUserData);
 1087
 01088        return _userDataRepository.GetUserDataDto(item, user);
 1089    }
 1090
 1091    /// <summary>
 1092    /// Update Item User Data.
 1093    /// </summary>
 1094    /// <param name="userId">The user id.</param>
 1095    /// <param name="itemId">The item id.</param>
 1096    /// <param name="userDataDto">New user data object.</param>
 1097    /// <response code="200">return updated user item data.</response>
 1098    /// <response code="404">Item is not found.</response>
 1099    /// <returns>Return <see cref="UserItemDataDto"/>.</returns>
 1100    [HttpPost("Users/{userId}/Items/{itemId}/UserData")]
 1101    [ProducesResponseType(StatusCodes.Status200OK)]
 1102    [ProducesResponseType(StatusCodes.Status404NotFound)]
 1103    [Obsolete("Kept for backwards compatibility")]
 1104    [ApiExplorerSettings(IgnoreApi = true)]
 1105    public ActionResult<UserItemDataDto?> UpdateItemUserDataLegacy(
 1106        [FromRoute, Required] Guid userId,
 1107        [FromRoute, Required] Guid itemId,
 1108        [FromBody, Required] UpdateUserItemDataDto userDataDto)
 1109        => UpdateItemUserData(userId, itemId, userDataDto);
 1110}