From 67112a37da75b739422bd344e711e24ef4d26743 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Mon, 13 Mar 2023 18:38:46 +0900 Subject: [PATCH] Rework exception handling --- .../Exceptions/DuplicatedItemException.cs | 18 +- .../Repositories/CollectionRepository.cs | 2 +- .../Repositories/EpisodeRepository.cs | 2 +- .../Repositories/GenreRepository.cs | 2 +- .../Repositories/LibraryRepository.cs | 2 +- .../Repositories/LocalRepository.cs | 218 ++++++------------ .../Repositories/PeopleRepository.cs | 2 +- .../Repositories/ProviderRepository.cs | 3 +- .../Repositories/SeasonRepository.cs | 2 +- .../Repositories/ShowRepository.cs | 2 +- .../Repositories/StudioRepository.cs | 2 +- .../Repositories/UserRepository.cs | 2 +- back/src/Kyoo.Core/CoreModule.cs | 17 +- back/src/Kyoo.Core/ExceptionFilter.cs | 80 +++++++ .../Kyoo.Core/Views/Admin/ConfigurationApi.cs | 26 +-- back/src/Kyoo.Core/Views/Admin/TaskApi.cs | 16 +- back/src/Kyoo.Core/Views/Helper/CrudApi.cs | 110 ++------- back/src/Kyoo.Core/Views/Metadata/GenreApi.cs | 30 +-- back/src/Kyoo.Core/Views/Metadata/StaffApi.cs | 33 +-- .../src/Kyoo.Core/Views/Metadata/StudioApi.cs | 29 +-- .../Views/Resources/CollectionApi.cs | 59 ++--- .../Kyoo.Core/Views/Resources/EpisodeApi.cs | 34 +-- .../Kyoo.Core/Views/Resources/LibraryApi.cs | 91 +++----- .../Views/Resources/LibraryItemApi.cs | 25 +- .../Kyoo.Core/Views/Resources/SeasonApi.cs | 29 +-- back/src/Kyoo.Core/Views/Resources/ShowApi.cs | 183 +++++---------- back/src/Kyoo.Core/Views/Watch/TrackApi.cs | 5 +- back/src/Kyoo.Database/DatabaseContext.cs | 31 +-- 28 files changed, 379 insertions(+), 676 deletions(-) create mode 100644 back/src/Kyoo.Core/ExceptionFilter.cs diff --git a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs index ddb17cd5..0cd21975 100644 --- a/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs +++ b/back/src/Kyoo.Abstractions/Models/Exceptions/DuplicatedItemException.cs @@ -28,19 +28,19 @@ namespace Kyoo.Abstractions.Models.Exceptions public class DuplicatedItemException : Exception { /// - /// Create a new with the default message. + /// The existing object. /// - public DuplicatedItemException() - : base("Already exists in the database.") - { } + public object Existing { get; } /// - /// Create a new with a custom message. + /// Create a new with the default message. /// - /// The message to use - public DuplicatedItemException(string message) - : base(message) - { } + /// The existing object. + public DuplicatedItemException(object existing = null) + : base("Already exists in the database.") + { + Existing = existing; + } /// /// The serialization constructor. diff --git a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs index ab54d442..0e6f4daa 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/CollectionRepository.cs @@ -72,7 +72,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated collection (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs index 6040b1d2..64650cf4 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/EpisodeRepository.cs @@ -142,7 +142,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated episode (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return await _ValidateTracks(obj); } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs index de104312..5c7a070b 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/GenreRepository.cs @@ -67,7 +67,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated genre (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs index 76bd2858..5177e120 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LibraryRepository.cs @@ -75,7 +75,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs index 0cb0600e..515f9ad0 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/LocalRepository.cs @@ -72,33 +72,37 @@ namespace Kyoo.Core.Controllers { sortBy ??= DefaultSort; - switch (sortBy) + IOrderedQueryable _Sort(IQueryable query, Sort sortBy) { - case Sort.Default: - return Sort(query, DefaultSort); - case Sort.By(var key, var desc): - return desc - ? query.OrderByDescending(x => EF.Property(x, key)) - : query.OrderBy(x => EF.Property(x, key)); - case Sort.Conglomerate(var keys): - IOrderedQueryable nQuery = Sort(query, keys[0]); - foreach ((string key, bool desc) in keys.Skip(1)) - { - nQuery = desc - ? nQuery.ThenByDescending(x => EF.Property(x, key)) - : nQuery.ThenBy(x => EF.Property(x, key)); - } - return nQuery; - default: - // The language should not require me to do this... - throw new SwitchExpressionException(); + switch (sortBy) + { + case Sort.Default: + return Sort(query, DefaultSort); + case Sort.By(var key, var desc): + return desc + ? query.OrderByDescending(x => EF.Property(x, key)) + : query.OrderBy(x => EF.Property(x, key)); + case Sort.Conglomerate(var keys): + IOrderedQueryable nQuery = _Sort(query, keys[0]); + foreach ((string key, bool desc) in keys.Skip(1)) + { + nQuery = desc + ? nQuery.ThenByDescending(x => EF.Property(x, key)) + : nQuery.ThenBy(x => EF.Property(x, key)); + } + return nQuery; + default: + // The language should not require me to do this... + throw new SwitchExpressionException(); + } } + return _Sort(query, sortBy).ThenBy(x => x.ID); } - private static Func GetComparisonExpression( - bool desc, - bool next, - bool orEqual) + private static Func _GetComparisonExpression( + bool desc, + bool next, + bool orEqual) { bool greaterThan = desc ^ next; @@ -107,7 +111,6 @@ namespace Kyoo.Core.Controllers : (greaterThan ? Expression.GreaterThan : Expression.LessThan); } - /// /// Create a filter (where) expression on the query to skip everything before/after the referenceID. /// The generalized expression for this in pseudocode is: @@ -121,6 +124,10 @@ namespace Kyoo.Core.Controllers /// (x = a AND y < b) OR /// (x = a AND y = b AND z > c) OR... /// + /// How items are sorted in the query + /// The reference item (the AfterID query) + /// True if the following page should be returned, false for the previous. + /// An expression ready to be added to a Where close of a sorted query to handle the AfterID protected Expression> KeysetPaginatate( Sort sort, T reference, @@ -133,133 +140,52 @@ namespace Kyoo.Core.Controllers ParameterExpression x = Expression.Parameter(typeof(T), "x"); ConstantExpression referenceC = Expression.Constant(reference, typeof(T)); - if (sort is Sort.By(var key, var desc)) + // Don't forget that every sorts must end with a ID sort (to differenciate equalities). + Sort.By id = new(x => x.ID); + + IEnumerable.By> sorts = (sort switch { - Func comparer = GetComparisonExpression(desc, next, false); + Sort.By @sortBy => new[] { sortBy }, + Sort.Conglomerate(var list) => list, + _ => Array.Empty.By>(), + }).Append(id); + + BinaryExpression filter = null; + List.By> previousSteps = new(); + // TODO: Add an outer query >= for perf + // PERF: See https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic + foreach ((string key, bool desc) in sorts) + { + BinaryExpression compare = null; + + // Create all the equality statements for previous sorts. + foreach ((string pKey, bool pDesc) in previousSteps) + { + BinaryExpression pcompare = Expression.Equal( + Expression.Property(x, pKey), + Expression.Property(referenceC, pKey) + ); + compare = compare != null + ? Expression.AndAlso(compare, pcompare) + : pcompare; + } + + // Create the last comparison of the statement. + Func comparer = _GetComparisonExpression(desc, next, false); MemberExpression xkey = Expression.Property(x, key); MemberExpression rkey = Expression.Property(referenceC, key); - BinaryExpression compare = ApiHelper.StringCompatibleExpression(comparer, xkey, rkey); - return Expression.Lambda>(compare, x); - } + BinaryExpression lastCompare = ApiHelper.StringCompatibleExpression(comparer, xkey, rkey); + compare = compare != null + ? Expression.AndAlso(compare, lastCompare) + : lastCompare; - if (sort is Sort.Conglomerate(var list)) - { - throw new NotImplementedException(); - // BinaryExpression orExpression; - // - // foreach ((string key, bool desc) in list) - // { - // query.Where(x => - // } - } - throw new SwitchExpressionException(); + filter = filter != null + ? Expression.OrElse(filter, compare) + : compare; - // Shamlessly stollen from https://github.com/mrahhal/MR.EntityFrameworkCore.KeysetPagination/blob/main/src/MR.EntityFrameworkCore.KeysetPagination/KeysetPaginationExtensions.cs#L191 - // // A composite keyset pagination in sql looks something like this: - // // (x, y, ...) > (a, b, ...) - // // Where, x/y/... represent the column and a/b/... represent the reference's respective values. - // // - // // In sql standard this syntax is called "row value". Check here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-row-values - // // Unfortunately, not all databases support this properly. - // // Further, if we were to use this we would somehow need EF Core to recognise it and translate it - // // perhaps by using a new DbFunction (https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.dbfunctions). - // // There's an ongoing issue for this here: https://github.com/dotnet/efcore/issues/26822 - // // - // // In addition, row value won't work for mixed ordered columns. i.e if x > a but y < b. - // // So even if we can use it we'll still have to fallback to this logic in these cases. - // // - // // The generalized expression for this in pseudocode is: - // // (x > a) OR - // // (x = a AND y > b) OR - // // (x = a AND y = b AND z > c) OR... - // // - // // Of course, this will be a bit more complex when ASC and DESC are mixed. - // // Assume x is ASC, y is DESC, and z is ASC: - // // (x > a) OR - // // (x = a AND y < b) OR - // // (x = a AND y = b AND z > c) OR... - // // - // // An optimization is to include an additional redundant wrapping clause for the 1st column when there are - // // more than one column we're acting on, which would allow the db to use it as an access predicate on the 1st column. - // // See here: https://use-the-index-luke.com/sql/partial-results/fetch-next-page#sb-equivalent-logic - // - // var referenceValues = GetValues(columns, reference); - // - // MemberExpression firstMemberAccessExpression; - // Expression firstReferenceValueExpression; - // - // // entity => - // ParameterExpression param = Expression.Parameter(typeof(T), "entity"); - // - // BinaryExpression orExpression; - // int innerLimit = 1; - // // This loop compounds the outer OR expressions. - // for (int i = 0; i < sort.list.Length; i++) - // { - // BinaryExpression andExpression; - // - // // This loop compounds the inner AND expressions. - // // innerLimit implicitly grows from 1 to items.Count by each iteration. - // for (int j = 0; j < innerLimit; j++) - // { - // bool isInnerLastOperation = j + 1 == innerLimit; - // var column = columns[j]; - // var memberAccess = column.MakeMemberAccessExpression(param); - // var referenceValue = referenceValues[j]; - // Expression> referenceValueFunc = () => referenceValue; - // var referenceValueExpression = referenceValueFunc.Body; - // - // if (firstMemberAccessExpression == null) - // { - // // This might be used later on in an optimization. - // firstMemberAccessExpression = memberAccess; - // firstReferenceValueExpression = referenceValueExpression; - // } - // - // BinaryExpression innerExpression; - // if (!isInnerLastOperation) - // { - // innerExpression = Expression.Equal( - // memberAccess, - // EnsureMatchingType(memberAccess, referenceValueExpression)); - // } - // else - // { - // var compare = GetComparisonExpressionToApply(direction, column, orEqual: false); - // innerExpression = MakeComparisonExpression( - // column, - // memberAccess, referenceValueExpression, - // compare); - // } - // - // andExpression = andExpression == null ? innerExpression : Expression.And(andExpression, innerExpression); - // } - // - // orExpression = orExpression == null ? andExpression : Expression.Or(orExpression, andExpression); - // - // innerLimit++; - // } - // - // var finalExpression = orExpression; - // if (columns.Count > 1) - // { - // // Implement the optimization that allows an access predicate on the 1st column. - // // This is done by generating the following expression: - // // (x >=|<= a) AND (previous generated expression) - // // - // // This effectively adds a redundant clause on the 1st column, but it's a clause all dbs - // // understand and can use as an access predicate (most commonly when the column is indexed). - // - // var firstColumn = columns[0]; - // var compare = GetComparisonExpressionToApply(direction, firstColumn, orEqual: true); - // var accessPredicateClause = MakeComparisonExpression( - // firstColumn, - // firstMemberAccessExpression!, firstReferenceValueExpression!, - // compare); - // finalExpression = Expression.And(accessPredicateClause, finalExpression); - // } - // - // return Expression.Lambda>(finalExpression, param); + previousSteps.Add(new(key, desc)); + } + return Expression.Lambda>(filter, x); } /// diff --git a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs index 618587ff..79a30dd7 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/PeopleRepository.cs @@ -85,7 +85,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated people (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs index 2c9cb799..b10e8578 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ProviderRepository.cs @@ -67,8 +67,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync("Trying to insert a duplicated provider " + - $"(slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs index 02f3b9a8..7dcd9845 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/SeasonRepository.cs @@ -108,7 +108,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated season (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs index efc2b00e..a2d26da1 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/ShowRepository.cs @@ -101,7 +101,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated show (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs index fc9aeda6..3c4acc3a 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/StudioRepository.cs @@ -74,7 +74,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated studio (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs index 2f21f7c4..68835752 100644 --- a/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs +++ b/back/src/Kyoo.Core/Controllers/Repositories/UserRepository.cs @@ -67,7 +67,7 @@ namespace Kyoo.Core.Controllers { await base.Create(obj); _database.Entry(obj).State = EntityState.Added; - await _database.SaveChangesAsync($"Trying to insert a duplicated user (slug {obj.Slug} already exists)."); + await _database.SaveChangesAsync(() => Get(obj.Slug)); return obj; } diff --git a/back/src/Kyoo.Core/CoreModule.cs b/back/src/Kyoo.Core/CoreModule.cs index ee512dce..9b775511 100644 --- a/back/src/Kyoo.Core/CoreModule.cs +++ b/back/src/Kyoo.Core/CoreModule.cs @@ -34,7 +34,6 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.StaticFiles; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Options; using IMetadataProvider = Kyoo.Abstractions.Controllers.IMetadataProvider; using JsonOptions = Kyoo.Core.Api.JsonOptions; @@ -122,7 +121,10 @@ namespace Kyoo.Core services.AddHttpContextAccessor(); services.AddTransient, JsonOptions>(); - services.AddMvcCore() + services.AddMvcCore(options => + { + options.Filters.Add(); + }) .AddNewtonsoftJson() .AddDataAnnotations() .AddControllersAsServices() @@ -156,16 +158,7 @@ namespace Kyoo.Core /// public IEnumerable ConfigureSteps => new IStartupAction[] { - SA.New((app, env) => - { - if (env.IsDevelopment()) - app.UseDeveloperExceptionPage(); - else - { - app.UseExceptionHandler("/error"); - app.UseHsts(); - } - }, SA.Before), + SA.New(app => app.UseHsts(), SA.Before), SA.New(app => app.UseResponseCompression(), SA.Routing + 1), SA.New(app => app.UseRouting(), SA.Routing), SA.New(app => app.UseEndpoints(x => x.MapControllers()), SA.Endpoint) diff --git a/back/src/Kyoo.Core/ExceptionFilter.cs b/back/src/Kyoo.Core/ExceptionFilter.cs new file mode 100644 index 00000000..62f9fbc4 --- /dev/null +++ b/back/src/Kyoo.Core/ExceptionFilter.cs @@ -0,0 +1,80 @@ +// Kyoo - A portable and vast media library solution. +// Copyright (c) Kyoo. +// +// See AUTHORS.md and LICENSE file in the project root for full license information. +// +// Kyoo is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// Kyoo is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kyoo. If not, see . + +using System; +using Kyoo.Abstractions.Models.Exceptions; +using Kyoo.Abstractions.Models.Utils; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; + +namespace Kyoo.Core +{ + /// + /// A middleware to handle errors globally. + /// + public class ExceptionFilter : IExceptionFilter + { + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger used to log errors. + public ExceptionFilter(ILogger logger) + { + _logger = logger; + } + + /// + public void OnException(ExceptionContext context) + { + switch (context.Exception) + { + case ArgumentException ex: + context.Result = new BadRequestObjectResult(new RequestError(ex.Message)); + break; + case ItemNotFoundException ex: + context.Result = new NotFoundObjectResult(new RequestError(ex.Message)); + break; + case DuplicatedItemException ex: + context.Result = new ConflictObjectResult(ex.Existing); + break; + case Exception ex: + _logger.LogError("Unhandled error", ex); + context.Result = new ServerErrorObjectResult(new RequestError("Internal Server Error")); + break; + } + } + } + + /// + public class ServerErrorObjectResult : ObjectResult + { + /// + /// Initializes a new instance of the class. + /// + /// The object to return. + public ServerErrorObjectResult(object value) + : base(value) + { + StatusCode = StatusCodes.Status500InternalServerError; + } + } +} diff --git a/back/src/Kyoo.Core/Views/Admin/ConfigurationApi.cs b/back/src/Kyoo.Core/Views/Admin/ConfigurationApi.cs index b21a59cb..e1d51bb7 100644 --- a/back/src/Kyoo.Core/Views/Admin/ConfigurationApi.cs +++ b/back/src/Kyoo.Core/Views/Admin/ConfigurationApi.cs @@ -16,11 +16,9 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -68,14 +66,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult GetConfiguration(string slug) { - try - { - return _manager.GetValue(slug); - } - catch (ItemNotFoundException) - { - return NotFound(); - } + return _manager.GetValue(slug); } /// @@ -95,19 +86,8 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> EditConfiguration(string slug, [FromBody] object newValue) { - try - { - await _manager.EditValue(slug, newValue); - return newValue; - } - catch (ItemNotFoundException) - { - return NotFound(); - } - catch (ArgumentException ex) - { - return BadRequest(ex.Message); - } + await _manager.EditValue(slug, newValue); + return newValue; } } } diff --git a/back/src/Kyoo.Core/Views/Admin/TaskApi.cs b/back/src/Kyoo.Core/Views/Admin/TaskApi.cs index 96d42b01..5ed8790e 100644 --- a/back/src/Kyoo.Core/Views/Admin/TaskApi.cs +++ b/back/src/Kyoo.Core/Views/Admin/TaskApi.cs @@ -20,7 +20,6 @@ using System; using System.Collections.Generic; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; @@ -90,19 +89,8 @@ namespace Kyoo.Core.Api public IActionResult RunTask(string taskSlug, [FromQuery] Dictionary args) { - try - { - _taskManager.StartTask(taskSlug, new Progress(), args); - return Ok(); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + _taskManager.StartTask(taskSlug, new Progress(), args); + return Ok(); } } } diff --git a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs index fb2a5f28..76ef01eb 100644 --- a/back/src/Kyoo.Core/Views/Helper/CrudApi.cs +++ b/back/src/Kyoo.Core/Views/Helper/CrudApi.cs @@ -16,12 +16,10 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; @@ -93,14 +91,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] public async Task> GetCount([FromQuery] Dictionary where) { - try - { - return await Repository.GetCount(ApiHelper.ParseWhere(where)); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + return await Repository.GetCount(ApiHelper.ParseWhere(where)); } /// @@ -111,8 +102,7 @@ namespace Kyoo.Core.Api /// /// Sort information about the query (sort by, sort order). /// Filter the returned items. - /// How many items per page should be returned. - /// Where the pagination should start. + /// How many items per page should be returned, where should the page start... /// A list of resources that match every filters. /// Invalid filters or sort information. [HttpGet] @@ -122,23 +112,15 @@ namespace Kyoo.Core.Api public async Task>> GetAll( [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 20, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await Repository.GetAll( - ApiHelper.ParseWhere(where), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await Repository.GetAll( + ApiHelper.ParseWhere(where), + Sort.From(sortBy), + pagination + ); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + return Page(resources, pagination.Count); } /// @@ -158,19 +140,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status409Conflict, Type = typeof(ActionResult<>))] public async Task> Create([FromBody] T resource) { - try - { - return await Repository.Create(resource); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } - catch (DuplicatedItemException) - { - T existing = await Repository.GetOrDefault(resource.Slug); - return Conflict(existing); - } + return await Repository.Create(resource); } /// @@ -191,19 +161,12 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Edit([FromBody] T resource) { - try - { - if (resource.ID > 0) - return await Repository.Edit(resource, true); - - T old = await Repository.Get(resource.Slug); - resource.ID = old.ID; + if (resource.ID > 0) return await Repository.Edit(resource, true); - } - catch (ItemNotFoundException) - { - return NotFound(); - } + + T old = await Repository.Get(resource.Slug); + resource.ID = old.ID; + return await Repository.Edit(resource, true); } /// @@ -224,19 +187,12 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Patch([FromBody] T resource) { - try - { - if (resource.ID > 0) - return await Repository.Edit(resource, false); - - T old = await Repository.Get(resource.Slug); - resource.ID = old.ID; + if (resource.ID > 0) return await Repository.Edit(resource, false); - } - catch (ItemNotFoundException) - { - return NotFound(); - } + + T old = await Repository.Get(resource.Slug); + resource.ID = old.ID; + return await Repository.Edit(resource, false); } /// @@ -254,18 +210,10 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Delete(Identifier identifier) { - try - { - await identifier.Match( - id => Repository.Delete(id), - slug => Repository.Delete(slug) - ); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - + await identifier.Match( + id => Repository.Delete(id), + slug => Repository.Delete(slug) + ); return NoContent(); } @@ -284,15 +232,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(RequestError))] public async Task Delete([FromQuery] Dictionary where) { - try - { - await Repository.DeleteAll(ApiHelper.ParseWhere(where)); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } - + await Repository.DeleteAll(ApiHelper.ParseWhere(where)); return NoContent(); } } diff --git a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs b/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs index 1b966f44..48df7490 100644 --- a/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/GenreApi.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -67,8 +66,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of shows to return and where to start. /// A page of shows. /// The filters or the sort parameters are invalid. /// No genre with the given ID could be found. @@ -81,25 +79,17 @@ namespace Kyoo.Core.Api public async Task>> GetShows(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 20, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Genres)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Genres)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs b/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs index 9f5ab352..040e7eca 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StaffApi.cs @@ -23,7 +23,6 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; @@ -73,8 +72,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the person. /// A key to sort roles by. /// An optional list of filters. - /// The number of roles to return. - /// An optional role's ID to start the query from this specific item. + /// The number of roles to return. /// A page of roles. /// The filters or the sort parameters are invalid. /// No person with the given ID or slug could be found. @@ -87,30 +85,17 @@ namespace Kyoo.Core.Api public async Task>> GetRoles(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 20, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = Sort.From(sortBy); - Pagination pagination = new(limit, afterID); + Expression> whereQuery = ApiHelper.ParseWhere(where); + Sort sort = Sort.From(sortBy); - ICollection resources = await identifier.Match( - id => _libraryManager.GetRolesFromPeople(id, whereQuery, sort, pagination), - slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sort, pagination) - ); + ICollection resources = await identifier.Match( + id => _libraryManager.GetRolesFromPeople(id, whereQuery, sort, pagination), + slug => _libraryManager.GetRolesFromPeople(slug, whereQuery, sort, pagination) + ); - return Page(resources, limit); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs index 1feb79af..48da7042 100644 --- a/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs +++ b/back/src/Kyoo.Core/Views/Metadata/StudioApi.cs @@ -67,8 +67,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of shows to return. /// A page of shows. /// The filters or the sort parameters are invalid. /// No studio with the given ID or slug could be found. @@ -81,25 +80,17 @@ namespace Kyoo.Core.Api public async Task>> GetShows(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 20, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioID, x => x.Studio.Slug)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.StudioID, x => x.Studio.Slug)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs index 32e49f2b..24ff4356 100644 --- a/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/CollectionApi.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -71,8 +70,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of shows to return. /// A page of shows. /// The filters or the sort parameters are invalid. /// No collection with the given ID could be found. @@ -85,25 +83,17 @@ namespace Kyoo.Core.Api public async Task>> GetShows(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -115,8 +105,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort libraries by. /// An optional list of filters. - /// The number of libraries to return. - /// An optional library's ID to start the query from this specific item. + /// The number of libraries to return. /// A page of libraries. /// The filters or the sort parameters are invalid. /// No collection with the given ID or slug could be found. @@ -129,25 +118,17 @@ namespace Kyoo.Core.Api public async Task>> GetLibraries(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Collections)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs index 9241b26f..d8cc7633 100644 --- a/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/EpisodeApi.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -92,10 +91,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetShow(Identifier identifier) { - Show ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn(x => x.Episodes)); - if (ret == null) - return NotFound(); - return ret; + return await _libraryManager.Get(identifier.IsContainedIn(x => x.Episodes)); } /// @@ -138,8 +134,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort tracks by. /// An optional list of filters. - /// The number of tracks to return. - /// An optional track's ID to start the query from this specific item. + /// The number of tracks to return. /// A page of tracks. /// The filters or the sort parameters are invalid. /// No episode with the given ID or slug could be found. @@ -153,24 +148,17 @@ namespace Kyoo.Core.Api public async Task>> GetEpisode(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.EpisodeID, x => x.Episode.Slug)), - Sort.From(sortBy), - new Pagination(limit, afterID)); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.EpisodeID, x => x.Episode.Slug)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs index ed4b42b7..4700de8b 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryApi.cs @@ -24,7 +24,6 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; @@ -70,8 +69,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of shows to return. /// A page of shows. /// The filters or the sort parameters are invalid. /// No library with the given ID or slug could be found. @@ -84,25 +82,17 @@ namespace Kyoo.Core.Api public async Task>> GetShows(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 50, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -114,8 +104,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of collections to return. /// A page of collections. /// The filters or the sort parameters are invalid. /// No library with the given ID or slug could be found. @@ -128,25 +117,17 @@ namespace Kyoo.Core.Api public async Task>> GetCollections(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 50, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Libraries)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -161,8 +142,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of items to return. /// A page of items. /// The filters or the sort parameters are invalid. /// No library with the given ID or slug could be found. @@ -175,30 +155,17 @@ namespace Kyoo.Core.Api public async Task>> GetItems(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 50, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = Sort.From(sortBy); - Pagination pagination = new(limit, afterID); + Expression> whereQuery = ApiHelper.ParseWhere(where); + Sort sort = Sort.From(sortBy); - ICollection resources = await identifier.Match( - id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination), - slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination) - ); + ICollection resources = await identifier.Match( + id => _libraryManager.GetItemsFromLibrary(id, whereQuery, sort, pagination), + slug => _libraryManager.GetItemsFromLibrary(slug, whereQuery, sort, pagination) + ); - return Page(resources, limit); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs index 4dc9851e..ba582f7e 100644 --- a/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/LibraryItemApi.cs @@ -69,8 +69,7 @@ namespace Kyoo.Core.Api /// /// 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. + /// The number of items to return. /// A page of items. /// The filters or the sort parameters are invalid. /// No library with the given ID or slug could be found. @@ -82,23 +81,15 @@ namespace Kyoo.Core.Api public async Task>> GetAll( [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 50, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryItems.GetAll( - ApiHelper.ParseWhere(where), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryItems.GetAll( + ApiHelper.ParseWhere(where), + Sort.From(sortBy), + pagination + ); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs index f71e5c57..74b92318 100644 --- a/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/SeasonApi.cs @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . -using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -71,8 +70,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of episodes to return. /// A page of episodes. /// The filters or the sort parameters are invalid. /// No season with the given ID or slug could be found. @@ -85,24 +83,17 @@ namespace Kyoo.Core.Api public async Task>> GetEpisode(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), - Sort.From(sortBy), - new Pagination(limit, afterID)); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.SeasonID, x => x.Season.Slug)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// diff --git a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs index e5ad20b6..e77de8ff 100644 --- a/back/src/Kyoo.Core/Views/Resources/ShowApi.cs +++ b/back/src/Kyoo.Core/Views/Resources/ShowApi.cs @@ -24,7 +24,6 @@ using System.Threading.Tasks; using Kyoo.Abstractions.Controllers; using Kyoo.Abstractions.Models; using Kyoo.Abstractions.Models.Attributes; -using Kyoo.Abstractions.Models.Exceptions; using Kyoo.Abstractions.Models.Permissions; using Kyoo.Abstractions.Models.Utils; using Microsoft.AspNetCore.Http; @@ -75,8 +74,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort seasons by. /// An optional list of filters. - /// The number of seasons to return. - /// An optional season's ID to start the query from this specific item. + /// The number of seasons to return. /// A page of seasons. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -89,25 +87,17 @@ namespace Kyoo.Core.Api public async Task>> GetSeasons(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 20, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -119,8 +109,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of episodes to return. /// A page of episodes. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -133,25 +122,17 @@ namespace Kyoo.Core.Api public async Task>> GetEpisodes(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 50, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.Matcher(x => x.ShowID, x => x.Show.Slug)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -163,8 +144,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort staff members by. /// An optional list of filters. - /// The number of people to return. - /// An optional person's ID to start the query from this specific item. + /// The number of people to return. /// A page of people. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -177,29 +157,16 @@ namespace Kyoo.Core.Api public async Task>> GetPeople(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - Expression> whereQuery = ApiHelper.ParseWhere(where); - Sort sort = Sort.From(sortBy); - Pagination pagination = new(limit, afterID); + Expression> whereQuery = ApiHelper.ParseWhere(where); + Sort sort = Sort.From(sortBy); - ICollection resources = await identifier.Match( - id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination), - slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination) - ); - return Page(resources, limit); - } - catch (ItemNotFoundException) - { - return NotFound(); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + ICollection resources = await identifier.Match( + id => _libraryManager.GetPeopleFromShow(id, whereQuery, sort, pagination), + slug => _libraryManager.GetPeopleFromShow(slug, whereQuery, sort, pagination) + ); + return Page(resources, pagination.Count); } /// @@ -211,8 +178,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort genres by. /// An optional list of filters. - /// The number of genres to return. - /// An optional genre's ID to start the query from this specific item. + /// The number of genres to return. /// A page of genres. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -225,25 +191,17 @@ namespace Kyoo.Core.Api public async Task>> GetGenres(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -261,10 +219,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetStudio(Identifier identifier) { - Studio studio = await _libraryManager.GetOrDefault(identifier.IsContainedIn(x => x.Shows)); - if (studio == null) - return NotFound(); - return studio; + return await _libraryManager.Get(identifier.IsContainedIn(x => x.Shows)); } /// @@ -277,8 +232,7 @@ namespace Kyoo.Core.Api /// The ID or slug of the . /// A key to sort libraries by. /// An optional list of filters. - /// The number of libraries to return. - /// An optional library's ID to start the query from this specific item. + /// The number of libraries to return. /// A page of libraries. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -291,25 +245,17 @@ namespace Kyoo.Core.Api public async Task>> GetLibraries(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } /// @@ -321,8 +267,7 @@ namespace Kyoo.Core.Api /// 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. + /// The number of collections to return. /// A page of collections. /// The filters or the sort parameters are invalid. /// No show with the given ID or slug could be found. @@ -335,25 +280,17 @@ namespace Kyoo.Core.Api public async Task>> GetCollections(Identifier identifier, [FromQuery] string sortBy, [FromQuery] Dictionary where, - [FromQuery] int limit = 30, - [FromQuery] int? afterID = null) + [FromQuery] Pagination pagination) { - try - { - ICollection resources = await _libraryManager.GetAll( - ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), - Sort.From(sortBy), - new Pagination(limit, afterID) - ); + ICollection resources = await _libraryManager.GetAll( + ApiHelper.ParseWhere(where, identifier.IsContainedIn(x => x.Shows)), + Sort.From(sortBy), + pagination + ); - if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) - return NotFound(); - return Page(resources, limit); - } - catch (ArgumentException ex) - { - return BadRequest(new RequestError(ex.Message)); - } + if (!resources.Any() && await _libraryManager.GetOrDefault(identifier.IsSame()) == null) + return NotFound(); + return Page(resources, pagination.Count); } } } diff --git a/back/src/Kyoo.Core/Views/Watch/TrackApi.cs b/back/src/Kyoo.Core/Views/Watch/TrackApi.cs index d2af40dd..29d31323 100644 --- a/back/src/Kyoo.Core/Views/Watch/TrackApi.cs +++ b/back/src/Kyoo.Core/Views/Watch/TrackApi.cs @@ -72,10 +72,7 @@ namespace Kyoo.Core.Api [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetEpisode(Identifier identifier) { - Episode ret = await _libraryManager.GetOrDefault(identifier.IsContainedIn(x => x.Tracks)); - if (ret == null) - return NotFound(); - return ret; + return await _libraryManager.Get(identifier.IsContainedIn(x => x.Tracks)); } } } diff --git a/back/src/Kyoo.Database/DatabaseContext.cs b/back/src/Kyoo.Database/DatabaseContext.cs index 2b77aa7f..12cb22d4 100644 --- a/back/src/Kyoo.Database/DatabaseContext.cs +++ b/back/src/Kyoo.Database/DatabaseContext.cs @@ -421,28 +421,6 @@ namespace Kyoo.Database } } - /// - /// Save changes that are applied to this context. - /// - /// The message that will have the - /// (if a duplicate is found). - /// A duplicated item has been found. - /// The number of state entries written to the database. - public int SaveChanges(string duplicateMessage) - { - try - { - return base.SaveChanges(); - } - catch (DbUpdateException ex) - { - DiscardChanges(); - if (IsDuplicateException(ex)) - throw new DuplicatedItemException(duplicateMessage); - throw; - } - } - /// /// Save changes that are applied to this context. /// @@ -491,12 +469,13 @@ namespace Kyoo.Database /// /// Save changes that are applied to this context. /// - /// The message that will have the - /// (if a duplicate is found). + /// How to retrieve the conflicting item. /// A to observe while waiting for the task to complete /// A duplicated item has been found. + /// The type of the potential duplicate (this is unused). /// The number of state entries written to the database. - public async Task SaveChangesAsync(string duplicateMessage, + public async Task SaveChangesAsync( + Func> getExisting, CancellationToken cancellationToken = default) { try @@ -507,7 +486,7 @@ namespace Kyoo.Database { DiscardChanges(); if (IsDuplicateException(ex)) - throw new DuplicatedItemException(duplicateMessage); + throw new DuplicatedItemException(await getExisting()); throw; } }