Working oidc

This commit is contained in:
Zoe Roux 2021-05-09 02:38:54 +02:00
parent 429af9b252
commit d7972704dd
45 changed files with 347 additions and 328 deletions

View File

@ -3,16 +3,19 @@ using System.Collections.Generic;
using IdentityServer4.Extensions;
using IdentityServer4.Services;
using Kyoo.Authentication.Models;
using Kyoo.Authentication.Views;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Logging;
namespace Kyoo.Authentication
{
@ -53,16 +56,25 @@ namespace Kyoo.Authentication
/// </summary>
private readonly ILoggerFactory _loggerFactory;
/// <summary>
/// The environment information to check if the app runs in debug mode
/// </summary>
private readonly IWebHostEnvironment _environment;
/// <summary>
/// Create a new authentication module instance and use the given configuration and environment.
/// </summary>
/// <param name="configuration">The configuration to use</param>
/// <param name="loggerFactory">The logger factory to allow IdentityServer to log things</param>
public AuthenticationModule(IConfiguration configuration, ILoggerFactory loggerFactory)
/// <param name="environment">The environment information to check if the app runs in debug mode</param>
public AuthenticationModule(IConfiguration configuration,
ILoggerFactory loggerFactory,
IWebHostEnvironment environment)
{
_configuration = configuration;
_loggerFactory = loggerFactory;
_environment = environment;
}
/// <inheritdoc />
@ -70,8 +82,16 @@ namespace Kyoo.Authentication
{
string publicUrl = _configuration.GetValue<string>("public_url").TrimEnd('/');
if (_environment.IsDevelopment())
IdentityModelEventSource.ShowPII = true;
services.AddControllers();
// services.AddIdentityCore<User>()
// .AddSignInManager()
// .AddDefaultTokenProviders()
// .AddUserStore<UserStore>();
// services.AddDbContext<IdentityDatabase>(options =>
// {
// options.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
@ -113,25 +133,25 @@ namespace Kyoo.Authentication
// options.EnableTokenCleanup = true;
// })
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
.AddInMemoryApiScopes(IdentityContext.GetScopes())
.AddInMemoryApiResources(IdentityContext.GetApis())
.AddInMemoryClients(IdentityContext.GetClients())
.AddDeveloperSigningCredential();
// .AddProfileService<AccountApi>()
// .AddSigninKeys(certificateOptions);
.AddProfileService<AccountApi>()
.AddSigninKeys(certificateOptions);
// TODO implement means to add clients or api scopes for other plugins.
// TODO split scopes (kyoo.read should be task.read, video.read etc)
services.AddAuthentication(o =>
{
o.DefaultScheme = IdentityConstants.ApplicationScheme;
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies(_ => { });
// services.AddAuthentication(o =>
// {
// o.DefaultScheme = IdentityConstants.ApplicationScheme;
// o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
// })
// .AddIdentityCookies(_ => { });
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.Authority = publicUrl;
options.Audience = "Kyoo";
options.Audience = "kyoo";
options.RequireHttpsMetadata = false;
});
@ -146,10 +166,10 @@ namespace Kyoo.Authentication
{
options.AddPolicy(permission, policy =>
{
policy.AuthenticationSchemes.Add(IdentityConstants.ApplicationScheme);
// policy.AuthenticationSchemes.Add(IdentityConstants.ApplicationScheme);
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
policy.AddRequirements(new AuthRequirement(permission));
policy.RequireScope($"kyoo.{permission.ToLower()}");
// policy.RequireScope($"kyoo.{permission.ToLower()}");
});
}
});

View File

@ -41,7 +41,7 @@ namespace Kyoo.Authentication
else
{
ICollection<string> defaultPerms = _options.CurrentValue.Default;
if (defaultPerms.Contains(requirement.Permission.ToLower()))
if (defaultPerms?.Contains(requirement.Permission.ToLower()) == true)
context.Succeed(requirement);
}

View File

@ -0,0 +1,133 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.AspNetCore.Identity;
namespace Kyoo.Authentication
{
/// <summary>
/// An implementation of an <see cref="IUserStore{TUser}"/> that uses an <see cref="IUserRepository"/>.
/// </summary>
public class UserStore : IUserStore<User>
{
/// <summary>
/// The user repository used to store users.
/// </summary>
private readonly IUserRepository _users;
/// <summary>
/// Create a new <see cref="UserStore"/>.
/// </summary>
/// <param name="users">The user repository to use</param>
public UserStore(IUserRepository users)
{
_users = users;
}
/// <inheritdoc />
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Implementation of the IDisposable pattern
/// </summary>
/// <param name="disposing">True if this class should be disposed.</param>
protected virtual void Dispose(bool disposing)
{
bool _ = disposing;
// Not implemented because this class has nothing to dispose.
}
/// <inheritdoc />
public Task<string> GetUserIdAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult(user.ID.ToString());
}
/// <inheritdoc />
public Task<string> GetUserNameAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Username);
}
/// <inheritdoc />
public Task SetUserNameAsync(User user, string userName, CancellationToken cancellationToken)
{
user.Username = userName;
return Task.CompletedTask;
}
/// <inheritdoc />
public Task<string> GetNormalizedUserNameAsync(User user, CancellationToken cancellationToken)
{
return Task.FromResult(user.Slug);
}
/// <inheritdoc />
public Task SetNormalizedUserNameAsync(User user, string normalizedName, CancellationToken cancellationToken)
{
user.Slug = normalizedName;
return Task.CompletedTask;
}
/// <inheritdoc />
public async Task<IdentityResult> CreateAsync(User user, CancellationToken cancellationToken)
{
try
{
await _users.Create(user);
return IdentityResult.Success;
}
catch (Exception ex)
{
return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
}
}
/// <inheritdoc />
public async Task<IdentityResult> UpdateAsync(User user, CancellationToken cancellationToken)
{
try
{
await _users.Edit(user, false);
return IdentityResult.Success;
}
catch (Exception ex)
{
return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
}
}
/// <inheritdoc />
public async Task<IdentityResult> DeleteAsync(User user, CancellationToken cancellationToken)
{
try
{
await _users.Delete(user);
return IdentityResult.Success;
}
catch (Exception ex)
{
return IdentityResult.Failed(new IdentityError {Code = ex.GetType().Name, Description = ex.Message});
}
}
/// <inheritdoc />
public Task<User> FindByIdAsync(string userId, CancellationToken cancellationToken)
{
return _users.GetOrDefault(int.Parse(userId));
}
/// <inheritdoc />
public Task<User> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
return _users.GetOrDefault(normalizedUserName);
}
}
}

