API: documenting the season api

This commit is contained in:
Zoe Roux 2021-09-23 11:44:37 +02:00
parent bd4fd848e1
commit 8a5e7fea06
4 changed files with 88 additions and 120 deletions

View File

@ -34,6 +34,6 @@ namespace Kyoo.Abstractions.Models.Utils
/// <summary> /// <summary>
/// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for every <see cref="IResource"/>. /// A group name for <see cref="ApiDefinitionAttribute"/>. It should be used for every <see cref="IResource"/>.
/// </summary> /// </summary>
public const string ResourceGroup = "Resource"; public const string ResourceGroup = "Resources";
} }
} }

View File

@ -110,6 +110,23 @@ namespace Kyoo.Abstractions.Models.Utils
return Expression.Lambda<Func<T, bool>>(equal); return Expression.Lambda<Func<T, bool>>(equal);
} }
/// <summary>
/// A matcher overload for nullable IDs. See
/// <see cref="Matcher{T}(System.Linq.Expressions.Expression{System.Func{T,int}},System.Linq.Expressions.Expression{System.Func{T,string}})"/>
/// for more details.
/// </summary>
/// <param name="idGetter">An expression to retrieve an ID from the type <typeparamref name="T"/>.</param>
/// <param name="slugGetter">An expression to retrieve a slug from the type <typeparamref name="T"/>.</param>
/// <typeparam name="T">The type to match against this identifier.</typeparam>
/// <returns>An expression to match the type <typeparamref name="T"/> to this identifier.</returns>
public Expression<Func<T, bool>> Matcher<T>(Expression<Func<T, int?>> idGetter,
Expression<Func<T, string>> slugGetter)
{
ConstantExpression self = Expression.Constant(_id.HasValue ? _id.Value : _slug);
BinaryExpression equal = Expression.Equal(_id.HasValue ? idGetter.Body : slugGetter.Body, self);
return Expression.Lambda<Func<T, bool>>(equal);
}
/// <summary> /// <summary>
/// Return true if this <see cref="Identifier"/> match a resource. /// Return true if this <see cref="Identifier"/> match a resource.
/// </summary> /// </summary>

View File

