diff --git a/src/Kyoo.Abstractions/Models/Utils/Constants.cs b/src/Kyoo.Abstractions/Models/Utils/Constants.cs index 190307af..dd0ac925 100644 --- a/src/Kyoo.Abstractions/Models/Utils/Constants.cs +++ b/src/Kyoo.Abstractions/Models/Utils/Constants.cs @@ -34,6 +34,6 @@ namespace Kyoo.Abstractions.Models.Utils /// /// A group name for . It should be used for every . /// - public const string ResourceGroup = "Resource"; + public const string ResourceGroup = "Resources"; } } diff --git a/src/Kyoo.Abstractions/Models/Utils/Identifier.cs b/src/Kyoo.Abstractions/Models/Utils/Identifier.cs index bd83e975..228a6a6e 100644 --- a/src/Kyoo.Abstractions/Models/Utils/Identifier.cs +++ b/src/Kyoo.Abstractions/Models/Utils/Identifier.cs @@ -110,6 +110,23 @@ namespace Kyoo.Abstractions.Models.Utils return Expression.Lambda>(equal); } + /// + /// A matcher overload for nullable IDs. See + /// + /// for more details. + /// + /// An expression to retrieve an ID from the type . + /// An expression to retrieve a slug from the type . + /// The type to match against this identifier. + /// An expression to match the type to this identifier. + public Expression> Matcher(Expression> idGetter, + Expression> slugGetter) + { + ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug); + BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self); + return Expression.Lambda>(equal); + } + /// /// Return true if this match a resource. /// diff --git a/src/Kyoo.Core/Views/SeasonApi.cs b/src/Kyoo.Core/Views/SeasonApi.cs index 126603ea..99bac0dc 100644 --- a/src/Kyoo.Core/Views/SeasonApi.cs +++ b/src/Kyoo.Core/Views/SeasonApi.cs @@ -22,163 +22,114 @@ using System.Linq; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; +using Kyoo.Abstractions.Models.Attributes; using Kyoo.Abstractions.Models.Permissions; +using Kyoo.Abstractions.Models.Utils; using Kyoo.Core.Models.Options; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; +using static Kyoo.Abstractions.Models.Utils.Constants; namespace Kyoo.Core.Api { - [Route("api/season")] + /// + /// Information about one or multiple . + /// [Route("api/seasons")] + [Route("api/season", Order = AlternativeRoute)] [ApiController] [PartialPermission(nameof(SeasonApi))] - public class SeasonApi : CrudApi + [ApiDefinition("Seasons", Group = ResourceGroup)] + public class SeasonApi : CrudThumbsApi { + /// + /// The library manager used to modify or retrieve information in the data store. + /// private readonly ILibraryManager _libraryManager; - private readonly IThumbnailsManager _thumbs; - private readonly IFileSystem _files; + /// + /// Create a new . + /// + /// + /// The library manager used to modify or retrieve information in the data store. + /// + /// The file manager used to send images. + /// The thumbnail manager used to retrieve images paths. + /// + /// Options used to retrieve the base URL of Kyoo. + /// public SeasonApi(ILibraryManager libraryManager, - IOptions options, + IFileSystem files, IThumbnailsManager thumbs, - IFileSystem files) - : base(libraryManager.SeasonRepository, options.Value.PublicUrl) + IOptions options) + : base(libraryManager.SeasonRepository, files, thumbs, options.Value.PublicUrl) { _libraryManager = libraryManager; - _thumbs = thumbs; - _files = files; } - [HttpGet("{seasonID:int}/episode")] - [HttpGet("{seasonID:int}/episodes")] + /// + /// Get episodes in the season + /// + /// + /// List the episodes that are part of the specified season. + /// + /// The ID or slug of the . + /// A key to sort episodes by. + /// An optional list of filters. + /// The number of episodes to return. + /// An optional episode's ID to start the query from this specific item. + /// A page of episodes. + /// The filters or the sort parameters are invalid. + /// No season with the given ID or slug could be found. + [HttpGet("{identifier:id}/episodes")] + [HttpGet("{identifier:id}/episode", Order = AlternativeRoute)] [PartialPermission(Kind.Read)] - public async Task>> GetEpisode(int seasonID, + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetEpisode(Identifier identifier, [FromQuery] string sortBy, - [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 30) + [FromQuery] int limit = 30, + [FromQuery] int? afterID = null) { try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.SeasonID == seasonID), + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), new Sort(sortBy), new Pagination(limit, afterID)); - if (!resources.Any() && await _libraryManager.GetOrDefault(seasonID) == null) + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) return NotFound(); return Page(resources, limit); } catch (ArgumentException ex) { - return BadRequest(new { Error = ex.Message }); + return BadRequest(new RequestError(ex.Message)); } } - [HttpGet("{showSlug}-s{seasonNumber:int}/episode")] - [HttpGet("{showSlug}-s{seasonNumber:int}/episodes")] + /// + /// Get season's show + /// + /// + /// Get the show that this season is part of. + /// + /// The ID or slug of the . + /// The show that contains this season. + /// No season with the given ID or slug could be found. + [HttpGet("{identifier:id}/show")] [PartialPermission(Kind.Read)] - public async Task>> GetEpisode(string showSlug, - int seasonNumber, - [FromQuery] string sortBy, - [FromQuery] int afterID, - [FromQuery] Dictionary where, - [FromQuery] int limit = 30) + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task> GetShow(Identifier identifier) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Show.Slug == showSlug - && x.SeasonNumber == seasonNumber), - new Sort(sortBy), - new Pagination(limit, afterID)); - - if (!resources.Any() && await _libraryManager.GetOrDefault(showSlug, seasonNumber) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new { Error = ex.Message }); - } - } - - [HttpGet("{showID:int}-s{seasonNumber:int}/episode")] - [HttpGet("{showID:int}-s{seasonNumber:int}/episodes")] - [PartialPermission(Kind.Read)] - public async Task>> GetEpisode(int showID, - int seasonNumber, - [FromQuery] string sortBy, - [FromQuery] int afterID, - [FromQuery] Dictionary where, - [FromQuery] int limit = 30) - { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.ShowID == showID && x.SeasonNumber == seasonNumber), - new Sort(sortBy), - new Pagination(limit, afterID)); - - if (!resources.Any() && await _libraryManager.GetOrDefault(showID, seasonNumber) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new { Error = ex.Message }); - } - } - - [HttpGet("{seasonID:int}/show")] - [PartialPermission(Kind.Read)] - public async Task> GetShow(int seasonID) - { - Show ret = await _libraryManager.GetOrDefault(x => x.Seasons.Any(y => y.ID == seasonID)); + Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn(x => x.Seasons)); if (ret == null) return NotFound(); return ret; } - - [HttpGet("{showSlug}-s{seasonNumber:int}/show")] - [PartialPermission(Kind.Read)] - public async Task> GetShow(string showSlug, int seasonNumber) - { - Show ret = await _libraryManager.GetOrDefault(showSlug); - if (ret == null) - return NotFound(); - return ret; - } - - [HttpGet("{showID:int}-s{seasonNumber:int}/show")] - [PartialPermission(Kind.Read)] - public async Task> GetShow(int showID, int seasonNumber) - { - Show ret = await _libraryManager.GetOrDefault(showID); - if (ret == null) - return NotFound(); - return ret; - } - - [HttpGet("{id:int}/poster")] - public async Task GetPoster(int id) - { - Season season = await _libraryManager.GetOrDefault(id); - if (season == null) - return NotFound(); - await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); - } - - [HttpGet("{slug}/poster")] - public async Task GetPoster(string slug) - { - Season season = await _libraryManager.GetOrDefault(slug); - if (season == null) - return NotFound(); - await _libraryManager.Load(season, x => x.Show); - return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster)); - } } } diff --git a/src/Kyoo.Core/Views/ShowApi.cs b/src/Kyoo.Core/Views/ShowApi.cs index f819e9ab..306de40b 100644 --- a/src/Kyoo.Core/Views/ShowApi.cs +++ b/src/Kyoo.Core/Views/ShowApi.cs @@ -48,7 +48,7 @@ namespace Kyoo.Core.Api public class ShowApi : CrudThumbsApi { /// - /// The library manager used to modify or retrieve information about the data store. + /// The library manager used to modify or retrieve information in the data store. /// private readonly ILibraryManager _libraryManager; @@ -279,7 +279,7 @@ namespace Kyoo.Core.Api } /// - /// Get libraries containing this show. + /// Get libraries containing this show /// /// /// List the libraries that contain this show. If this show is contained in a collection that is contained in @@ -324,7 +324,7 @@ namespace Kyoo.Core.Api } /// - /// Get collections containing this show. + /// Get collections containing this show /// /// /// List the collections that contain this show. @@ -337,8 +337,8 @@ namespace Kyoo.Core.Api /// A page of collections. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. - [HttpGet("{identifier:id}/collection")] [HttpGet("{identifier:id}/collections")] + [HttpGet("{identifier:id}/collection", Order = AlternativeRoute)] [PartialPermission(Kind.Read)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]