View File

@ -33,7 +33,7 @@ namespace Kyoo.Authentication
AllowOfflineAccess = true,
RequireConsent = false,
AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.download", "kyoo.admin" },
AllowedScopes = { "openid", "profile", "kyoo.read", "kyoo.write", "kyoo.play", "kyoo.admin" },
RedirectUris = { "/", "/silent.html" },
PostLogoutRedirectUris = { "/logout" }
}
@ -60,11 +60,6 @@ namespace Kyoo.Authentication
DisplayName = "Allow playback of movies and episodes."
},
new ApiScope
{
Name = "kyoo.download",
DisplayName = "Allow downloading of episodes and movies from kyoo."
},
new ApiScope
{
Name = "kyoo.admin",
DisplayName = "Full access to the admin's API and the public API."
@ -76,9 +71,8 @@ namespace Kyoo.Authentication
{
return new[]
{
new ApiResource
new ApiResource("kyoo", "Kyoo")
{
Name = "Kyoo",
Scopes = GetScopes().Select(x => x.Name).ToArray()
}
};

View File

@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace Kyoo.Authentication.Models
{
/// <summary>

View File

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.Extensions;
using IdentityServer4.Models;
using IdentityServer4.Services;
@ -14,6 +16,7 @@ using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
@ -39,6 +42,7 @@ namespace Kyoo.Authentication.Views
/// A file manager to send profile pictures
/// </summary>
private readonly IFileManager _files;
// private readonly SignInManager<User> _signInManager;
/// <summary>
/// Options about authentication. Those options are monitored and reloads are supported.
@ -57,11 +61,13 @@ namespace Kyoo.Authentication.Views
// IIdentityServerInteractionService interaction,
IFileManager files,
IOptions<AuthenticationOption> options)
//, SignInManager<User> signInManager)
{
_users = users;
// _interaction = interaction;
_files = files;
_options = options;
// _signInManager = signInManager;
}
@ -114,14 +120,14 @@ namespace Kyoo.Authentication.Views
public async Task<IActionResult> Login([FromBody] LoginRequest login)
{
// AuthorizationRequest context = await _interaction.GetAuthorizationContextAsync(login.ReturnURL);
User user = await _users.Get(x => x.Username == login.Username);
User user = await _users.GetOrDefault(x => x.Username == login.Username);
if (user == null)
return Unauthorized();
if (!PasswordUtils.CheckPassword(login.Password, user.Password))
return Unauthorized();
await HttpContext.SignInAsync(user.ID.ToString(), user.ToPrincipal(), StayLogged(login.StayLoggedIn));
// await _signInManager.SignInAsync(user, login.StayLoggedIn);
return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true });
}
@ -142,7 +148,15 @@ namespace Kyoo.Authentication.Views
{
code = "ExpiredOTAC", description = "The OTAC has expired. Try to login with your password."
});
await HttpContext.SignInAsync(user.ID.ToString(), user.ToPrincipal(), StayLogged(otac.StayLoggedIn));
IdentityServerUser iduser = new(user.ID.ToString())
{
DisplayName = user.Username
};
await HttpContext.SignInAsync(iduser, StayLogged(otac.StayLoggedIn));
// await _signInManager.SignInAsync(user, otac.StayLoggedIn);
return Ok();
}
@ -153,22 +167,23 @@ namespace Kyoo.Authentication.Views
[Authorize]
public async Task<IActionResult> Logout()
{
await HttpContext.SignOutAsync();
// await _signInManager.SignOutAsync();
return Ok();
}
// TODO check with the extension method
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
User user = await _users.Get(int.Parse(context.Subject.GetSubjectId()));
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
if (user == null)
return;
context.IssuedClaims.AddRange(user.GetClaims());
context.IssuedClaims.Add(new Claim("permissions", string.Join(',', user.Permissions)));
}
public async Task IsActiveAsync(IsActiveContext context)
{
User user = await _users.Get(int.Parse(context.Subject.GetSubjectId()));
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
context.IsActive = user != null;
}
@ -186,8 +201,10 @@ namespace Kyoo.Authentication.Views
[Authorize]
public async Task<ActionResult<User>> Update([FromForm] AccountUpdateRequest data)
{
User user = await _users.Get(int.Parse(HttpContext.User.GetSubjectId()));
User user = await _users.GetOrDefault(int.Parse(HttpContext.User.GetSubjectId()));
if (user == null)
return Unauthorized();
if (!string.IsNullOrEmpty(data.Email))
user.Email = data.Email;
if (!string.IsNullOrEmpty(data.Username))
@ -204,7 +221,7 @@ namespace Kyoo.Authentication.Views
[HttpGet("permissions")]
public ActionResult<IEnumerable<string>> GetDefaultPermissions()
{
return _options.Value.Permissions.Default;
return _options.Value.Permissions.Default ?? Array.Empty<string>();
}
}
}