@ -22,163 +22,114 @@ using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Controllers;
using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models;
using Kyoo.Abstractions.Models.Attributes;
using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Permissions;
using Kyoo.Abstractions.Models.Utils;
using Kyoo.Core.Models.Options; using Kyoo.Core.Models.Options;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using static Kyoo.Abstractions.Models.Utils.Constants;
namespace Kyoo.Core.Api namespace Kyoo.Core.Api
{ {
[Route("api/season")] /// <summary>
/// Information about one or multiple <see cref="Season"/>.
/// </summary>
[Route("api/seasons")] [Route("api/seasons")]
[Route("api/season", Order = AlternativeRoute)]
[ApiController] [ApiController]
[PartialPermission(nameof(SeasonApi))] [PartialPermission(nameof(SeasonApi))]
public class SeasonApi : CrudApi<Season> [ApiDefinition("Seasons", Group = ResourceGroup)]
public class SeasonApi : CrudThumbsApi<Season>
{ {
/// <summary>
/// The library manager used to modify or retrieve information in the data store.
/// </summary>
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly IThumbnailsManager _thumbs;
private readonly IFileSystem _files;
/// <summary>
/// Create a new <see cref="SeasonApi"/>.
/// </summary>
/// <param name="libraryManager">
/// The library manager used to modify or retrieve information in the data store.
/// </param>
/// <param name="files">The file manager used to send images.</param>
/// <param name="thumbs">The thumbnail manager used to retrieve images paths.</param>
/// <param name="options">
/// Options used to retrieve the base URL of Kyoo.
/// </param>
public SeasonApi(ILibraryManager libraryManager, public SeasonApi(ILibraryManager libraryManager,
IOptions<BasicOptions> options, IFileSystem files,
IThumbnailsManager thumbs, IThumbnailsManager thumbs,
IFileSystem files) IOptions<BasicOptions> options)
: base(libraryManager.SeasonRepository, options.Value.PublicUrl) : base(libraryManager.SeasonRepository, files, thumbs, options.Value.PublicUrl)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_thumbs = thumbs;
_files = files;
} }
[HttpGet("{seasonID:int}/episode")] /// <summary>
[HttpGet("{seasonID:int}/episodes")] /// Get episodes in the season
/// </summary>
/// <remarks>
/// List the episodes that are part of the specified season.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Season"/>.</param>
/// <param name="sortBy">A key to sort episodes by.</param>
/// <param name="where">An optional list of filters.</param>
/// <param name="limit">The number of episodes to return.</param>
/// <param name="afterID">An optional episode's ID to start the query from this specific item.</param>
/// <returns>A page of episodes.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No season with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/episodes")]
[HttpGet("{identifier:id}/episode", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)] [PartialPermission(Kind.Read)]
public async Task<ActionResult<Page<Episode>>> GetEpisode(int seasonID, [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<Page<Episode>>> GetEpisode(Identifier identifier,
[FromQuery] string sortBy, [FromQuery] string sortBy,
[FromQuery] int afterID,
[FromQuery] Dictionary<string, string> where, [FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30) [FromQuery] int limit = 30,
[FromQuery] int? afterID = null)
{ {
try try
{ {
ICollection<Episode> resources = await _libraryManager.GetAll( ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere<Episode>(where, x => x.SeasonID == seasonID), ApiHelper.ParseWhere(where, identifier.Matcher<Episode>(x => x.SeasonID, x => x.Season.Slug)),
new Sort<Episode>(sortBy), new Sort<Episode>(sortBy),
new Pagination(limit, afterID)); new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.GetOrDefault<Season>(seasonID) == null) if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame<Season>()) == null)
return NotFound(); return NotFound();
return Page(resources, limit); return Page(resources, limit);
} }
catch (ArgumentException ex) catch (ArgumentException ex)
{ {
return BadRequest(new { Error = ex.Message }); return BadRequest(new RequestError(ex.Message));
} }
} }
[HttpGet("{showSlug}-s{seasonNumber:int}/episode")] /// <summary>
[HttpGet("{showSlug}-s{seasonNumber:int}/episodes")] /// Get season's show
/// </summary>
/// <remarks>
/// Get the show that this season is part of.
/// </remarks>
/// <param name="identifier">The ID or slug of the <see cref="Season"/>.</param>
/// <returns>The show that contains this season.</returns>
/// <response code="404">No season with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/show")]
[PartialPermission(Kind.Read)] [PartialPermission(Kind.Read)]
public async Task<ActionResult<Page<Episode>>> GetEpisode(string showSlug, [ProducesResponseType(StatusCodes.Status200OK)]
int seasonNumber, [ProducesResponseType(StatusCodes.Status404NotFound)]
[FromQuery] string sortBy, public async Task<ActionResult<Show>> GetShow(Identifier identifier)
[FromQuery] int afterID,
[FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30)
{ {
try Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn<Show, Season>(x => x.Seasons));
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere<Episode>(where, x => x.Show.Slug == showSlug
&& x.SeasonNumber == seasonNumber),
new Sort<Episode>(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<ActionResult<Page<Episode>>> GetEpisode(int showID,
int seasonNumber,
[FromQuery] string sortBy,
[FromQuery] int afterID,
[FromQuery] Dictionary<string, string> where,
[FromQuery] int limit = 30)
{
try
{
ICollection<Episode> resources = await _libraryManager.GetAll(
ApiHelper.ParseWhere<Episode>(where, x => x.ShowID == showID && x.SeasonNumber == seasonNumber),
new Sort<Episode>(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<ActionResult<Show>> GetShow(int seasonID)
{
Show ret = await _libraryManager.GetOrDefault<Show>(x => x.Seasons.Any(y => y.ID == seasonID));
if (ret == null) if (ret == null)
return NotFound(); return NotFound();
return ret; return ret;
} }
[HttpGet("{showSlug}-s{seasonNumber:int}/show")]
[PartialPermission(Kind.Read)]
public async Task<ActionResult<Show>> GetShow(string showSlug, int seasonNumber)
{
Show ret = await _libraryManager.GetOrDefault<Show>(showSlug);
if (ret == null)
return NotFound();
return ret;
}
[HttpGet("{showID:int}-s{seasonNumber:int}/show")]
[PartialPermission(Kind.Read)]
public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber)
{
Show ret = await _libraryManager.GetOrDefault<Show>(showID);
if (ret == null)
return NotFound();
return ret;
}
[HttpGet("{id:int}/poster")]
public async Task<IActionResult> GetPoster(int id)
{
Season season = await _libraryManager.GetOrDefault<Season>(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<IActionResult> GetPoster(string slug)
{
Season season = await _libraryManager.GetOrDefault<Season>(slug);
if (season == null)
return NotFound();
await _libraryManager.Load(season, x => x.Show);
return _files.FileResult(await _thumbs.GetImagePath(season, Images.Poster));
}
} }
} }

View File

@ -48,7 +48,7 @@ namespace Kyoo.Core.Api
public class ShowApi : CrudThumbsApi<Show> public class ShowApi : CrudThumbsApi<Show>
{ {
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
@ -279,7 +279,7 @@ namespace Kyoo.Core.Api
} }
/// <summary> /// <summary>
/// Get libraries containing this show. /// Get libraries containing this show
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// List the libraries that contain this show. If this show is contained in a collection that is contained in /// 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
} }
/// <summary> /// <summary>
/// Get collections containing this show. /// Get collections containing this show
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// List the collections that contain this show. /// List the collections that contain this show.
@ -337,8 +337,8 @@ namespace Kyoo.Core.Api
/// <returns>A page of collections.</returns> /// <returns>A page of collections.</returns>
/// <response code="400">The filters or the sort parameters are invalid.</response> /// <response code="400">The filters or the sort parameters are invalid.</response>
/// <response code="404">No show with the given ID or slug could be found.</response> /// <response code="404">No show with the given ID or slug could be found.</response>
[HttpGet("{identifier:id}/collection")]
[HttpGet("{identifier:id}/collections")] [HttpGet("{identifier:id}/collections")]
[HttpGet("{identifier:id}/collection", Order = AlternativeRoute)]
[PartialPermission(Kind.Read)] [PartialPermission(Kind.Read)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))]