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))]