View File

@ -84,6 +84,7 @@ namespace Kyoo.Controllers
/// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get<T>(int id) where T : class, IResource;
/// <summary>
@ -93,6 +94,7 @@ namespace Kyoo.Controllers
/// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The resource found</returns>
[ItemNotNull]
Task<T> Get<T>(string slug) where T : class, IResource;
/// <summary>
@ -102,6 +104,7 @@ namespace Kyoo.Controllers
/// <typeparam name="T">The type of the resource</typeparam>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The first resource found that match the where function</returns>
[ItemNotNull]
Task<T> Get<T>(Expression<Func<T, bool>> where) where T : class, IResource;
/// <summary>
@ -111,6 +114,7 @@ namespace Kyoo.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The season found</returns>
[ItemNotNull]
Task<Season> Get(int showID, int seasonNumber);
/// <summary>
@ -120,6 +124,7 @@ namespace Kyoo.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The season found</returns>
[ItemNotNull]
Task<Season> Get(string showSlug, int seasonNumber);
/// <summary>
@ -130,6 +135,7 @@ namespace Kyoo.Controllers
/// <param name="episodeNumber">The episode's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The episode found</returns>
[ItemNotNull]
Task<Episode> Get(int showID, int seasonNumber, int episodeNumber);
/// <summary>
@ -140,6 +146,7 @@ namespace Kyoo.Controllers
/// <param name="episodeNumber">The episode's number</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The episode found</returns>
[ItemNotNull]
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
/// <summary>
@ -148,7 +155,8 @@ namespace Kyoo.Controllers
/// <param name="slug">The slug of the track</param>
/// <param name="type">The type (Video, Audio or Subtitle)</param>
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
/// <returns>The tracl found</returns>
/// <returns>The track found</returns>
[ItemNotNull]
Task<Track> Get(string slug, StreamType type = StreamType.Unknown);
/// <summary>
@ -157,6 +165,7 @@ namespace Kyoo.Controllers
/// <param name="id">The id of the resource</param>
/// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The resource found</returns>
[ItemCanBeNull]
Task<T> GetOrDefault<T>(int id) where T : class, IResource;
/// <summary>
@ -165,6 +174,7 @@ namespace Kyoo.Controllers
/// <param name="slug">The slug of the resource</param>
/// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The resource found</returns>
[ItemCanBeNull]
Task<T> GetOrDefault<T>(string slug) where T : class, IResource;
/// <summary>
@ -173,6 +183,7 @@ namespace Kyoo.Controllers
/// <param name="where">The filter function.</param>
/// <typeparam name="T">The type of the resource</typeparam>
/// <returns>The first resource found that match the where function</returns>
[ItemCanBeNull]
Task<T> GetOrDefault<T>(Expression<Func<T, bool>> where) where T : class, IResource;
/// <summary>
@ -181,6 +192,7 @@ namespace Kyoo.Controllers
/// <param name="showID">The id of the show</param>
/// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns>
[ItemCanBeNull]
Task<Season> GetOrDefault(int showID, int seasonNumber);
/// <summary>
@ -189,6 +201,7 @@ namespace Kyoo.Controllers
/// <param name="showSlug">The slug of the show</param>
/// <param name="seasonNumber">The season's number</param>
/// <returns>The season found</returns>
[ItemCanBeNull]
Task<Season> GetOrDefault(string showSlug, int seasonNumber);
/// <summary>
@ -198,6 +211,7 @@ namespace Kyoo.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns>
[ItemCanBeNull]
Task<Episode> GetOrDefault(int showID, int seasonNumber, int episodeNumber);
/// <summary>
@ -207,6 +221,7 @@ namespace Kyoo.Controllers
/// <param name="seasonNumber">The season's number</param>
/// <param name="episodeNumber">The episode's number</param>
/// <returns>The episode found</returns>
[ItemCanBeNull]
Task<Episode> GetOrDefault(string showSlug, int seasonNumber, int episodeNumber);
/// <summary>
@ -214,7 +229,8 @@ namespace Kyoo.Controllers
/// </summary>
/// <param name="slug">The slug of the track</param>
/// <param name="type">The type (Video, Audio or Subtitle)</param>
/// <returns>The tracl found</returns>
/// <returns>The track found</returns>
[ItemCanBeNull]
Task<Track> GetOrDefault(string slug, StreamType type = StreamType.Unknown);

View File

@ -212,9 +212,8 @@ namespace Kyoo.Controllers
/// Create a new resource if it does not exist already. If it does, the existing value is returned instead.
/// </summary>
/// <param name="obj">The object to create</param>
/// <param name="silentFail">Allow issues to occurs in this method. Every issue is caught and ignored.</param>
/// <returns>The newly created item or the existing value if it existed.</returns>
Task<T> CreateIfNotExists([NotNull] T obj, bool silentFail = false);
Task<T> CreateIfNotExists([NotNull] T obj);
/// <summary>
/// Edit a resource

