From 2287919b60a1dc09804ed2f9946fd352978ea907 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Fri, 24 Sep 2021 10:22:02 +0200 Subject: [PATCH] API: Documenting the libraries'api --- .../Permission/PartialPermissionAttribute.cs | 5 + .../Controllers/PermissionValidator.cs | 25 +- src/Kyoo.Core/Views/EpisodeApi.cs | 2 +- src/Kyoo.Core/Views/Helper/CrudApi.cs | 2 +- src/Kyoo.Core/Views/LibraryApi.cs | 238 +++++++++--------- 5 files changed, 137 insertions(+), 135 deletions(-) diff --git a/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs b/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs index 58e6366a..3cad4b6d 100644 --- a/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs +++ b/src/Kyoo.Abstractions/Models/Attributes/Permission/PartialPermissionAttribute.cs @@ -39,6 +39,11 @@ namespace Kyoo.Abstractions.Models.Permissions /// public Kind Kind { get; } + /// + /// The group of this permission. + /// + public Group Group { get; set; } + /// /// Ask a permission to run an action. /// diff --git a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs index 2f4a84f4..490c7649 100644 --- a/src/Kyoo.Authentication/Controllers/PermissionValidator.cs +++ b/src/Kyoo.Authentication/Controllers/PermissionValidator.cs @@ -61,7 +61,7 @@ namespace Kyoo.Authentication /// public IFilterMetadata Create(PartialPermissionAttribute attribute) { - return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, _options); + return new PermissionValidatorFilter((object)attribute.Type ?? attribute.Kind, attribute.Group, _options); } /// @@ -109,15 +109,24 @@ namespace Kyoo.Authentication /// Create a new permission validator with the given options. /// /// The partial permission to validate. + /// The group of the permission. /// The option containing default values. - public PermissionValidatorFilter(object partialInfo, IOptionsMonitor options) + public PermissionValidatorFilter(object partialInfo, Group? group, IOptionsMonitor options) { - if (partialInfo is Kind kind) - _kind = kind; - else if (partialInfo is string perm) - _permission = perm; - else - throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind."); + switch (partialInfo) + { + case Kind kind: + _kind = kind; + break; + case string perm: + _permission = perm; + break; + default: + throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind."); + } + + if (group != null) + _group = group.Value; _options = options; } diff --git a/src/Kyoo.Core/Views/EpisodeApi.cs b/src/Kyoo.Core/Views/EpisodeApi.cs index fc5b03ae..bdba37a2 100644 --- a/src/Kyoo.Core/Views/EpisodeApi.cs +++ b/src/Kyoo.Core/Views/EpisodeApi.cs @@ -133,7 +133,7 @@ namespace Kyoo.Core.Api /// An optional track's ID to start the query from this specific item. /// A page of tracks. /// The filters or the sort parameters are invalid. - /// No track with the given ID or slug could be found. + /// No episode with the given ID or slug could be found. /// TODO fix the /watch endpoint link (when operations ID are specified). [HttpGet("{identifier:id}/tracks")] [HttpGet("{identifier:id}/track", Order = AlternativeRoute)] diff --git a/src/Kyoo.Core/Views/Helper/CrudApi.cs b/src/Kyoo.Core/Views/Helper/CrudApi.cs index 6e11ed6b..1b16b2b0 100644 --- a/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -186,7 +186,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] [ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(ActionResult<>))] - public virtual async Task> Create([FromBody] T resource) + public async Task> Create([FromBody] T resource) { try { diff --git a/src/Kyoo.Core/Views/LibraryApi.cs b/src/Kyoo.Core/Views/LibraryApi.cs index 0199aa3c..26ebebde 100644 --- a/src/Kyoo.Core/Views/LibraryApi.cs +++ b/src/Kyoo.Core/Views/LibraryApi.cs @@ -19,198 +19,186 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; 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/library")] + /// + /// Information about one or multiple . + /// [Route("api/libraries")] + [Route("api/library", Order = AlternativeRoute)] [ApiController] - [PartialPermission(nameof(LibraryApi))] + [PartialPermission(nameof(LibraryApi), Group = Group.Admin)] + [ApiDefinition("Library", Group = ResourcesGroup)] public class LibraryApi : CrudApi { + /// + /// The library manager used to modify or retrieve information in the data store. + /// private readonly ILibraryManager _libraryManager; - private readonly ITaskManager _taskManager; - public LibraryApi(ILibraryManager libraryManager, ITaskManager taskManager, IOptions options) + /// + /// Create a new . + /// + /// + /// The library manager used to modify or retrieve information in the data store. + /// + /// + /// Options used to retrieve the base URL of Kyoo. + /// + public LibraryApi(ILibraryManager libraryManager, IOptions options) : base(libraryManager.LibraryRepository, options.Value.PublicUrl) { _libraryManager = libraryManager; - _taskManager = taskManager; } - [PartialPermission(Kind.Create)] - public override async Task> Create(Library resource) - { - ActionResult result = await base.Create(resource); - if (result.Value != null) - { - _taskManager.StartTask("scan", - new Progress(), - new Dictionary { { "slug", result.Value.Slug } }); - } - return result; - } - - [HttpGet("{id:int}/show")] - [HttpGet("{id:int}/shows")] + /// + /// Get shows + /// + /// + /// List the shows that are part of this library. + /// + /// The ID or slug of the . + /// A key to sort shows by. + /// An optional list of filters. + /// The number of shows to return. + /// An optional show's ID to start the query from this specific item. + /// A page of shows. + /// The filters or the sort parameters are invalid. + /// No library with the given ID or slug could be found. + [HttpGet("{identifier:id}/shows")] + [HttpGet("{identifier:id}/show", Order = AlternativeRoute)] [PartialPermission(Kind.Read)] - public async Task>> GetShows(int id, + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetShows(Identifier identifier, [FromQuery] string sortBy, - [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 50) + [FromQuery] int limit = 50, + [FromQuery] int? afterID = null) { try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.ID == id)), + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), new Sort(sortBy), - new Pagination(limit, afterID)); + new Pagination(limit, afterID) + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(id) == 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("{slug}/show")] - [HttpGet("{slug}/shows")] + /// + /// Get collections + /// + /// + /// List the collections that are part of this library. + /// + /// The ID or slug of the . + /// A key to sort collections by. + /// An optional list of filters. + /// The number of collections to return. + /// An optional collection's ID to start the query from this specific item. + /// A page of collections. + /// The filters or the sort parameters are invalid. + /// No library with the given ID or slug could be found. + [HttpGet("{identifier:id}/collections")] + [HttpGet("{identifier:id}/collection", Order = AlternativeRoute)] [PartialPermission(Kind.Read)] - public async Task>> GetShows(string slug, + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetCollections(Identifier identifier, [FromQuery] string sortBy, - [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) - { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.Slug == slug)), - new Sort(sortBy), - new Pagination(limit, afterID)); - - if (!resources.Any() && await _libraryManager.GetOrDefault(slug) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new { Error = ex.Message }); - } - } - - [HttpGet("{id:int}/collection")] - [HttpGet("{id:int}/collections")] - [PartialPermission(Kind.Read)] - public async Task>> GetCollections(int id, - [FromQuery] string sortBy, - [FromQuery] int afterID, - [FromQuery] Dictionary where, - [FromQuery] int limit = 50) + [FromQuery] int limit = 50, + [FromQuery] int? afterID = null) { try { ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.ID == id)), + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), new Sort(sortBy), - new Pagination(limit, afterID)); + new Pagination(limit, afterID) + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(id) == 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("{slug}/collection")] - [HttpGet("{slug}/collections")] + /// + /// Get items + /// + /// + /// List all items of this library. + /// An item can ether represent a collection or a show. + /// This endpoint allow one to retrieve all collections and shows that are not contained in a collection. + /// This is what is displayed on the /browse page of the webapp. + /// + /// The ID or slug of the . + /// A key to sort items by. + /// An optional list of filters. + /// The number of items to return. + /// An optional item's ID to start the query from this specific item. + /// A page of items. + /// The filters or the sort parameters are invalid. + /// No library with the given ID or slug could be found. + [HttpGet("{identifier:id}/items")] + [HttpGet("{identifier:id}/item", Order = AlternativeRoute)] [PartialPermission(Kind.Read)] - public async Task>> GetCollections(string slug, + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task>> GetItems(Identifier identifier, [FromQuery] string sortBy, - [FromQuery] int afterID, [FromQuery] Dictionary where, - [FromQuery] int limit = 20) + [FromQuery] int limit = 50, + [FromQuery] int? afterID = null) { try { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, x => x.Libraries.Any(y => y.Slug == slug)), - new Sort(sortBy), - new Pagination(limit, afterID)); + Expression> whereQuery = ApiHelper.ParseWhere(where); + Sort sort = new(sortBy); + Pagination pagination = new(limit, afterID); - if (!resources.Any() && await _libraryManager.GetOrDefault(slug) == null) + ICollection resources = await identifier.Match( + id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination), + slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination) + ); + + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) return NotFound(); return Page(resources, limit); } catch (ArgumentException ex) { - return BadRequest(new { Error = ex.Message }); - } - } - - [HttpGet("{id:int}/item")] - [HttpGet("{id:int}/items")] - [PartialPermission(Kind.Read)] - public async Task>> GetItems(int id, - [FromQuery] string sortBy, - [FromQuery] int afterID, - [FromQuery] Dictionary where, - [FromQuery] int limit = 50) - { - try - { - ICollection resources = await _libraryManager.GetItemsFromLibrary(id, - ApiHelper.ParseWhere(where), - new Sort(sortBy), - new Pagination(limit, afterID)); - - if (!resources.Any() && await _libraryManager.GetOrDefault(id) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new { Error = ex.Message }); - } - } - - [HttpGet("{slug}/item")] - [HttpGet("{slug}/items")] - [PartialPermission(Kind.Read)] - public async Task>> GetItems(string slug, - [FromQuery] string sortBy, - [FromQuery] int afterID, - [FromQuery] Dictionary where, - [FromQuery] int limit = 50) - { - try - { - ICollection resources = await _libraryManager.GetItemsFromLibrary(slug, - ApiHelper.ParseWhere(where), - new Sort(sortBy), - new Pagination(limit, afterID)); - - if (!resources.Any() && await _libraryManager.GetOrDefault(slug) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new { Error = ex.Message }); + return BadRequest(new RequestError(ex.Message)); } } }