< Summary - Jellyfin

Information
Class: Jellyfin.Api.Controllers.PlaylistsController
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Controllers/PlaylistsController.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 67
Coverable lines: 67
Total lines: 548
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 34
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

0255075100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%210%
GetPlaylist(...)0%620%
GetPlaylistUsers(...)0%2040%
GetPlaylistUser(...)0%110100%
GetPlaylistItems(...)0%342180%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.ComponentModel.DataAnnotations;
 4using System.Globalization;
 5using System.Linq;
 6using System.Threading.Tasks;
 7using Jellyfin.Api.Attributes;
 8using Jellyfin.Api.Extensions;
 9using Jellyfin.Api.Helpers;
 10using Jellyfin.Api.ModelBinders;
 11using Jellyfin.Api.Models.PlaylistDtos;
 12using Jellyfin.Data.Enums;
 13using Jellyfin.Extensions;
 14using MediaBrowser.Controller.Dto;
 15using MediaBrowser.Controller.Library;
 16using MediaBrowser.Controller.Playlists;
 17using MediaBrowser.Model.Dto;
 18using MediaBrowser.Model.Entities;
 19using MediaBrowser.Model.Playlists;
 20using MediaBrowser.Model.Querying;
 21using Microsoft.AspNetCore.Authorization;
 22using Microsoft.AspNetCore.Http;
 23using Microsoft.AspNetCore.Mvc;
 24using Microsoft.AspNetCore.Mvc.ModelBinding;
 25
 26namespace Jellyfin.Api.Controllers;
 27
 28/// <summary>
 29/// Playlists controller.
 30/// </summary>
 31[Authorize]
 32public class PlaylistsController : BaseJellyfinApiController
 33{
 34    private readonly IPlaylistManager _playlistManager;
 35    private readonly IDtoService _dtoService;
 36    private readonly IUserManager _userManager;
 37    private readonly ILibraryManager _libraryManager;
 38
 39    /// <summary>
 40    /// Initializes a new instance of the <see cref="PlaylistsController"/> class.
 41    /// </summary>
 42    /// <param name="dtoService">Instance of the <see cref="IDtoService"/> interface.</param>
 43    /// <param name="playlistManager">Instance of the <see cref="IPlaylistManager"/> interface.</param>
 44    /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
 45    /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
 046    public PlaylistsController(
 047        IDtoService dtoService,
 048        IPlaylistManager playlistManager,
 049        IUserManager userManager,
 050        ILibraryManager libraryManager)
 51    {
 052        _dtoService = dtoService;
 053        _playlistManager = playlistManager;
 054        _userManager = userManager;
 055        _libraryManager = libraryManager;
 056    }
 57
 58    /// <summary>
 59    /// Creates a new playlist.
 60    /// </summary>
 61    /// <remarks>
 62    /// For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.
 63    /// Query parameters are obsolete.
 64    /// </remarks>
 65    /// <param name="name">The playlist name.</param>
 66    /// <param name="ids">The item ids.</param>
 67    /// <param name="userId">The user id.</param>
 68    /// <param name="mediaType">The media type.</param>
 69    /// <param name="createPlaylistRequest">The create playlist payload.</param>
 70    /// <response code="200">Playlist created.</response>
 71    /// <returns>
 72    /// A <see cref="Task" /> that represents the asynchronous operation to create a playlist.
 73    /// The task result contains an <see cref="OkResult"/> indicating success.
 74    /// </returns>
 75    [HttpPost]
 76    [ProducesResponseType(StatusCodes.Status200OK)]
 77    public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
 78        [FromQuery, ParameterObsolete] string? name,
 79        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids
 80        [FromQuery, ParameterObsolete] Guid? userId,
 81        [FromQuery, ParameterObsolete] MediaType? mediaType,
 82        [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] CreatePlaylistDto? createPlaylistRequest)
 83    {
 84        if (ids.Count == 0)
 85        {
 86            ids = createPlaylistRequest?.Ids ?? Array.Empty<Guid>();
 87        }
 88
 89        userId ??= createPlaylistRequest?.UserId ?? default;
 90        userId = RequestHelpers.GetUserId(User, userId);
 91        var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
 92        {
 93            Name = name ?? createPlaylistRequest?.Name,
 94            ItemIdList = ids,
 95            UserId = userId.Value,
 96            MediaType = mediaType ?? createPlaylistRequest?.MediaType,
 97            Users = createPlaylistRequest?.Users.ToArray() ?? [],
 98            Public = createPlaylistRequest?.IsPublic
 99        }).ConfigureAwait(false);
 100
 101        return result;
 102    }
 103
 104    /// <summary>
 105    /// Updates a playlist.
 106    /// </summary>
 107    /// <param name="playlistId">The playlist id.</param>
 108    /// <param name="updatePlaylistRequest">The <see cref="UpdatePlaylistDto"/> id.</param>
 109    /// <response code="204">Playlist updated.</response>
 110    /// <response code="403">Access forbidden.</response>
 111    /// <response code="404">Playlist not found.</response>
 112    /// <returns>
 113    /// A <see cref="Task" /> that represents the asynchronous operation to update a playlist.
 114    /// The task result contains an <see cref="OkResult"/> indicating success.
 115    /// </returns>
 116    [HttpPost("{playlistId}")]
 117    [ProducesResponseType(StatusCodes.Status204NoContent)]
 118    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 119    [ProducesResponseType(StatusCodes.Status404NotFound)]
 120    public async Task<ActionResult> UpdatePlaylist(
 121        [FromRoute, Required] Guid playlistId,
 122        [FromBody, Required] UpdatePlaylistDto updatePlaylistRequest)
 123    {
 124        var callingUserId = User.GetUserId();
 125
 126        var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
 127        if (playlist is null)
 128        {
 129            return NotFound("Playlist not found");
 130        }
 131
 132        var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
 133            || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
 134
 135        if (!isPermitted)
 136        {
 137            return Forbid();
 138        }
 139
 140        await _playlistManager.UpdatePlaylist(new PlaylistUpdateRequest
 141        {
 142            UserId = callingUserId,
 143            Id = playlistId,
 144            Name = updatePlaylistRequest.Name,
 145            Ids = updatePlaylistRequest.Ids,
 146            Users = updatePlaylistRequest.Users,
 147            Public = updatePlaylistRequest.IsPublic
 148        }).ConfigureAwait(false);
 149
 150        return NoContent();
 151    }
 152
 153    /// <summary>
 154    /// Get a playlist.
 155    /// </summary>
 156    /// <param name="playlistId">The playlist id.</param>
 157    /// <response code="200">The playlist.</response>
 158    /// <response code="404">Playlist not found.</response>
 159    /// <returns>
 160    /// A <see cref="Playlist"/> objects.
 161    /// </returns>
 162    [HttpGet("{playlistId}")]
 163    [ProducesResponseType(StatusCodes.Status200OK)]
 164    [ProducesResponseType(StatusCodes.Status404NotFound)]
 165    public ActionResult<PlaylistDto> GetPlaylist(
 166        [FromRoute, Required] Guid playlistId)
 167    {
 0168        var userId = User.GetUserId();
 169
 0170        var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId);
 0171        if (playlist is null)
 172        {
 0173            return NotFound("Playlist not found");
 174        }
 175
 0176        return new PlaylistDto()
 0177        {
 0178            Shares = playlist.Shares,
 0179            OpenAccess = playlist.OpenAccess,
 0180            ItemIds = playlist.GetManageableItems().Select(t => t.Item2.Id).ToList()
 0181        };
 182    }
 183
 184    /// <summary>
 185    /// Get a playlist's users.
 186    /// </summary>
 187    /// <param name="playlistId">The playlist id.</param>
 188    /// <response code="200">Found shares.</response>
 189    /// <response code="403">Access forbidden.</response>
 190    /// <response code="404">Playlist not found.</response>
 191    /// <returns>
 192    /// A list of <see cref="PlaylistUserPermissions"/> objects.
 193    /// </returns>
 194    [HttpGet("{playlistId}/Users")]
 195    [ProducesResponseType(StatusCodes.Status200OK)]
 196    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 197    [ProducesResponseType(StatusCodes.Status404NotFound)]
 198    public ActionResult<IReadOnlyList<PlaylistUserPermissions>> GetPlaylistUsers(
 199        [FromRoute, Required] Guid playlistId)
 200    {
 0201        var userId = User.GetUserId();
 202
 0203        var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId);
 0204        if (playlist is null)
 205        {
 0206            return NotFound("Playlist not found");
 207        }
 208
 0209        var isPermitted = playlist.OwnerUserId.Equals(userId);
 210
 0211        return isPermitted ? playlist.Shares.ToList() : Forbid();
 212    }
 213
 214    /// <summary>
 215    /// Get a playlist user.
 216    /// </summary>
 217    /// <param name="playlistId">The playlist id.</param>
 218    /// <param name="userId">The user id.</param>
 219    /// <response code="200">User permission found.</response>
 220    /// <response code="403">Access forbidden.</response>
 221    /// <response code="404">Playlist not found.</response>
 222    /// <returns>
 223    /// <see cref="PlaylistUserPermissions"/>.
 224    /// </returns>
 225    [HttpGet("{playlistId}/Users/{userId}")]
 226    [ProducesResponseType(StatusCodes.Status200OK)]
 227    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 228    [ProducesResponseType(StatusCodes.Status404NotFound)]
 229    public ActionResult<PlaylistUserPermissions?> GetPlaylistUser(
 230        [FromRoute, Required] Guid playlistId,
 231        [FromRoute, Required] Guid userId)
 232    {
 0233        var callingUserId = User.GetUserId();
 234
 0235        var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
 0236        if (playlist is null)
 237        {
 0238            return NotFound("Playlist not found");
 239        }
 240
 0241        if (playlist.OwnerUserId.Equals(callingUserId))
 242        {
 0243            return new PlaylistUserPermissions(callingUserId, true);
 244        }
 245
 0246        var userPermission = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId));
 0247        var isPermitted = playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId))
 0248            || userId.Equals(callingUserId);
 249
 0250        if (!isPermitted)
 251        {
 0252            return Forbid();
 253        }
 254
 0255        if (userPermission is not null)
 256        {
 0257            return userPermission;
 258        }
 259
 0260        return NotFound("User permissions not found");
 261    }
 262
 263    /// <summary>
 264    /// Modify a user of a playlist's users.
 265    /// </summary>
 266    /// <param name="playlistId">The playlist id.</param>
 267    /// <param name="userId">The user id.</param>
 268    /// <param name="updatePlaylistUserRequest">The <see cref="UpdatePlaylistUserDto"/>.</param>
 269    /// <response code="204">User's permissions modified.</response>
 270    /// <response code="403">Access forbidden.</response>
 271    /// <response code="404">Playlist not found.</response>
 272    /// <returns>
 273    /// A <see cref="Task" /> that represents the asynchronous operation to modify an user's playlist permissions.
 274    /// The task result contains an <see cref="OkResult"/> indicating success.
 275    /// </returns>
 276    [HttpPost("{playlistId}/Users/{userId}")]
 277    [ProducesResponseType(StatusCodes.Status204NoContent)]
 278    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 279    [ProducesResponseType(StatusCodes.Status404NotFound)]
 280    public async Task<ActionResult> UpdatePlaylistUser(
 281        [FromRoute, Required] Guid playlistId,
 282        [FromRoute, Required] Guid userId,
 283        [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow), Required] UpdatePlaylistUserDto updatePlaylistUserReques
 284    {
 285        var callingUserId = User.GetUserId();
 286
 287        var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
 288        if (playlist is null)
 289        {
 290            return NotFound("Playlist not found");
 291        }
 292
 293        var isPermitted = playlist.OwnerUserId.Equals(callingUserId);
 294
 295        if (!isPermitted)
 296        {
 297            return Forbid();
 298        }
 299
 300        await _playlistManager.AddUserToShares(new PlaylistUserUpdateRequest
 301        {
 302            Id = playlistId,
 303            UserId = userId,
 304            CanEdit = updatePlaylistUserRequest.CanEdit
 305        }).ConfigureAwait(false);
 306
 307        return NoContent();
 308    }
 309
 310    /// <summary>
 311    /// Remove a user from a playlist's users.
 312    /// </summary>
 313    /// <param name="playlistId">The playlist id.</param>
 314    /// <param name="userId">The user id.</param>
 315    /// <response code="204">User permissions removed from playlist.</response>
 316    /// <response code="401">Unauthorized access.</response>
 317    /// <response code="404">No playlist or user permissions found.</response>
 318    /// <returns>
 319    /// A <see cref="Task" /> that represents the asynchronous operation to delete a user from a playlist's shares.
 320    /// The task result contains an <see cref="OkResult"/> indicating success.
 321    /// </returns>
 322    [HttpDelete("{playlistId}/Users/{userId}")]
 323    [ProducesResponseType(StatusCodes.Status204NoContent)]
 324    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 325    [ProducesResponseType(StatusCodes.Status404NotFound)]
 326    public async Task<ActionResult> RemoveUserFromPlaylist(
 327        [FromRoute, Required] Guid playlistId,
 328        [FromRoute, Required] Guid userId)
 329    {
 330        var callingUserId = User.GetUserId();
 331
 332        var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
 333        if (playlist is null)
 334        {
 335            return NotFound("Playlist not found");
 336        }
 337
 338        var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
 339            || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
 340
 341        if (!isPermitted)
 342        {
 343            return Forbid();
 344        }
 345
 346        var share = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId));
 347        if (share is null)
 348        {
 349            return NotFound("User permissions not found");
 350        }
 351
 352        await _playlistManager.RemoveUserFromShares(playlistId, callingUserId, share).ConfigureAwait(false);
 353
 354        return NoContent();
 355    }
 356
 357    /// <summary>
 358    /// Adds items to a playlist.
 359    /// </summary>
 360    /// <param name="playlistId">The playlist id.</param>
 361    /// <param name="ids">Item id, comma delimited.</param>
 362    /// <param name="userId">The userId.</param>
 363    /// <response code="204">Items added to playlist.</response>
 364    /// <response code="403">Access forbidden.</response>
 365    /// <response code="404">Playlist not found.</response>
 366    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 367    [HttpPost("{playlistId}/Items")]
 368    [ProducesResponseType(StatusCodes.Status204NoContent)]
 369    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 370    [ProducesResponseType(StatusCodes.Status404NotFound)]
 371    public async Task<ActionResult> AddItemToPlaylist(
 372        [FromRoute, Required] Guid playlistId,
 373        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] Guid[] ids,
 374        [FromQuery] Guid? userId)
 375    {
 376        userId = RequestHelpers.GetUserId(User, userId);
 377        var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId.Value);
 378        if (playlist is null)
 379        {
 380            return NotFound("Playlist not found");
 381        }
 382
 383        var isPermitted = playlist.OwnerUserId.Equals(userId.Value)
 384            || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId.Value));
 385
 386        if (!isPermitted)
 387        {
 388            return Forbid();
 389        }
 390
 391        await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false);
 392        return NoContent();
 393    }
 394
 395    /// <summary>
 396    /// Moves a playlist item.
 397    /// </summary>
 398    /// <param name="playlistId">The playlist id.</param>
 399    /// <param name="itemId">The item id.</param>
 400    /// <param name="newIndex">The new index.</param>
 401    /// <response code="204">Item moved to new index.</response>
 402    /// <response code="403">Access forbidden.</response>
 403    /// <response code="404">Playlist not found.</response>
 404    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 405    [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
 406    [ProducesResponseType(StatusCodes.Status204NoContent)]
 407    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 408    [ProducesResponseType(StatusCodes.Status404NotFound)]
 409    public async Task<ActionResult> MoveItem(
 410        [FromRoute, Required] string playlistId,
 411        [FromRoute, Required] string itemId,
 412        [FromRoute, Required] int newIndex)
 413    {
 414        var callingUserId = User.GetUserId();
 415
 416        var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
 417        if (playlist is null)
 418        {
 419            return NotFound("Playlist not found");
 420        }
 421
 422        var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
 423            || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
 424
 425        if (!isPermitted)
 426        {
 427            return Forbid();
 428        }
 429
 430        await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex, callingUserId).ConfigureAwait(false);
 431        return NoContent();
 432    }
 433
 434    /// <summary>
 435    /// Removes items from a playlist.
 436    /// </summary>
 437    /// <param name="playlistId">The playlist id.</param>
 438    /// <param name="entryIds">The item ids, comma delimited.</param>
 439    /// <response code="204">Items removed.</response>
 440    /// <response code="403">Access forbidden.</response>
 441    /// <response code="404">Playlist not found.</response>
 442    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 443    [HttpDelete("{playlistId}/Items")]
 444    [ProducesResponseType(StatusCodes.Status204NoContent)]
 445    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 446    [ProducesResponseType(StatusCodes.Status404NotFound)]
 447    public async Task<ActionResult> RemoveItemFromPlaylist(
 448        [FromRoute, Required] string playlistId,
 449        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] string[] entryIds)
 450    {
 451        var callingUserId = User.GetUserId();
 452
 453        var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId);
 454        if (playlist is null)
 455        {
 456            return NotFound("Playlist not found");
 457        }
 458
 459        var isPermitted = playlist.OwnerUserId.Equals(callingUserId)
 460            || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId));
 461
 462        if (!isPermitted)
 463        {
 464            return Forbid();
 465        }
 466
 467        await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false);
 468        return NoContent();
 469    }
 470
 471    /// <summary>
 472    /// Gets the original items of a playlist.
 473    /// </summary>
 474    /// <param name="playlistId">The playlist id.</param>
 475    /// <param name="userId">User id.</param>
 476    /// <param name="startIndex">Optional. The record index to start at. All items with a lower index will be dropped fr
 477    /// <param name="limit">Optional. The maximum number of records to return.</param>
 478    /// <param name="fields">Optional. Specify additional fields of information to return in the output.</param>
 479    /// <param name="enableImages">Optional. Include image information in output.</param>
 480    /// <param name="enableUserData">Optional. Include user data.</param>
 481    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 482    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 483    /// <response code="200">Original playlist returned.</response>
 484    /// <response code="404">Access forbidden.</response>
 485    /// <response code="404">Playlist not found.</response>
 486    /// <returns>The original playlist items.</returns>
 487    [HttpGet("{playlistId}/Items")]
 488    [ProducesResponseType(StatusCodes.Status200OK)]
 489    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 490    [ProducesResponseType(StatusCodes.Status404NotFound)]
 491    public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
 492        [FromRoute, Required] Guid playlistId,
 493        [FromQuery] Guid? userId,
 494        [FromQuery] int? startIndex,
 495        [FromQuery] int? limit,
 496        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ItemFields[] fields,
 497        [FromQuery] bool? enableImages,
 498        [FromQuery] bool? enableUserData,
 499        [FromQuery] int? imageTypeLimit,
 500        [FromQuery, ModelBinder(typeof(CommaDelimitedCollectionModelBinder))] ImageType[] enableImageTypes)
 501    {
 0502        var callingUserId = userId ?? User.GetUserId();
 0503        var playlist = _playlistManager.GetPlaylistForUser(playlistId, callingUserId);
 0504        if (playlist is null)
 505        {
 0506            return NotFound("Playlist not found");
 507        }
 508
 0509        var isPermitted = playlist.OpenAccess
 0510            || playlist.OwnerUserId.Equals(callingUserId)
 0511            || playlist.Shares.Any(s => s.UserId.Equals(callingUserId));
 512
 0513        if (!isPermitted)
 514        {
 0515            return Forbid();
 516        }
 517
 0518        var user = _userManager.GetUserById(callingUserId);
 0519        var items = playlist.GetManageableItems().Where(i => i.Item2.IsVisible(user)).ToArray();
 0520        var count = items.Length;
 0521        if (startIndex.HasValue)
 522        {
 0523            items = items.Skip(startIndex.Value).ToArray();
 524        }
 525
 0526        if (limit.HasValue)
 527        {
 0528            items = items.Take(limit.Value).ToArray();
 529        }
 530
 0531        var dtoOptions = new DtoOptions { Fields = fields }
 0532            .AddClientFields(User)
 0533            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 534
 0535        var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
 0536        for (int index = 0; index < dtos.Count; index++)
 537        {
 0538            dtos[index].PlaylistItemId = items[index].Item1.ItemId?.ToString("N", CultureInfo.InvariantCulture);
 539        }
 540
 0541        var result = new QueryResult<BaseItemDto>(
 0542            startIndex,
 0543            count,
 0544            dtos);
 545
 0546        return result;
 547    }
 548}