View File

@ -1,4 +1,5 @@
using System;
using System.Runtime.Serialization;
namespace Kyoo.Models.Exceptions
{
@ -22,5 +23,14 @@ namespace Kyoo.Models.Exceptions
public DuplicatedItemException(string message)
: base(message)
{ }
/// <summary>
/// The serialization constructor
/// </summary>
/// <param name="info">Serialization infos</param>
/// <param name="context">The serialization context</param>
protected DuplicatedItemException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Runtime.Serialization;
namespace Kyoo.Models.Exceptions
{
@ -20,5 +21,14 @@ namespace Kyoo.Models.Exceptions
public ItemNotFoundException(string message)
: base(message)
{ }
/// <summary>
/// The serialization constructor
/// </summary>
/// <param name="info">Serialization infos</param>
/// <param name="context">The serialization context</param>
protected ItemNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}

View File

@ -1,5 +1,6 @@
using System;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -22,7 +23,7 @@ namespace Kyoo.Models
public int? StartYear { get; set; }
public int? EndYear { get; set; }
[SerializeAs("{HOST}/api/{_type}/{Slug}/poster")] public string Poster { get; set; }
private string _type => Type == ItemType.Collection ? "collection" : "show";
[UsedImplicitly] private string _type => Type == ItemType.Collection ? "collection" : "show";
public ItemType Type { get; set; }
public LibraryItem() {}

View File

@ -1,4 +1,5 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
namespace Kyoo.Models
@ -60,6 +61,7 @@ namespace Kyoo.Models
public Link() {}
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
public Link(T1 first, T2 second, bool privateItems = false)
: base(first, second)
{

View File

@ -22,14 +22,5 @@ namespace Kyoo.Models
public string DataID { get; set; }
public string Link { get; set; }
public MetadataID() { }
public MetadataID(Provider provider, string dataID, string link)
{
Provider = provider;
DataID = dataID;
Link = link;
}
}
}

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -14,27 +13,5 @@ namespace Kyoo.Models
[SerializeIgnore] public virtual Show Show { get; set; }
public string Role { get; set; }
public string Type { get; set; }
public PeopleRole() {}
public PeopleRole(People people, Show show, string role, string type)
{
People = people;
Show = show;
Role = role;
Type = type;
}
public PeopleRole(string slug,
string name,
string role,
string type,
string poster,
IEnumerable<MetadataID> externalIDs)
{
People = new People(slug, name, poster, externalIDs);
Role = role;
Type = type;
}
}
}

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -32,48 +31,6 @@ namespace Kyoo.Models
[EditableRelation] [LoadableRelation] public virtual ICollection<Track> Tracks { get; set; }
public Episode() { }
public Episode(int seasonNumber,
int episodeNumber,
int absoluteNumber,
string title,
string overview,
DateTime? releaseDate,
int runtime,
string thumb,
IEnumerable<MetadataID> externalIDs)
{
SeasonNumber = seasonNumber;
EpisodeNumber = episodeNumber;
AbsoluteNumber = absoluteNumber;
Title = title;
Overview = overview;
ReleaseDate = releaseDate;
Runtime = runtime;
Thumb = thumb;
ExternalIDs = externalIDs?.ToArray();
}
public Episode(int showID,
int seasonID,
int seasonNumber,
int episodeNumber,
int absoluteNumber,
string path,
string title,
string overview,
DateTime? releaseDate,
int runtime,
string poster,
IEnumerable<MetadataID> externalIDs)
: this(seasonNumber, episodeNumber, absoluteNumber, title, overview, releaseDate, runtime, poster, externalIDs)
{
ShowID = showID;
SeasonID = seasonID;
Path = path;
}
public static string GetSlug(string showSlug, int seasonNumber, int episodeNumber, int absoluteNumber)
{
if (showSlug == null)

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -21,15 +20,5 @@ namespace Kyoo.Models
[SerializeIgnore] public virtual ICollection<Link<Library, Show>> ShowLinks { get; set; }
[SerializeIgnore] public virtual ICollection<Link<Library, Collection>> CollectionLinks { get; set; }
#endif
public Library() { }
public Library(string slug, string name, IEnumerable<string> paths, IEnumerable<Provider> providers)
{
Slug = slug;
Name = name;
Paths = paths?.ToArray();
Providers = providers?.ToArray();
}
}
}

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -13,15 +12,5 @@ namespace Kyoo.Models
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[EditableRelation] [LoadableRelation] public virtual ICollection<PeopleRole> Roles { get; set; }
public People() {}
public People(string slug, string name, string poster, IEnumerable<MetadataID> externalIDs)
{
Slug = slug;
Name = name;
Poster = poster;
ExternalIDs = externalIDs?.ToArray();
}
}
}

View File

@ -1,5 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Kyoo.Models.Attributes;
namespace Kyoo.Models
@ -22,24 +21,5 @@ namespace Kyoo.Models
[EditableRelation] [LoadableRelation] public virtual ICollection<MetadataID> ExternalIDs { get; set; }
[LoadableRelation] public virtual ICollection<Episode> Episodes { get; set; }
public Season() { }
public Season(int showID,
int seasonNumber,
string title,
string overview,
int? year,
string poster,
IEnumerable<MetadataID> externalIDs)
{
ShowID = showID;
SeasonNumber = seasonNumber;
Title = title;
Overview = overview;
Year = year;
Poster = poster;
ExternalIDs = externalIDs?.ToArray();
}
}
}

