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));
}
}
}