View File

@ -42,62 +42,6 @@ namespace Kyoo.Models
[SerializeIgnore] public virtual ICollection<Link<Show, Genre>> GenreLinks { get; set; }
#endif
public Show() { }
public Show(string slug,
string title,
IEnumerable<string> aliases,
string path, string overview,
string trailerUrl,
IEnumerable<Genre> genres,
Status? status,
int? startYear,
int? endYear,
IEnumerable<MetadataID> externalIDs)
{
Slug = slug;
Title = title;
Aliases = aliases?.ToArray();
Path = path;
Overview = overview;
TrailerUrl = trailerUrl;
Genres = genres?.ToArray();
Status = status;
StartYear = startYear;
EndYear = endYear;
ExternalIDs = externalIDs?.ToArray();
}
public Show(string slug,
string title,
IEnumerable<string> aliases,
string path,
string overview,
string trailerUrl,
Status? status,
int? startYear,
int? endYear,
string poster,
string logo,
string backdrop,
IEnumerable<MetadataID> externalIDs)
{
Slug = slug;
Title = title;
Aliases = aliases?.ToArray();
Path = path;
Overview = overview;
TrailerUrl = trailerUrl;
Status = status;
StartYear = startYear;
EndYear = endYear;
Poster = poster;
Logo = logo;
Backdrop = backdrop;
ExternalIDs = externalIDs?.ToArray();
}
public string GetID(string provider)
{
return ExternalIDs?.FirstOrDefault(x => x.Provider.Name == provider)?.DataID;

View File

@ -29,28 +29,20 @@ namespace Kyoo.CommonApi
[Authorize(Policy = "Read")]
public virtual async Task<ActionResult<T>> Get(int id)
{
try
{
return await _repository.Get(id);
}
catch (ItemNotFoundException)
{
T ret = await _repository.GetOrDefault(id);
if (ret == null)
return NotFound();
}
return ret;
}
[HttpGet("{slug}")]
[Authorize(Policy = "Read")]
public virtual async Task<ActionResult<T>> Get(string slug)
{
try
{
return await _repository.Get(slug);
}
catch (ItemNotFoundException)
{
T ret = await _repository.Get(slug);
if (ret == null)
return NotFound();
}
return ret;
}
[HttpGet("count")]
@ -111,7 +103,7 @@ namespace Kyoo.CommonApi
}
catch (DuplicatedItemException)
{
T existing = await _repository.Get(resource.Slug);
T existing = await _repository.GetOrDefault(resource.Slug);
return Conflict(existing);
}
}

View File

@ -8,7 +8,6 @@ using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using Microsoft.EntityFrameworkCore.Diagnostics;
namespace Kyoo
{

View File

@ -125,7 +125,7 @@ namespace Kyoo.Controllers
Sort<T> sort = default,
Pagination limit = default)
{
return ApplyFilters(query, Get, DefaultSort, where, sort, limit);
return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit);
}
/// <summary>
@ -193,14 +193,14 @@ namespace Kyoo.Controllers
}
/// <inheritdoc/>
public virtual async Task<T> CreateIfNotExists(T obj, bool silentFail = false)
public virtual async Task<T> CreateIfNotExists(T obj)
{
try
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
T old = await Get(obj.Slug);
T old = await GetOrDefault(obj.Slug);
if (old != null)
return old;
@ -208,13 +208,7 @@ namespace Kyoo.Controllers
}
catch (DuplicatedItemException)
{
return await Get(obj.Slug);
}
catch
{
if (silentFail)
return default;
throw;
return await GetOrDefault(obj.Slug);
}
}

View File

@ -63,7 +63,7 @@ namespace Kyoo.Postgresql
services.AddDbContext<DatabaseContext, PostgresContext>(x =>
{
x.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
if (_configuration.GetValue<bool>("logging:dotnet-ef"))
if (_environment.IsDevelopment())
x.EnableDetailedErrors().EnableSensitiveDataLogging();
});
// services.AddScoped<DatabaseContext>(_ => new PostgresContext(

View File

@ -1,6 +1,3 @@
using System.Linq;
using Xunit;
namespace Kyoo.Tests
{
public class SetupTests

@ -1 +1 @@
Subproject commit 6802bc11e66331f0e77d7604838c8f1c219bef99
Subproject commit d3a860fa8ffccade9e3b17022482e11c9a18303e

View File

@ -234,7 +234,7 @@ namespace Kyoo.Controllers
await base.Validate(resource);
resource.ExternalIDs = await resource.ExternalIDs.SelectAsync(async x =>
{
x.Provider = await _providers.CreateIfNotExists(x.Provider, true);
x.Provider = await _providers.CreateIfNotExists(x.Provider);
x.ProviderID = x.Provider.ID;
_database.Entry(x.Provider).State = EntityState.Detached;
return x;

View File

@ -111,12 +111,7 @@ namespace Kyoo.Controllers
public override Task<LibraryItem> Create(LibraryItem obj) => throw new InvalidOperationException();
/// <inheritdoc />
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj, bool silentFail = false)
{
if (silentFail)
return Task.FromResult<LibraryItem>(default);
throw new InvalidOperationException();
}
public override Task<LibraryItem> CreateIfNotExists(LibraryItem obj) => throw new InvalidOperationException();
/// <inheritdoc />
public override Task<LibraryItem> Edit(LibraryItem obj, bool reset) => throw new InvalidOperationException();
/// <inheritdoc />

View File

@ -53,9 +53,8 @@ namespace Kyoo.Controllers
public override async Task<Library> Create(Library obj)
{
await base.Create(obj);
obj.ProviderLinks = obj.Providers?.Select(x => Link.Create(obj, x)).ToList();
_database.Entry(obj).State = EntityState.Added;
obj.ProviderLinks = obj.Providers?.Select(x => Link.Create(obj, x)).ToArray();
obj.ProviderLinks.ForEach(x => _database.Entry(x).State = EntityState.Added);
await _database.SaveChangesAsync($"Trying to insert a duplicated library (slug {obj.Slug} already exists).");
return obj;
}
@ -65,7 +64,7 @@ namespace Kyoo.Controllers
{
await base.Validate(resource);
resource.Providers = await resource.Providers
.SelectAsync(x => _providers.CreateIfNotExists(x, true))
.SelectAsync(x => _providers.CreateIfNotExists(x))
.ToListAsync();
}

View File

@ -73,13 +73,13 @@ namespace Kyoo.Controllers
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Provider = await _providers.CreateIfNotExists(id.Provider, true);
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
_database.Entry(id.Provider).State = EntityState.Detached;
});
await resource.Roles.ForEachAsync(async role =>
{
role.Show = await _shows.Value.CreateIfNotExists(role.Show, true);
role.Show = await _shows.Value.CreateIfNotExists(role.Show);
role.ShowID = role.Show.ID;
_database.Entry(role.Show).State = EntityState.Detached;
});
@ -129,7 +129,7 @@ namespace Kyoo.Controllers
where,
sort,
limit);
if (!people.Any() && await _shows.Value.Get(showID) == null)
if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null)
throw new ItemNotFoundException();
foreach (PeopleRole role in people)
role.ForPeople = true;
@ -151,7 +151,7 @@ namespace Kyoo.Controllers
where,
sort,
limit);
if (!people.Any() && await _shows.Value.Get(showSlug) == null)
if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null)
throw new ItemNotFoundException();
foreach (PeopleRole role in people)
role.ForPeople = true;
@ -172,7 +172,7 @@ namespace Kyoo.Controllers
where,
sort,
limit);
if (!roles.Any() && await Get(id) == null)
if (!roles.Any() && await GetOrDefault(id) == null)
throw new ItemNotFoundException();
return roles;
}
@ -191,7 +191,7 @@ namespace Kyoo.Controllers
where,
sort,
limit);
if (!roles.Any() && await Get(slug) == null)
if (!roles.Any() && await GetOrDefault(slug) == null)
throw new ItemNotFoundException();
return roles;
}

View File

@ -160,7 +160,7 @@ namespace Kyoo.Controllers
await base.Validate(resource);
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Provider = await _providers.CreateIfNotExists(id.Provider, true);
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
_database.Entry(id.Provider).State = EntityState.Detached;
});

View File

@ -102,22 +102,22 @@ namespace Kyoo.Controllers
{
await base.Validate(resource);
if (resource.Studio != null)
resource.Studio = await _studios.CreateIfNotExists(resource.Studio, true);
resource.Studio = await _studios.CreateIfNotExists(resource.Studio);
resource.Genres = await resource.Genres
.SelectAsync(x => _genres.CreateIfNotExists(x, true))
.SelectAsync(x => _genres.CreateIfNotExists(x))
.ToListAsync();
resource.GenreLinks = resource.Genres?
.Select(x => Link.UCreate(resource, x))
.ToList();
await resource.ExternalIDs.ForEachAsync(async id =>
{
id.Provider = await _providers.CreateIfNotExists(id.Provider, true);
id.Provider = await _providers.CreateIfNotExists(id.Provider);
id.ProviderID = id.Provider.ID;
_database.Entry(id.Provider).State = EntityState.Detached;
});
await resource.People.ForEachAsync(async role =>
{
role.People = await _people.CreateIfNotExists(role.People, true);
role.People = await _people.CreateIfNotExists(role.People);
role.PeopleID = role.People.ID;
_database.Entry(role.People).State = EntityState.Detached;
});

View File

@ -59,7 +59,7 @@ namespace Kyoo.Controllers
if (!match.Success)
{
if (int.TryParse(slug, out int id))
return Get(id);
return GetOrDefault(id);
match = Regex.Match(slug, @"(?<show>.*)\.(?<language>.{0,3})(?<forced>-forced)?(\..*)?");
if (!match.Success)
throw new ArgumentException("Invalid track slug. " +
@ -102,6 +102,7 @@ namespace Kyoo.Controllers
await base.Create(obj);
_database.Entry(obj).State = EntityState.Added;
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local
await _database.SaveOrRetry(obj, (x, i) =>
{
if (i > 10)

View File

@ -83,7 +83,10 @@ namespace Kyoo
.ConfigureLogging((context, builder) =>
{
builder.AddConfiguration(context.Configuration.GetSection("logging"))
.AddConsole()
.AddSimpleConsole(x =>
{
x.TimestampFormat = "[hh:mm:ss] ";
})
.AddDebug()
.AddEventSourceLogger();
})

View File

@ -47,7 +47,8 @@ namespace Kyoo
_configuration = configuration;
_plugins = new PluginManager(hostProvider, _configuration, loggerFactory.CreateLogger<PluginManager>());
_plugins.LoadPlugins(new IPlugin[] {new CoreModule(), new PostgresModule(configuration, host), new AuthenticationModule(configuration, loggerFactory)});
_plugins.LoadPlugins(new IPlugin[] {new CoreModule(), new PostgresModule(configuration, host),
new AuthenticationModule(configuration, loggerFactory, host)});
}
/// <summary>

View File

@ -73,7 +73,7 @@ namespace Kyoo.Tasks
ICollection<Library> libraries = argument == null
? await libraryManager.GetAll<Library>()
: new [] { await libraryManager.Get<Library>(argument)};
: new [] { await libraryManager.GetOrDefault<Library>(argument)};
if (argument != null && libraries.First() == null)
throw new ArgumentException($"No library found with the name {argument}");
@ -253,7 +253,7 @@ namespace Kyoo.Tasks
{
if (string.IsNullOrEmpty(collectionName))
return null;
Collection collection = await libraryManager.Get<Collection>(Utility.ToSlug(collectionName));
Collection collection = await libraryManager.GetOrDefault<Collection>(Utility.ToSlug(collectionName));
if (collection != null)
return collection;
collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
@ -265,7 +265,7 @@ namespace Kyoo.Tasks
}
catch (DuplicatedItemException)
{
return await libraryManager.Get<Collection>(collection.Slug);
return await libraryManager.GetOrDefault<Collection>(collection.Slug);
}
}
@ -275,7 +275,7 @@ namespace Kyoo.Tasks
bool isMovie,
Library library)
{
Show old = await libraryManager.Get<Show>(x => x.Path == showPath);
Show old = await libraryManager.GetOrDefault<Show>(x => x.Path == showPath);
if (old != null)
{
await libraryManager.Load(old, x => x.ExternalIDs);
@ -291,7 +291,7 @@ namespace Kyoo.Tasks
}
catch (DuplicatedItemException)
{
old = await libraryManager.Get<Show>(show.Slug);
old = await libraryManager.GetOrDefault<Show>(show.Slug);
if (old.Path == showPath)
{
await libraryManager.Load(old, x => x.ExternalIDs);
@ -320,8 +320,15 @@ namespace Kyoo.Tasks
catch (ItemNotFoundException)
{
Season season = await MetadataProvider.GetSeason(show, seasonNumber, library);
await libraryManager.CreateIfNotExists(season);
try
{
await libraryManager.Create(season);
await ThumbnailsManager.Validate(season);
}
catch (DuplicatedItemException)
{
season = await libraryManager.Get(show.Slug, seasonNumber);
}
season.Show = show;
return season;
}

View File

@ -40,7 +40,7 @@ namespace Kyoo.Api
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Collection>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(id) == null)
return NotFound();
return Page(resources, limit);
}
@ -92,7 +92,7 @@ namespace Kyoo.Api
new Sort<Library>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Collection>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Collection>(id) == null)
return NotFound();
return Page(resources, limit);
}

View File

@ -50,14 +50,20 @@ namespace Kyoo.Api
[Authorize(Policy = "Read")]
public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber, int episodeNumber)
{
return await _libraryManager.Get<Show>(showID);
Show ret = await _libraryManager.GetOrDefault<Show>(showID);
if (ret == null)
return NotFound();
return ret;
}
[HttpGet("{episodeID:int}/season")]
[Authorize(Policy = "Read")]
public async Task<ActionResult<Season>> GetSeason(int episodeID)
{
return await _libraryManager.Get<Season>(x => x.Episodes.Any(y => y.ID == episodeID));
Season ret = await _libraryManager.GetOrDefault<Season>(x => x.Episodes.Any(y => y.ID == episodeID));
if (ret == null)
return NotFound();
return ret;
}
[HttpGet("{showSlug}-s{seasonNumber:int}e{episodeNumber:int}/season")]
@ -104,7 +110,7 @@ namespace Kyoo.Api
new Sort<Track>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Episode>(episodeID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Episode>(episodeID) == null)
return NotFound();
return Page(resources, limit);
}
@ -175,7 +181,6 @@ namespace Kyoo.Api
}
[HttpGet("{id:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(int id)
{
try
@ -190,7 +195,6 @@ namespace Kyoo.Api
}
[HttpGet("{slug}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(string slug)
{
try

View File

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Controllers;
using Kyoo.Models;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
@ -41,7 +40,7 @@ namespace Kyoo.Api
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Genre>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Genre>(id) == null)
return NotFound();
return Page(resources, limit);
}

View File

@ -6,7 +6,6 @@ using Kyoo.Models;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Models.Exceptions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
@ -27,7 +26,7 @@ namespace Kyoo.Api
_taskManager = taskManager;
}
[Authorize(Policy = "Admin")]
[Authorize(Policy = "Write")]
public override async Task<ActionResult<Library>> Create(Library resource)
{
ActionResult<Library> result = await base.Create(resource);
@ -52,7 +51,7 @@ namespace Kyoo.Api
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Library>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
return NotFound();
return Page(resources, limit);
}
@ -104,7 +103,7 @@ namespace Kyoo.Api
new Sort<Collection>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Library>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
return NotFound();
return Page(resources, limit);
}
@ -156,7 +155,7 @@ namespace Kyoo.Api
new Sort<LibraryItem>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Library>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Library>(id) == null)
return NotFound();
return Page(resources, limit);
}

View File

@ -87,18 +87,20 @@ namespace Kyoo.Api
}
[HttpGet("{id:int}/poster")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetPeopleIcon(int id)
{
People people = await _libraryManager.Get<People>(id);
People people = await _libraryManager.GetOrDefault<People>(id);
if (people == null)
return NotFound();
return _files.FileResult(await _thumbs.GetPeoplePoster(people));
}
[HttpGet("{slug}/poster")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetPeopleIcon(string slug)
{
People people = await _libraryManager.Get<People>(slug);
People people = await _libraryManager.GetOrDefault<People>(slug);
if (people == null)
return NotFound();
return _files.FileResult(await _thumbs.GetPeoplePoster(people));
}
}

View File

@ -2,7 +2,6 @@ using System.Threading.Tasks;
using Kyoo.CommonApi;
using Kyoo.Controllers;
using Kyoo.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
@ -29,18 +28,20 @@ namespace Kyoo.Api
}
[HttpGet("{id:int}/logo")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetLogo(int id)
{
Provider provider = await _libraryManager.Get<Provider>(id);
Provider provider = await _libraryManager.GetOrDefault<Provider>(id);
if (provider == null)
return NotFound();
return _files.FileResult(await _thumbnails.GetProviderLogo(provider));
}
[HttpGet("{slug}/logo")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetLogo(string slug)
{
Provider provider = await _libraryManager.Get<Provider>(slug);
Provider provider = await _libraryManager.GetOrDefault<Provider>(slug);
if (provider == null)
return NotFound();
return _files.FileResult(await _thumbnails.GetProviderLogo(provider));
}
}

View File

@ -47,7 +47,7 @@ namespace Kyoo.Api
new Sort<Episode>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Season>(seasonID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Season>(seasonID) == null)
return NotFound();
return Page(resources, limit);
}
@ -130,23 +130,28 @@ namespace Kyoo.Api
[Authorize(Policy = "Read")]
public async Task<ActionResult<Show>> GetShow(int showID, int seasonNumber)
{
return await _libraryManager.Get<Show>(showID);
Show ret = await _libraryManager.GetOrDefault<Show>(showID);
if (ret == null)
return NotFound();
return ret;
}
[HttpGet("{id:int}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(int id)
{
Season season = await _libraryManager.Get<Season>(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.GetSeasonPoster(season));
}
[HttpGet("{slug}/thumb")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetThumb(string slug)
{
Season season = await _libraryManager.Get<Season>(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.GetSeasonPoster(season));
}

View File

@ -49,7 +49,7 @@ namespace Kyoo.Api
new Sort<Season>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -75,7 +75,7 @@ namespace Kyoo.Api
new Sort<Season>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -101,7 +101,7 @@ namespace Kyoo.Api
new Sort<Episode>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -127,7 +127,7 @@ namespace Kyoo.Api
new Sort<Episode>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -152,7 +152,7 @@ namespace Kyoo.Api
new Sort<PeopleRole>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -177,7 +177,7 @@ namespace Kyoo.Api
new Sort<PeopleRole>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -203,7 +203,7 @@ namespace Kyoo.Api
new Sort<Genre>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -229,7 +229,7 @@ namespace Kyoo.Api
new Sort<Genre>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -283,7 +283,7 @@ namespace Kyoo.Api
new Sort<Library>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -309,7 +309,7 @@ namespace Kyoo.Api
new Sort<Library>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -335,7 +335,7 @@ namespace Kyoo.Api
new Sort<Collection>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(showID) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(showID) == null)
return NotFound();
return Page(resources, limit);
}
@ -361,7 +361,7 @@ namespace Kyoo.Api
new Sort<Collection>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Show>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Show>(slug) == null)
return NotFound();
return Page(resources, limit);
}
@ -408,7 +408,6 @@ namespace Kyoo.Api
}
[HttpGet("{slug}/poster")]
[Authorize(Policy = "Read")]
public async Task<IActionResult> GetPoster(string slug)
{
try
@ -423,7 +422,6 @@ namespace Kyoo.Api
}
[HttpGet("{slug}/logo")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetLogo(string slug)
{
try
@ -438,7 +436,6 @@ namespace Kyoo.Api
}
[HttpGet("{slug}/backdrop")]
[Authorize(Policy="Read")]
public async Task<IActionResult> GetBackdrop(string slug)
{
try

View File

@ -40,7 +40,7 @@ namespace Kyoo.Api
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Studio>(id) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Studio>(id) == null)
return NotFound();
return Page(resources, limit);
}
@ -66,7 +66,7 @@ namespace Kyoo.Api
new Sort<Show>(sortBy),
new Pagination(limit, afterID));
if (!resources.Any() && await _libraryManager.Get<Studio>(slug) == null)
if (!resources.Any() && await _libraryManager.GetOrDefault<Studio>(slug) == null)
return NotFound();
return Page(resources, limit);
}

View File

@ -20,12 +20,9 @@
"default": "Trace",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.DbUpdateException": "None",
"Microsoft.EntityFrameworkCore.Update": "None",
"Microsoft.EntityFrameworkCore.Database.Command": "None",
"Microsoft.EntityFrameworkCore": "None",
"Kyoo": "Trace"
},
"dotnet-ef": "false"
}
},
"authentication": {
@ -35,7 +32,7 @@
"password": "passphrase"
},
"permissions": {
"default": ["read", "play", "write", "admin"],
"default": [],
"newUser": ["read", "play", "write", "admin"]
},
"profilePicturePath": "users/"