mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-07-09 03:04:20 -04:00
Merge pull request #27 from AnonymusRaccoon/plugins
Plugins rework and module splitting
This commit is contained in:
commit
01d7c008a7
170
Kyoo.Authentication/AuthenticationModule.cs
Normal file
170
Kyoo.Authentication/AuthenticationModule.cs
Normal file
@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using Kyoo.Authentication.Models;
|
||||
using Kyoo.Authentication.Views;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models.Permissions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.IdentityModel.Logging;
|
||||
using SameSiteMode = Microsoft.AspNetCore.Http.SameSiteMode;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// A module that enable OpenID authentication for Kyoo.
|
||||
/// </summary>
|
||||
public class AuthenticationModule : IPlugin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Slug => "auth";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Authentication";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "Enable OpenID authentication for Kyoo.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Provides => ArraySegment<Type>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Requires => new []
|
||||
{
|
||||
typeof(IUserRepository)
|
||||
};
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The configuration to use.
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// A logger factory to allow IdentityServer to log things.
|
||||
/// </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>
|
||||
/// <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 />
|
||||
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
|
||||
{
|
||||
string publicUrl = _configuration.GetValue<string>("publicUrl").TrimEnd('/');
|
||||
|
||||
if (_environment.IsDevelopment())
|
||||
IdentityModelEventSource.ShowPII = true;
|
||||
|
||||
services.AddControllers();
|
||||
|
||||
// TODO handle direct-videos with bearers (probably add a cookie and a app.Use to translate that for videos)
|
||||
|
||||
// TODO Check if tokens should be stored.
|
||||
|
||||
services.Configure<PermissionOption>(_configuration.GetSection(PermissionOption.Path));
|
||||
services.Configure<CertificateOption>(_configuration.GetSection(CertificateOption.Path));
|
||||
services.Configure<AuthenticationOption>(_configuration.GetSection(AuthenticationOption.Path));
|
||||
|
||||
|
||||
List<Client> clients = new();
|
||||
_configuration.GetSection("authentication:clients").Bind(clients);
|
||||
CertificateOption certificateOptions = new();
|
||||
_configuration.GetSection(CertificateOption.Path).Bind(certificateOptions);
|
||||
|
||||
services.AddIdentityServer(options =>
|
||||
{
|
||||
options.IssuerUri = publicUrl;
|
||||
options.UserInteraction.LoginUrl = $"{publicUrl}/login";
|
||||
options.UserInteraction.ErrorUrl = $"{publicUrl}/error";
|
||||
options.UserInteraction.LogoutUrl = $"{publicUrl}/logout";
|
||||
})
|
||||
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
|
||||
.AddInMemoryApiScopes(IdentityContext.GetScopes())
|
||||
.AddInMemoryApiResources(IdentityContext.GetApis())
|
||||
.AddInMemoryClients(IdentityContext.GetClients().Concat(clients))
|
||||
.AddProfileService<AccountApi>()
|
||||
.AddSigninKeys(certificateOptions);
|
||||
|
||||
services.AddAuthentication()
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = publicUrl;
|
||||
options.Audience = "kyoo";
|
||||
options.RequireHttpsMetadata = false;
|
||||
});
|
||||
services.AddSingleton<IPermissionValidator, PermissionValidatorFactory>();
|
||||
|
||||
DefaultCorsPolicyService cors = new(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
|
||||
{
|
||||
AllowedOrigins = {new Uri(publicUrl).GetLeftPart(UriPartial.Authority)}
|
||||
};
|
||||
services.AddSingleton<ICorsPolicyService>(cors);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConfigureAspNet(IApplicationBuilder app)
|
||||
{
|
||||
app.UseCookiePolicy(new CookiePolicyOptions
|
||||
{
|
||||
MinimumSameSitePolicy = SameSiteMode.Strict
|
||||
});
|
||||
app.UseAuthentication();
|
||||
app.Use((ctx, next) =>
|
||||
{
|
||||
ctx.SetIdentityServerOrigin(_configuration.GetValue<string>("publicUrl").TrimEnd('/'));
|
||||
return next();
|
||||
});
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthorization();
|
||||
|
||||
PhysicalFileProvider provider = new(Path.Combine(
|
||||
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!,
|
||||
"login"));
|
||||
app.UseDefaultFiles(new DefaultFilesOptions
|
||||
{
|
||||
RequestPath = new PathString("/login"),
|
||||
FileProvider = provider,
|
||||
RedirectToAppendTrailingSlash = true
|
||||
});
|
||||
app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
RequestPath = new PathString("/login"),
|
||||
FileProvider = provider
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
120
Kyoo.Authentication/Controllers/Certificates.cs
Normal file
120
Kyoo.Authentication/Controllers/Certificates.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Kyoo.Authentication.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Operators;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Pkcs;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.X509;
|
||||
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// A class containing multiple extensions methods to manage certificates.
|
||||
/// </summary>
|
||||
public static class Certificates
|
||||
{
|
||||
/// <summary>
|
||||
/// Add the certificate file to the identity server. If the certificate will expire soon, automatically renew it.
|
||||
/// If no certificate exists, one is generated.
|
||||
/// </summary>
|
||||
/// <param name="builder">The identity server that will be modified.</param>
|
||||
/// <param name="options">The certificate options</param>
|
||||
/// <returns></returns>
|
||||
public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder,
|
||||
CertificateOption options)
|
||||
{
|
||||
X509Certificate2 certificate = GetCertificate(options);
|
||||
builder.AddSigningCredential(certificate);
|
||||
|
||||
if (certificate.NotAfter.AddDays(7) <= DateTime.UtcNow)
|
||||
{
|
||||
Console.WriteLine("Signin certificate will expire soon, renewing it.");
|
||||
if (File.Exists(options.OldFile))
|
||||
File.Delete(options.OldFile);
|
||||
File.Move(options.File, options.OldFile);
|
||||
builder.AddValidationKey(GenerateCertificate(options.File, options.Password));
|
||||
}
|
||||
else if (File.Exists(options.OldFile))
|
||||
builder.AddValidationKey(GetExistingCredential(options.OldFile, options.Password));
|
||||
return builder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or generate the sign-in certificate.
|
||||
/// </summary>
|
||||
/// <param name="options">The certificate options</param>
|
||||
/// <returns>A valid certificate</returns>
|
||||
private static X509Certificate2 GetCertificate(CertificateOption options)
|
||||
{
|
||||
return File.Exists(options.File)
|
||||
? GetExistingCredential(options.File, options.Password)
|
||||
: GenerateCertificate(options.File, options.Password);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a certificate from a file
|
||||
/// </summary>
|
||||
/// <param name="file">The path of the certificate</param>
|
||||
/// <param name="password">The password of the certificate</param>
|
||||
/// <returns>The loaded certificate</returns>
|
||||
private static X509Certificate2 GetExistingCredential(string file, string password)
|
||||
{
|
||||
return new(file, password,
|
||||
X509KeyStorageFlags.MachineKeySet |
|
||||
X509KeyStorageFlags.PersistKeySet |
|
||||
X509KeyStorageFlags.Exportable
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new certificate key and put it in the file at <see cref="file"/>.
|
||||
/// </summary>
|
||||
/// <param name="file">The path of the output file</param>
|
||||
/// <param name="password">The password of the new certificate</param>
|
||||
/// <returns>The generated certificate</returns>
|
||||
private static X509Certificate2 GenerateCertificate(string file, string password)
|
||||
{
|
||||
SecureRandom random = new();
|
||||
|
||||
X509V3CertificateGenerator certificateGenerator = new();
|
||||
certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One,
|
||||
BigInteger.ValueOf(long.MaxValue), random));
|
||||
certificateGenerator.SetIssuerDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
|
||||
certificateGenerator.SetSubjectDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
|
||||
certificateGenerator.SetNotBefore(DateTime.UtcNow.Date);
|
||||
certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddMonths(3));
|
||||
|
||||
KeyGenerationParameters keyGenerationParameters = new(random, 2048);
|
||||
RsaKeyPairGenerator keyPairGenerator = new();
|
||||
keyPairGenerator.Init(keyGenerationParameters);
|
||||
|
||||
AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
|
||||
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
|
||||
|
||||
const string signatureAlgorithm = "MD5WithRSA";
|
||||
Asn1SignatureFactory signatureFactory = new(signatureAlgorithm, subjectKeyPair.Private);
|
||||
X509Certificate bouncyCert = certificateGenerator.Generate(signatureFactory);
|
||||
|
||||
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
|
||||
store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new []
|
||||
{
|
||||
new X509CertificateEntry(bouncyCert)
|
||||
});
|
||||
|
||||
using MemoryStream pfxStream = new();
|
||||
store.Save(pfxStream, password.ToCharArray(), random);
|
||||
X509Certificate2 certificate = new(pfxStream.ToArray(), password, X509KeyStorageFlags.Exportable);
|
||||
using FileStream fileStream = File.OpenWrite(file);
|
||||
pfxStream.WriteTo(fileStream);
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
}
|
54
Kyoo.Authentication/Controllers/PasswordUtils.cs
Normal file
54
Kyoo.Authentication/Controllers/PasswordUtils.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using IdentityModel;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
public static class PasswordUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate an OneTimeAccessCode.
|
||||
/// </summary>
|
||||
/// <returns>A new otac.</returns>
|
||||
public static string GenerateOTAC()
|
||||
{
|
||||
return CryptoRandom.CreateUniqueId();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hash a password to store it has a verification only.
|
||||
/// </summary>
|
||||
/// <param name="password">The password to hash</param>
|
||||
/// <returns>The hashed password</returns>
|
||||
public static string HashPassword(string password)
|
||||
{
|
||||
byte[] salt = new byte[16];
|
||||
new RNGCryptoServiceProvider().GetBytes(salt);
|
||||
Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
byte[] hashBytes = new byte[36];
|
||||
Array.Copy(salt, 0, hashBytes, 0, 16);
|
||||
Array.Copy(hash, 0, hashBytes, 16, 20);
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a password is the same as a valid hashed password.
|
||||
/// </summary>
|
||||
/// <param name="password">The password to check</param>
|
||||
/// <param name="validPassword">
|
||||
/// The valid hashed password. This password must be hashed via <see cref="HashPassword"/>.
|
||||
/// </param>
|
||||
/// <returns>True if the password is valid, false otherwise.</returns>
|
||||
public static bool CheckPassword(string password, string validPassword)
|
||||
{
|
||||
byte[] validHash = Convert.FromBase64String(validPassword);
|
||||
byte[] salt = new byte[16];
|
||||
Array.Copy(validHash, 0, salt, 0, 16);
|
||||
Rfc2898DeriveBytes pbkdf2 = new(password, salt, 100000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
return hash.SequenceEqual(validHash.Skip(16));
|
||||
}
|
||||
}
|
||||
}
|
145
Kyoo.Authentication/Controllers/PremissionValidator.cs
Normal file
145
Kyoo.Authentication/Controllers/PremissionValidator.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Authentication.Models;
|
||||
using Kyoo.Models.Permissions;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// A permission validator to validate permission with user Permission array
|
||||
/// or the default array from the configurations if the user is not logged.
|
||||
/// </summary>
|
||||
public class PermissionValidatorFactory : IPermissionValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// The permissions options to retrieve default permissions.
|
||||
/// </summary>
|
||||
private readonly IOptionsMonitor<PermissionOption> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new factory with the given options
|
||||
/// </summary>
|
||||
/// <param name="options">The option containing default values.</param>
|
||||
public PermissionValidatorFactory(IOptionsMonitor<PermissionOption> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata Create(PermissionAttribute attribute)
|
||||
{
|
||||
return new PermissionValidator(attribute.Type, attribute.Kind, _options);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata Create(PartialPermissionAttribute attribute)
|
||||
{
|
||||
return new PermissionValidator((object)attribute.Type ?? attribute.Kind, _options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The authorization filter used by <see cref="PermissionValidatorFactory"/>
|
||||
/// </summary>
|
||||
private class PermissionValidator : IAsyncAuthorizationFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// The permission to validate
|
||||
/// </summary>
|
||||
private readonly string _permission;
|
||||
/// <summary>
|
||||
/// The kind of permission needed
|
||||
/// </summary>
|
||||
private readonly Kind? _kind;
|
||||
/// <summary>
|
||||
/// The permissions options to retrieve default permissions.
|
||||
/// </summary>
|
||||
private readonly IOptionsMonitor<PermissionOption> _options;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new permission validator with the given options
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission to validate</param>
|
||||
/// <param name="kind">The kind of permission needed</param>
|
||||
/// <param name="options">The option containing default values.</param>
|
||||
public PermissionValidator(string permission, Kind kind, IOptionsMonitor<PermissionOption> options)
|
||||
{
|
||||
_permission = permission;
|
||||
_kind = kind;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new permission validator with the given options
|
||||
/// </summary>
|
||||
/// <param name="partialInfo">The partial permission to validate</param>
|
||||
/// <param name="options">The option containing default values.</param>
|
||||
public PermissionValidator(object partialInfo, IOptionsMonitor<PermissionOption> options)
|
||||
{
|
||||
if (partialInfo is Kind kind)
|
||||
_kind = kind;
|
||||
else if (partialInfo is string perm)
|
||||
_permission = perm;
|
||||
else
|
||||
throw new ArgumentException($"{nameof(partialInfo)} can only be a permission string or a kind.");
|
||||
_options = options;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
|
||||
{
|
||||
string permission = _permission;
|
||||
Kind? kind = _kind;
|
||||
|
||||
if (permission == null || kind == null)
|
||||
{
|
||||
switch (context.HttpContext.Items["PermissionType"])
|
||||
{
|
||||
case string perm:
|
||||
permission = perm;
|
||||
break;
|
||||
case Kind kin:
|
||||
kind = kin;
|
||||
break;
|
||||
case null when kind != null:
|
||||
context.HttpContext.Items["PermissionType"] = kind;
|
||||
return;
|
||||
case null when permission != null:
|
||||
context.HttpContext.Items["PermissionType"] = permission;
|
||||
return;
|
||||
default:
|
||||
throw new ArgumentException("Multiple non-matching partial permission attribute " +
|
||||
"are not supported.");
|
||||
}
|
||||
if (permission == null || kind == null)
|
||||
throw new ArgumentException("The permission type or kind is still missing after two partial " +
|
||||
"permission attributes, this is unsupported.");
|
||||
}
|
||||
|
||||
string permStr = $"{permission.ToLower()}.{kind.ToString()!.ToLower()}";
|
||||
string overallStr = $"overall.{kind.ToString()!.ToLower()}";
|
||||
AuthenticateResult res = await context.HttpContext.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme);
|
||||
if (res.Succeeded)
|
||||
{
|
||||
ICollection<string> permissions = res.Principal.GetPermissions();
|
||||
if (permissions.All(x => x != permStr && x != overallStr))
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
|
||||
}
|
||||
else
|
||||
{
|
||||
ICollection<string> permissions = _options.CurrentValue.Default ?? Array.Empty<string>();
|
||||
if (res.Failure != null || permissions.All(x => x != permStr && x != overallStr))
|
||||
context.Result = new StatusCodeResult(StatusCodes.Status401Unauthorized);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
54
Kyoo.Authentication/Extensions.cs
Normal file
54
Kyoo.Authentication/Extensions.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using IdentityModel;
|
||||
using IdentityServer4;
|
||||
using Kyoo.Models;
|
||||
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get claims of an user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user concerned</param>
|
||||
/// <returns>The list of claims the user has</returns>
|
||||
public static ICollection<Claim> GetClaims(this User user)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Claim(JwtClaimTypes.Subject, user.ID.ToString()),
|
||||
new Claim(JwtClaimTypes.Name, user.Username),
|
||||
new Claim(JwtClaimTypes.Picture, $"api/account/picture/{user.Slug}")
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert a user to an <see cref="IdentityServerUser"/>.
|
||||
/// </summary>
|
||||
/// <param name="user">The user to convert</param>
|
||||
/// <returns>The corresponding identity server user.</returns>
|
||||
public static IdentityServerUser ToIdentityUser(this User user)
|
||||
{
|
||||
return new(user.ID.ToString())
|
||||
{
|
||||
DisplayName = user.Username,
|
||||
AdditionalClaims = new[] {new Claim("permissions", string.Join(',', user.Permissions))}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the permissions of an user.
|
||||
/// </summary>
|
||||
/// <param name="user">The user</param>
|
||||
/// <returns>The list of permissions</returns>
|
||||
public static ICollection<string> GetPermissions(this ClaimsPrincipal user)
|
||||
{
|
||||
return user.Claims.FirstOrDefault(x => x.Type == "permissions")?.Value.Split(',');
|
||||
}
|
||||
}
|
||||
}
|
50
Kyoo.Authentication/Kyoo.Authentication.csproj
Normal file
50
Kyoo.Authentication/Kyoo.Authentication.csproj
Normal file
@ -0,0 +1,50 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/authentication</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
|
||||
<Company>SDG</Company>
|
||||
<Authors>Zoe Roux</Authors>
|
||||
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||
<LangVersion>default</LangVersion>
|
||||
<LoginRoot>../Kyoo.WebLogin/</LoginRoot>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="IdentityServer4" Version="4.1.2" />
|
||||
<PackageReference Include="IdentityServer4.Storage" Version="4.1.2" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication" Version="2.2.0" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
|
||||
|
||||
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<LoginFiles Include="$(LoginRoot)**" Visible="false" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Publish login files" AfterTargets="ComputeFilesToPublish">
|
||||
<ItemGroup>
|
||||
<ResolvedFileToPublish Include="@(LoginFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>login/%(LoginFiles.RecursiveDir)%(LoginFiles.Filename)%(LoginFiles.Extension)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="Prepare static files" AfterTargets="Build" Condition="$(Configuration) == 'Debug'">
|
||||
<Copy SourceFiles="@(LoginFiles)" DestinationFolder="$(OutputPath)/login/%(RecursiveDir)" />
|
||||
</Target>
|
||||
</Project>
|
28
Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs
Normal file
28
Kyoo.Authentication/Models/DTO/AccountUpdateRequest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Kyoo.Authentication.Models.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// A model only used on account update requests.
|
||||
/// </summary>
|
||||
public class AccountUpdateRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The new email address of the user
|
||||
/// </summary>
|
||||
[EmailAddress(ErrorMessage = "The email is invalid.")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The new username of the user.
|
||||
/// </summary>
|
||||
[MinLength(4, ErrorMessage = "The username must have at least 4 characters")]
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The picture icon.
|
||||
/// </summary>
|
||||
public IFormFile Picture { get; set; }
|
||||
}
|
||||
}
|
28
Kyoo.Authentication/Models/DTO/LoginRequest.cs
Normal file
28
Kyoo.Authentication/Models/DTO/LoginRequest.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Kyoo.Authentication.Models.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// A model only used on login requests.
|
||||
/// </summary>
|
||||
public class LoginRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The user's username.
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's password.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the user stay logged in? If true a cookie will be put.
|
||||
/// </summary>
|
||||
public bool StayLoggedIn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The return url of the login flow.
|
||||
/// </summary>
|
||||
public string ReturnURL { get; set; }
|
||||
}
|
||||
}
|
18
Kyoo.Authentication/Models/DTO/OtacRequest.cs
Normal file
18
Kyoo.Authentication/Models/DTO/OtacRequest.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Kyoo.Authentication.Models.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// A model to represent an otac request
|
||||
/// </summary>
|
||||
public class OtacRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The One Time Access Code
|
||||
/// </summary>
|
||||
public string Otac { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the user stay logged
|
||||
/// </summary>
|
||||
public bool StayLoggedIn { get; set; }
|
||||
}
|
||||
}
|
47
Kyoo.Authentication/Models/DTO/RegisterRequest.cs
Normal file
47
Kyoo.Authentication/Models/DTO/RegisterRequest.cs
Normal file
@ -0,0 +1,47 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Kyoo.Models;
|
||||
|
||||
namespace Kyoo.Authentication.Models.DTO
|
||||
{
|
||||
/// <summary>
|
||||
/// A model only used on register requests.
|
||||
/// </summary>
|
||||
public class RegisterRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// The user email address
|
||||
/// </summary>
|
||||
[EmailAddress(ErrorMessage = "The email must be a valid email address")]
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's username.
|
||||
/// </summary>
|
||||
[MinLength(4, ErrorMessage = "The username must have at least {1} characters")]
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user's password.
|
||||
/// </summary>
|
||||
[MinLength(8, ErrorMessage = "The password must have at least {1} characters")]
|
||||
public string Password { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Convert this register request to a new <see cref="User"/> class.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public User ToUser()
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Slug = Utility.ToSlug(Username),
|
||||
Username = Username,
|
||||
Password = Password,
|
||||
Email = Email,
|
||||
ExtraData = new Dictionary<string, string>()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,17 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using IdentityServer4.Models;
|
||||
|
||||
namespace Kyoo
|
||||
namespace Kyoo.Authentication
|
||||
{
|
||||
/// <summary>
|
||||
/// The hard coded context of the identity server.
|
||||
/// </summary>
|
||||
public static class IdentityContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The list of identity resources supported (email, profile and openid)
|
||||
/// </summary>
|
||||
/// <returns>The list of identity resources supported</returns>
|
||||
public static IEnumerable<IdentityResource> GetIdentityResources()
|
||||
{
|
||||
return new List<IdentityResource>
|
||||
@ -16,6 +23,13 @@ namespace Kyoo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of officially supported clients.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You can add custom clients in the settings.json file.
|
||||
/// </remarks>
|
||||
/// <returns>The list of officially supported clients.</returns>
|
||||
public static IEnumerable<Client> GetClients()
|
||||
{
|
||||
return new List<Client>
|
||||
@ -33,13 +47,17 @@ namespace Kyoo
|
||||
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" }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of scopes supported by the API.
|
||||
/// </summary>
|
||||
/// <returns>The list of scopes</returns>
|
||||
public static IEnumerable<ApiScope> GetScopes()
|
||||
{
|
||||
return new[]
|
||||
@ -60,11 +78,6 @@ namespace Kyoo
|
||||
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."
|
||||
@ -72,13 +85,16 @@ namespace Kyoo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The list of APIs (this is used to create Audiences)
|
||||
/// </summary>
|
||||
/// <returns>The list of apis</returns>
|
||||
public static IEnumerable<ApiResource> GetApis()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ApiResource
|
||||
new ApiResource("kyoo", "Kyoo")
|
||||
{
|
||||
Name = "Kyoo",
|
||||
Scopes = GetScopes().Select(x => x.Name).ToArray()
|
||||
}
|
||||
};
|
28
Kyoo.Authentication/Models/Options/AuthenticationOption.cs
Normal file
28
Kyoo.Authentication/Models/Options/AuthenticationOption.cs
Normal file
@ -0,0 +1,28 @@
|
||||
namespace Kyoo.Authentication.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// The main authentication options.
|
||||
/// </summary>
|
||||
public class AuthenticationOption
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to get this option from the root configuration.
|
||||
/// </summary>
|
||||
public const string Path = "authentication";
|
||||
|
||||
/// <summary>
|
||||
/// The options for certificates
|
||||
/// </summary>
|
||||
public CertificateOption Certificate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Options for permissions
|
||||
/// </summary>
|
||||
public PermissionOption Permissions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Root path of user's profile pictures.
|
||||
/// </summary>
|
||||
public string ProfilePicturePath { get; set; }
|
||||
}
|
||||
}
|
26
Kyoo.Authentication/Models/Options/CertificateOption.cs
Normal file
26
Kyoo.Authentication/Models/Options/CertificateOption.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Kyoo.Authentication.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A typed option model for the certificate
|
||||
/// </summary>
|
||||
public class CertificateOption
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to get this option from the root configuration.
|
||||
/// </summary>
|
||||
public const string Path = "authentication:certificate";
|
||||
|
||||
/// <summary>
|
||||
/// The path of the certificate file.
|
||||
/// </summary>
|
||||
public string File { get; set; }
|
||||
/// <summary>
|
||||
/// The path of the old certificate file.
|
||||
/// </summary>
|
||||
public string OldFile { get; set; }
|
||||
/// <summary>
|
||||
/// The password of the certificates.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
23
Kyoo.Authentication/Models/Options/PermissionOption.cs
Normal file
23
Kyoo.Authentication/Models/Options/PermissionOption.cs
Normal file
@ -0,0 +1,23 @@
|
||||
namespace Kyoo.Authentication.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Permission options.
|
||||
/// </summary>
|
||||
public class PermissionOption
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to get this option from the root configuration.
|
||||
/// </summary>
|
||||
public const string Path = "authentication:permissions";
|
||||
|
||||
/// <summary>
|
||||
/// The default permissions that will be given to a non-connected user.
|
||||
/// </summary>
|
||||
public string[] Default { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Permissions applied to a new user.
|
||||
/// </summary>
|
||||
public string[] NewUser { get; set; }
|
||||
}
|
||||
}
|
225
Kyoo.Authentication/Views/AccountApi.cs
Normal file
225
Kyoo.Authentication/Views/AccountApi.cs
Normal file
@ -0,0 +1,225 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Models;
|
||||
using IdentityServer4.Services;
|
||||
using Kyoo.Authentication.Models;
|
||||
using Kyoo.Authentication.Models.DTO;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo.Authentication.Views
|
||||
{
|
||||
/// <summary>
|
||||
/// The class responsible for login, logout, permissions and claims of a user.
|
||||
/// </summary>
|
||||
[Route("api/account")]
|
||||
[Route("api/accounts")]
|
||||
[ApiController]
|
||||
public class AccountApi : Controller, IProfileService
|
||||
{
|
||||
/// <summary>
|
||||
/// The repository to handle users.
|
||||
/// </summary>
|
||||
private readonly IUserRepository _users;
|
||||
/// <summary>
|
||||
/// A file manager to send profile pictures
|
||||
/// </summary>
|
||||
private readonly IFileManager _files;
|
||||
/// <summary>
|
||||
/// Options about authentication. Those options are monitored and reloads are supported.
|
||||
/// </summary>
|
||||
private readonly IOptions<AuthenticationOption> _options;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="AccountApi"/> handle to handle login/users requests.
|
||||
/// </summary>
|
||||
/// <param name="users">The user repository to create and manage users</param>
|
||||
/// <param name="files">A file manager to send profile pictures</param>
|
||||
/// <param name="options">Authentication options (this may be hot reloaded)</param>
|
||||
public AccountApi(IUserRepository users,
|
||||
IFileManager files,
|
||||
IOptions<AuthenticationOption> options)
|
||||
{
|
||||
_users = users;
|
||||
_files = files;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Register a new user and return a OTAC to connect to it.
|
||||
/// </summary>
|
||||
/// <param name="request">The DTO register request</param>
|
||||
/// <returns>A OTAC to connect to this new account</returns>
|
||||
[HttpPost("register")]
|
||||
public async Task<IActionResult> Register([FromBody] RegisterRequest request)
|
||||
{
|
||||
User user = request.ToUser();
|
||||
user.Permissions = _options.Value.Permissions.NewUser;
|
||||
user.Password = PasswordUtils.HashPassword(user.Password);
|
||||
user.ExtraData["otac"] = PasswordUtils.GenerateOTAC();
|
||||
user.ExtraData["otac-expire"] = DateTime.Now.AddMinutes(1).ToString("s");
|
||||
try
|
||||
{
|
||||
await _users.Create(user);
|
||||
}
|
||||
catch (DuplicatedItemException)
|
||||
{
|
||||
return Conflict(new {Errors = new {Duplicate = new[] {"A user with this name already exists"}}});
|
||||
}
|
||||
|
||||
return Ok(new {Otac = user.ExtraData["otac"]});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return an authentication properties based on a stay login property
|
||||
/// </summary>
|
||||
/// <param name="stayLogged">Should the user stay logged</param>
|
||||
/// <returns>Authentication properties based on a stay login</returns>
|
||||
private static AuthenticationProperties StayLogged(bool stayLogged)
|
||||
{
|
||||
if (!stayLogged)
|
||||
return null;
|
||||
return new AuthenticationProperties
|
||||
{
|
||||
IsPersistent = true,
|
||||
ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Login the user.
|
||||
/// </summary>
|
||||
/// <param name="login">The DTO login request</param>
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginRequest login)
|
||||
{
|
||||
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.ToIdentityUser(), StayLogged(login.StayLoggedIn));
|
||||
return Ok(new { RedirectUrl = login.ReturnURL, IsOk = true });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use a OTAC to login a user.
|
||||
/// </summary>
|
||||
/// <param name="otac">The OTAC request</param>
|
||||
[HttpPost("otac-login")]
|
||||
public async Task<IActionResult> OtacLogin([FromBody] OtacRequest otac)
|
||||
{
|
||||
// TODO once hstore (Dictionary<string, string> accessor) are supported, use them.
|
||||
// We retrieve all users, this is inefficient.
|
||||
User user = (await _users.GetAll()).FirstOrDefault(x => x.ExtraData.GetValueOrDefault("otac") == otac.Otac);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
if (DateTime.ParseExact(user.ExtraData["otac-expire"], "s", CultureInfo.InvariantCulture) <=
|
||||
DateTime.UtcNow)
|
||||
{
|
||||
return BadRequest(new
|
||||
{
|
||||
code = "ExpiredOTAC", description = "The OTAC has expired. Try to login with your password."
|
||||
});
|
||||
}
|
||||
|
||||
await HttpContext.SignInAsync(user.ToIdentityUser(), StayLogged(otac.StayLoggedIn));
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sign out an user
|
||||
/// </summary>
|
||||
[HttpGet("logout")]
|
||||
[Authorize]
|
||||
public async Task<IActionResult> Logout()
|
||||
{
|
||||
await HttpContext.SignOutAsync();
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
|
||||
{
|
||||
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)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task IsActiveAsync(IsActiveContext context)
|
||||
{
|
||||
User user = await _users.GetOrDefault(int.Parse(context.Subject.GetSubjectId()));
|
||||
context.IsActive = user != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the user's profile picture.
|
||||
/// </summary>
|
||||
/// <param name="slug">The user slug</param>
|
||||
/// <returns>The profile picture of the user or 404 if not found</returns>
|
||||
[HttpGet("picture/{slug}")]
|
||||
public async Task<IActionResult> GetPicture(string slug)
|
||||
{
|
||||
User user = await _users.GetOrDefault(slug);
|
||||
if (user == null)
|
||||
return NotFound();
|
||||
string path = Path.Combine(_options.Value.ProfilePicturePath, user.ID.ToString());
|
||||
return _files.FileResult(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update profile information (email, username, profile picture...)
|
||||
/// </summary>
|
||||
/// <param name="data">The new information</param>
|
||||
/// <returns>The edited user</returns>
|
||||
[HttpPut]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<User>> Update([FromForm] AccountUpdateRequest data)
|
||||
{
|
||||
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))
|
||||
user.Username = data.Username;
|
||||
if (data.Picture?.Length > 0)
|
||||
{
|
||||
string path = Path.Combine(_options.Value.ProfilePicturePath, user.ID.ToString());
|
||||
await using Stream file = _files.NewFile(path);
|
||||
await data.Picture.CopyToAsync(file);
|
||||
}
|
||||
return await _users.Edit(user, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get permissions for a non connected user.
|
||||
/// </summary>
|
||||
/// <returns>The list of permissions of a default user.</returns>
|
||||
[HttpGet("permissions")]
|
||||
public ActionResult<IEnumerable<string>> GetDefaultPermissions()
|
||||
{
|
||||
return _options.Value.Permissions.Default ?? Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,21 +7,88 @@ using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to abstract the file system to allow custom file systems (like distant file systems or external providers)
|
||||
/// </summary>
|
||||
public interface IFileManager
|
||||
{
|
||||
public IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false);
|
||||
|
||||
public StreamReader GetReader([NotNull] string path);
|
||||
|
||||
public Task<ICollection<string>> ListFiles([NotNull] string path);
|
||||
|
||||
public Task<bool> Exists([NotNull] string path);
|
||||
// TODO find a way to handle Transmux/Transcode with this system.
|
||||
|
||||
/// <summary>
|
||||
/// Used for http queries returning a file. This should be used to return local files
|
||||
/// or proxy them from a distant server
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If no file exists at the given path or if the path is null, a NotFoundResult is returned
|
||||
/// to handle it gracefully.
|
||||
/// </remarks>
|
||||
/// <param name="path">The path of the file.</param>
|
||||
/// <param name="rangeSupport">
|
||||
/// Should the file be downloaded at once or is the client allowed to request only part of the file
|
||||
/// </param>
|
||||
/// <param name="type">
|
||||
/// You can manually specify the content type of your file.
|
||||
/// For example you can force a file to be returned as plain text using <c>text/plain</c>.
|
||||
/// If the type is not specified, it will be deduced automatically (from the extension or by sniffing the file).
|
||||
/// </param>
|
||||
/// <returns>An <see cref="IActionResult"/> representing the file returned.</returns>
|
||||
public IActionResult FileResult([CanBeNull] string path, bool rangeSupport = false, string type = null);
|
||||
|
||||
/// <summary>
|
||||
/// Read a file present at <paramref name="path"/>. The reader can be used in an arbitrary context.
|
||||
/// To return files from an http endpoint, use <see cref="FileResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="FileNotFoundException">If the file could not be found.</exception>
|
||||
/// <returns>A reader to read the file.</returns>
|
||||
public Stream GetReader([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new file at <paramref name="path"></paramref>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the new file.</param>
|
||||
/// <returns>A writer to write to the new file.</returns>
|
||||
public Stream NewFile([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// List files in a directory.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the directory</param>
|
||||
/// <returns>A list of files's path.</returns>
|
||||
public Task<ICollection<string>> ListFiles([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists at the given path.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check</param>
|
||||
/// <returns>True if the path exists, false otherwise</returns>
|
||||
public Task<bool> Exists([NotNull] string path);
|
||||
|
||||
/// <summary>
|
||||
/// Get the extra directory of a show.
|
||||
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
||||
/// It can be useful if the filesystem is readonly.
|
||||
/// </summary>
|
||||
/// <param name="show">The show to proceed</param>
|
||||
/// <returns>The extra directory of the show</returns>
|
||||
public string GetExtraDirectory(Show show);
|
||||
|
||||
/// <summary>
|
||||
/// Get the extra directory of a season.
|
||||
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
||||
/// It can be useful if the filesystem is readonly.
|
||||
/// </summary>
|
||||
/// <param name="season">The season to proceed</param>
|
||||
/// <returns>The extra directory of the season</returns>
|
||||
public string GetExtraDirectory(Season season);
|
||||
|
||||
/// <summary>
|
||||
/// Get the extra directory of an episode.
|
||||
/// This method is in this system to allow a filesystem to use a different metadata policy for one.
|
||||
/// It can be useful if the filesystem is readonly.
|
||||
/// </summary>
|
||||
/// <param name="episode">The episode to proceed</param>
|
||||
/// <returns>The extra directory of the episode</returns>
|
||||
public string GetExtraDirectory(Episode episode);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ namespace Kyoo.Controllers
|
||||
/// Get the repository corresponding to the T item.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type you want</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The repository corresponding</returns>
|
||||
IRepository<T> GetRepository<T>() where T : class, IResource;
|
||||
|
||||
@ -82,8 +82,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <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>
|
||||
@ -91,8 +92,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <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>
|
||||
@ -100,8 +102,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="where">The filter function.</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <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>
|
||||
@ -109,8 +112,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Season> Get(int showID, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -118,8 +122,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
[ItemNotNull]
|
||||
Task<Season> Get(string showSlug, int seasonNumber);
|
||||
|
||||
/// <summary>
|
||||
@ -128,8 +133,9 @@ namespace Kyoo.Controllers
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <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>
|
||||
@ -138,8 +144,9 @@ namespace Kyoo.Controllers
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <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>
|
||||
@ -147,8 +154,9 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the track</param>
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The tracl found</returns>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <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);
|
||||
|
||||
|
||||
@ -505,7 +521,7 @@ namespace Kyoo.Controllers
|
||||
/// <param name="item">The resourcce to edit, it's ID can't change.</param>
|
||||
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
|
||||
/// <typeparam name="T">The type of resources</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's informations (related items & so on)</returns>
|
||||
Task<T> Edit<T>(T item, bool resetOld) where T : class, IResource;
|
||||
|
||||
@ -514,7 +530,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="item">The resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete<T>(T item) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -522,7 +538,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete<T>(int id) where T : class, IResource;
|
||||
|
||||
/// <summary>
|
||||
@ -530,7 +546,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource to delete</param>
|
||||
/// <typeparam name="T">The type of resource to delete</typeparam>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete<T>(string slug) where T : class, IResource;
|
||||
}
|
||||
}
|
||||
|
222
Kyoo.Common/Controllers/IPlugin.cs
Normal file
222
Kyoo.Common/Controllers/IPlugin.cs
Normal file
@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A common interface used to discord plugins
|
||||
/// </summary>
|
||||
/// <remarks>You can inject services in the IPlugin constructor.
|
||||
/// You should only inject well known services like an ILogger, IConfiguration or IWebHostEnvironment.</remarks>
|
||||
[UsedImplicitly(ImplicitUseTargetFlags.WithInheritors)]
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// A slug to identify this plugin in queries.
|
||||
/// </summary>
|
||||
string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the plugin
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of this plugin. This will be displayed on the "installed plugins" page.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of services that are provided by this service. This allow other plugins to declare dependencies
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You should put the type's interface that will be register in configure.
|
||||
/// </remarks>
|
||||
ICollection<Type> Provides { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of types that will be provided only if a condition is met. The condition can be an arbitrary method or
|
||||
/// a condition based on other type availability. For more information, see <see cref="ConditionalProvides"/>.
|
||||
/// </summary>
|
||||
ICollection<ConditionalProvide> ConditionalProvides { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of services that are required by this plugin.
|
||||
/// You can put services that you provide conditionally here if you want.
|
||||
/// Kyoo will warn the user that this plugin can't be loaded if a required service is not found.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Put here the most complete type that are needed for your plugin to work. If you need a LibraryManager,
|
||||
/// put typeof(ILibraryManager).
|
||||
/// </remarks>
|
||||
ICollection<Type> Requires { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A configure method that will be run on plugin's startup.
|
||||
/// </summary>
|
||||
/// <param name="services">A service container to register new services.</param>
|
||||
/// <param name="availableTypes">The list of types that are available for this instance. This can be used
|
||||
/// for conditional type. See <see cref="ProviderCondition.Has(System.Type,System.Collections.Generic.ICollection{System.Type})"/>
|
||||
/// or <see cref="ProviderCondition.Has(System.Collections.Generic.ICollection{System.Type},System.Collections.Generic.ICollection{System.Type})"/>>
|
||||
/// You can't simply check on the service collection because some dependencies might be registered after your plugin.
|
||||
/// </param>
|
||||
void Configure(IServiceCollection services, ICollection<Type> availableTypes);
|
||||
|
||||
/// <summary>
|
||||
/// An optional configuration step to allow a plugin to change asp net configurations.
|
||||
/// WARNING: This is only called on Kyoo's startup so you must restart the app to apply this changes.
|
||||
/// </summary>
|
||||
/// <param name="app">The Asp.Net application builder. On most case it is not needed but you can use it to add asp net functionalities.</param>
|
||||
void ConfigureAspNet(IApplicationBuilder app) {}
|
||||
|
||||
/// <summary>
|
||||
/// An optional function to execute and initialize your plugin.
|
||||
/// It can be used to initialize a database connection, fill initial data or anything.
|
||||
/// </summary>
|
||||
/// <param name="provider">A service provider to request services</param>
|
||||
void Initialize(IServiceProvider provider) {}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A type that will only be provided if a special condition is met. To check that your condition is met,
|
||||
/// you can check the <see cref="ProviderCondition"/> class.
|
||||
/// </summary>
|
||||
public class ConditionalProvide : Tuple<Type, ProviderCondition>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the type that may be provided
|
||||
/// </summary>
|
||||
public Type Type => Item1;
|
||||
|
||||
/// <summary>
|
||||
/// Get the condition.
|
||||
/// </summary>
|
||||
public ProviderCondition Condition => Item2;
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ConditionalProvide"/> from a type and a condition.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to provide</param>
|
||||
/// <param name="condition">The condition</param>
|
||||
public ConditionalProvide(Type type, ProviderCondition condition)
|
||||
: base(type, condition)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ConditionalProvide"/> from a tuple of (Type, ProviderCondition).
|
||||
/// </summary>
|
||||
/// <param name="tuple">The tuple to convert</param>
|
||||
public ConditionalProvide((Type type, ProviderCondition condition) tuple)
|
||||
: base(tuple.type, tuple.condition)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a tuple to a <see cref="ConditionalProvide"/>.
|
||||
/// </summary>
|
||||
/// <param name="tuple">The tuple to convert</param>
|
||||
/// <returns>A new <see cref="ConditionalProvide"/> based on the given tuple.</returns>
|
||||
public static implicit operator ConditionalProvide((Type, Type) tuple) => new (tuple);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A condition for a conditional type.
|
||||
/// </summary>
|
||||
public class ProviderCondition
|
||||
{
|
||||
/// <summary>
|
||||
/// The condition as a method. If true is returned, the type will be provided.
|
||||
/// </summary>
|
||||
public Func<bool> Condition { get; } = () => true;
|
||||
/// <summary>
|
||||
/// The list of types that this method needs.
|
||||
/// </summary>
|
||||
public ICollection<Type> Needed { get; } = ArraySegment<Type>.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderCondition"/> from a raw function.
|
||||
/// </summary>
|
||||
/// <param name="condition">The predicate that will be used as condition</param>
|
||||
public ProviderCondition(Func<bool> condition)
|
||||
{
|
||||
Condition = condition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderCondition"/> from a type. This allow you to inform that a type will
|
||||
/// only be available if a dependency is met.
|
||||
/// </summary>
|
||||
/// <param name="needed">The type that you need</param>
|
||||
public ProviderCondition(Type needed)
|
||||
{
|
||||
Needed = new[] {needed};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderCondition"/> from a list of type. This allow you to inform that a type will
|
||||
/// only be available if a list of dependencies are met.
|
||||
/// </summary>
|
||||
/// <param name="needed">The types that you need</param>
|
||||
public ProviderCondition(ICollection<Type> needed)
|
||||
{
|
||||
Needed = needed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ProviderCondition"/> with a list of types as dependencies and a predicate
|
||||
/// for arbitrary conditions.
|
||||
/// </summary>
|
||||
/// <param name="needed">The list of dependencies</param>
|
||||
/// <param name="condition">An arbitrary condition</param>
|
||||
public ProviderCondition(ICollection<Type> needed, Func<bool> condition)
|
||||
{
|
||||
Needed = needed;
|
||||
Condition = condition;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a type to a <see cref="ProviderCondition"/>.
|
||||
/// </summary>
|
||||
/// <param name="type">The type dependency</param>
|
||||
/// <returns>A <see cref="ProviderCondition"/> that will return true if the given type is available.</returns>
|
||||
public static implicit operator ProviderCondition(Type type) => new(type);
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a list of type to a <see cref="ProviderCondition"/>.
|
||||
/// </summary>
|
||||
/// <param name="types">The list of type dependencies</param>
|
||||
/// <returns>A <see cref="ProviderCondition"/> that will return true if the given types are available.</returns>
|
||||
public static implicit operator ProviderCondition(Type[] types) => new(types);
|
||||
|
||||
/// <inheritdoc cref="op_Implicit(System.Type[])"/>
|
||||
public static implicit operator ProviderCondition(List<Type> types) => new(types);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if a type is available.
|
||||
/// </summary>
|
||||
/// <param name="needed">The type to check</param>
|
||||
/// <param name="available">The list of types</param>
|
||||
/// <returns>True if the dependency is met, false otherwise</returns>
|
||||
public static bool Has(Type needed, ICollection<Type> available)
|
||||
{
|
||||
return available.Contains(needed);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a list of type are available.
|
||||
/// </summary>
|
||||
/// <param name="needed">The list of types to check</param>
|
||||
/// <param name="available">The list of types</param>
|
||||
/// <returns>True if the dependencies are met, false otherwise</returns>
|
||||
public static bool Has(ICollection<Type> needed, ICollection<Type> available)
|
||||
{
|
||||
return needed.All(x => Has(x, available));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,56 @@
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A manager to load plugins and retrieve information from them.
|
||||
/// </summary>
|
||||
public interface IPluginManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a single plugin that match the type and name given.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the plugin</param>
|
||||
/// <typeparam name="T">The type of the plugin</typeparam>
|
||||
/// <exception cref="ItemNotFoundException">If no plugins match the query</exception>
|
||||
/// <returns>A plugin that match the queries</returns>
|
||||
public T GetPlugin<T>(string name);
|
||||
public IEnumerable<T> GetPlugins<T>();
|
||||
public IEnumerable<IPlugin> GetAllPlugins();
|
||||
public void ReloadPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Get all plugins of the given type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of plugins to get</typeparam>
|
||||
/// <returns>A list of plugins matching the given type or an empty list of none match.</returns>
|
||||
public ICollection<T> GetPlugins<T>();
|
||||
|
||||
/// <summary>
|
||||
/// Get all plugins currently running on Kyoo. This also includes deleted plugins if the app as not been restarted.
|
||||
/// </summary>
|
||||
/// <returns>All plugins currently loaded.</returns>
|
||||
public ICollection<IPlugin> GetAllPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// Load plugins and their dependencies from the plugin directory.
|
||||
/// </summary>
|
||||
/// <param name="plugins">
|
||||
/// An initial plugin list to use.
|
||||
/// You should not try to put plugins from the plugins directory here as they will get automatically loaded.
|
||||
/// </param>
|
||||
public void LoadPlugins(ICollection<IPlugin> plugins);
|
||||
|
||||
/// <summary>
|
||||
/// Configure services adding or removing services as the plugins wants.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to populate</param>
|
||||
public void ConfigureServices(IServiceCollection services);
|
||||
|
||||
/// <summary>
|
||||
/// Configure an asp net application applying plugins policies.
|
||||
/// </summary>
|
||||
/// <param name="app">The asp net application to configure</param>
|
||||
public void ConfigureAspnet(IApplicationBuilder app);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ using Kyoo.Models.Exceptions;
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// Informations about the pagination. How many items should be displayed and where to start.
|
||||
/// Information about the pagination. How many items should be displayed and where to start.
|
||||
/// </summary>
|
||||
public readonly struct Pagination
|
||||
{
|
||||
@ -44,7 +44,7 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Informations about how a query should be sorted. What factor should decide the sort and in which order.
|
||||
/// Information about how a query should be sorted. What factor should decide the sort and in which order.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">For witch type this sort applies</typeparam>
|
||||
public readonly struct Sort<T>
|
||||
@ -54,7 +54,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
public Expression<Func<T, object>> Key { get; }
|
||||
/// <summary>
|
||||
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendent order.
|
||||
/// If this is set to true, items will be sorted in descend order else, they will be sorted in ascendant order.
|
||||
/// </summary>
|
||||
public bool Descendant { get; }
|
||||
|
||||
@ -127,21 +127,21 @@ namespace Kyoo.Controllers
|
||||
/// Get a resource from it's ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item could not be found.</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> Get(int id);
|
||||
/// <summary>
|
||||
/// Get a resource from it's slug.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item could not be found.</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> Get(string slug);
|
||||
/// <summary>
|
||||
/// Get the first resource that match the predicate.
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to filter the resource.</param>
|
||||
/// <exception cref="ItemNotFound">If the item could not be found.</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item could not be found.</exception>
|
||||
/// <returns>The resource found</returns>
|
||||
Task<T> Get(Expression<Func<T, bool>> where);
|
||||
|
||||
@ -175,7 +175,7 @@ namespace Kyoo.Controllers
|
||||
/// Get every resources that match all filters
|
||||
/// </summary>
|
||||
/// <param name="where">A filter predicate</param>
|
||||
/// <param name="sort">Sort informations about the query (sort by, sort order)</param>
|
||||
/// <param name="sort">Sort information about the query (sort by, sort order)</param>
|
||||
/// <param name="limit">How pagination should be done (where to start and how many to return)</param>
|
||||
/// <returns>A list of resources that match every filters</returns>
|
||||
Task<ICollection<T>> GetAll(Expression<Func<T, bool>> where = null,
|
||||
@ -205,86 +205,85 @@ namespace Kyoo.Controllers
|
||||
/// Create a new resource.
|
||||
/// </summary>
|
||||
/// <param name="obj">The item to register</param>
|
||||
/// <returns>The resource registers and completed by database's informations (related items & so on)</returns>
|
||||
/// <returns>The resource registers and completed by database's information (related items & so on)</returns>
|
||||
Task<T> Create([NotNull] T obj);
|
||||
|
||||
/// <summary>
|
||||
/// 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 catched 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
|
||||
/// </summary>
|
||||
/// <param name="edited">The resourcce to edit, it's ID can't change.</param>
|
||||
/// <param name="edited">The resource to edit, it's ID can't change.</param>
|
||||
/// <param name="resetOld">Should old properties of the resource be discarded or should null values considered as not changed?</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's informations (related items & so on)</returns>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The resource edited and completed by database's information (related items & so on)</returns>
|
||||
Task<T> Edit([NotNull] T edited, bool resetOld);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a resource by it's ID
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete(int id);
|
||||
/// <summary>
|
||||
/// Delete a resource by it's slug
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete(string slug);
|
||||
/// <summary>
|
||||
/// Delete a resource
|
||||
/// </summary>
|
||||
/// <param name="obj">The resource to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task Delete([NotNull] T obj);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="objs">One or multiple resources to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(params T[] objs) => DeleteRange(objs.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="objs">An enumerable of resources to delete</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<T> objs);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="ids">One or multiple resources's id</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <param name="ids">One or multiple resource's id</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(params int[] ids) => DeleteRange(ids.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="ids">An enumearble of resources's id</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <param name="ids">An enumerable of resource's id</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<int> ids);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="slugs">One or multiple resources's slug</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <param name="slugs">One or multiple resource's slug</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(params string[] slugs) => DeleteRange(slugs.AsEnumerable());
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="slugs">An enumerable of resources's slug</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <param name="slugs">An enumerable of resource's slug</param>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange(IEnumerable<string> slugs);
|
||||
/// <summary>
|
||||
/// Delete a list of resources.
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to filter resources to delete. Every resource that match this will be deleted.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
Task DeleteRange([NotNull] Expression<Func<T, bool>> where);
|
||||
}
|
||||
|
||||
@ -294,7 +293,7 @@ namespace Kyoo.Controllers
|
||||
public interface IShowRepository : IRepository<Show>
|
||||
{
|
||||
/// <summary>
|
||||
/// Link a show to a collection and/or a library. The given show is now part of thoses containers.
|
||||
/// Link a show to a collection and/or a library. The given show is now part of those containers.
|
||||
/// If both a library and a collection are given, the collection is added to the library too.
|
||||
/// </summary>
|
||||
/// <param name="showID">The ID of the show</param>
|
||||
@ -306,7 +305,7 @@ namespace Kyoo.Controllers
|
||||
/// Get a show's slug from it's ID.
|
||||
/// </summary>
|
||||
/// <param name="showID">The ID of the show</param>
|
||||
/// <exception cref="ItemNotFound">If a show with the given ID is not found.</exception>
|
||||
/// <exception cref="ItemNotFoundException">If a show with the given ID is not found.</exception>
|
||||
/// <returns>The show's slug</returns>
|
||||
Task<string> GetSlug(int showID);
|
||||
}
|
||||
@ -321,7 +320,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> Get(int showID, int seasonNumber);
|
||||
|
||||
@ -330,7 +329,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The season found</returns>
|
||||
Task<Season> Get(string showSlug, int seasonNumber);
|
||||
|
||||
@ -362,7 +361,7 @@ namespace Kyoo.Controllers
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> Get(int showID, int seasonNumber, int episodeNumber);
|
||||
/// <summary>
|
||||
@ -371,7 +370,7 @@ namespace Kyoo.Controllers
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="seasonNumber">The season's number</param>
|
||||
/// <param name="episodeNumber">The episode's number</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> Get(string showSlug, int seasonNumber, int episodeNumber);
|
||||
|
||||
@ -397,7 +396,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showID">The id of the show</param>
|
||||
/// <param name="absoluteNumber">The episode's absolute number (The episode number does not reset to 1 after the end of a season.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetAbsolute(int showID, int absoluteNumber);
|
||||
/// <summary>
|
||||
@ -405,7 +404,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="absoluteNumber">The episode's absolute number (The episode number does not reset to 1 after the end of a season.</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The episode found</returns>
|
||||
Task<Episode> GetAbsolute(string showSlug, int absoluteNumber);
|
||||
}
|
||||
@ -420,8 +419,8 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the track</param>
|
||||
/// <param name="type">The type (Video, Audio or Subtitle)</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <returns>The tracl found</returns>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The track found</returns>
|
||||
Task<Track> Get(string slug, StreamType type = StreamType.Unknown);
|
||||
|
||||
/// <summary>
|
||||
@ -429,7 +428,7 @@ 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>
|
||||
Task<Track> GetOrDefault(string slug, StreamType type = StreamType.Unknown);
|
||||
}
|
||||
|
||||
@ -439,16 +438,16 @@ namespace Kyoo.Controllers
|
||||
public interface ILibraryRepository : IRepository<Library> { }
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle library items (A wrapper arround shows and collections).
|
||||
/// A repository to handle library items (A wrapper around shows and collections).
|
||||
/// </summary>
|
||||
public interface ILibraryItemRepository : IRepository<LibraryItem>
|
||||
{
|
||||
/// <summary>
|
||||
/// Get items (A wrapper arround shows or collections) from a library.
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
public Task<ICollection<LibraryItem>> GetFromLibrary(int id,
|
||||
@ -456,7 +455,7 @@ namespace Kyoo.Controllers
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
/// <summary>
|
||||
/// Get items (A wrapper arround shows or collections) from a library.
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
@ -470,11 +469,11 @@ namespace Kyoo.Controllers
|
||||
) => GetFromLibrary(id, where, new Sort<LibraryItem>(sort), limit);
|
||||
|
||||
/// <summary>
|
||||
/// Get items (A wrapper arround shows or collections) from a library.
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
public Task<ICollection<LibraryItem>> GetFromLibrary(string slug,
|
||||
@ -482,7 +481,7 @@ namespace Kyoo.Controllers
|
||||
Sort<LibraryItem> sort = default,
|
||||
Pagination limit = default);
|
||||
/// <summary>
|
||||
/// Get items (A wrapper arround shows or collections) from a library.
|
||||
/// Get items (A wrapper around shows or collections) from a library.
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the library</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
@ -521,7 +520,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showID">The ID of the show</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromShow(int showID,
|
||||
@ -547,7 +546,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="showSlug">The slug of the show</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromShow(string showSlug,
|
||||
@ -573,7 +572,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="id">The id of the person</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromPeople(int id,
|
||||
@ -599,7 +598,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="slug">The slug of the person</param>
|
||||
/// <param name="where">A filter function</param>
|
||||
/// <param name="sort">Sort informations (sort order & sort by)</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">How many items to return and where to start</param>
|
||||
/// <returns>A list of items that match every filters</returns>
|
||||
Task<ICollection<PeopleRole>> GetFromPeople(string slug,
|
||||
@ -631,7 +630,7 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to add arbitrary filter</param>
|
||||
/// <param name="sort">Sort information (sort order & sort by)</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <returns>A filtered list of external ids.</returns>
|
||||
Task<ICollection<MetadataID>> GetMetadataID(Expression<Func<MetadataID, bool>> where = null,
|
||||
Sort<MetadataID> sort = default,
|
||||
@ -642,11 +641,16 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="where">A predicate to add arbitrary filter</param>
|
||||
/// <param name="sort">A sort by expression</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <returns>A filtered list of external ids.</returns>
|
||||
Task<ICollection<MetadataID>> GetMetadataID([Optional] Expression<Func<MetadataID, bool>> where,
|
||||
Expression<Func<MetadataID, object>> sort,
|
||||
Pagination limit = default
|
||||
) => GetMetadataID(where, new Sort<MetadataID>(sort), limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A repository to handle users.
|
||||
/// </summary>
|
||||
public interface IUserRepository : IRepository<User> {}
|
||||
}
|
||||
|
188
Kyoo.Common/Controllers/ITask.cs
Normal file
188
Kyoo.Common/Controllers/ITask.cs
Normal file
@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models.Attributes;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A single task parameter. This struct contains metadata to display and utility functions to get them in the taks.
|
||||
/// </summary>
|
||||
/// <remarks>This struct will be used to generate the swagger documentation of the task.</remarks>
|
||||
public record TaskParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of this parameter.
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The description of this parameter.
|
||||
/// </summary>
|
||||
public string Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The type of this parameter.
|
||||
/// </summary>
|
||||
public Type Type { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this parameter required or can it be ignored?
|
||||
/// </summary>
|
||||
public bool IsRequired { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The default value of this object.
|
||||
/// </summary>
|
||||
public object DefaultValue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The value of the parameter.
|
||||
/// </summary>
|
||||
private object Value { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new task parameter.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter</param>
|
||||
/// <param name="description">The description of the parameter</param>
|
||||
/// <typeparam name="T">The type of the parameter.</typeparam>
|
||||
/// <returns>A new task parameter.</returns>
|
||||
public static TaskParameter Create<T>(string name, string description)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Name = name,
|
||||
Description = description,
|
||||
Type = typeof(T)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a parameter's value to give to a task.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the parameter</param>
|
||||
/// <param name="value">The value of the parameter. It's type will be used as parameter's type.</param>
|
||||
/// <typeparam name="T">The type of the parameter</typeparam>
|
||||
/// <returns>A TaskParameter that can be used as value.</returns>
|
||||
public static TaskParameter CreateValue<T>(string name, T value)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Name = name,
|
||||
Type = typeof(T),
|
||||
Value = value
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a parameter's value for the current parameter.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to use</param>
|
||||
/// <returns>A new parameter's value for this current parameter</returns>
|
||||
public TaskParameter CreateValue(object value)
|
||||
{
|
||||
return this with {Value = value};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the value of this parameter. If the value is of the wrong type, it will be converted.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of this parameter</typeparam>
|
||||
/// <returns>The value of this parameter.</returns>
|
||||
public T As<T>()
|
||||
{
|
||||
return (T)Convert.ChangeType(Value, typeof(T));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A parameters container implementing an indexer to allow simple usage of parameters.
|
||||
/// </summary>
|
||||
public class TaskParameters : List<TaskParameter>
|
||||
{
|
||||
/// <summary>
|
||||
/// An indexer that return the parameter with the specified name.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the task (case sensitive)</param>
|
||||
public TaskParameter this[string name] => this.FirstOrDefault(x => x.Name == name);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new, empty, <see cref="TaskParameters"/>
|
||||
/// </summary>
|
||||
public TaskParameters() {}
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="TaskParameters"/> with an initial parameters content
|
||||
/// </summary>
|
||||
/// <param name="parameters">The list of parameters</param>
|
||||
public TaskParameters(IEnumerable<TaskParameter> parameters)
|
||||
{
|
||||
AddRange(parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A common interface that tasks should implement.
|
||||
/// </summary>
|
||||
public interface ITask
|
||||
{
|
||||
/// <summary>
|
||||
/// The slug of the task, used to start it.
|
||||
/// </summary>
|
||||
public string Slug { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the task that will be displayed to the user.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A quick description of what this task will do.
|
||||
/// </summary>
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// An optional message to display to help the user.
|
||||
/// </summary>
|
||||
public string HelpMessage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Should this task be automatically run at app startup?
|
||||
/// </summary>
|
||||
public bool RunOnStartup { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The priority of this task. Only used if <see cref="RunOnStartup"/> is true.
|
||||
/// It allow one to specify witch task will be started first as tasked are run on a Priority's descending order.
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Start this task.
|
||||
/// </summary>
|
||||
/// <param name="arguments">The list of parameters.</param>
|
||||
/// <param name="cancellationToken">A token to request the task's cancellation.
|
||||
/// If this task is not cancelled quickly, it might be killed by the runner.</param>
|
||||
/// <remarks>
|
||||
/// Your task can have any service as a public field and use the <see cref="InjectedAttribute"/>,
|
||||
/// they will be set to an available service from the service container before calling this method.
|
||||
/// </remarks>
|
||||
public Task Run(TaskParameters arguments, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// The list of parameters
|
||||
/// </summary>
|
||||
/// <returns>All parameters that this task as. Every one of them will be given to the run function with a value.</returns>
|
||||
public TaskParameters GetParameters();
|
||||
|
||||
/// <summary>
|
||||
/// If this task is running, return the percentage of completion of this task or null if no percentage can be given.
|
||||
/// </summary>
|
||||
/// <returns>The percentage of completion of the task.</returns>
|
||||
public int? Progress();
|
||||
}
|
||||
}
|
@ -1,13 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to handle long running tasks.
|
||||
/// </summary>
|
||||
/// <remarks>The concurrent number of running tasks is implementation dependent.</remarks>
|
||||
public interface ITaskManager
|
||||
{
|
||||
bool StartTask(string taskSlug, string arguments = null);
|
||||
ITask GetRunningTask();
|
||||
void ReloadTask();
|
||||
IEnumerable<ITask> GetAllTasks();
|
||||
/// <summary>
|
||||
/// Start a new task (or queue it).
|
||||
/// </summary>
|
||||
/// <param name="taskSlug">The slug of the task to run</param>
|
||||
/// <param name="arguments">A list of arguments to pass to the task. An automatic conversion will be made if arguments to not fit.</param>
|
||||
/// <exception cref="ArgumentException">If the number of arguments is invalid or if an argument can't be converted.</exception>
|
||||
/// <exception cref="ItemNotFoundException">The task could not be found.</exception>
|
||||
void StartTask(string taskSlug, Dictionary<string, object> arguments = null);
|
||||
|
||||
/// <summary>
|
||||
/// Get all currently running tasks
|
||||
/// </summary>
|
||||
/// <returns>A list of currently running tasks.</returns>
|
||||
ICollection<ITask> GetRunningTasks();
|
||||
|
||||
/// <summary>
|
||||
/// Get all available tasks
|
||||
/// </summary>
|
||||
/// <returns>A list of every tasks that this instance know.</returns>
|
||||
ICollection<ITask> GetAllTasks();
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using Kyoo.Models;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
|
@ -40,7 +40,7 @@ namespace Kyoo.Controllers
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="LibraryManager"/> instancce with every repository available.
|
||||
/// Create a new <see cref="LibraryManager"/> instance with every repository available.
|
||||
/// </summary>
|
||||
/// <param name="repositories">The list of repositories that this library manager should manage.
|
||||
/// If a repository for every base type is not available, this instance won't be stable.</param>
|
||||
@ -66,7 +66,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
if (_repositories.FirstOrDefault(x => x.RepositoryType == typeof(T)) is IRepository<T> ret)
|
||||
return ret;
|
||||
throw new ItemNotFound();
|
||||
throw new ItemNotFoundException($"No repository found for the type {typeof(T).Name}.");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -21,8 +21,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" />
|
||||
<PackageReference Include="JetBrains.Annotations" Version="2021.1.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.0-beta-20204-02" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
|
||||
|
91
Kyoo.Common/MethodOfUtils.cs
Normal file
91
Kyoo.Common/MethodOfUtils.cs
Normal file
@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
/// <summary>
|
||||
/// Static class containing MethodOf calls.
|
||||
/// </summary>
|
||||
public static class MethodOfUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf(Action action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T>(Action<T> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T, T2>(Action<T, T2> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T, T2, T3>(Action<T, T2, T3> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T>(Func<T> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T, T2>(Func<T, T2> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T, T2, T3>(Func<T, T2, T3> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a MethodInfo from a direct method.
|
||||
/// </summary>
|
||||
/// <param name="action">The method (without any arguments or return value.</param>
|
||||
/// <returns>The <see cref="MethodInfo"/> of the given method</returns>
|
||||
public static MethodInfo MethodOf<T, T2, T3, T4>(Func<T, T2, T3, T4> action)
|
||||
{
|
||||
return action.Method;
|
||||
}
|
||||
}
|
||||
}
|
16
Kyoo.Common/Models/Attributes/InjectedAttribute.cs
Normal file
16
Kyoo.Common/Models/Attributes/InjectedAttribute.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Controllers;
|
||||
|
||||
namespace Kyoo.Models.Attributes
|
||||
{
|
||||
/// <summary>
|
||||
/// An attribute to inform that the service will be injected automatically by a service provider.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It should only be used on <see cref="ITask"/> and will be injected before calling <see cref="ITask.Run"/>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
[MeansImplicitUse(ImplicitUseKindFlags.Assign)]
|
||||
public class InjectedAttribute : Attribute { }
|
||||
}
|
@ -2,10 +2,21 @@ using System;
|
||||
|
||||
namespace Kyoo.Models.Attributes
|
||||
{
|
||||
public class NotMergableAttribute : Attribute { }
|
||||
/// <summary>
|
||||
/// Specify that a property can't be merged.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class NotMergeableAttribute : Attribute { }
|
||||
|
||||
/// <summary>
|
||||
/// An interface with a method called when this object is merged.
|
||||
/// </summary>
|
||||
public interface IOnMerge
|
||||
{
|
||||
/// <summary>
|
||||
/// This function is called after the object has been merged.
|
||||
/// </summary>
|
||||
/// <param name="merged">The object that has been merged with this.</param>
|
||||
void OnMerge(object merged);
|
||||
}
|
||||
}
|
154
Kyoo.Common/Models/Attributes/PermissionAttribute.cs
Normal file
154
Kyoo.Common/Models/Attributes/PermissionAttribute.cs
Normal file
@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Models.Permissions
|
||||
{
|
||||
/// <summary>
|
||||
/// The kind of permission needed.
|
||||
/// </summary>
|
||||
public enum Kind
|
||||
{
|
||||
Read,
|
||||
Write,
|
||||
Create,
|
||||
Delete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify permissions needed for the API.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class PermissionAttribute : Attribute, IFilterFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The needed permission as string.
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
/// <summary>
|
||||
/// The needed permission kind.
|
||||
/// </summary>
|
||||
public Kind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ask a permission to run an action.
|
||||
/// </summary>
|
||||
/// <param name="type">
|
||||
/// The type of the action
|
||||
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
|
||||
/// </param>
|
||||
/// <param name="permission">The kind of permission needed</param>
|
||||
public PermissionAttribute(string type, Kind permission)
|
||||
{
|
||||
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
|
||||
type = type[..^3];
|
||||
Type = type.ToLower();
|
||||
Kind = permission;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||
{
|
||||
return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReusable => true;
|
||||
|
||||
/// <summary>
|
||||
/// Return this permission attribute as a string
|
||||
/// </summary>
|
||||
/// <returns>The string representation.</returns>
|
||||
public string AsPermissionString()
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specify one part of a permissions needed for the API (the kind or the type).
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
|
||||
public class PartialPermissionAttribute : Attribute, IFilterFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// The needed permission type.
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
/// <summary>
|
||||
/// The needed permission kind.
|
||||
/// </summary>
|
||||
public Kind Kind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Ask a permission to run an action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// With this attribute, you can only specify a type or a kind.
|
||||
/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
|
||||
/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
|
||||
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
|
||||
/// lead to unspecified behaviors.
|
||||
/// </remarks>
|
||||
/// <param name="type">
|
||||
/// The type of the action
|
||||
/// (if the type ends with api, it will be removed. This allow you to use nameof(YourApi)).
|
||||
/// </param>
|
||||
public PartialPermissionAttribute(string type)
|
||||
{
|
||||
if (type.EndsWith("API", StringComparison.OrdinalIgnoreCase))
|
||||
type = type[..^3];
|
||||
Type = type.ToLower();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ask a permission to run an action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// With this attribute, you can only specify a type or a kind.
|
||||
/// To have a valid permission attribute, you must specify the kind and the permission using two attributes.
|
||||
/// Those attributes can be dispatched at different places (one on the class, one on the method for example).
|
||||
/// If you don't put exactly two of those attributes, the permission attribute will be ill-formed and will
|
||||
/// lead to unspecified behaviors.
|
||||
/// </remarks>
|
||||
/// <param name="permission">The kind of permission needed</param>
|
||||
public PartialPermissionAttribute(Kind permission)
|
||||
{
|
||||
Kind = permission;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||
{
|
||||
return serviceProvider.GetRequiredService<IPermissionValidator>().Create(this);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsReusable => true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A service to validate permissions
|
||||
/// </summary>
|
||||
public interface IPermissionValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Create an IAuthorizationFilter that will be used to validate permissions.
|
||||
/// This can registered with any lifetime.
|
||||
/// </summary>
|
||||
/// <param name="attribute">The permission attribute to validate</param>
|
||||
/// <returns>An authorization filter used to validate the permission</returns>
|
||||
IFilterMetadata Create(PermissionAttribute attribute);
|
||||
|
||||
/// <summary>
|
||||
/// Create an IAuthorizationFilter that will be used to validate permissions.
|
||||
/// This can registered with any lifetime.
|
||||
/// </summary>
|
||||
/// <param name="attribute">
|
||||
/// A partial attribute to validate. See <see cref="PartialPermissionAttribute"/>.
|
||||
/// </param>
|
||||
/// <returns>An authorization filter used to validate the permission</returns>
|
||||
IFilterMetadata Create(PartialPermissionAttribute attribute);
|
||||
}
|
||||
}
|
@ -1,19 +1,36 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Kyoo.Models.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception raised when an item already exists in the database.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class DuplicatedItemException : Exception
|
||||
{
|
||||
public override string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DuplicatedItemException"/> with the default message.
|
||||
/// </summary>
|
||||
public DuplicatedItemException()
|
||||
{
|
||||
Message = "Already exists in the databse.";
|
||||
}
|
||||
: base("Already exists in the database.")
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DuplicatedItemException"/> with a custom message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to use</param>
|
||||
public DuplicatedItemException(string message)
|
||||
{
|
||||
Message = 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)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Kyoo.Models.Exceptions
|
||||
{
|
||||
public class ItemNotFound : Exception
|
||||
{
|
||||
public override string Message { get; }
|
||||
|
||||
public ItemNotFound() {}
|
||||
|
||||
public ItemNotFound(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
}
|
||||
}
|
34
Kyoo.Common/Models/Exceptions/ItemNotFoundException.cs
Normal file
34
Kyoo.Common/Models/Exceptions/ItemNotFoundException.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Kyoo.Models.Exceptions
|
||||
{
|
||||
/// <summary>
|
||||
/// An exception raised when an item could not be found.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ItemNotFoundException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Create a default <see cref="ItemNotFoundException"/> with no message.
|
||||
/// </summary>
|
||||
public ItemNotFoundException() {}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ItemNotFoundException"/> with a message
|
||||
/// </summary>
|
||||
/// <param name="message">The message of the exception</param>
|
||||
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)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -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() {}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
public interface IPlugin
|
||||
{
|
||||
public string Name { get; }
|
||||
public ICollection<ITask> Tasks { get; }
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -1,30 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to represent a resource that can be retrieved from the database.
|
||||
/// </summary>
|
||||
public interface IResource
|
||||
{
|
||||
/// <summary>
|
||||
/// A unique ID for this type of resource. This can't be changed and duplicates are not allowed.
|
||||
/// </summary>
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A human-readable identifier that can be used instead of an ID.
|
||||
/// A slug must be unique for a type of resource but it can be changed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There is no setter for a slug since it can be computed from other fields.
|
||||
/// For example, a season slug is {ShowSlug}-s{SeasonNumber}.
|
||||
/// </remarks>
|
||||
public string Slug { get; }
|
||||
}
|
||||
|
||||
public class ResourceComparer<T> : IEqualityComparer<T> where T : IResource
|
||||
{
|
||||
public bool Equals(T x, T y)
|
||||
{
|
||||
if (ReferenceEquals(x, y))
|
||||
return true;
|
||||
if (ReferenceEquals(x, null))
|
||||
return false;
|
||||
if (ReferenceEquals(y, null))
|
||||
return false;
|
||||
return x.ID == y.ID || x.Slug == y.Slug;
|
||||
}
|
||||
|
||||
public int GetHashCode(T obj)
|
||||
{
|
||||
return HashCode.Combine(obj.ID, obj.Slug);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
69
Kyoo.Common/Models/Resources/User.cs
Normal file
69
Kyoo.Common/Models/Resources/User.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A single user of the app.
|
||||
/// </summary>
|
||||
public class User : IResource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public int ID { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Slug { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A username displayed to the user.
|
||||
/// </summary>
|
||||
public string Username { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user email address.
|
||||
/// </summary>
|
||||
public string Email { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The user password (hashed, it can't be read like that). The hashing format is implementation defined.
|
||||
/// </summary>
|
||||
public string Password { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of permissions of the user. The format of this is implementation dependent.
|
||||
/// </summary>
|
||||
public string[] Permissions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Arbitrary extra data that can be used by specific authentication implementations.
|
||||
/// </summary>
|
||||
public Dictionary<string, string> ExtraData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of shows the user has finished.
|
||||
/// </summary>
|
||||
public ICollection<Show> Watched { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The list of episodes the user is watching (stopped in progress or the next episode of the show)
|
||||
/// </summary>
|
||||
public ICollection<WatchedEpisode> CurrentlyWatching { get; set; }
|
||||
|
||||
#if ENABLE_INTERNAL_LINKS
|
||||
/// <summary>
|
||||
/// Links between Users and Shows.
|
||||
/// </summary>
|
||||
public ICollection<Link<User, Show>> ShowLinks { get; set; }
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Metadata of episode currently watching by an user
|
||||
/// </summary>
|
||||
public class WatchedEpisode : Link<User, Episode>
|
||||
{
|
||||
/// <summary>
|
||||
/// Where the player has stopped watching the episode (-1 if not started, else between 0 and 100).
|
||||
/// </summary>
|
||||
public int WatchedPercentage { get; set; }
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
public interface ITask
|
||||
{
|
||||
public string Slug { get; }
|
||||
public string Name { get; }
|
||||
public string Description { get; }
|
||||
public string HelpMessage { get; }
|
||||
public bool RunOnStartup { get; }
|
||||
public int Priority { get; }
|
||||
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null);
|
||||
public Task<IEnumerable<string>> GetPossibleParameters();
|
||||
public int? Progress();
|
||||
}
|
||||
}
|
66
Kyoo.Common/Module.cs
Normal file
66
Kyoo.Common/Module.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using Kyoo.Controllers;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
/// <summary>
|
||||
/// A static class with helper functions to setup external modules
|
||||
/// </summary>
|
||||
public static class Module
|
||||
{
|
||||
/// <summary>
|
||||
/// Register a new task to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">The container</param>
|
||||
/// <typeparam name="T">The type of the task</typeparam>
|
||||
/// <returns>The initial container.</returns>
|
||||
public static IServiceCollection AddTask<T>(this IServiceCollection services)
|
||||
where T : class, ITask
|
||||
{
|
||||
services.AddSingleton<ITask, T>();
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new repository to the container.
|
||||
/// </summary>
|
||||
/// <param name="services">The container</param>
|
||||
/// <param name="lifetime">The lifetime of the repository. The default is scoped.</param>
|
||||
/// <typeparam name="T">The type of the repository.</typeparam>
|
||||
/// <remarks>
|
||||
/// If your repository implements a special interface, please use <see cref="AddRepository{T,T2}"/>
|
||||
/// </remarks>
|
||||
/// <returns>The initial container.</returns>
|
||||
public static IServiceCollection AddRepository<T>(this IServiceCollection services,
|
||||
ServiceLifetime lifetime = ServiceLifetime.Scoped)
|
||||
where T : IBaseRepository
|
||||
{
|
||||
Type repository = Utility.GetGenericDefinition(typeof(T), typeof(IRepository<>));
|
||||
|
||||
if (repository != null)
|
||||
services.Add(ServiceDescriptor.Describe(repository, typeof(T), lifetime));
|
||||
services.Add(ServiceDescriptor.Describe(typeof(IBaseRepository), typeof(T), lifetime));
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register a new repository with a custom mapping to the container.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="lifetime">The lifetime of the repository. The default is scoped.</param>
|
||||
/// <typeparam name="T">The custom mapping you have for your repository.</typeparam>
|
||||
/// <typeparam name="T2">The type of the repository.</typeparam>
|
||||
/// <remarks>
|
||||
/// If your repository does not implements a special interface, please use <see cref="AddRepository{T}"/>
|
||||
/// </remarks>
|
||||
/// <returns>The initial container.</returns>
|
||||
public static IServiceCollection AddRepository<T, T2>(this IServiceCollection services,
|
||||
ServiceLifetime lifetime = ServiceLifetime.Scoped)
|
||||
where T2 : IBaseRepository, T
|
||||
{
|
||||
services.Add(ServiceDescriptor.Describe(typeof(T), typeof(T2), lifetime));
|
||||
return services.AddRepository<T2>(lifetime);
|
||||
}
|
||||
}
|
||||
}
|
@ -146,7 +146,7 @@ namespace Kyoo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergableAttribute"/> attribute
|
||||
/// Set every fields of first to those of second. Ignore fields marked with the <see cref="NotMergeableAttribute"/> attribute
|
||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>
|
||||
/// </summary>
|
||||
/// <param name="first">The object to assign</param>
|
||||
@ -158,7 +158,7 @@ namespace Kyoo
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null);
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
@ -191,7 +191,7 @@ namespace Kyoo
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null);
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
if (where != null)
|
||||
properties = properties.Where(where);
|
||||
@ -215,7 +215,7 @@ namespace Kyoo
|
||||
/// <summary>
|
||||
/// An advanced <see cref="Complete{T}"/> function.
|
||||
/// This will set missing values of <see cref="first"/> to the corresponding values of <see cref="second"/>.
|
||||
/// Enumerables will be merged (concatened).
|
||||
/// Enumerable will be merged (concatenated).
|
||||
/// At the end, the OnMerge method of first will be called if first is a <see cref="IOnMerge"/>.
|
||||
/// </summary>
|
||||
/// <param name="first">The object to complete</param>
|
||||
@ -232,7 +232,7 @@ namespace Kyoo
|
||||
Type type = typeof(T);
|
||||
IEnumerable<PropertyInfo> properties = type.GetProperties()
|
||||
.Where(x => x.CanRead && x.CanWrite
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergableAttribute)) == null);
|
||||
&& Attribute.GetCustomAttribute(x, typeof(NotMergeableAttribute)) == null);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
{
|
||||
@ -529,9 +529,9 @@ namespace Kyoo
|
||||
await action(i);
|
||||
}
|
||||
|
||||
private static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args)
|
||||
public static MethodInfo GetMethod(Type type, BindingFlags flag, string name, Type[] generics, object[] args)
|
||||
{
|
||||
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
MethodInfo[] methods = type.GetMethods(flag | BindingFlags.Public)
|
||||
.Where(x => x.Name == name)
|
||||
.Where(x => x.GetGenericArguments().Length == generics.Length)
|
||||
.Where(x => x.GetParameters().Length == args.Length)
|
||||
@ -712,70 +712,18 @@ namespace Kyoo
|
||||
}, TaskContinuationOptions.ExecuteSynchronously);
|
||||
}
|
||||
|
||||
public static Expression<Func<T, bool>> ResourceEquals<T>(IResource obj)
|
||||
where T : IResource
|
||||
/// <summary>
|
||||
/// Get a friendly type name (supporting generics)
|
||||
/// For example a list of string will be displayed as List<string> and not as List`1.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to use</param>
|
||||
/// <returns>The friendly name of the type</returns>
|
||||
public static string FriendlyName(this Type type)
|
||||
{
|
||||
if (obj.ID > 0)
|
||||
return x => x.ID == obj.ID || x.Slug == obj.Slug;
|
||||
return x => x.Slug == obj.Slug;
|
||||
}
|
||||
|
||||
public static Func<T, bool> ResourceEqualsFunc<T>(IResource obj)
|
||||
where T : IResource
|
||||
{
|
||||
if (obj.ID > 0)
|
||||
return x => x.ID == obj.ID || x.Slug == obj.Slug;
|
||||
return x => x.Slug == obj.Slug;
|
||||
}
|
||||
|
||||
public static bool ResourceEquals([CanBeNull] object first, [CanBeNull] object second)
|
||||
{
|
||||
if (ReferenceEquals(first, second))
|
||||
return true;
|
||||
if (first is IResource f && second is IResource s)
|
||||
return ResourceEquals(f, s);
|
||||
IEnumerable eno = first as IEnumerable;
|
||||
IEnumerable ens = second as IEnumerable;
|
||||
if (eno == null || ens == null)
|
||||
throw new ArgumentException("Arguments are not resources or lists of resources.");
|
||||
Type type = GetEnumerableType(eno);
|
||||
if (typeof(IResource).IsAssignableFrom(type))
|
||||
return ResourceEquals(eno.Cast<IResource>(), ens.Cast<IResource>());
|
||||
return RunGenericMethod<bool>(typeof(Enumerable), "SequenceEqual", type, first, second);
|
||||
}
|
||||
|
||||
public static bool ResourceEquals<T>([CanBeNull] T first, [CanBeNull] T second)
|
||||
where T : IResource
|
||||
{
|
||||
if (ReferenceEquals(first, second))
|
||||
return true;
|
||||
if (first == null || second == null)
|
||||
return false;
|
||||
return first.ID == second.ID || first.Slug == second.Slug;
|
||||
}
|
||||
|
||||
public static bool ResourceEquals<T>([CanBeNull] IEnumerable<T> first, [CanBeNull] IEnumerable<T> second)
|
||||
where T : IResource
|
||||
{
|
||||
if (ReferenceEquals(first, second))
|
||||
return true;
|
||||
if (first == null || second == null)
|
||||
return false;
|
||||
return first.SequenceEqual(second, new ResourceComparer<T>());
|
||||
}
|
||||
|
||||
public static bool LinkEquals<T>([CanBeNull] T first, int? firstID, [CanBeNull] T second, int? secondID)
|
||||
where T : IResource
|
||||
{
|
||||
if (ResourceEquals(first, second))
|
||||
return true;
|
||||
if (first == null && second != null
|
||||
&& firstID == second.ID)
|
||||
return true;
|
||||
if (first != null && second == null
|
||||
&& first.ID == secondID)
|
||||
return true;
|
||||
return firstID == secondID;
|
||||
if (!type.IsGenericType)
|
||||
return type.Name;
|
||||
string generics = string.Join(", ", type.GetGenericArguments().Select(x => x.FriendlyName()));
|
||||
return $"{type.Name[..type.Name.IndexOf('`')]}<{generics}>";
|
||||
}
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Kyoo.Models.Permissions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
|
||||
@ -21,40 +21,32 @@ namespace Kyoo.CommonApi
|
||||
public CrudApi(IRepository<T> repository, IConfiguration configuration)
|
||||
{
|
||||
_repository = repository;
|
||||
BaseURL = configuration.GetValue<string>("public_url").TrimEnd('/');
|
||||
BaseURL = configuration.GetValue<string>("publicUrl").TrimEnd('/');
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{id:int}")]
|
||||
[Authorize(Policy = "Read")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<T>> Get(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.Get(id);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
T ret = await _repository.GetOrDefault(id);
|
||||
if (ret == null)
|
||||
return NotFound();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
[HttpGet("{slug}")]
|
||||
[Authorize(Policy = "Read")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<T>> Get(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.Get(slug);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
{
|
||||
T ret = await _repository.Get(slug);
|
||||
if (ret == null)
|
||||
return NotFound();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
[HttpGet("count")]
|
||||
[Authorize(Policy = "Read")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<int>> GetCount([FromQuery] Dictionary<string, string> where)
|
||||
{
|
||||
try
|
||||
@ -68,7 +60,7 @@ namespace Kyoo.CommonApi
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize(Policy = "Read")]
|
||||
[PartialPermission(Kind.Read)]
|
||||
public virtual async Task<ActionResult<Page<T>>> GetAll([FromQuery] string sortBy,
|
||||
[FromQuery] int afterID,
|
||||
[FromQuery] Dictionary<string, string> where,
|
||||
@ -98,7 +90,7 @@ namespace Kyoo.CommonApi
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Create)]
|
||||
public virtual async Task<ActionResult<T>> Create([FromBody] T resource)
|
||||
{
|
||||
try
|
||||
@ -111,13 +103,13 @@ namespace Kyoo.CommonApi
|
||||
}
|
||||
catch (DuplicatedItemException)
|
||||
{
|
||||
T existing = await _repository.Get(resource.Slug);
|
||||
T existing = await _repository.GetOrDefault(resource.Slug);
|
||||
return Conflict(existing);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit([FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
try
|
||||
@ -129,14 +121,14 @@ namespace Kyoo.CommonApi
|
||||
resource.ID = old.ID;
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{id:int}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit(int id, [FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
resource.ID = id;
|
||||
@ -144,14 +136,14 @@ namespace Kyoo.CommonApi
|
||||
{
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("{slug}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Write)]
|
||||
public virtual async Task<ActionResult<T>> Edit(string slug, [FromQuery] bool resetOld, [FromBody] T resource)
|
||||
{
|
||||
try
|
||||
@ -160,21 +152,21 @@ namespace Kyoo.CommonApi
|
||||
resource.ID = old.ID;
|
||||
return await _repository.Edit(resource, resetOld);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpDelete("{id:int}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Delete)]
|
||||
public virtual async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repository.Delete(id);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@ -183,14 +175,14 @@ namespace Kyoo.CommonApi
|
||||
}
|
||||
|
||||
[HttpDelete("{slug}")]
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Delete)]
|
||||
public virtual async Task<IActionResult> Delete(string slug)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repository.Delete(slug);
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@ -198,14 +190,14 @@ namespace Kyoo.CommonApi
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = "Write")]
|
||||
[PartialPermission(Kind.Delete)]
|
||||
public virtual async Task<IActionResult> Delete(Dictionary<string, string> where)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repository.DeleteRange(ApiHelper.ParseWhere<T>(where));
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
@ -7,17 +8,17 @@ using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using Npgsql;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle used for all local repositories.
|
||||
/// This is an abstract class. It is meant to be implemented by plugins. This allow the core to be database agnostic.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It should not be used directly, to access the database use a <see cref="ILibraryManager"/> or repositories.
|
||||
/// </remarks>
|
||||
public class DatabaseContext : DbContext
|
||||
public abstract class DatabaseContext : DbContext
|
||||
{
|
||||
/// <summary>
|
||||
/// All libraries of Kyoo. See <see cref="Library"/>.
|
||||
@ -63,12 +64,21 @@ namespace Kyoo
|
||||
/// All metadataIDs (ExternalIDs) of Kyoo. See <see cref="MetadataID"/>.
|
||||
/// </summary>
|
||||
public DbSet<MetadataID> MetadataIds { get; set; }
|
||||
/// <summary>
|
||||
/// The list of registered users.
|
||||
/// </summary>
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// All people's role. See <see cref="PeopleRole"/>.
|
||||
/// </summary>
|
||||
public DbSet<PeopleRole> PeopleRoles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Episodes with a watch percentage. See <see cref="WatchedEpisode"/>
|
||||
/// </summary>
|
||||
public DbSet<WatchedEpisode> WatchedEpisodes { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get a generic link between two resource types.
|
||||
/// </summary>
|
||||
@ -85,27 +95,26 @@ namespace Kyoo
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
|
||||
/// The default constructor
|
||||
/// </summary>
|
||||
public DatabaseContext()
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
|
||||
|
||||
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
ChangeTracker.LazyLoadingEnabled = false;
|
||||
}
|
||||
protected DatabaseContext() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DatabaseContext"/>.
|
||||
/// Create a new <see cref="DatabaseContext"/> using specific options
|
||||
/// </summary>
|
||||
/// <param name="options">Connection options to use (witch databse provider to use, connection strings...)</param>
|
||||
public DatabaseContext(DbContextOptions<DatabaseContext> options)
|
||||
/// <param name="options">The options to use.</param>
|
||||
protected DatabaseContext(DbContextOptions options)
|
||||
: base(options)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Set basic configurations (like preventing query tracking)
|
||||
/// </summary>
|
||||
/// <param name="optionsBuilder">An option builder to fill.</param>
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
|
||||
ChangeTracker.LazyLoadingEnabled = false;
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
optionsBuilder.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -116,10 +125,6 @@ namespace Kyoo
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
modelBuilder.HasPostgresEnum<Status>();
|
||||
modelBuilder.HasPostgresEnum<ItemType>();
|
||||
modelBuilder.HasPostgresEnum<StreamType>();
|
||||
|
||||
modelBuilder.Entity<Track>()
|
||||
.Property(t => t.IsDefault)
|
||||
.ValueGeneratedNever();
|
||||
@ -188,6 +193,17 @@ namespace Kyoo
|
||||
.WithMany(x => x.ShowLinks),
|
||||
y => y.HasKey(Link<Show, Genre>.PrimaryKey));
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.HasMany(x => x.Watched)
|
||||
.WithMany("users")
|
||||
.UsingEntity<Link<User, Show>>(
|
||||
y => y
|
||||
.HasOne(x => x.Second)
|
||||
.WithMany(),
|
||||
y => y
|
||||
.HasOne(x => x.First)
|
||||
.WithMany(x => x.ShowLinks),
|
||||
y => y.HasKey(Link<User, Show>.PrimaryKey));
|
||||
|
||||
modelBuilder.Entity<MetadataID>()
|
||||
.HasOne(x => x.Show)
|
||||
@ -210,6 +226,9 @@ namespace Kyoo
|
||||
.WithMany(x => x.MetadataLinks)
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<WatchedEpisode>()
|
||||
.HasKey(x => new {First = x.FirstID, Second = x.SecondID});
|
||||
|
||||
modelBuilder.Entity<Collection>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Genre>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Library>().Property(x => x.Slug).IsRequired();
|
||||
@ -217,6 +236,7 @@ namespace Kyoo
|
||||
modelBuilder.Entity<Provider>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Show>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<Studio>().Property(x => x.Slug).IsRequired();
|
||||
modelBuilder.Entity<User>().Property(x => x.Slug).IsRequired();
|
||||
|
||||
modelBuilder.Entity<Collection>()
|
||||
.HasIndex(x => x.Slug)
|
||||
@ -248,13 +268,16 @@ namespace Kyoo
|
||||
modelBuilder.Entity<Track>()
|
||||
.HasIndex(x => new {x.EpisodeID, x.Type, x.Language, x.TrackIndex, x.IsForced})
|
||||
.IsUnique();
|
||||
modelBuilder.Entity<User>()
|
||||
.HasIndex(x => x.Slug)
|
||||
.IsUnique();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a new or an in cache temporary object wih the same ID as the one given
|
||||
/// </summary>
|
||||
/// <param name="model">If a resource with the same ID is found in the database, it will be used.
|
||||
/// <see cref="model"/> will be used overwise</param>
|
||||
/// <see cref="model"/> will be used otherwise</param>
|
||||
/// <typeparam name="T">The type of the resource</typeparam>
|
||||
/// <returns>A resource that is now tracked by this context.</returns>
|
||||
public T GetTemporaryObject<T>(T model)
|
||||
@ -467,13 +490,9 @@ namespace Kyoo
|
||||
/// <summary>
|
||||
/// Check if the exception is a duplicated exception.
|
||||
/// </summary>
|
||||
/// <remarks>WARNING: this only works for PostgreSQL</remarks>
|
||||
/// <param name="ex">The exception to check</param>
|
||||
/// <returns>True if the exception is a duplicate exception. False otherwise</returns>
|
||||
private static bool IsDuplicateException(Exception ex)
|
||||
{
|
||||
return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation};
|
||||
}
|
||||
protected abstract bool IsDuplicateException(Exception ex);
|
||||
|
||||
/// <summary>
|
||||
/// Delete every changes that are on this context.
|
||||
@ -486,5 +505,15 @@ namespace Kyoo
|
||||
entry.State = EntityState.Detached;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Perform a case insensitive like operation.
|
||||
/// </summary>
|
||||
/// <param name="query">An accessor to get the item that will be checked.</param>
|
||||
/// <param name="format">The second operator of the like format.</param>
|
||||
/// <typeparam name="T">The type of the item to query</typeparam>
|
||||
/// <returns>An expression representing the like query. It can directly be passed to a where call.</returns>
|
||||
public abstract Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> query, string format);
|
||||
}
|
||||
}
|
@ -9,14 +9,15 @@ namespace Kyoo
|
||||
public static class Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a connection string from the Configuration's section "Databse"
|
||||
/// Get a connection string from the Configuration's section "Database"
|
||||
/// </summary>
|
||||
/// <param name="config">The IConfiguration instance to load.</param>
|
||||
/// <param name="database">The database's name.</param>
|
||||
/// <returns>A parsed connection string</returns>
|
||||
public static string GetDatabaseConnection(this IConfiguration config)
|
||||
public static string GetDatabaseConnection(this IConfiguration config, string database)
|
||||
{
|
||||
DbConnectionStringBuilder builder = new();
|
||||
IConfigurationSection section = config.GetSection("Database");
|
||||
IConfigurationSection section = config.GetSection("Database").GetSection(database);
|
||||
foreach (IConfigurationSection child in section.GetChildren())
|
||||
builder[child.Key] = child.Value;
|
||||
return builder.ConnectionString;
|
@ -12,10 +12,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="5.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -46,13 +46,13 @@ namespace Kyoo.Controllers
|
||||
/// Get a resource from it's ID and make the <see cref="Database"/> instance track it.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the resource</param>
|
||||
/// <exception cref="ItemNotFound">If the item is not found</exception>
|
||||
/// <exception cref="ItemNotFoundException">If the item is not found</exception>
|
||||
/// <returns>The tracked resource with the given ID</returns>
|
||||
protected virtual async Task<T> GetWithTracking(int id)
|
||||
{
|
||||
T ret = await Database.Set<T>().AsTracking().FirstOrDefaultAsync(x => x.ID == id);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the id {id}");
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
T ret = await GetOrDefault(id);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the id {id}");
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the id {id}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
T ret = await GetOrDefault(slug);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the slug {slug}");
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the slug {slug}");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
T ret = await GetOrDefault(where);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No {typeof(T).Name} found with the given predicate.");
|
||||
throw new ItemNotFoundException($"No {typeof(T).Name} found with the given predicate.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -118,14 +118,14 @@ namespace Kyoo.Controllers
|
||||
/// <param name="query">The base query to filter.</param>
|
||||
/// <param name="where">An expression to filter based on arbitrary conditions</param>
|
||||
/// <param name="sort">The sort settings (sort order & sort by)</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <returns>The filtered query</returns>
|
||||
protected Task<ICollection<T>> ApplyFilters(IQueryable<T> query,
|
||||
Expression<Func<T, bool>> where = null,
|
||||
Sort<T> sort = default,
|
||||
Pagination limit = default)
|
||||
{
|
||||
return ApplyFilters(query, Get, DefaultSort, where, sort, limit);
|
||||
return ApplyFilters(query, GetOrDefault, DefaultSort, where, sort, limit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -137,7 +137,7 @@ namespace Kyoo.Controllers
|
||||
/// <param name="query">The base query to filter.</param>
|
||||
/// <param name="where">An expression to filter based on arbitrary conditions</param>
|
||||
/// <param name="sort">The sort settings (sort order & sort by)</param>
|
||||
/// <param name="limit">Paginations information (where to start and how many to get)</param>
|
||||
/// <param name="limit">Pagination information (where to start and how many to get)</param>
|
||||
/// <returns>The filtered query</returns>
|
||||
protected async Task<ICollection<TValue>> ApplyFilters<TValue>(IQueryable<TValue> query,
|
||||
Func<int, Task<TValue>> get,
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -244,7 +238,7 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An overridable method to edit relatiosn of a resource.
|
||||
/// An overridable method to edit relation of a resource.
|
||||
/// </summary>
|
||||
/// <param name="resource">The non edited resource</param>
|
||||
/// <param name="changed">The new version of <see cref="resource"/>. This item will be saved on the databse and replace <see cref="resource"/></param>
|
||||
|
41
Kyoo.Postgresql/Kyoo.Postgresql.csproj
Normal file
41
Kyoo.Postgresql/Kyoo.Postgresql.csproj
Normal file
@ -0,0 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
|
||||
<Company>SDG</Company>
|
||||
<Authors>Zoe Roux</Authors>
|
||||
<RepositoryUrl>https://github.com/AnonymusRaccoon/Kyoo</RepositoryUrl>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputPath>../Kyoo/bin/$(Configuration)/$(TargetFramework)/plugins/postgresql</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<GenerateDependencyFile>false</GenerateDependencyFile>
|
||||
<GenerateRuntimeConfigurationFiles>false</GenerateRuntimeConfigurationFiles>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.5">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<Private>false</Private>
|
||||
<ExcludeAssets>runtime</ExcludeAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,16 +1,18 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Kyoo;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
[Migration("20210420221509_Initial")]
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
[Migration("20210507203809_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
@ -21,7 +23,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
.HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" })
|
||||
.HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "attachment" })
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("ProductVersion", "5.0.5")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||
@ -224,6 +226,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.ToTable("Link<Show, Genre>");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Link<Kyoo.Models.User, Kyoo.Models.Show>", b =>
|
||||
{
|
||||
b.Property<int>("FirstID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("FirstID", "SecondID");
|
||||
|
||||
b.HasIndex("SecondID");
|
||||
|
||||
b.ToTable("Link<User, Show>");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.MetadataID", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
@ -419,8 +436,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Property<int?>("StartYear")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("Status")
|
||||
.HasColumnType("integer");
|
||||
b.Property<Status?>("Status")
|
||||
.HasColumnType("status");
|
||||
|
||||
b.Property<int?>("StudioID")
|
||||
.HasColumnType("integer");
|
||||
@ -497,8 +514,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Property<int>("TrackIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
b.Property<StreamType>("Type")
|
||||
.HasColumnType("stream_type");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
@ -508,6 +525,58 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.ToTable("Tracks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Dictionary<string, string>>("ExtraData")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string[]>("Permissions")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b =>
|
||||
{
|
||||
b.Property<int>("FirstID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("WatchedPercentage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("FirstID", "SecondID");
|
||||
|
||||
b.HasIndex("SecondID");
|
||||
|
||||
b.ToTable("WatchedEpisodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Episode", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.Season", "Season")
|
||||
@ -620,6 +689,25 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Link<Kyoo.Models.User, Kyoo.Models.Show>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", "First")
|
||||
.WithMany("ShowLinks")
|
||||
.HasForeignKey("FirstID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.Show", "Second")
|
||||
.WithMany()
|
||||
.HasForeignKey("SecondID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("First");
|
||||
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.MetadataID", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.Episode", "Episode")
|
||||
@ -709,6 +797,25 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Navigation("Episode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", "First")
|
||||
.WithMany("CurrentlyWatching")
|
||||
.HasForeignKey("FirstID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.Episode", "Second")
|
||||
.WithMany()
|
||||
.HasForeignKey("SecondID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("First");
|
||||
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||
{
|
||||
b.Navigation("LibraryLinks");
|
||||
@ -779,6 +886,13 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
{
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Navigation("CurrentlyWatching");
|
||||
|
||||
b.Navigation("ShowLinks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
@ -103,6 +105,24 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
table.PrimaryKey("PK_Studios", x => x.ID);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
ID = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Slug = table.Column<string>(type: "text", nullable: false),
|
||||
Username = table.Column<string>(type: "text", nullable: true),
|
||||
Email = table.Column<string>(type: "text", nullable: true),
|
||||
Password = table.Column<string>(type: "text", nullable: true),
|
||||
Permissions = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
ExtraData = table.Column<Dictionary<string, string>>(type: "jsonb", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.ID);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Link<Library, Collection>",
|
||||
columns: table => new
|
||||
@ -162,7 +182,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
Aliases = table.Column<string[]>(type: "text[]", nullable: true),
|
||||
Path = table.Column<string>(type: "text", nullable: true),
|
||||
Overview = table.Column<string>(type: "text", nullable: true),
|
||||
Status = table.Column<int>(type: "integer", nullable: true),
|
||||
Status = table.Column<Status>(type: "status", nullable: true),
|
||||
TrailerUrl = table.Column<string>(type: "text", nullable: true),
|
||||
StartYear = table.Column<int>(type: "integer", nullable: true),
|
||||
EndYear = table.Column<int>(type: "integer", nullable: true),
|
||||
@ -255,6 +275,30 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Link<User, Show>",
|
||||
columns: table => new
|
||||
{
|
||||
FirstID = table.Column<int>(type: "integer", nullable: false),
|
||||
SecondID = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Link<User, Show>", x => new { x.FirstID, x.SecondID });
|
||||
table.ForeignKey(
|
||||
name: "FK_Link<User, Show>_Shows_SecondID",
|
||||
column: x => x.SecondID,
|
||||
principalTable: "Shows",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Link<User, Show>_Users_FirstID",
|
||||
column: x => x.FirstID,
|
||||
principalTable: "Users",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PeopleRoles",
|
||||
columns: table => new
|
||||
@ -406,7 +450,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
Language = table.Column<string>(type: "text", nullable: true),
|
||||
Codec = table.Column<string>(type: "text", nullable: true),
|
||||
Path = table.Column<string>(type: "text", nullable: true),
|
||||
Type = table.Column<int>(type: "integer", nullable: false)
|
||||
Type = table.Column<StreamType>(type: "stream_type", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -419,6 +463,31 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WatchedEpisodes",
|
||||
columns: table => new
|
||||
{
|
||||
FirstID = table.Column<int>(type: "integer", nullable: false),
|
||||
SecondID = table.Column<int>(type: "integer", nullable: false),
|
||||
WatchedPercentage = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WatchedEpisodes", x => new { x.FirstID, x.SecondID });
|
||||
table.ForeignKey(
|
||||
name: "FK_WatchedEpisodes_Episodes_SecondID",
|
||||
column: x => x.SecondID,
|
||||
principalTable: "Episodes",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_WatchedEpisodes_Users_FirstID",
|
||||
column: x => x.FirstID,
|
||||
principalTable: "Users",
|
||||
principalColumn: "ID",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Collections_Slug",
|
||||
table: "Collections",
|
||||
@ -473,6 +542,11 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
table: "Link<Show, Genre>",
|
||||
column: "SecondID");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Link<User, Show>_SecondID",
|
||||
table: "Link<User, Show>",
|
||||
column: "SecondID");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MetadataIds_EpisodeID",
|
||||
table: "MetadataIds",
|
||||
@ -548,6 +622,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
table: "Tracks",
|
||||
columns: new[] { "EpisodeID", "Type", "Language", "TrackIndex", "IsForced" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_Slug",
|
||||
table: "Users",
|
||||
column: "Slug",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WatchedEpisodes_SecondID",
|
||||
table: "WatchedEpisodes",
|
||||
column: "SecondID");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
@ -567,6 +652,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
migrationBuilder.DropTable(
|
||||
name: "Link<Show, Genre>");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Link<User, Show>");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "MetadataIds");
|
||||
|
||||
@ -576,6 +664,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
migrationBuilder.DropTable(
|
||||
name: "Tracks");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WatchedEpisodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Collections");
|
||||
|
||||
@ -594,6 +685,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
migrationBuilder.DropTable(
|
||||
name: "Episodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Seasons");
|
||||
|
@ -1,15 +1,17 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Kyoo;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Postgresql;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
namespace Kyoo.Postgresql.Migrations
|
||||
{
|
||||
[DbContext(typeof(DatabaseContext))]
|
||||
partial class DatabaseContextModelSnapshot : ModelSnapshot
|
||||
[DbContext(typeof(PostgresContext))]
|
||||
partial class PostgresContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
@ -19,7 +21,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
.HasPostgresEnum(null, "status", new[] { "finished", "airing", "planned", "unknown" })
|
||||
.HasPostgresEnum(null, "stream_type", new[] { "unknown", "video", "audio", "subtitle", "attachment" })
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("ProductVersion", "5.0.5")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||
@ -222,6 +224,21 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.ToTable("Link<Show, Genre>");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Link<Kyoo.Models.User, Kyoo.Models.Show>", b =>
|
||||
{
|
||||
b.Property<int>("FirstID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("FirstID", "SecondID");
|
||||
|
||||
b.HasIndex("SecondID");
|
||||
|
||||
b.ToTable("Link<User, Show>");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.MetadataID", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
@ -417,8 +434,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Property<int?>("StartYear")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("Status")
|
||||
.HasColumnType("integer");
|
||||
b.Property<Status?>("Status")
|
||||
.HasColumnType("status");
|
||||
|
||||
b.Property<int?>("StudioID")
|
||||
.HasColumnType("integer");
|
||||
@ -495,8 +512,8 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Property<int>("TrackIndex")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
b.Property<StreamType>("Type")
|
||||
.HasColumnType("stream_type");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
@ -506,6 +523,58 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.ToTable("Tracks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Dictionary<string, string>>("ExtraData")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("Password")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string[]>("Permissions")
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<string>("Slug")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Slug")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b =>
|
||||
{
|
||||
b.Property<int>("FirstID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondID")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("WatchedPercentage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("FirstID", "SecondID");
|
||||
|
||||
b.HasIndex("SecondID");
|
||||
|
||||
b.ToTable("WatchedEpisodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Episode", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.Season", "Season")
|
||||
@ -618,6 +687,25 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Link<Kyoo.Models.User, Kyoo.Models.Show>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", "First")
|
||||
.WithMany("ShowLinks")
|
||||
.HasForeignKey("FirstID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.Show", "Second")
|
||||
.WithMany()
|
||||
.HasForeignKey("SecondID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("First");
|
||||
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.MetadataID", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.Episode", "Episode")
|
||||
@ -707,6 +795,25 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
b.Navigation("Episode");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.WatchedEpisode", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", "First")
|
||||
.WithMany("CurrentlyWatching")
|
||||
.HasForeignKey("FirstID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.Episode", "Second")
|
||||
.WithMany()
|
||||
.HasForeignKey("SecondID")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("First");
|
||||
|
||||
b.Navigation("Second");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||
{
|
||||
b.Navigation("LibraryLinks");
|
||||
@ -777,6 +884,13 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||
{
|
||||
b.Navigation("Shows");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Navigation("CurrentlyWatching");
|
||||
|
||||
b.Navigation("ShowLinks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
115
Kyoo.Postgresql/PostgresContext.cs
Normal file
115
Kyoo.Postgresql/PostgresContext.cs
Normal file
@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Npgsql;
|
||||
|
||||
namespace Kyoo.Postgresql
|
||||
{
|
||||
/// <summary>
|
||||
/// A postgresql implementation of <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public class PostgresContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The connection string to use.
|
||||
/// </summary>
|
||||
private readonly string _connection;
|
||||
|
||||
/// <summary>
|
||||
/// Is this instance in debug mode?
|
||||
/// </summary>
|
||||
private readonly bool _debugMode;
|
||||
|
||||
/// <summary>
|
||||
/// Should the configure step be skipped? This is used when the database is created via DbContextOptions.
|
||||
/// </summary>
|
||||
private readonly bool _skipConfigure;
|
||||
|
||||
/// <summary>
|
||||
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
|
||||
/// </summary>
|
||||
public PostgresContext()
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PostgresContext"/> using specific options
|
||||
/// </summary>
|
||||
/// <param name="options">The options to use.</param>
|
||||
public PostgresContext(DbContextOptions options)
|
||||
: base(options)
|
||||
{
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<Status>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<ItemType>();
|
||||
NpgsqlConnection.GlobalTypeMapper.MapEnum<StreamType>();
|
||||
_skipConfigure = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A basic constructor that set default values (query tracker behaviors, mapping enums...)
|
||||
/// </summary>
|
||||
/// <param name="connection">The connection string to use</param>
|
||||
/// <param name="debugMode">Is this instance in debug mode?</param>
|
||||
public PostgresContext(string connection, bool debugMode)
|
||||
{
|
||||
_connection = connection;
|
||||
_debugMode = debugMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set connection information for this database context
|
||||
/// </summary>
|
||||
/// <param name="optionsBuilder">An option builder to fill.</param>
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
if (!_skipConfigure)
|
||||
{
|
||||
if (_connection != null)
|
||||
optionsBuilder.UseNpgsql(_connection);
|
||||
else
|
||||
optionsBuilder.UseNpgsql();
|
||||
if (_debugMode)
|
||||
optionsBuilder.EnableDetailedErrors().EnableSensitiveDataLogging();
|
||||
}
|
||||
|
||||
base.OnConfiguring(optionsBuilder);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set database parameters to support every types of Kyoo.
|
||||
/// </summary>
|
||||
/// <param name="modelBuilder">The database's model builder.</param>
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.HasPostgresEnum<Status>();
|
||||
modelBuilder.HasPostgresEnum<ItemType>();
|
||||
modelBuilder.HasPostgresEnum<StreamType>();
|
||||
|
||||
modelBuilder.Entity<User>()
|
||||
.Property(x => x.ExtraData)
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
base.OnModelCreating(modelBuilder);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override bool IsDuplicateException(Exception ex)
|
||||
{
|
||||
return ex.InnerException is PostgresException {SqlState: PostgresErrorCodes.UniqueViolation};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> query, string format)
|
||||
{
|
||||
MethodInfo iLike = MethodOfUtils.MethodOf<string, string, bool>(EF.Functions.ILike);
|
||||
MethodCallExpression call = Expression.Call(iLike, query.Body, Expression.Constant(format));
|
||||
|
||||
return Expression.Lambda<Func<T, bool>>(call, query.Parameters);
|
||||
}
|
||||
}
|
||||
}
|
82
Kyoo.Postgresql/PostgresModule.cs
Normal file
82
Kyoo.Postgresql/PostgresModule.cs
Normal file
@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Kyoo.Controllers;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
|
||||
namespace Kyoo.Postgresql
|
||||
{
|
||||
/// <summary>
|
||||
/// A module to add postgresql capacity to the app.
|
||||
/// </summary>
|
||||
public class PostgresModule : IPlugin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Slug => "postgresql";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Postgresql";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "A database context for postgresql.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Provides => new[]
|
||||
{
|
||||
typeof(DatabaseContext)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<ConditionalProvide> ConditionalProvides => ArraySegment<ConditionalProvide>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Requires => ArraySegment<Type>.Empty;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The configuration to use. The database connection string is pulled from it.
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
/// <summary>
|
||||
/// The host environment to check if the app is in debug mode.
|
||||
/// </summary>
|
||||
private readonly IWebHostEnvironment _environment;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new postgres module instance and use the given configuration and environment.
|
||||
/// </summary>
|
||||
/// <param name="configuration">The configuration to use</param>
|
||||
/// <param name="env">The environment that will be used (if the env is in development mode, more information will be displayed on errors.</param>
|
||||
public PostgresModule(IConfiguration configuration, IWebHostEnvironment env)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_environment = env;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
|
||||
{
|
||||
services.AddDbContext<DatabaseContext, PostgresContext>(x =>
|
||||
{
|
||||
x.UseNpgsql(_configuration.GetDatabaseConnection("postgres"));
|
||||
if (_environment.IsDevelopment())
|
||||
x.EnableDetailedErrors().EnableSensitiveDataLogging();
|
||||
});
|
||||
// services.AddScoped<DatabaseContext>(_ => new PostgresContext(
|
||||
// _configuration.GetDatabaseConnection("postgres"),
|
||||
// _environment.IsDevelopment()));
|
||||
// services.AddScoped<DbContext>(x => x.GetRequiredService<PostgresContext>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize(IServiceProvider provider)
|
||||
{
|
||||
DatabaseContext context = provider.GetRequiredService<DatabaseContext>();
|
||||
context.Database.Migrate();
|
||||
}
|
||||
}
|
||||
}
|
@ -14,14 +14,14 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -1,6 +1,3 @@
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
namespace Kyoo.Tests
|
||||
{
|
||||
public class SetupTests
|
||||
|
@ -1,79 +1,79 @@
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Class responsible to fill and create in memory databases for unit tests.
|
||||
/// </summary>
|
||||
public class TestContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The context's options that specify to use an in memory Sqlite database.
|
||||
/// </summary>
|
||||
private readonly DbContextOptions<DatabaseContext> _context;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new database and fill it with informations.
|
||||
/// </summary>
|
||||
public TestContext()
|
||||
{
|
||||
SqliteConnection connection = new("DataSource=:memory:");
|
||||
connection.Open();
|
||||
|
||||
try
|
||||
{
|
||||
_context = new DbContextOptionsBuilder<DatabaseContext>()
|
||||
.UseSqlite(connection)
|
||||
.Options;
|
||||
FillDatabase();
|
||||
}
|
||||
finally
|
||||
{
|
||||
connection.Close();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fill the database with pre defined values using a clean context.
|
||||
/// </summary>
|
||||
private void FillDatabase()
|
||||
{
|
||||
using DatabaseContext context = new(_context);
|
||||
context.Shows.Add(new Show
|
||||
{
|
||||
ID = 67,
|
||||
Slug = "anohana",
|
||||
Title = "Anohana: The Flower We Saw That Day",
|
||||
Aliases = new[]
|
||||
{
|
||||
"Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.",
|
||||
"AnoHana",
|
||||
"We Still Don't Know the Name of the Flower We Saw That Day."
|
||||
},
|
||||
Overview = "When Yadomi Jinta was a child, he was a central piece in a group of close friends. " +
|
||||
"In time, however, these childhood friends drifted apart, and when they became high " +
|
||||
"school students, they had long ceased to think of each other as friends.",
|
||||
Status = Status.Finished,
|
||||
TrailerUrl = null,
|
||||
StartYear = 2011,
|
||||
EndYear = 2011,
|
||||
Poster = "poster",
|
||||
Logo = "logo",
|
||||
Backdrop = "backdrop",
|
||||
IsMovie = false,
|
||||
Studio = null
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a new databse context connected to a in memory Sqlite databse.
|
||||
/// </summary>
|
||||
/// <returns>A valid DatabaseContext</returns>
|
||||
public DatabaseContext New()
|
||||
{
|
||||
return new(_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.Data.Sqlite;
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
//
|
||||
// namespace Kyoo.Tests
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// Class responsible to fill and create in memory databases for unit tests.
|
||||
// /// </summary>
|
||||
// public class TestContext
|
||||
// {
|
||||
// /// <summary>
|
||||
// /// The context's options that specify to use an in memory Sqlite database.
|
||||
// /// </summary>
|
||||
// private readonly DbContextOptions<DatabaseContext> _context;
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Create a new database and fill it with information.
|
||||
// /// </summary>
|
||||
// public TestContext()
|
||||
// {
|
||||
// SqliteConnection connection = new("DataSource=:memory:");
|
||||
// connection.Open();
|
||||
//
|
||||
// try
|
||||
// {
|
||||
// _context = new DbContextOptionsBuilder<DatabaseContext>()
|
||||
// .UseSqlite(connection)
|
||||
// .Options;
|
||||
// FillDatabase();
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// connection.Close();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Fill the database with pre defined values using a clean context.
|
||||
// /// </summary>
|
||||
// private void FillDatabase()
|
||||
// {
|
||||
// using DatabaseContext context = new(_context);
|
||||
// context.Shows.Add(new Show
|
||||
// {
|
||||
// ID = 67,
|
||||
// Slug = "anohana",
|
||||
// Title = "Anohana: The Flower We Saw That Day",
|
||||
// Aliases = new[]
|
||||
// {
|
||||
// "Ano Hi Mita Hana no Namae o Bokutachi wa Mada Shiranai.",
|
||||
// "AnoHana",
|
||||
// "We Still Don't Know the Name of the Flower We Saw That Day."
|
||||
// },
|
||||
// Overview = "When Yadomi Jinta was a child, he was a central piece in a group of close friends. " +
|
||||
// "In time, however, these childhood friends drifted apart, and when they became high " +
|
||||
// "school students, they had long ceased to think of each other as friends.",
|
||||
// Status = Status.Finished,
|
||||
// TrailerUrl = null,
|
||||
// StartYear = 2011,
|
||||
// EndYear = 2011,
|
||||
// Poster = "poster",
|
||||
// Logo = "logo",
|
||||
// Backdrop = "backdrop",
|
||||
// IsMovie = false,
|
||||
// Studio = null
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// /// <summary>
|
||||
// /// Get a new database context connected to a in memory Sqlite database.
|
||||
// /// </summary>
|
||||
// /// <returns>A valid DatabaseContext</returns>
|
||||
// public DatabaseContext New()
|
||||
// {
|
||||
// return new(_context);
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1 +1 @@
|
||||
Subproject commit da35a725a3e47db0994a697595aec4a10a4886e3
|
||||
Subproject commit 22a02671918201d6d9d4e80a76f01b59b216a82d
|
@ -3,11 +3,11 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kyoo - Login</title>
|
||||
<link rel="stylesheet" type="text/css" href="login/lib/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="login/login.css" />
|
||||
<link rel="stylesheet" type="text/css" href="login/material-icons.css" />
|
||||
<script src="login/lib/jquery.min.js"></script>
|
||||
<script src="login/lib/bootstrap.min.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="lib/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="login.css" />
|
||||
<link rel="stylesheet" type="text/css" href="material-icons.css" />
|
||||
<script src="lib/jquery.min.js"></script>
|
||||
<script src="lib/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body style="height: 100vh; align-items: center;" class="d-flex">
|
||||
<div class="container pb-5">
|
||||
@ -85,6 +85,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="login/login.js"></script>
|
||||
<script src="login.js"></script>
|
||||
</body>
|
||||
</html>
|
@ -89,7 +89,7 @@ $("#register-btn").on("click", function (e)
|
||||
{
|
||||
let error = $("#register-error");
|
||||
error.show();
|
||||
error.text(JSON.parse(xhr.responseText)[0].description);
|
||||
error.html(Object.values(JSON.parse(xhr.responseText).errors).map(x => x[0]).join("<br/>"));
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -101,7 +101,7 @@ function useOtac(otac)
|
||||
url: "/api/account/otac-login",
|
||||
type: "POST",
|
||||
contentType: 'application/json;charset=UTF-8',
|
||||
data: JSON.stringify({otac: otac, tayLoggedIn: $("#stay-logged-in")[0].checked}),
|
||||
data: JSON.stringify({otac: otac, stayLoggedIn: $("#stay-logged-in")[0].checked}),
|
||||
success: function()
|
||||
{
|
||||
let returnUrl = new URLSearchParams(window.location.search).get("ReturnUrl");
|
||||
|
12
Kyoo.sln
12
Kyoo.sln
@ -7,6 +7,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.CommonAPI", "Kyoo.Comm
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Tests", "Kyoo.Tests\Kyoo.Tests.csproj", "{D179D5FF-9F75-4B27-8E27-0DBDF1806611}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Postgresql", "Kyoo.Postgresql\Kyoo.Postgresql.csproj", "{3213C96D-0BF3-460B-A8B5-B9977229408A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kyoo.Authentication", "Kyoo.Authentication\Kyoo.Authentication.csproj", "{7A841335-6523-47DB-9717-80AA7BD943FD}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -29,5 +33,13 @@ Global
|
||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D179D5FF-9F75-4B27-8E27-0DBDF1806611}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3213C96D-0BF3-460B-A8B5-B9977229408A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7A841335-6523-47DB-9717-80AA7BD943FD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
@ -1,137 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.Extensions;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Operators;
|
||||
using Org.BouncyCastle.Math;
|
||||
using Org.BouncyCastle.Pkcs;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.Utilities;
|
||||
using Org.BouncyCastle.X509;
|
||||
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
public static class AuthExtension
|
||||
{
|
||||
private const string CertificateFile = "certificate.pfx";
|
||||
private const string OldCertificateFile = "oldCertificate.pfx";
|
||||
|
||||
public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, IConfiguration configuration)
|
||||
{
|
||||
X509Certificate2 certificate = GetSiginCredential(configuration);
|
||||
builder.AddSigningCredential(certificate);
|
||||
|
||||
if (certificate.NotAfter.AddDays(7) <= DateTime.UtcNow)
|
||||
{
|
||||
Console.WriteLine("Signin certificate will expire soon, renewing it.");
|
||||
if (File.Exists(OldCertificateFile))
|
||||
File.Delete(OldCertificateFile);
|
||||
File.Move(CertificateFile, OldCertificateFile);
|
||||
builder.AddValidationKey(GenerateCertificate(CertificateFile, configuration.GetValue<string>("certificatePassword")));
|
||||
}
|
||||
else if (File.Exists(OldCertificateFile))
|
||||
builder.AddValidationKey(GetExistingCredential(OldCertificateFile, configuration.GetValue<string>("certificatePassword")));
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static X509Certificate2 GetSiginCredential(IConfiguration configuration)
|
||||
{
|
||||
if (File.Exists(CertificateFile))
|
||||
return GetExistingCredential(CertificateFile, configuration.GetValue<string>("certificatePassword"));
|
||||
return GenerateCertificate(CertificateFile, configuration.GetValue<string>("certificatePassword"));
|
||||
}
|
||||
|
||||
private static X509Certificate2 GetExistingCredential(string file, string password)
|
||||
{
|
||||
return new X509Certificate2(file, password,
|
||||
X509KeyStorageFlags.MachineKeySet |
|
||||
X509KeyStorageFlags.PersistKeySet |
|
||||
X509KeyStorageFlags.Exportable
|
||||
);
|
||||
}
|
||||
|
||||
private static X509Certificate2 GenerateCertificate(string file, string password)
|
||||
{
|
||||
SecureRandom random = new SecureRandom();
|
||||
|
||||
X509V3CertificateGenerator certificateGenerator = new X509V3CertificateGenerator();
|
||||
certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random));
|
||||
certificateGenerator.SetIssuerDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
|
||||
certificateGenerator.SetSubjectDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
|
||||
certificateGenerator.SetNotBefore(DateTime.UtcNow.Date);
|
||||
certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddMonths(3));
|
||||
|
||||
KeyGenerationParameters keyGenerationParameters = new KeyGenerationParameters(random, 2048);
|
||||
RsaKeyPairGenerator keyPairGenerator = new RsaKeyPairGenerator();
|
||||
keyPairGenerator.Init(keyGenerationParameters);
|
||||
|
||||
AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
|
||||
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
|
||||
|
||||
AsymmetricCipherKeyPair issuerKeyPair = subjectKeyPair;
|
||||
const string signatureAlgorithm = "MD5WithRSA";
|
||||
Asn1SignatureFactory signatureFactory = new Asn1SignatureFactory(signatureAlgorithm, issuerKeyPair.Private);
|
||||
X509Certificate bouncyCert = certificateGenerator.Generate(signatureFactory);
|
||||
|
||||
X509Certificate2 certificate;
|
||||
|
||||
Pkcs12Store store = new Pkcs12StoreBuilder().Build();
|
||||
store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new [] {new X509CertificateEntry(bouncyCert)});
|
||||
|
||||
using MemoryStream pfxStream = new MemoryStream();
|
||||
store.Save(pfxStream, password.ToCharArray(), random);
|
||||
certificate = new X509Certificate2(pfxStream.ToArray(), password, X509KeyStorageFlags.Exportable);
|
||||
using FileStream fileStream = File.OpenWrite(file);
|
||||
pfxStream.WriteTo(fileStream);
|
||||
return certificate;
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthorizationValidatorHandler : AuthorizationHandler<AuthorizationValidator>
|
||||
{
|
||||
private readonly IConfiguration _configuration;
|
||||
|
||||
public AuthorizationValidatorHandler(IConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AuthorizationValidator requirement)
|
||||
{
|
||||
if (!context.User.IsAuthenticated())
|
||||
{
|
||||
string defaultPerms = _configuration.GetValue<string>("defaultPermissions");
|
||||
if (defaultPerms.Split(',').Contains(requirement.Permission.ToLower()))
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else
|
||||
{
|
||||
Claim perms = context.User.Claims.FirstOrDefault(x => x.Type == "permissions");
|
||||
if (perms != null && perms.Value.Split(",").Contains(requirement.Permission.ToLower()))
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
public class AuthorizationValidator : IAuthorizationRequirement
|
||||
{
|
||||
public string Permission;
|
||||
|
||||
public AuthorizationValidator(string permission)
|
||||
{
|
||||
Permission = permission;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,10 +8,22 @@ using Microsoft.AspNetCore.StaticFiles;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="IFileManager"/> for the local filesystem (using System.IO).
|
||||
/// </summary>
|
||||
public class FileManager : IFileManager
|
||||
{
|
||||
/// <summary>
|
||||
/// An extension provider to get content types from files extensions.
|
||||
/// </summary>
|
||||
private FileExtensionContentTypeProvider _provider;
|
||||
|
||||
/// <summary>
|
||||
/// Get the content type of a file using it's extension.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file</param>
|
||||
/// <exception cref="NotImplementedException">The extension of the file is not known.</exception>
|
||||
/// <returns>The content type of the file</returns>
|
||||
private string _GetContentType(string path)
|
||||
{
|
||||
if (_provider == null)
|
||||
@ -28,26 +40,36 @@ namespace Kyoo.Controllers
|
||||
throw new NotImplementedException($"Can't get the content type of the file at: {path}");
|
||||
}
|
||||
|
||||
// TODO add a way to force content type
|
||||
public IActionResult FileResult(string path, bool range)
|
||||
/// <inheritdoc />
|
||||
public IActionResult FileResult(string path, bool range = false, string type = null)
|
||||
{
|
||||
if (path == null)
|
||||
return new NotFoundResult();
|
||||
if (!File.Exists(path))
|
||||
return new NotFoundResult();
|
||||
return new PhysicalFileResult(Path.GetFullPath(path), _GetContentType(path))
|
||||
return new PhysicalFileResult(Path.GetFullPath(path), type ?? _GetContentType(path))
|
||||
{
|
||||
EnableRangeProcessing = range
|
||||
};
|
||||
}
|
||||
|
||||
public StreamReader GetReader(string path)
|
||||
/// <inheritdoc />
|
||||
public Stream GetReader(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return new StreamReader(path);
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Stream NewFile(string path)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
return File.Create(path);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ICollection<string>> ListFiles(string path)
|
||||
{
|
||||
if (path == null)
|
||||
@ -57,11 +79,13 @@ namespace Kyoo.Controllers
|
||||
: Array.Empty<string>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<bool> Exists(string path)
|
||||
{
|
||||
return Task.FromResult(File.Exists(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetExtraDirectory(Show show)
|
||||
{
|
||||
string path = Path.Combine(show.Path, "Extra");
|
||||
@ -69,6 +93,7 @@ namespace Kyoo.Controllers
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetExtraDirectory(Season season)
|
||||
{
|
||||
if (season.Show == null)
|
||||
@ -79,6 +104,7 @@ namespace Kyoo.Controllers
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetExtraDirectory(Episode episode)
|
||||
{
|
||||
string path = Path.Combine(Path.GetDirectoryName(episode.Path)!, "Extra");
|
||||
|
35
Kyoo/Controllers/PassthroughPermissionValidator.cs
Normal file
35
Kyoo/Controllers/PassthroughPermissionValidator.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using Kyoo.Models.Permissions;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A permission validator that always validate permissions. This effectively disable the permission system.
|
||||
/// </summary>
|
||||
public class PassthroughPermissionValidator : IPermissionValidator
|
||||
{
|
||||
// ReSharper disable once SuggestBaseTypeForParameter
|
||||
public PassthroughPermissionValidator(ILogger<PassthroughPermissionValidator> logger)
|
||||
{
|
||||
logger.LogWarning("No permission validator has been enabled, all users will have all permissions");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata Create(PermissionAttribute attribute)
|
||||
{
|
||||
return new PassthroughValidator();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IFilterMetadata Create(PartialPermissionAttribute attribute)
|
||||
{
|
||||
return new PassthroughValidator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An useless filter that does nothing.
|
||||
/// </summary>
|
||||
private class PassthroughValidator : IFilterMetadata { }
|
||||
}
|
||||
}
|
@ -4,100 +4,235 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Loader;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
public class PluginDependencyLoader : AssemblyLoadContext
|
||||
{
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
public PluginDependencyLoader(string pluginPath)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
return base.Load(assemblyName);
|
||||
}
|
||||
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
if (libraryPath != null)
|
||||
return LoadUnmanagedDllFromPath(libraryPath);
|
||||
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IPluginManager"/>.
|
||||
/// This is used to load plugins and retrieve information from them.
|
||||
/// </summary>
|
||||
public class PluginManager : IPluginManager
|
||||
{
|
||||
/// <summary>
|
||||
/// The service provider. It allow plugin's activation.
|
||||
/// </summary>
|
||||
private readonly IServiceProvider _provider;
|
||||
/// <summary>
|
||||
/// The configuration to get the plugin's directory.
|
||||
/// </summary>
|
||||
private readonly IConfiguration _config;
|
||||
private List<IPlugin> _plugins;
|
||||
/// <summary>
|
||||
/// The logger used by this class.
|
||||
/// </summary>
|
||||
private readonly ILogger<PluginManager> _logger;
|
||||
|
||||
public PluginManager(IServiceProvider provider, IConfiguration config)
|
||||
/// <summary>
|
||||
/// The list of plugins that are currently loaded.
|
||||
/// </summary>
|
||||
private readonly List<IPlugin> _plugins = new();
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PluginManager"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="provider">A service container to allow initialization of plugins</param>
|
||||
/// <param name="config">The configuration instance, to get the plugin's directory path.</param>
|
||||
/// <param name="logger">The logger used by this class.</param>
|
||||
public PluginManager(IServiceProvider provider,
|
||||
IConfiguration config,
|
||||
ILogger<PluginManager> logger)
|
||||
{
|
||||
_provider = provider;
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetPlugin<T>(string name)
|
||||
{
|
||||
return (T)_plugins?.FirstOrDefault(x => x.Name == name && x is T);
|
||||
}
|
||||
|
||||
public IEnumerable<T> GetPlugins<T>()
|
||||
/// <inheritdoc />
|
||||
public ICollection<T> GetPlugins<T>()
|
||||
{
|
||||
return _plugins?.OfType<T>() ?? new List<T>();
|
||||
return _plugins?.OfType<T>().ToArray();
|
||||
}
|
||||
|
||||
public IEnumerable<IPlugin> GetAllPlugins()
|
||||
/// <inheritdoc />
|
||||
public ICollection<IPlugin> GetAllPlugins()
|
||||
{
|
||||
return _plugins ?? new List<IPlugin>();
|
||||
return _plugins;
|
||||
}
|
||||
|
||||
public void ReloadPlugins()
|
||||
/// <summary>
|
||||
/// Load a single plugin and return all IPlugin implementations contained in the Assembly.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the dll</param>
|
||||
/// <returns>The list of dlls in hte assembly</returns>
|
||||
private IPlugin[] LoadPlugin(string path)
|
||||
{
|
||||
path = Path.GetFullPath(path);
|
||||
try
|
||||
{
|
||||
PluginDependencyLoader loader = new(path);
|
||||
Assembly assembly = loader.LoadFromAssemblyPath(path);
|
||||
return assembly.GetTypes()
|
||||
.Where(x => typeof(IPlugin).IsAssignableFrom(x))
|
||||
.Where(x => _plugins.All(y => y.GetType() != x))
|
||||
.Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x))
|
||||
.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Could not load the plugin at {Path}", path);
|
||||
return Array.Empty<IPlugin>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadPlugins(ICollection<IPlugin> plugins)
|
||||
{
|
||||
string pluginFolder = _config.GetValue<string>("plugins");
|
||||
if (!Directory.Exists(pluginFolder))
|
||||
Directory.CreateDirectory(pluginFolder);
|
||||
|
||||
string[] pluginsPaths = Directory.GetFiles(pluginFolder);
|
||||
_plugins = pluginsPaths.SelectMany(path =>
|
||||
_logger.LogTrace("Loading new plugins...");
|
||||
string[] pluginsPaths = Directory.GetFiles(pluginFolder, "*.dll", SearchOption.AllDirectories);
|
||||
plugins = plugins.Concat(pluginsPaths.SelectMany(LoadPlugin))
|
||||
.GroupBy(x => x.Name)
|
||||
.Select(x => x.First())
|
||||
.ToList();
|
||||
|
||||
ICollection<Type> available = GetProvidedTypes(plugins);
|
||||
_plugins.AddRange(plugins.Where(plugin =>
|
||||
{
|
||||
path = Path.GetFullPath(path);
|
||||
try
|
||||
{
|
||||
PluginDependencyLoader loader = new(path);
|
||||
Assembly ass = loader.LoadFromAssemblyPath(path);
|
||||
return ass.GetTypes()
|
||||
.Where(x => typeof(IPlugin).IsAssignableFrom(x))
|
||||
.Select(x => (IPlugin)ActivatorUtilities.CreateInstance(_provider, x));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine($"\nError loading the plugin at {path}.\n{ex.GetType().Name}: {ex.Message}\n");
|
||||
return Array.Empty<IPlugin>();
|
||||
}
|
||||
}).ToList();
|
||||
Type missing = plugin.Requires.FirstOrDefault(x => available.All(y => !y.IsAssignableTo(x)));
|
||||
if (missing == null)
|
||||
return true;
|
||||
|
||||
_logger.LogCritical("No {Dependency} available in Kyoo but the plugin {Plugin} requires it",
|
||||
missing.Name, plugin.Name);
|
||||
return false;
|
||||
}));
|
||||
|
||||
if (!_plugins.Any())
|
||||
_logger.LogInformation("No plugin enabled");
|
||||
else
|
||||
_logger.LogInformation("Plugin enabled: {Plugins}", _plugins.Select(x => x.Name));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
ICollection<Type> available = GetProvidedTypes(_plugins);
|
||||
foreach (IPlugin plugin in _plugins)
|
||||
plugin.Configure(services, available);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConfigureAspnet(IApplicationBuilder app)
|
||||
{
|
||||
foreach (IPlugin plugin in _plugins)
|
||||
plugin.ConfigureAspNet(app);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the list of types provided by the currently loaded plugins.
|
||||
/// </summary>
|
||||
/// <param name="plugins">The list of plugins that will be used as a plugin pool to get provided types.</param>
|
||||
/// <returns>The list of types available.</returns>
|
||||
private ICollection<Type> GetProvidedTypes(ICollection<IPlugin> plugins)
|
||||
{
|
||||
List<Type> available = plugins.SelectMany(x => x.Provides).ToList();
|
||||
List<ConditionalProvide> conditionals = plugins
|
||||
.SelectMany(x => x.ConditionalProvides)
|
||||
.Where(x => x.Condition.Condition())
|
||||
.ToList();
|
||||
|
||||
bool IsAvailable(ConditionalProvide conditional, bool log = false)
|
||||
{
|
||||
Console.WriteLine("\nNo plugin enabled.\n");
|
||||
return;
|
||||
if (!conditional.Condition.Condition())
|
||||
return false;
|
||||
|
||||
ICollection<Type> needed = conditional.Condition.Needed
|
||||
.Where(y => !available.Contains(y))
|
||||
.ToList();
|
||||
// TODO handle circular dependencies, actually it might stack overflow.
|
||||
needed = needed.Where(x => !conditionals
|
||||
.Where(y => y.Type == x)
|
||||
.Any(y => IsAvailable(y)))
|
||||
.ToList();
|
||||
if (!needed.Any())
|
||||
return true;
|
||||
if (log && available.All(x => x != conditional.Type))
|
||||
{
|
||||
_logger.LogWarning("The type {Type} is not available, {Dependencies} could not be met",
|
||||
conditional.Type.Name,
|
||||
needed.Select(x => x.Name));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLine("\nPlugin enabled:");
|
||||
foreach (IPlugin plugin in _plugins)
|
||||
Console.WriteLine($"\t{plugin.Name}");
|
||||
Console.WriteLine();
|
||||
// ReSharper disable once ForeachCanBeConvertedToQueryUsingAnotherGetEnumerator
|
||||
foreach (ConditionalProvide conditional in conditionals)
|
||||
{
|
||||
if (IsAvailable(conditional, true))
|
||||
available.Add(conditional.Type);
|
||||
}
|
||||
return available;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A custom <see cref="AssemblyLoadContext"/> to load plugin's dependency if they are on the same folder.
|
||||
/// </summary>
|
||||
private class PluginDependencyLoader : AssemblyLoadContext
|
||||
{
|
||||
/// <summary>
|
||||
/// The basic resolver that will be used to load dlls.
|
||||
/// </summary>
|
||||
private readonly AssemblyDependencyResolver _resolver;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PluginDependencyLoader"/> for the given path.
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">The path of the plugin and it's dependencies</param>
|
||||
public PluginDependencyLoader(string pluginPath)
|
||||
{
|
||||
_resolver = new AssemblyDependencyResolver(pluginPath);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Assembly Load(AssemblyName assemblyName)
|
||||
{
|
||||
Assembly existing = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.FirstOrDefault(x =>
|
||||
{
|
||||
AssemblyName name = x.GetName();
|
||||
return name.Name == assemblyName.Name && name.Version == assemblyName.Version;
|
||||
});
|
||||
if (existing != null)
|
||||
return existing;
|
||||
// TODO load the assembly from the common folder if the file exists (this would allow shared libraries)
|
||||
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
|
||||
if (assemblyPath != null)
|
||||
return LoadFromAssemblyPath(assemblyPath);
|
||||
return base.Load(assemblyName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
|
||||
{
|
||||
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
|
||||
if (libraryPath != null)
|
||||
return LoadUnmanagedDllFromPath(libraryPath);
|
||||
return base.LoadUnmanagedDll(unmanagedDllName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ namespace Kyoo.Controllers
|
||||
} catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync(
|
||||
$"The provider {provider.Provider.Name} coudln't work for {what}. Exception: {ex.Message}");
|
||||
$"The provider {provider.Provider.Name} could not work for {what}. Exception: {ex.Message}");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
|
@ -35,7 +35,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Collection>> Search(string query)
|
||||
{
|
||||
return await _database.Collections
|
||||
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
|
||||
.Where(_database.Like<Collection>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
|
@ -108,7 +108,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Episode ret = await GetOrDefault(showID, seasonNumber, episodeNumber);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No episode S{seasonNumber}E{episodeNumber} found on the show {showID}.");
|
||||
throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showID}.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Episode ret = await GetOrDefault(showSlug, seasonNumber, episodeNumber);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No episode S{seasonNumber}E{episodeNumber} found on the show {showSlug}.");
|
||||
throw new ItemNotFoundException($"No episode S{seasonNumber}E{episodeNumber} found on the show {showSlug}.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -156,7 +156,8 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Episode>> Search(string query)
|
||||
{
|
||||
List<Episode> episodes = await _database.Episodes
|
||||
.Where(x => EF.Functions.ILike(x.Title, $"%{query}%") && x.EpisodeNumber != -1)
|
||||
.Where(x => x.EpisodeNumber != -1)
|
||||
.Where(_database.Like<Episode>(x => x.Title, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -233,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;
|
||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Genre>> Search(string query)
|
||||
{
|
||||
return await _database.Genres
|
||||
.Where(genre => EF.Functions.ILike(genre.Name, $"%{query}%"))
|
||||
.Where(_database.Like<Genre>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
@ -20,10 +19,6 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
/// <summary>
|
||||
/// A provider repository to handle externalID creation and deletion
|
||||
/// </summary>
|
||||
private readonly IProviderRepository _providers;
|
||||
/// <summary>
|
||||
/// A lazy loaded library repository to validate queries (check if a library does exist)
|
||||
/// </summary>
|
||||
private readonly Lazy<ILibraryRepository> _libraries;
|
||||
@ -44,18 +39,19 @@ namespace Kyoo.Controllers
|
||||
/// Create a new <see cref="LibraryItemRepository"/>.
|
||||
/// </summary>
|
||||
/// <param name="database">The databse instance</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="services">A service provider to lazilly request a library, show or collection repository.</param>
|
||||
/// <param name="libraries">A lazy loaded library repository</param>
|
||||
/// <param name="shows">A lazy loaded show repository</param>
|
||||
/// <param name="collections">A lazy loaded collection repository</param>
|
||||
public LibraryItemRepository(DatabaseContext database,
|
||||
IProviderRepository providers,
|
||||
IServiceProvider services)
|
||||
Lazy<ILibraryRepository> libraries,
|
||||
Lazy<IShowRepository> shows,
|
||||
Lazy<ICollectionRepository> collections)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_libraries = new Lazy<ILibraryRepository>(services.GetRequiredService<ILibraryRepository>);
|
||||
_shows = new Lazy<IShowRepository>(services.GetRequiredService<IShowRepository>);
|
||||
_collections = new Lazy<ICollectionRepository>(services.GetRequiredService<ICollectionRepository>);
|
||||
_libraries = libraries;
|
||||
_shows = shows;
|
||||
_collections = collections;
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +101,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<LibraryItem>> Search(string query)
|
||||
{
|
||||
return await ItemsQuery
|
||||
.Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
|
||||
.Where(_database.Like<LibraryItem>(x => x.Title, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -115,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 />
|
||||
@ -158,7 +149,7 @@ namespace Kyoo.Controllers
|
||||
sort,
|
||||
limit);
|
||||
if (!items.Any() && await _libraries.Value.GetOrDefault(id) == null)
|
||||
throw new ItemNotFound();
|
||||
throw new ItemNotFoundException();
|
||||
return items;
|
||||
}
|
||||
|
||||
@ -173,7 +164,7 @@ namespace Kyoo.Controllers
|
||||
sort,
|
||||
limit);
|
||||
if (!items.Any() && await _libraries.Value.GetOrDefault(slug) == null)
|
||||
throw new ItemNotFound();
|
||||
throw new ItemNotFoundException();
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Library>> Search(string query)
|
||||
{
|
||||
return await _database.Libraries
|
||||
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
|
||||
.Where(_database.Like<Library>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
@ -36,15 +35,15 @@ namespace Kyoo.Controllers
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="services">A service provider to lazy load a show repository</param>
|
||||
/// <param name="shows">A lazy loaded show repository</param>
|
||||
public PeopleRepository(DatabaseContext database,
|
||||
IProviderRepository providers,
|
||||
IServiceProvider services)
|
||||
Lazy<IShowRepository> shows)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_shows = new Lazy<IShowRepository>(services.GetRequiredService<IShowRepository>);
|
||||
_shows = shows;
|
||||
}
|
||||
|
||||
|
||||
@ -52,7 +51,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<People>> Search(string query)
|
||||
{
|
||||
return await _database.People
|
||||
.Where(people => EF.Functions.ILike(people.Name, $"%{query}%"))
|
||||
.Where(_database.Like<People>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -74,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;
|
||||
});
|
||||
@ -130,8 +129,8 @@ namespace Kyoo.Controllers
|
||||
where,
|
||||
sort,
|
||||
limit);
|
||||
if (!people.Any() && await _shows.Value.Get(showID) == null)
|
||||
throw new ItemNotFound();
|
||||
if (!people.Any() && await _shows.Value.GetOrDefault(showID) == null)
|
||||
throw new ItemNotFoundException();
|
||||
foreach (PeopleRole role in people)
|
||||
role.ForPeople = true;
|
||||
return people;
|
||||
@ -152,8 +151,8 @@ namespace Kyoo.Controllers
|
||||
where,
|
||||
sort,
|
||||
limit);
|
||||
if (!people.Any() && await _shows.Value.Get(showSlug) == null)
|
||||
throw new ItemNotFound();
|
||||
if (!people.Any() && await _shows.Value.GetOrDefault(showSlug) == null)
|
||||
throw new ItemNotFoundException();
|
||||
foreach (PeopleRole role in people)
|
||||
role.ForPeople = true;
|
||||
return people;
|
||||
@ -173,8 +172,8 @@ namespace Kyoo.Controllers
|
||||
where,
|
||||
sort,
|
||||
limit);
|
||||
if (!roles.Any() && await Get(id) == null)
|
||||
throw new ItemNotFound();
|
||||
if (!roles.Any() && await GetOrDefault(id) == null)
|
||||
throw new ItemNotFoundException();
|
||||
return roles;
|
||||
}
|
||||
|
||||
@ -192,8 +191,8 @@ namespace Kyoo.Controllers
|
||||
where,
|
||||
sort,
|
||||
limit);
|
||||
if (!roles.Any() && await Get(slug) == null)
|
||||
throw new ItemNotFound();
|
||||
if (!roles.Any() && await GetOrDefault(slug) == null)
|
||||
throw new ItemNotFoundException();
|
||||
return roles;
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Provider>> Search(string query)
|
||||
{
|
||||
return await _database.Providers
|
||||
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
|
||||
.Where(_database.Like<Provider>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
|
@ -7,7 +7,6 @@ using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
@ -44,17 +43,17 @@ namespace Kyoo.Controllers
|
||||
/// <param name="database">The database handle that will be used</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="shows">A show repository</param>
|
||||
/// <param name="services">A service provider to lazilly request an episode repository.</param>
|
||||
/// <param name="episodes">A lazy loaded episode repository.</param>
|
||||
public SeasonRepository(DatabaseContext database,
|
||||
IProviderRepository providers,
|
||||
IShowRepository shows,
|
||||
IServiceProvider services)
|
||||
Lazy<IEpisodeRepository> episodes)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
_providers = providers;
|
||||
_shows = shows;
|
||||
_episodes = new Lazy<IEpisodeRepository>(services.GetRequiredService<IEpisodeRepository>);
|
||||
_episodes = episodes;
|
||||
}
|
||||
|
||||
|
||||
@ -89,7 +88,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Season ret = await GetOrDefault(showID, seasonNumber);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No season {seasonNumber} found for the show {showID}");
|
||||
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showID}");
|
||||
ret.ShowSlug = await _shows.GetSlug(showID);
|
||||
return ret;
|
||||
}
|
||||
@ -99,7 +98,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Season ret = await GetOrDefault(showSlug, seasonNumber);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No season {seasonNumber} found for the show {showSlug}");
|
||||
throw new ItemNotFoundException($"No season {seasonNumber} found for the show {showSlug}");
|
||||
ret.ShowSlug = showSlug;
|
||||
return ret;
|
||||
}
|
||||
@ -122,7 +121,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Season>> Search(string query)
|
||||
{
|
||||
List<Season> seasons = await _database.Seasons
|
||||
.Where(x => EF.Functions.ILike(x.Title, $"%{query}%"))
|
||||
.Where(_database.Like<Season>(x => x.Title, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -161,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;
|
||||
});
|
||||
|
@ -5,7 +5,6 @@ using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
@ -15,7 +14,7 @@ namespace Kyoo.Controllers
|
||||
public class ShowRepository : LocalRepository<Show>, IShowRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The databse handle
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
/// <summary>
|
||||
@ -54,13 +53,15 @@ namespace Kyoo.Controllers
|
||||
/// <param name="people">A people repository</param>
|
||||
/// <param name="genres">A genres repository</param>
|
||||
/// <param name="providers">A provider repository</param>
|
||||
/// <param name="services">A service provider to lazilly request a season and an episode repository</param>
|
||||
/// <param name="seasons">A lazy loaded season repository</param>
|
||||
/// <param name="episodes">A lazy loaded episode repository</param>
|
||||
public ShowRepository(DatabaseContext database,
|
||||
IStudioRepository studios,
|
||||
IPeopleRepository people,
|
||||
IGenreRepository genres,
|
||||
IProviderRepository providers,
|
||||
IServiceProvider services)
|
||||
Lazy<ISeasonRepository> seasons,
|
||||
Lazy<IEpisodeRepository> episodes)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
@ -68,8 +69,8 @@ namespace Kyoo.Controllers
|
||||
_people = people;
|
||||
_genres = genres;
|
||||
_providers = providers;
|
||||
_seasons = new Lazy<ISeasonRepository>(services.GetRequiredService<ISeasonRepository>);
|
||||
_episodes = new Lazy<IEpisodeRepository>(services.GetRequiredService<IEpisodeRepository>);
|
||||
_seasons = seasons;
|
||||
_episodes = episodes;
|
||||
}
|
||||
|
||||
|
||||
@ -78,9 +79,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
query = $"%{query}%";
|
||||
return await _database.Shows
|
||||
.Where(x => EF.Functions.ILike(x.Title, query)
|
||||
|| EF.Functions.ILike(x.Slug, query)
|
||||
/*|| x.Aliases.Any(y => EF.Functions.ILike(y, query))*/) // NOT TRANSLATABLE.
|
||||
.Where(_database.Like<Show>(x => x.Title + " " + x.Slug, query))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
@ -103,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;
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ namespace Kyoo.Controllers
|
||||
public override async Task<ICollection<Studio>> Search(string query)
|
||||
{
|
||||
return await _database.Studios
|
||||
.Where(x => EF.Functions.ILike(x.Name, $"%{query}%"))
|
||||
.Where(_database.Like<Studio>(x => x.Name, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
|
@ -46,7 +46,7 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
Track ret = await GetOrDefault(slug, type);
|
||||
if (ret == null)
|
||||
throw new ItemNotFound($"No track found with the slug {slug} and the type {type}.");
|
||||
throw new ItemNotFoundException($"No track found with the slug {slug} and the type {type}.");
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
64
Kyoo/Controllers/Repositories/UserRepository.cs
Normal file
64
Kyoo/Controllers/Repositories/UserRepository.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A repository for users.
|
||||
/// </summary>
|
||||
public class UserRepository : LocalRepository<User>, IUserRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// The database handle
|
||||
/// </summary>
|
||||
private readonly DatabaseContext _database;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Expression<Func<User, object>> DefaultSort => x => x.Username;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="UserRepository"/>
|
||||
/// </summary>
|
||||
/// <param name="database">The database handle to use</param>
|
||||
public UserRepository(DatabaseContext database)
|
||||
: base(database)
|
||||
{
|
||||
_database = database;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<ICollection<User>> Search(string query)
|
||||
{
|
||||
return await _database.Users
|
||||
.Where(_database.Like<User>(x => x.Username, $"%{query}%"))
|
||||
.OrderBy(DefaultSort)
|
||||
.Take(20)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<User> Create(User obj)
|
||||
{
|
||||
await base.Create(obj);
|
||||
_database.Entry(obj).State = EntityState.Added;
|
||||
await _database.SaveChangesAsync($"Trying to insert a duplicated user (slug {obj.Slug} already exists).");
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task Delete(User obj)
|
||||
{
|
||||
if (obj == null)
|
||||
throw new ArgumentNullException(nameof(obj));
|
||||
|
||||
_database.Entry(obj).State = EntityState.Deleted;
|
||||
await _database.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Models;
|
||||
using Kyoo.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using Kyoo.Models.Attributes;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
{
|
||||
/// <summary>
|
||||
/// A service to handle long running tasks and a background runner.
|
||||
/// </summary>
|
||||
/// <remarks>Task will be queued, only one can run simultaneously.</remarks>
|
||||
public class TaskManager : BackgroundService, ITaskManager
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IPluginManager _pluginManager;
|
||||
/// <summary>
|
||||
/// The service provider used to activate
|
||||
/// </summary>
|
||||
private readonly IServiceProvider _provider;
|
||||
/// <summary>
|
||||
/// The configuration instance used to get schedule information
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
/// <summary>
|
||||
/// The logger instance.
|
||||
/// </summary>
|
||||
private readonly ILogger<TaskManager> _logger;
|
||||
|
||||
private List<(ITask task, DateTime scheduledDate)> _tasks = new List<(ITask, DateTime)>();
|
||||
private CancellationTokenSource _taskToken = new CancellationTokenSource();
|
||||
/// <summary>
|
||||
/// The list of tasks and their next scheduled run.
|
||||
/// </summary>
|
||||
private readonly List<(ITask task, DateTime scheduledDate)> _tasks;
|
||||
/// <summary>
|
||||
/// The queue of tasks that should be run as soon as possible.
|
||||
/// </summary>
|
||||
private readonly Queue<(ITask, Dictionary<string, object>)> _queuedTasks = new();
|
||||
/// <summary>
|
||||
/// The currently running task.
|
||||
/// </summary>
|
||||
private ITask _runningTask;
|
||||
private Queue<(ITask, string)> _queuedTasks = new Queue<(ITask, string)>();
|
||||
/// <summary>
|
||||
/// The cancellation token used to cancel the running task when the runner should shutdown.
|
||||
/// </summary>
|
||||
private readonly CancellationTokenSource _taskToken = new();
|
||||
|
||||
public TaskManager(IServiceProvider serviceProvider, IPluginManager pluginManager, IConfiguration configuration)
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TaskManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="tasks">The list of tasks to manage</param>
|
||||
/// <param name="provider">The service provider to request services for tasks</param>
|
||||
/// <param name="configuration">The configuration to load schedule information.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
public TaskManager(IEnumerable<ITask> tasks,
|
||||
IServiceProvider provider,
|
||||
IConfiguration configuration,
|
||||
ILogger<TaskManager> logger)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_pluginManager = pluginManager;
|
||||
_configuration = configuration;
|
||||
_provider = provider;
|
||||
_configuration = configuration.GetSection("scheduledTasks");
|
||||
_logger = logger;
|
||||
_tasks = tasks.Select(x => (x, GetNextTaskDate(x.Slug))).ToList();
|
||||
|
||||
if (_tasks.Any())
|
||||
_logger.LogTrace("Task manager initiated with: {Tasks}", _tasks.Select(x => x.task.Name));
|
||||
else
|
||||
_logger.LogInformation("Task manager initiated without any tasks");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Triggered when the application host is ready to start the service.
|
||||
/// </summary>
|
||||
/// <remarks>Start the runner in another thread.</remarks>
|
||||
/// <param name="cancellationToken">Indicates that the start process has been aborted.</param>
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Task.Run(() => base.StartAsync(cancellationToken), CancellationToken.None);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_taskToken.Cancel();
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The runner that will host tasks and run queued tasks.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">A token to stop the runner</param>
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
ReloadTask();
|
||||
|
||||
IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
|
||||
.Where(x => x.RunOnStartup && x.Priority != Int32.MaxValue)
|
||||
.OrderByDescending(x => x.Priority);
|
||||
foreach (ITask task in startupTasks)
|
||||
_queuedTasks.Enqueue((task, null));
|
||||
EnqueueStartupTasks();
|
||||
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (_queuedTasks.Any())
|
||||
{
|
||||
(ITask task, string arguments) = _queuedTasks.Dequeue();
|
||||
(ITask task, Dictionary<string, object> arguments) = _queuedTasks.Dequeue();
|
||||
_runningTask = task;
|
||||
try
|
||||
{
|
||||
await task.Run(_serviceProvider, _taskToken.Token, arguments);
|
||||
await RunTask(task, arguments);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.Error.WriteLine($"An unhandled exception occured while running the task {task.Name}.\nInner exception: {e.Message}\n\n");
|
||||
_logger.LogError(e, "An unhandled exception occured while running the task {Task}", task.Name);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -61,67 +124,122 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse parameters, inject a task and run it.
|
||||
/// </summary>
|
||||
/// <param name="task">The task to run</param>
|
||||
/// <param name="arguments">The arguments to pass to the function</param>
|
||||
/// <exception cref="ArgumentException">There was an invalid argument or a required argument was not found.</exception>
|
||||
private async Task RunTask(ITask task, Dictionary<string, object> arguments)
|
||||
{
|
||||
_logger.LogInformation("Task starting: {Task}", task.Name);
|
||||
|
||||
ICollection<TaskParameter> all = task.GetParameters();
|
||||
|
||||
ICollection<string> invalids = arguments.Keys
|
||||
.Where(x => all.Any(y => x != y.Name))
|
||||
.ToArray();
|
||||
if (invalids.Any())
|
||||
{
|
||||
string invalidsStr = string.Join(", ", invalids);
|
||||
throw new ArgumentException($"{invalidsStr} are invalid arguments for the task {task.Name}");
|
||||
}
|
||||
|
||||
TaskParameters args = new(all
|
||||
.Select(x =>
|
||||
{
|
||||
object value = arguments
|
||||
.FirstOrDefault(y => string.Equals(y.Key, x.Name, StringComparison.OrdinalIgnoreCase))
|
||||
.Value;
|
||||
if (value == null && x.IsRequired)
|
||||
throw new ArgumentException($"The argument {x.Name} is required to run {task.Name}" +
|
||||
" but it was not specified.");
|
||||
return x.CreateValue(value ?? x.DefaultValue);
|
||||
}));
|
||||
|
||||
using IServiceScope scope = _provider.CreateScope();
|
||||
InjectServices(task, x => scope.ServiceProvider.GetRequiredService(x));
|
||||
await task.Run(args, _taskToken.Token);
|
||||
InjectServices(task, _ => null);
|
||||
_logger.LogInformation("Task finished: {Task}", task.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inject services into the <see cref="InjectedAttribute"/> marked properties of the given object.
|
||||
/// </summary>
|
||||
/// <param name="obj">The object to inject</param>
|
||||
/// <param name="retrieve">The function used to retrieve services. (The function is called immediately)</param>
|
||||
private static void InjectServices(ITask obj, [InstantHandle] Func<Type, object> retrieve)
|
||||
{
|
||||
IEnumerable<PropertyInfo> properties = obj.GetType().GetProperties()
|
||||
.Where(x => x.GetCustomAttribute<InjectedAttribute>() != null)
|
||||
.Where(x => x.CanWrite);
|
||||
|
||||
foreach (PropertyInfo property in properties)
|
||||
property.SetValue(obj, retrieve(property.PropertyType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start tasks that are scheduled for start.
|
||||
/// </summary>
|
||||
private void QueueScheduledTasks()
|
||||
{
|
||||
IEnumerable<string> tasksToQueue = _tasks.Where(x => x.scheduledDate <= DateTime.Now)
|
||||
.Select(x => x.task.Slug);
|
||||
foreach (string task in tasksToQueue)
|
||||
StartTask(task);
|
||||
{
|
||||
_logger.LogDebug("Queuing task scheduled for running: {Task}", task);
|
||||
StartTask(task, new Dictionary<string, object>());
|
||||
}
|
||||
}
|
||||
|
||||
public override Task StartAsync(CancellationToken cancellationToken)
|
||||
/// <summary>
|
||||
/// Queue startup tasks with respect to the priority rules.
|
||||
/// </summary>
|
||||
private void EnqueueStartupTasks()
|
||||
{
|
||||
Task.Run(() => base.StartAsync(cancellationToken));
|
||||
return Task.CompletedTask;
|
||||
IEnumerable<ITask> startupTasks = _tasks.Select(x => x.task)
|
||||
.Where(x => x.RunOnStartup)
|
||||
.OrderByDescending(x => x.Priority);
|
||||
foreach (ITask task in startupTasks)
|
||||
_queuedTasks.Enqueue((task, new Dictionary<string, object>()));
|
||||
}
|
||||
|
||||
public override Task StopAsync(CancellationToken cancellationToken)
|
||||
/// <inheritdoc />
|
||||
public void StartTask(string taskSlug, Dictionary<string, object> arguments = null)
|
||||
{
|
||||
_taskToken.Cancel();
|
||||
return base.StopAsync(cancellationToken);
|
||||
}
|
||||
arguments ??= new Dictionary<string, object>();
|
||||
|
||||
public bool StartTask(string taskSlug, string arguments = null)
|
||||
{
|
||||
int index = _tasks.FindIndex(x => x.task.Slug == taskSlug);
|
||||
if (index == -1)
|
||||
return false;
|
||||
throw new ItemNotFoundException($"No task found with the slug {taskSlug}");
|
||||
_queuedTasks.Enqueue((_tasks[index].task, arguments));
|
||||
_tasks[index] = (_tasks[index].task, DateTime.Now + GetTaskDelay(taskSlug));
|
||||
return true;
|
||||
_tasks[index] = (_tasks[index].task, GetNextTaskDate(taskSlug));
|
||||
}
|
||||
|
||||
public TimeSpan GetTaskDelay(string taskSlug)
|
||||
/// <summary>
|
||||
/// Get the next date of the execution of the given task.
|
||||
/// </summary>
|
||||
/// <param name="taskSlug">The slug of the task</param>
|
||||
/// <returns>The next date.</returns>
|
||||
private DateTime GetNextTaskDate(string taskSlug)
|
||||
{
|
||||
TimeSpan delay = _configuration.GetSection("scheduledTasks").GetValue<TimeSpan>(taskSlug);
|
||||
TimeSpan delay = _configuration.GetValue<TimeSpan>(taskSlug);
|
||||
if (delay == default)
|
||||
delay = TimeSpan.FromDays(365);
|
||||
return delay;
|
||||
return DateTime.MaxValue;
|
||||
return DateTime.Now + delay;
|
||||
}
|
||||
|
||||
public ITask GetRunningTask()
|
||||
/// <inheritdoc />
|
||||
public ICollection<ITask> GetRunningTasks()
|
||||
{
|
||||
return _runningTask;
|
||||
return new[] {_runningTask};
|
||||
}
|
||||
|
||||
public void ReloadTask()
|
||||
/// <inheritdoc />
|
||||
public ICollection<ITask> GetAllTasks()
|
||||
{
|
||||
_tasks.Clear();
|
||||
_tasks.AddRange(CoreTaskHolder.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))));
|
||||
|
||||
IEnumerable<ITask> prerunTasks = _tasks.Select(x => x.task)
|
||||
.Where(x => x.RunOnStartup && x.Priority == int.MaxValue);
|
||||
|
||||
foreach (ITask task in prerunTasks)
|
||||
task.Run(_serviceProvider, _taskToken.Token);
|
||||
foreach (IPlugin plugin in _pluginManager.GetAllPlugins())
|
||||
if (plugin.Tasks != null)
|
||||
_tasks.AddRange(plugin.Tasks.Select(x => (x, DateTime.Now + GetTaskDelay(x.Slug))));
|
||||
}
|
||||
|
||||
public IEnumerable<ITask> GetAllTasks()
|
||||
{
|
||||
return _tasks.Select(x => x.task);
|
||||
return _tasks.Select(x => x.task).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
103
Kyoo/CoreModule.cs
Normal file
103
Kyoo/CoreModule.cs
Normal file
@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models.Permissions;
|
||||
using Kyoo.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
/// <summary>
|
||||
/// The core module containing default implementations
|
||||
/// </summary>
|
||||
public class CoreModule : IPlugin
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Slug => "core";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name => "Core";
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description => "The core module containing default implementations.";
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Provides => new[]
|
||||
{
|
||||
typeof(IFileManager),
|
||||
typeof(ITranscoder),
|
||||
typeof(IThumbnailsManager),
|
||||
typeof(IProviderManager),
|
||||
typeof(ITaskManager),
|
||||
typeof(ILibraryManager)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<ConditionalProvide> ConditionalProvides => new ConditionalProvide[]
|
||||
{
|
||||
(typeof(ILibraryRepository), typeof(DatabaseContext)),
|
||||
(typeof(ILibraryItemRepository), typeof(DatabaseContext)),
|
||||
(typeof(ICollectionRepository), typeof(DatabaseContext)),
|
||||
(typeof(IShowRepository), typeof(DatabaseContext)),
|
||||
(typeof(ISeasonRepository), typeof(DatabaseContext)),
|
||||
(typeof(IEpisodeRepository), typeof(DatabaseContext)),
|
||||
(typeof(ITrackRepository), typeof(DatabaseContext)),
|
||||
(typeof(IPeopleRepository), typeof(DatabaseContext)),
|
||||
(typeof(IStudioRepository), typeof(DatabaseContext)),
|
||||
(typeof(IGenreRepository), typeof(DatabaseContext)),
|
||||
(typeof(IProviderRepository), typeof(DatabaseContext)),
|
||||
(typeof(IUserRepository), typeof(DatabaseContext))
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public ICollection<Type> Requires => new []
|
||||
{
|
||||
typeof(ILibraryRepository),
|
||||
typeof(ILibraryItemRepository),
|
||||
typeof(ICollectionRepository),
|
||||
typeof(IShowRepository),
|
||||
typeof(ISeasonRepository),
|
||||
typeof(IEpisodeRepository),
|
||||
typeof(ITrackRepository),
|
||||
typeof(IPeopleRepository),
|
||||
typeof(IStudioRepository),
|
||||
typeof(IGenreRepository),
|
||||
typeof(IProviderRepository)
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Configure(IServiceCollection services, ICollection<Type> availableTypes)
|
||||
{
|
||||
services.AddSingleton<IFileManager, FileManager>();
|
||||
services.AddSingleton<ITranscoder, Transcoder>();
|
||||
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
|
||||
services.AddSingleton<IProviderManager, ProviderManager>();
|
||||
services.AddSingleton<ITaskManager, TaskManager>();
|
||||
services.AddHostedService(x => x.GetService<ITaskManager>() as TaskManager);
|
||||
|
||||
services.AddScoped<ILibraryManager, LibraryManager>();
|
||||
|
||||
if (ProviderCondition.Has(typeof(DatabaseContext), availableTypes))
|
||||
{
|
||||
services.AddRepository<ILibraryRepository, LibraryRepository>();
|
||||
services.AddRepository<ILibraryItemRepository, LibraryItemRepository>();
|
||||
services.AddRepository<ICollectionRepository, CollectionRepository>();
|
||||
services.AddRepository<IShowRepository, ShowRepository>();
|
||||
services.AddRepository<ISeasonRepository, SeasonRepository>();
|
||||
services.AddRepository<IEpisodeRepository, EpisodeRepository>();
|
||||
services.AddRepository<ITrackRepository, TrackRepository>();
|
||||
services.AddRepository<IPeopleRepository, PeopleRepository>();
|
||||
services.AddRepository<IStudioRepository, StudioRepository>();
|
||||
services.AddRepository<IGenreRepository, GenreRepository>();
|
||||
services.AddRepository<IProviderRepository, ProviderRepository>();
|
||||
services.AddRepository<IUserRepository, UserRepository>();
|
||||
}
|
||||
|
||||
services.AddTask<Crawler>();
|
||||
|
||||
if (services.All(x => x.ServiceType != typeof(IPermissionValidator)))
|
||||
services.AddSingleton<IPermissionValidator, PassthroughPermissionValidator>();
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@
|
||||
<TypeScriptToolsVersion>Latest</TypeScriptToolsVersion>
|
||||
<IsPackable>false</IsPackable>
|
||||
<SpaRoot>../Kyoo.WebApp/</SpaRoot>
|
||||
<LoginRoot>../Kyoo.WebLogin/</LoginRoot>
|
||||
<TranscoderRoot>../Kyoo.Transcoder/</TranscoderRoot>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules/**</DefaultItemExcludes>
|
||||
|
||||
@ -35,36 +34,25 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Kyoo.Common/Kyoo.Common.csproj" />
|
||||
<ProjectReference Include="../Kyoo.CommonAPI/Kyoo.CommonAPI.csproj" />
|
||||
<PackageReference Include="IdentityServer4" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.EntityFramework.Storage" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.Storage" Version="4.1.1" />
|
||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="3.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2" />
|
||||
<PackageReference Include="Portable.BouncyCastle" Version="1.8.9" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.12" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="5.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices" Version="3.1.14" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SpaServices.Extensions" Version="5.0.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Kyoo.Postgresql/Kyoo.Postgresql.csproj">
|
||||
<!-- <ExcludeAssets>all</ExcludeAssets>-->
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="../Kyoo.Authentication/Kyoo.Authentication.csproj">
|
||||
<!-- <ExcludeAssets>all</ExcludeAssets>-->
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules/**" Visible="false" />
|
||||
<StaticFiles Include="$(SpaRoot)static/**" Visible="false" />
|
||||
<LoginFiles Include="$(LoginRoot)**" Visible="false" />
|
||||
<Content Remove="$(SpaRoot)**" />
|
||||
</ItemGroup>
|
||||
|
||||
@ -84,18 +72,13 @@
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<Target Name="Publish static and login" AfterTargets="ComputeFilesToPublish">
|
||||
<Target Name="Publish static files" AfterTargets="ComputeFilesToPublish">
|
||||
<ItemGroup>
|
||||
<ResolvedFileToPublish Include="@(StaticFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>wwwroot/%(StaticFiles.RecursiveDir)%(StaticFiles.Filename)%(StaticFiles.Extension)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
<ResolvedFileToPublish Include="@(LoginFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>login/%(LoginFiles.RecursiveDir)%(LoginFiles.Filename)%(LoginFiles.Extension)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
@ -103,9 +86,8 @@
|
||||
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Prepare static and login pages" AfterTargets="Build" Condition="$(Configuration) == 'Debug'">
|
||||
<Target Name="Prepare static files" AfterTargets="Build" Condition="$(Configuration) == 'Debug'">
|
||||
<Copy SourceFiles="@(StaticFiles)" DestinationFolder="$(OutputPath)/wwwroot/%(RecursiveDir)" />
|
||||
<Copy SourceFiles="@(LoginFiles)" DestinationFolder="$(OutputPath)/wwwroot/%(RecursiveDir)" />
|
||||
</Target>
|
||||
|
||||
<Target Name="Symlink views to output - Linux" AfterTargets="Build" Condition="$(Configuration) == 'Debug' And $(OS) == 'Unix'">
|
||||
@ -124,4 +106,25 @@
|
||||
<Visible>false</Visible>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<!--TODO remove this once plugins are reworked. This is useful because the authentication plugin is loaded manually and not by the plugin manager-->
|
||||
<PropertyGroup>
|
||||
<LoginRoot>../Kyoo.WebLogin/</LoginRoot>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<LoginFiles Include="$(LoginRoot)**" Visible="false" />
|
||||
</ItemGroup>
|
||||
<Target Name="Publish login files" AfterTargets="ComputeFilesToPublish">
|
||||
<ItemGroup>
|
||||
<ResolvedFileToPublish Include="@(LoginFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
|
||||
<RelativePath>login/%(LoginFiles.RecursiveDir)%(LoginFiles.Filename)%(LoginFiles.Extension)</RelativePath>
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
</ResolvedFileToPublish>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
<Target Name="Prepare static files" AfterTargets="Build" Condition="$(Configuration) == 'Debug'">
|
||||
<Copy SourceFiles="@(LoginFiles)" DestinationFolder="$(OutputPath)/login/%(RecursiveDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -1,985 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using IdentityServer4.EntityFramework.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration
|
||||
{
|
||||
[DbContext(typeof(ConfigurationDbContext))]
|
||||
[Migration("20210306161631_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AllowedAccessTokenSigningAlgorithms")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastAccessed")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiResources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Scope")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceSecrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Emphasize")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ScopeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ScopeId");
|
||||
|
||||
b.ToTable("ApiScopeClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<int>("ScopeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ScopeId");
|
||||
|
||||
b.ToTable("ApiScopeProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("AbsoluteRefreshTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AccessTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AccessTokenType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("AllowAccessTokensViaBrowser")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowOfflineAccess")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowPlainTextPkce")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowRememberConsent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("AllowedIdentityTokenSigningAlgorithms")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<bool>("AlwaysIncludeUserClaimsInIdToken")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AlwaysSendClientClaims")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("AuthorizationCodeLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("BackChannelLogoutSessionRequired")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("BackChannelLogoutUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<string>("ClientClaimsPrefix")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<int?>("ConsentLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<int>("DeviceCodeLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("EnableLocalLogin")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("FrontChannelLogoutSessionRequired")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FrontChannelLogoutUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<int>("IdentityTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("IncludeJwtId")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastAccessed")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("LogoUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("PairWiseSubjectSalt")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ProtocolType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int>("RefreshTokenExpiration")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("RefreshTokenUsage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("RequireClientSecret")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequireConsent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequirePkce")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequireRequestObject")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("SlidingRefreshTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("UpdateAccessTokenClaimsOnRefresh")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("UserCodeType")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<int?>("UserSsoLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Clients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Origin")
|
||||
.IsRequired()
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("character varying(150)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientCorsOrigins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("GrantType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientGrantTypes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Provider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientIdPRestrictions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("PostLogoutRedirectUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientPostLogoutRedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("RedirectUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientRedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Scope")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientSecrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Emphasize")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("IdentityResources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("IdentityResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IdentityResourceId");
|
||||
|
||||
b.ToTable("IdentityResourceClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("IdentityResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IdentityResourceId");
|
||||
|
||||
b.ToTable("IdentityResourceProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Scopes")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Secrets")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("ScopeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Scope");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ScopeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Scope");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("Claims")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedCorsOrigins")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedGrantTypes")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("IdentityProviderRestrictions")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("PostLogoutRedirectUris")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("RedirectUris")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedScopes")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("ClientSecrets")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("IdentityResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdentityResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("IdentityResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdentityResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("Scopes");
|
||||
|
||||
b.Navigation("Secrets");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
|
||||
{
|
||||
b.Navigation("AllowedCorsOrigins");
|
||||
|
||||
b.Navigation("AllowedGrantTypes");
|
||||
|
||||
b.Navigation("AllowedScopes");
|
||||
|
||||
b.Navigation("Claims");
|
||||
|
||||
b.Navigation("ClientSecrets");
|
||||
|
||||
b.Navigation("IdentityProviderRestrictions");
|
||||
|
||||
b.Navigation("PostLogoutRedirectUris");
|
||||
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("RedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,658 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration
|
||||
{
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiResources",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DisplayName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
AllowedAccessTokenSigningAlgorithms = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
ShowInDiscoveryDocument = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Updated = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
LastAccessed = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
NonEditable = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiResources", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiScopes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DisplayName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
Required = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Emphasize = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ShowInDiscoveryDocument = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiScopes", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Clients",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ClientId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
ProtocolType = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
RequireClientSecret = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ClientName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
ClientUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
LogoUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
RequireConsent = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AllowRememberConsent = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AlwaysIncludeUserClaimsInIdToken = table.Column<bool>(type: "boolean", nullable: false),
|
||||
RequirePkce = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AllowPlainTextPkce = table.Column<bool>(type: "boolean", nullable: false),
|
||||
RequireRequestObject = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AllowAccessTokensViaBrowser = table.Column<bool>(type: "boolean", nullable: false),
|
||||
FrontChannelLogoutUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
FrontChannelLogoutSessionRequired = table.Column<bool>(type: "boolean", nullable: false),
|
||||
BackChannelLogoutUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
BackChannelLogoutSessionRequired = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AllowOfflineAccess = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IdentityTokenLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
AllowedIdentityTokenSigningAlgorithms = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
AccessTokenLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
AuthorizationCodeLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
ConsentLifetime = table.Column<int>(type: "integer", nullable: true),
|
||||
AbsoluteRefreshTokenLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
SlidingRefreshTokenLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
RefreshTokenUsage = table.Column<int>(type: "integer", nullable: false),
|
||||
UpdateAccessTokenClaimsOnRefresh = table.Column<bool>(type: "boolean", nullable: false),
|
||||
RefreshTokenExpiration = table.Column<int>(type: "integer", nullable: false),
|
||||
AccessTokenType = table.Column<int>(type: "integer", nullable: false),
|
||||
EnableLocalLogin = table.Column<bool>(type: "boolean", nullable: false),
|
||||
IncludeJwtId = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AlwaysSendClientClaims = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ClientClaimsPrefix = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
PairWiseSubjectSalt = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Updated = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
LastAccessed = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
UserSsoLifetime = table.Column<int>(type: "integer", nullable: true),
|
||||
UserCodeType = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
DeviceCodeLifetime = table.Column<int>(type: "integer", nullable: false),
|
||||
NonEditable = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Clients", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IdentityResources",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Enabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DisplayName = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
Required = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Emphasize = table.Column<bool>(type: "boolean", nullable: false),
|
||||
ShowInDiscoveryDocument = table.Column<bool>(type: "boolean", nullable: false),
|
||||
Created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Updated = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
NonEditable = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IdentityResources", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiResourceClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ApiResourceId = table.Column<int>(type: "integer", nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiResourceClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiResourceClaims_ApiResources_ApiResourceId",
|
||||
column: x => x.ApiResourceId,
|
||||
principalTable: "ApiResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiResourceProperties",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ApiResourceId = table.Column<int>(type: "integer", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiResourceProperties", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiResourceProperties_ApiResources_ApiResourceId",
|
||||
column: x => x.ApiResourceId,
|
||||
principalTable: "ApiResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiResourceScopes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Scope = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
ApiResourceId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiResourceScopes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiResourceScopes_ApiResources_ApiResourceId",
|
||||
column: x => x.ApiResourceId,
|
||||
principalTable: "ApiResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiResourceSecrets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ApiResourceId = table.Column<int>(type: "integer", nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(1000)", maxLength: 1000, nullable: true),
|
||||
Value = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
Type = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiResourceSecrets", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiResourceSecrets_ApiResources_ApiResourceId",
|
||||
column: x => x.ApiResourceId,
|
||||
principalTable: "ApiResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiScopeClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ScopeId = table.Column<int>(type: "integer", nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiScopeClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiScopeClaims_ApiScopes_ScopeId",
|
||||
column: x => x.ScopeId,
|
||||
principalTable: "ApiScopes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiScopeProperties",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ScopeId = table.Column<int>(type: "integer", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ApiScopeProperties", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ApiScopeProperties_ApiScopes_ScopeId",
|
||||
column: x => x.ScopeId,
|
||||
principalTable: "ApiScopes",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Type = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientClaims_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientCorsOrigins",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Origin = table.Column<string>(type: "character varying(150)", maxLength: 150, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientCorsOrigins_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientGrantTypes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
GrantType = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientGrantTypes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientGrantTypes_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientIdPRestrictions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Provider = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientIdPRestrictions_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientPostLogoutRedirectUris",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
PostLogoutRedirectUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientProperties",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientProperties", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientProperties_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientRedirectUris",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
RedirectUri = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientRedirectUris", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientRedirectUris_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientScopes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Scope = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientScopes", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientScopes_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ClientSecrets",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ClientId = table.Column<int>(type: "integer", nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: true),
|
||||
Value = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
Type = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Created = table.Column<DateTime>(type: "timestamp without time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ClientSecrets", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ClientSecrets_Clients_ClientId",
|
||||
column: x => x.ClientId,
|
||||
principalTable: "Clients",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IdentityResourceClaims",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
IdentityResourceId = table.Column<int>(type: "integer", nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IdentityResourceClaims", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_IdentityResourceClaims_IdentityResources_IdentityResourceId",
|
||||
column: x => x.IdentityResourceId,
|
||||
principalTable: "IdentityResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "IdentityResourceProperties",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
IdentityResourceId = table.Column<int>(type: "integer", nullable: false),
|
||||
Key = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: false),
|
||||
Value = table.Column<string>(type: "character varying(2000)", maxLength: 2000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_IdentityResourceProperties", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_IdentityResourceProperties_IdentityResources_IdentityResour~",
|
||||
column: x => x.IdentityResourceId,
|
||||
principalTable: "IdentityResources",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiResourceClaims_ApiResourceId",
|
||||
table: "ApiResourceClaims",
|
||||
column: "ApiResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiResourceProperties_ApiResourceId",
|
||||
table: "ApiResourceProperties",
|
||||
column: "ApiResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiResources_Name",
|
||||
table: "ApiResources",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiResourceScopes_ApiResourceId",
|
||||
table: "ApiResourceScopes",
|
||||
column: "ApiResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiResourceSecrets_ApiResourceId",
|
||||
table: "ApiResourceSecrets",
|
||||
column: "ApiResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiScopeClaims_ScopeId",
|
||||
table: "ApiScopeClaims",
|
||||
column: "ScopeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiScopeProperties_ScopeId",
|
||||
table: "ApiScopeProperties",
|
||||
column: "ScopeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ApiScopes_Name",
|
||||
table: "ApiScopes",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientClaims_ClientId",
|
||||
table: "ClientClaims",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientCorsOrigins_ClientId",
|
||||
table: "ClientCorsOrigins",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientGrantTypes_ClientId",
|
||||
table: "ClientGrantTypes",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientIdPRestrictions_ClientId",
|
||||
table: "ClientIdPRestrictions",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientPostLogoutRedirectUris_ClientId",
|
||||
table: "ClientPostLogoutRedirectUris",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientProperties_ClientId",
|
||||
table: "ClientProperties",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientRedirectUris_ClientId",
|
||||
table: "ClientRedirectUris",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Clients_ClientId",
|
||||
table: "Clients",
|
||||
column: "ClientId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientScopes_ClientId",
|
||||
table: "ClientScopes",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ClientSecrets_ClientId",
|
||||
table: "ClientSecrets",
|
||||
column: "ClientId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IdentityResourceClaims_IdentityResourceId",
|
||||
table: "IdentityResourceClaims",
|
||||
column: "IdentityResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IdentityResourceProperties_IdentityResourceId",
|
||||
table: "IdentityResourceProperties",
|
||||
column: "IdentityResourceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_IdentityResources_Name",
|
||||
table: "IdentityResources",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiResourceClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiResourceProperties");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiResourceScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiResourceSecrets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiScopeClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiScopeProperties");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientCorsOrigins");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientGrantTypes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientIdPRestrictions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientPostLogoutRedirectUris");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientProperties");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientRedirectUris");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ClientSecrets");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IdentityResourceClaims");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IdentityResourceProperties");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiResources");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ApiScopes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Clients");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "IdentityResources");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,983 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using IdentityServer4.EntityFramework.DbContexts;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Models.DatabaseMigrations.IdentityConfiguration
|
||||
{
|
||||
[DbContext(typeof(ConfigurationDbContext))]
|
||||
partial class ConfigurationDbContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AllowedAccessTokenSigningAlgorithms")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastAccessed")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiResources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Scope")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ApiResourceId");
|
||||
|
||||
b.ToTable("ApiResourceSecrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Emphasize")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ApiScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ScopeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ScopeId");
|
||||
|
||||
b.ToTable("ApiScopeClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<int>("ScopeId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ScopeId");
|
||||
|
||||
b.ToTable("ApiScopeProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("AbsoluteRefreshTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AccessTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("AccessTokenType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("AllowAccessTokensViaBrowser")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowOfflineAccess")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowPlainTextPkce")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AllowRememberConsent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("AllowedIdentityTokenSigningAlgorithms")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<bool>("AlwaysIncludeUserClaimsInIdToken")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("AlwaysSendClientClaims")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("AuthorizationCodeLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("BackChannelLogoutSessionRequired")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("BackChannelLogoutUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<string>("ClientClaimsPrefix")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<int?>("ConsentLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<int>("DeviceCodeLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("EnableLocalLogin")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("FrontChannelLogoutSessionRequired")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("FrontChannelLogoutUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<int>("IdentityTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("IncludeJwtId")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("LastAccessed")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("LogoUri")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("PairWiseSubjectSalt")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ProtocolType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int>("RefreshTokenExpiration")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("RefreshTokenUsage")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("RequireClientSecret")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequireConsent")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequirePkce")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("RequireRequestObject")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("SlidingRefreshTokenLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("UpdateAccessTokenClaimsOnRefresh")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("UserCodeType")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<int?>("UserSsoLifetime")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Clients");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Origin")
|
||||
.IsRequired()
|
||||
.HasMaxLength(150)
|
||||
.HasColumnType("character varying(150)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientCorsOrigins");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("GrantType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientGrantTypes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Provider")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientIdPRestrictions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("PostLogoutRedirectUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientPostLogoutRedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("RedirectUri")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientRedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Scope")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientScopes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ClientId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("character varying(4000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ClientId");
|
||||
|
||||
b.ToTable("ClientSecrets");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("DisplayName")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("Emphasize")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<bool>("NonEditable")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("Required")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("ShowInDiscoveryDocument")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTime?>("Updated")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("IdentityResources");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("IdentityResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IdentityResourceId");
|
||||
|
||||
b.ToTable("IdentityResourceClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("IdentityResourceId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Key")
|
||||
.IsRequired()
|
||||
.HasMaxLength(250)
|
||||
.HasColumnType("character varying(250)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("character varying(2000)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("IdentityResourceId");
|
||||
|
||||
b.ToTable("IdentityResourceProperties");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceScope", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Scopes")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource")
|
||||
.WithMany("Secrets")
|
||||
.HasForeignKey("ApiResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("ApiResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("ScopeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Scope");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "Scope")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ScopeId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Scope");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("Claims")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedCorsOrigins")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedGrantTypes")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("IdentityProviderRestrictions")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("PostLogoutRedirectUris")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("RedirectUris")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("AllowedScopes")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client")
|
||||
.WithMany("ClientSecrets")
|
||||
.HasForeignKey("ClientId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Client");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceClaim", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
|
||||
.WithMany("UserClaims")
|
||||
.HasForeignKey("IdentityResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdentityResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResourceProperty", b =>
|
||||
{
|
||||
b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource")
|
||||
.WithMany("Properties")
|
||||
.HasForeignKey("IdentityResourceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("IdentityResource");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("Scopes");
|
||||
|
||||
b.Navigation("Secrets");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b =>
|
||||
{
|
||||
b.Navigation("AllowedCorsOrigins");
|
||||
|
||||
b.Navigation("AllowedGrantTypes");
|
||||
|
||||
b.Navigation("AllowedScopes");
|
||||
|
||||
b.Navigation("Claims");
|
||||
|
||||
b.Navigation("ClientSecrets");
|
||||
|
||||
b.Navigation("IdentityProviderRestrictions");
|
||||
|
||||
b.Navigation("PostLogoutRedirectUris");
|
||||
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("RedirectUris");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b =>
|
||||
{
|
||||
b.Navigation("Properties");
|
||||
|
||||
b.Navigation("UserClaims");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,384 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Kyoo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase
|
||||
{
|
||||
[DbContext(typeof(IdentityDatabase))]
|
||||
[Migration("20210216205030_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||
{
|
||||
b.Property<string>("UserCode")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("character varying(50000)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("DeviceCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("UserCode");
|
||||
|
||||
b.HasIndex("DeviceCode")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.ToTable("DeviceCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("ConsumedTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("character varying(50000)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.HasIndex("SubjectId", "ClientId", "Type");
|
||||
|
||||
b.HasIndex("SubjectId", "SessionId", "Type");
|
||||
|
||||
b.ToTable("PersistedGrants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("OTAC")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("OTACExpires")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("UserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoleClaim");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserClaim");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserLogin");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRole");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserToken");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,291 +0,0 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase
|
||||
{
|
||||
public partial class Initial : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DeviceCodes",
|
||||
columns: table => new
|
||||
{
|
||||
UserCode = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
DeviceCode = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
SubjectId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
SessionId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
ClientId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Data = table.Column<string>(type: "character varying(50000)", maxLength: 50000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DeviceCodes", x => x.UserCode);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PersistedGrants",
|
||||
columns: table => new
|
||||
{
|
||||
Key = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Type = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
SubjectId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
SessionId = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: true),
|
||||
ClientId = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: false),
|
||||
Expiration = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
ConsumedTime = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
Data = table.Column<string>(type: "character varying(50000)", maxLength: 50000, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PersistedGrants", x => x.Key);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "User",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
OTAC = table.Column<string>(type: "text", nullable: true),
|
||||
OTACExpires = table.Column<DateTime>(type: "timestamp without time zone", nullable: true),
|
||||
UserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
Email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "text", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_User", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserRoles", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserClaim",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "text", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserClaim", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserClaim_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserLogin",
|
||||
columns: table => new
|
||||
{
|
||||
LoginProvider = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
ProviderKey = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(type: "text", nullable: true),
|
||||
UserId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserLogin", x => new { x.LoginProvider, x.ProviderKey });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserLogin_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserToken",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
LoginProvider = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Name = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
||||
Value = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserToken", x => new { x.UserId, x.LoginProvider, x.Name });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserToken_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRole",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
RoleId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserRole", x => new { x.UserId, x.RoleId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRole_User_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "User",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRole_UserRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "UserRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserRoleClaim",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
RoleId = table.Column<string>(type: "text", nullable: false),
|
||||
ClaimType = table.Column<string>(type: "text", nullable: true),
|
||||
ClaimValue = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserRoleClaim", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserRoleClaim_UserRoles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalTable: "UserRoles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeviceCodes_DeviceCode",
|
||||
table: "DeviceCodes",
|
||||
column: "DeviceCode",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DeviceCodes_Expiration",
|
||||
table: "DeviceCodes",
|
||||
column: "Expiration");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_Expiration",
|
||||
table: "PersistedGrants",
|
||||
column: "Expiration");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_SubjectId_ClientId_Type",
|
||||
table: "PersistedGrants",
|
||||
columns: new[] { "SubjectId", "ClientId", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PersistedGrants_SubjectId_SessionId_Type",
|
||||
table: "PersistedGrants",
|
||||
columns: new[] { "SubjectId", "SessionId", "Type" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "EmailIndex",
|
||||
table: "User",
|
||||
column: "NormalizedEmail");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "UserNameIndex",
|
||||
table: "User",
|
||||
column: "NormalizedUserName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserClaim_UserId",
|
||||
table: "UserClaim",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserLogin_UserId",
|
||||
table: "UserLogin",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserRole_RoleId",
|
||||
table: "UserRole",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserRoleClaim_RoleId",
|
||||
table: "UserRoleClaim",
|
||||
column: "RoleId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "RoleNameIndex",
|
||||
table: "UserRoles",
|
||||
column: "NormalizedName",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DeviceCodes");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PersistedGrants");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserClaim");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserLogin");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRole");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRoleClaim");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserToken");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserRoles");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "User");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,382 +0,0 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Kyoo;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Kyoo.Kyoo.Models.DatabaseMigrations.IdentityDatbase
|
||||
{
|
||||
[DbContext(typeof(IdentityDatabase))]
|
||||
partial class IdentityDatabaseModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63)
|
||||
.HasAnnotation("ProductVersion", "5.0.3")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b =>
|
||||
{
|
||||
b.Property<string>("UserCode")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("character varying(50000)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("DeviceCode")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.HasKey("UserCode");
|
||||
|
||||
b.HasIndex("DeviceCode")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.ToTable("DeviceCodes");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b =>
|
||||
{
|
||||
b.Property<string>("Key")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("ClientId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("ConsumedTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<DateTime>("CreationTime")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("Data")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50000)
|
||||
.HasColumnType("character varying(50000)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<DateTime?>("Expiration")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("SessionId")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("character varying(50)");
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.HasIndex("Expiration");
|
||||
|
||||
b.HasIndex("SubjectId", "ClientId", "Type");
|
||||
|
||||
b.HasIndex("SubjectId", "SessionId", "Type");
|
||||
|
||||
b.ToTable("PersistedGrants");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kyoo.Models.User", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("OTAC")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("OTACExpires")
|
||||
.HasColumnType("timestamp without time zone");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("UserRoles");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoleClaim");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserClaim");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserLogin");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRole");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("character varying(128)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserToken");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Kyoo.Models.User", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.EntityFramework.Entities;
|
||||
using IdentityServer4.EntityFramework.Extensions;
|
||||
using IdentityServer4.EntityFramework.Interfaces;
|
||||
using IdentityServer4.EntityFramework.Options;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
// The configuration's database is named ConfigurationDbContext.
|
||||
public class IdentityDatabase : IdentityDbContext<User>, IPersistedGrantDbContext
|
||||
{
|
||||
private readonly IOptions<OperationalStoreOptions> _operationalStoreOptions;
|
||||
|
||||
public IdentityDatabase(DbContextOptions<IdentityDatabase> options, IOptions<OperationalStoreOptions> operationalStoreOptions)
|
||||
: base(options)
|
||||
{
|
||||
_operationalStoreOptions = operationalStoreOptions;
|
||||
}
|
||||
|
||||
public DbSet<User> Accounts { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
modelBuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value);
|
||||
|
||||
modelBuilder.Entity<User>().ToTable("User");
|
||||
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
|
||||
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
|
||||
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
|
||||
modelBuilder.Entity<IdentityRole>().ToTable("UserRoles");
|
||||
modelBuilder.Entity<IdentityRoleClaim<string>>().ToTable("UserRoleClaim");
|
||||
modelBuilder.Entity<IdentityUserToken<string>>().ToTable("UserToken");
|
||||
}
|
||||
|
||||
public Task<int> SaveChangesAsync() => base.SaveChangesAsync();
|
||||
|
||||
public DbSet<PersistedGrant> PersistedGrants { get; set; }
|
||||
public DbSet<DeviceFlowCodes> DeviceFlowCodes { get; set; }
|
||||
|
||||
}
|
||||
}
|
12
Kyoo/Models/LazyDi.cs
Normal file
12
Kyoo/Models/LazyDi.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
public class LazyDi<T> : Lazy<T>
|
||||
{
|
||||
public LazyDi(IServiceProvider provider)
|
||||
: base(provider.GetRequiredService<T>)
|
||||
{ }
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using IdentityModel;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Kyoo.Models
|
||||
{
|
||||
public class User : IdentityUser
|
||||
{
|
||||
public string OTAC { get; set; }
|
||||
public DateTime? OTACExpires { get; set; }
|
||||
|
||||
public string GenerateOTAC(TimeSpan validFor)
|
||||
{
|
||||
string otac = CryptoRandom.CreateUniqueId();
|
||||
string hashed = otac; // TODO should add a good hashing here.
|
||||
|
||||
OTAC = hashed;
|
||||
OTACExpires = DateTime.UtcNow.Add(validFor);
|
||||
return otac;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,12 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Hosting.StaticWebAssets;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Kyoo
|
||||
{
|
||||
/// <summary>
|
||||
@ -39,15 +37,23 @@ namespace Kyoo
|
||||
|
||||
if (debug == null && Environment.GetEnvironmentVariable("ENVIRONMENT") != null)
|
||||
Console.WriteLine($"Invalid ENVIRONMENT variable. Supported values are \"debug\" and \"prod\". Ignoring...");
|
||||
|
||||
#if DEBUG
|
||||
debug ??= true;
|
||||
#endif
|
||||
|
||||
Console.WriteLine($"Running as {Environment.UserName}.");
|
||||
IWebHostBuilder host = CreateWebHostBuilder(args);
|
||||
IWebHostBuilder builder = CreateWebHostBuilder(args);
|
||||
if (debug != null)
|
||||
host = host.UseEnvironment(debug == true ? "Development" : "Production");
|
||||
await host.Build().RunAsync();
|
||||
builder = builder.UseEnvironment(debug == true ? "Development" : "Production");
|
||||
try
|
||||
{
|
||||
await builder.Build().RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync($"Unhandled exception: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -64,14 +70,12 @@ namespace Kyoo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Createa a web host
|
||||
/// Create a a web host
|
||||
/// </summary>
|
||||
/// <param name="args">Command line parameters that can be handled by kestrel</param>
|
||||
/// <returns>A new web host instance</returns>
|
||||
private static IWebHostBuilder CreateWebHostBuilder(string[] args)
|
||||
{
|
||||
WebHost.CreateDefaultBuilder(args);
|
||||
|
||||
return new WebHostBuilder()
|
||||
.UseContentRoot(AppDomain.CurrentDomain.BaseDirectory)
|
||||
.UseConfiguration(SetupConfig(new ConfigurationBuilder(), args).Build())
|
||||
@ -79,7 +83,10 @@ namespace Kyoo
|
||||
.ConfigureLogging((context, builder) =>
|
||||
{
|
||||
builder.AddConfiguration(context.Configuration.GetSection("logging"))
|
||||
.AddConsole()
|
||||
.AddSimpleConsole(x =>
|
||||
{
|
||||
x.TimestampFormat = "[hh:mm:ss] ";
|
||||
})
|
||||
.AddDebug()
|
||||
.AddEventSourceLogger();
|
||||
})
|
||||
|
199
Kyoo/Startup.cs
199
Kyoo/Startup.cs
@ -1,20 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using IdentityServer4.Extensions;
|
||||
using IdentityServer4.Services;
|
||||
using Kyoo.Api;
|
||||
using Kyoo.Authentication;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Kyoo.Postgresql;
|
||||
using Kyoo.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.SpaServices.AngularCli;
|
||||
using Microsoft.AspNetCore.StaticFiles;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
@ -28,19 +22,47 @@ namespace Kyoo
|
||||
/// </summary>
|
||||
public class Startup
|
||||
{
|
||||
/// <summary>
|
||||
/// The configuration context
|
||||
/// </summary>
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
/// <summary>
|
||||
/// A plugin manager used to load plugins and allow them to configure services / asp net.
|
||||
/// </summary>
|
||||
private readonly IPluginManager _plugins;
|
||||
|
||||
|
||||
public Startup(IConfiguration configuration, ILoggerFactory loggerFactory)
|
||||
/// <summary>
|
||||
/// Created from the DI container, those services are needed to load information and instantiate plugins.s
|
||||
/// </summary>
|
||||
/// <param name="hostProvider">
|
||||
/// The ServiceProvider used to create this <see cref="Startup"/> instance.
|
||||
/// The host provider that contains only well-known services that are Kyoo independent.
|
||||
/// This is used to instantiate plugins that might need a logger, a configuration or an host environment.
|
||||
/// </param>
|
||||
/// <param name="configuration">The configuration context</param>
|
||||
/// <param name="loggerFactory">A logger factory used to create a logger for the plugin manager.</param>
|
||||
public Startup(IServiceProvider hostProvider, IConfiguration configuration, ILoggerFactory loggerFactory, IWebHostEnvironment host)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_loggerFactory = loggerFactory;
|
||||
_plugins = new PluginManager(hostProvider, _configuration, loggerFactory.CreateLogger<PluginManager>());
|
||||
|
||||
// TODO remove postgres from here and load it like a normal plugin.
|
||||
_plugins.LoadPlugins(new IPlugin[] {new CoreModule(),
|
||||
new PostgresModule(configuration, host),
|
||||
new AuthenticationModule(configuration, loggerFactory, host)
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the WebApp services context.
|
||||
/// </summary>
|
||||
/// <param name="services">The service collection to fill.</param>
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
string publicUrl = _configuration.GetValue<string>("public_url");
|
||||
string publicUrl = _configuration.GetValue<string>("publicUrl");
|
||||
|
||||
services.AddMvc().AddControllersAsServices();
|
||||
|
||||
services.AddSpaStaticFiles(configuration =>
|
||||
{
|
||||
@ -59,138 +81,22 @@ namespace Kyoo
|
||||
});
|
||||
services.AddHttpClient();
|
||||
|
||||
services.AddDbContext<DatabaseContext>(options =>
|
||||
{
|
||||
options.UseNpgsql(_configuration.GetDatabaseConnection());
|
||||
// .EnableSensitiveDataLogging()
|
||||
// .UseLoggerFactory(LoggerFactory.Create(builder => builder.AddConsole()));
|
||||
}, ServiceLifetime.Transient);
|
||||
services.AddTransient(typeof(Lazy<>), typeof(LazyDi<>));
|
||||
|
||||
services.AddDbContext<IdentityDatabase>(options =>
|
||||
{
|
||||
options.UseNpgsql(_configuration.GetDatabaseConnection());
|
||||
});
|
||||
|
||||
string assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
|
||||
|
||||
services.AddIdentityCore<User>(o =>
|
||||
{
|
||||
o.Stores.MaxLengthForKeys = 128;
|
||||
})
|
||||
.AddSignInManager()
|
||||
.AddDefaultTokenProviders()
|
||||
.AddEntityFrameworkStores<IdentityDatabase>();
|
||||
|
||||
services.AddIdentityServer(options =>
|
||||
{
|
||||
options.IssuerUri = publicUrl;
|
||||
options.UserInteraction.LoginUrl = publicUrl + "login";
|
||||
options.UserInteraction.ErrorUrl = publicUrl + "error";
|
||||
options.UserInteraction.LogoutUrl = publicUrl + "logout";
|
||||
})
|
||||
.AddAspNetIdentity<User>()
|
||||
.AddConfigurationStore(options =>
|
||||
{
|
||||
options.ConfigureDbContext = builder =>
|
||||
builder.UseNpgsql(_configuration.GetDatabaseConnection(),
|
||||
sql => sql.MigrationsAssembly(assemblyName));
|
||||
})
|
||||
.AddOperationalStore(options =>
|
||||
{
|
||||
options.ConfigureDbContext = builder =>
|
||||
builder.UseNpgsql(_configuration.GetDatabaseConnection(),
|
||||
sql => sql.MigrationsAssembly(assemblyName));
|
||||
options.EnableTokenCleanup = true;
|
||||
})
|
||||
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
|
||||
.AddInMemoryApiScopes(IdentityContext.GetScopes())
|
||||
.AddInMemoryApiResources(IdentityContext.GetApis())
|
||||
.AddProfileService<AccountController>()
|
||||
.AddSigninKeys(_configuration);
|
||||
|
||||
services.AddAuthentication(o =>
|
||||
{
|
||||
o.DefaultScheme = IdentityConstants.ApplicationScheme;
|
||||
o.DefaultSignInScheme = IdentityConstants.ExternalScheme;
|
||||
})
|
||||
.AddIdentityCookies(_ => { });
|
||||
services.AddAuthentication()
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.Authority = publicUrl;
|
||||
options.Audience = "Kyoo";
|
||||
options.RequireHttpsMetadata = false;
|
||||
});
|
||||
|
||||
services.AddAuthorization(options =>
|
||||
{
|
||||
AuthorizationPolicyBuilder scheme = new(IdentityConstants.ApplicationScheme, JwtBearerDefaults.AuthenticationScheme);
|
||||
options.DefaultPolicy = scheme.RequireAuthenticatedUser().Build();
|
||||
|
||||
string[] permissions = {"Read", "Write", "Play", "Admin"};
|
||||
foreach (string permission in permissions)
|
||||
{
|
||||
options.AddPolicy(permission, policy =>
|
||||
{
|
||||
policy.AuthenticationSchemes.Add(IdentityConstants.ApplicationScheme);
|
||||
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
|
||||
policy.AddRequirements(new AuthorizationValidator(permission));
|
||||
// policy.RequireScope($"kyoo.{permission.ToLower()}");
|
||||
});
|
||||
}
|
||||
});
|
||||
services.AddSingleton<IAuthorizationHandler, AuthorizationValidatorHandler>();
|
||||
|
||||
services.AddSingleton<ICorsPolicyService>(new DefaultCorsPolicyService(_loggerFactory.CreateLogger<DefaultCorsPolicyService>())
|
||||
{
|
||||
AllowedOrigins = { new Uri(publicUrl).GetLeftPart(UriPartial.Authority) }
|
||||
});
|
||||
|
||||
|
||||
// TODO Add custom method to the service container and expose those methods to the plugin
|
||||
// TODO Add for example a AddRepository that will automatically register the complex interface, the IRepository<T> and the IBaseRepository
|
||||
services.AddScoped<IBaseRepository, LibraryRepository>();
|
||||
services.AddScoped<IBaseRepository, LibraryItemRepository>();
|
||||
services.AddScoped<IBaseRepository, CollectionRepository>();
|
||||
services.AddScoped<IBaseRepository, ShowRepository>();
|
||||
services.AddScoped<IBaseRepository, SeasonRepository>();
|
||||
services.AddScoped<IBaseRepository, EpisodeRepository>();
|
||||
services.AddScoped<IBaseRepository, TrackRepository>();
|
||||
services.AddScoped<IBaseRepository, PeopleRepository>();
|
||||
services.AddScoped<IBaseRepository, StudioRepository>();
|
||||
services.AddScoped<IBaseRepository, GenreRepository>();
|
||||
services.AddScoped<IBaseRepository, ProviderRepository>();
|
||||
|
||||
services.AddScoped<ILibraryRepository, LibraryRepository>();
|
||||
services.AddScoped<ILibraryItemRepository, LibraryItemRepository>();
|
||||
services.AddScoped<ICollectionRepository, CollectionRepository>();
|
||||
services.AddScoped<IShowRepository, ShowRepository>();
|
||||
services.AddScoped<ISeasonRepository, SeasonRepository>();
|
||||
services.AddScoped<IEpisodeRepository, EpisodeRepository>();
|
||||
services.AddScoped<ITrackRepository, TrackRepository>();
|
||||
services.AddScoped<IPeopleRepository, PeopleRepository>();
|
||||
services.AddScoped<IStudioRepository, StudioRepository>();
|
||||
services.AddScoped<IGenreRepository, GenreRepository>();
|
||||
services.AddScoped<IProviderRepository, ProviderRepository>();
|
||||
services.AddScoped<DbContext, DatabaseContext>();
|
||||
|
||||
services.AddScoped<ILibraryManager, LibraryManager>();
|
||||
services.AddSingleton<IFileManager, FileManager>();
|
||||
services.AddSingleton<ITranscoder, Transcoder>();
|
||||
services.AddSingleton<IThumbnailsManager, ThumbnailsManager>();
|
||||
services.AddSingleton<IProviderManager, ProviderManager>();
|
||||
services.AddSingleton<IPluginManager, PluginManager>();
|
||||
services.AddSingleton<ITaskManager, TaskManager>();
|
||||
|
||||
services.AddHostedService(provider => (TaskManager)provider.GetService<ITaskManager>());
|
||||
services.AddSingleton(_plugins);
|
||||
services.AddTask<PluginInitializer>();
|
||||
_plugins.ConfigureServices(services);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configure the asp net host.
|
||||
/// </summary>
|
||||
/// <param name="app">The asp net host to configure</param>
|
||||
/// <param name="env">The host environment (is the app in development mode?)</param>
|
||||
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
app.UseExceptionHandler("/Error");
|
||||
@ -222,24 +128,15 @@ namespace Kyoo
|
||||
return next();
|
||||
});
|
||||
app.UseResponseCompression();
|
||||
app.UseCookiePolicy(new CookiePolicyOptions
|
||||
{
|
||||
MinimumSameSitePolicy = SameSiteMode.Strict
|
||||
});
|
||||
app.UseAuthentication();
|
||||
app.Use((ctx, next) =>
|
||||
{
|
||||
ctx.SetIdentityServerOrigin(_configuration.GetValue<string>("public_url"));
|
||||
return next();
|
||||
});
|
||||
app.UseIdentityServer();
|
||||
app.UseAuthorization();
|
||||
|
||||
_plugins.ConfigureAspnet(app);
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllerRoute("Kyoo", "api/{controller=Home}/{action=Index}/{id?}");
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
|
||||
app.UseSpa(spa =>
|
||||
{
|
||||
spa.Options.SourcePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "Kyoo.WebApp");
|
||||
|
@ -1,18 +0,0 @@
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public static class CoreTaskHolder
|
||||
{
|
||||
public static readonly ITask[] Tasks =
|
||||
{
|
||||
new CreateDatabase(),
|
||||
new PluginLoader(),
|
||||
new Crawler(),
|
||||
new MetadataProviderLoader(),
|
||||
// new ReScan(),
|
||||
new ExtractMetadata()
|
||||
};
|
||||
}
|
||||
}
|
@ -7,10 +7,12 @@ using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models.Attributes;
|
||||
using Kyoo.Models.Exceptions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Controllers
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class Crawler : ITask
|
||||
{
|
||||
@ -21,19 +23,20 @@ namespace Kyoo.Controllers
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => 0;
|
||||
|
||||
private IServiceProvider _serviceProvider;
|
||||
private IThumbnailsManager _thumbnailsManager;
|
||||
private IProviderManager _metadataProvider;
|
||||
private ITranscoder _transcoder;
|
||||
private IConfiguration _config;
|
||||
[Injected] public IServiceProvider ServiceProvider { private get; set; }
|
||||
[Injected] public IThumbnailsManager ThumbnailsManager { private get; set; }
|
||||
[Injected] public IProviderManager MetadataProvider { private get; set; }
|
||||
[Injected] public ITranscoder Transcoder { private get; set; }
|
||||
[Injected] public IConfiguration Config { private get; set; }
|
||||
|
||||
private int _parallelTasks;
|
||||
|
||||
public async Task<IEnumerable<string>> GetPossibleParameters()
|
||||
public TaskParameters GetParameters()
|
||||
{
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
return (await libraryManager!.GetAll<Library>()).Select(x => x.Slug);
|
||||
return new()
|
||||
{
|
||||
TaskParameter.Create<string>("slug", "A library slug to restrict the scan to this library.")
|
||||
};
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
@ -42,20 +45,16 @@ namespace Kyoo.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task Run(IServiceProvider serviceProvider,
|
||||
CancellationToken cancellationToken,
|
||||
string argument = null)
|
||||
public async Task Run(TaskParameters parameters,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_thumbnailsManager = serviceProvider.GetService<IThumbnailsManager>();
|
||||
_metadataProvider = serviceProvider.GetService<IProviderManager>();
|
||||
_transcoder = serviceProvider.GetService<ITranscoder>();
|
||||
_config = serviceProvider.GetService<IConfiguration>();
|
||||
_parallelTasks = _config.GetValue<int>("parallelTasks");
|
||||
string argument = parameters["slug"].As<string>();
|
||||
|
||||
_parallelTasks = Config.GetValue<int>("parallelTasks");
|
||||
if (_parallelTasks <= 0)
|
||||
_parallelTasks = 30;
|
||||
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
foreach (Show show in await libraryManager!.GetAll<Show>())
|
||||
@ -74,7 +73,7 @@ namespace Kyoo.Controllers
|
||||
|
||||
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}");
|
||||
@ -148,10 +147,10 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
if (token.IsCancellationRequested || path.Split(Path.DirectorySeparatorChar).Contains("Subtitles"))
|
||||
return;
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
string patern = _config.GetValue<string>("subtitleRegex");
|
||||
string patern = Config.GetValue<string>("subtitleRegex");
|
||||
Regex regex = new(patern, RegexOptions.IgnoreCase);
|
||||
Match match = regex.Match(path);
|
||||
|
||||
@ -178,7 +177,7 @@ namespace Kyoo.Controllers
|
||||
await libraryManager.Create(track);
|
||||
Console.WriteLine($"Registering subtitle at: {path}.");
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
await Console.Error.WriteLineAsync($"No episode found for subtitle at: ${path}.");
|
||||
}
|
||||
@ -195,10 +194,10 @@ namespace Kyoo.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
using IServiceScope serviceScope = _serviceProvider.CreateScope();
|
||||
using IServiceScope serviceScope = ServiceProvider.CreateScope();
|
||||
ILibraryManager libraryManager = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
|
||||
string patern = _config.GetValue<string>("regex");
|
||||
string patern = Config.GetValue<string>("regex");
|
||||
Regex regex = new(patern, RegexOptions.IgnoreCase);
|
||||
Match match = regex.Match(relativePath);
|
||||
|
||||
@ -254,10 +253,10 @@ namespace Kyoo.Controllers
|
||||
{
|
||||
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);
|
||||
collection = await MetadataProvider.GetCollectionFromName(collectionName, library);
|
||||
|
||||
try
|
||||
{
|
||||
@ -266,7 +265,7 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
catch (DuplicatedItemException)
|
||||
{
|
||||
return await libraryManager.Get<Collection>(collection.Slug);
|
||||
return await libraryManager.GetOrDefault<Collection>(collection.Slug);
|
||||
}
|
||||
}
|
||||
|
||||
@ -276,15 +275,15 @@ namespace Kyoo.Controllers
|
||||
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);
|
||||
return old;
|
||||
}
|
||||
Show show = await _metadataProvider.SearchShow(showTitle, isMovie, library);
|
||||
Show show = await MetadataProvider.SearchShow(showTitle, isMovie, library);
|
||||
show.Path = showPath;
|
||||
show.People = await _metadataProvider.GetPeople(show, library);
|
||||
show.People = await MetadataProvider.GetPeople(show, library);
|
||||
|
||||
try
|
||||
{
|
||||
@ -292,7 +291,7 @@ namespace Kyoo.Controllers
|
||||
}
|
||||
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);
|
||||
@ -301,7 +300,7 @@ namespace Kyoo.Controllers
|
||||
show.Slug += $"-{show.StartYear}";
|
||||
await libraryManager.Create(show);
|
||||
}
|
||||
await _thumbnailsManager.Validate(show);
|
||||
await ThumbnailsManager.Validate(show);
|
||||
return show;
|
||||
}
|
||||
|
||||
@ -318,11 +317,18 @@ namespace Kyoo.Controllers
|
||||
season.Show = show;
|
||||
return season;
|
||||
}
|
||||
catch (ItemNotFound)
|
||||
catch (ItemNotFoundException)
|
||||
{
|
||||
Season season = await _metadataProvider.GetSeason(show, seasonNumber, library);
|
||||
await libraryManager.CreateIfNotExists(season);
|
||||
await _thumbnailsManager.Validate(season);
|
||||
Season season = await MetadataProvider.GetSeason(show, seasonNumber, library);
|
||||
try
|
||||
{
|
||||
await libraryManager.Create(season);
|
||||
await ThumbnailsManager.Validate(season);
|
||||
}
|
||||
catch (DuplicatedItemException)
|
||||
{
|
||||
season = await libraryManager.Get(show.Slug, seasonNumber);
|
||||
}
|
||||
season.Show = show;
|
||||
return season;
|
||||
}
|
||||
@ -336,7 +342,7 @@ namespace Kyoo.Controllers
|
||||
string episodePath,
|
||||
Library library)
|
||||
{
|
||||
Episode episode = await _metadataProvider.GetEpisode(show,
|
||||
Episode episode = await MetadataProvider.GetEpisode(show,
|
||||
episodePath,
|
||||
season?.SeasonNumber ?? -1,
|
||||
episodeNumber,
|
||||
@ -346,7 +352,7 @@ namespace Kyoo.Controllers
|
||||
season ??= await GetSeason(libraryManager, show, episode.SeasonNumber, library);
|
||||
episode.Season = season;
|
||||
episode.SeasonID = season?.ID;
|
||||
await _thumbnailsManager.Validate(episode);
|
||||
await ThumbnailsManager.Validate(episode);
|
||||
await GetTracks(episode);
|
||||
return episode;
|
||||
}
|
||||
@ -367,7 +373,7 @@ namespace Kyoo.Controllers
|
||||
|
||||
private async Task<ICollection<Track>> GetTracks(Episode episode)
|
||||
{
|
||||
episode.Tracks = (await _transcoder.ExtractInfos(episode, false))
|
||||
episode.Tracks = (await Transcoder.ExtractInfos(episode, false))
|
||||
.Where(x => x.Type != StreamType.Attachment)
|
||||
.ToArray();
|
||||
return episode.Tracks;
|
||||
|
@ -1,66 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using IdentityServer4.EntityFramework.DbContexts;
|
||||
using IdentityServer4.EntityFramework.Mappers;
|
||||
using IdentityServer4.Models;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class CreateDatabase : ITask
|
||||
{
|
||||
public string Slug => "create-database";
|
||||
public string Name => "Create the database";
|
||||
public string Description => "Create the database if it does not exit and initialize it with defaults value.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => true;
|
||||
public int Priority => int.MaxValue;
|
||||
|
||||
public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
{
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>();
|
||||
IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>();
|
||||
ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
|
||||
|
||||
databaseContext!.Database.Migrate();
|
||||
identityDatabase!.Database.Migrate();
|
||||
identityContext!.Database.Migrate();
|
||||
|
||||
if (!identityContext.Clients.Any())
|
||||
{
|
||||
foreach (Client client in IdentityContext.GetClients())
|
||||
identityContext.Clients.Add(client.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
if (!identityContext.IdentityResources.Any())
|
||||
{
|
||||
foreach (IdentityResource resource in IdentityContext.GetIdentityResources())
|
||||
identityContext.IdentityResources.Add(resource.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
if (!identityContext.ApiResources.Any())
|
||||
{
|
||||
foreach (ApiResource resource in IdentityContext.GetApis())
|
||||
identityContext.ApiResources.Add(resource.ToEntity());
|
||||
identityContext.SaveChanges();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using IdentityServer4.EntityFramework.DbContexts;
|
||||
// using IdentityServer4.EntityFramework.Mappers;
|
||||
// using IdentityServer4.Models;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.EntityFrameworkCore;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class CreateDatabase : ITask
|
||||
// {
|
||||
// public string Slug => "create-database";
|
||||
// public string Name => "Create the database";
|
||||
// public string Description => "Create the database if it does not exit and initialize it with defaults value.";
|
||||
// public string HelpMessage => null;
|
||||
// public bool RunOnStartup => true;
|
||||
// public int Priority => int.MaxValue;
|
||||
//
|
||||
// public Task Run(IServiceProvider serviceProvider, CancellationToken cancellationToken, string arguments = null)
|
||||
// {
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// DatabaseContext databaseContext = serviceScope.ServiceProvider.GetService<DatabaseContext>();
|
||||
// IdentityDatabase identityDatabase = serviceScope.ServiceProvider.GetService<IdentityDatabase>();
|
||||
// ConfigurationDbContext identityContext = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
|
||||
//
|
||||
// databaseContext!.Database.Migrate();
|
||||
// identityDatabase!.Database.Migrate();
|
||||
// identityContext!.Database.Migrate();
|
||||
//
|
||||
// if (!identityContext.Clients.Any())
|
||||
// {
|
||||
// foreach (Client client in IdentityContext.GetClients())
|
||||
// identityContext.Clients.Add(client.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// if (!identityContext.IdentityResources.Any())
|
||||
// {
|
||||
// foreach (IdentityResource resource in IdentityContext.GetIdentityResources())
|
||||
// identityContext.IdentityResources.Add(resource.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// if (!identityContext.ApiResources.Any())
|
||||
// {
|
||||
// foreach (ApiResource resource in IdentityContext.GetApis())
|
||||
// identityContext.ApiResources.Add(resource.ToEntity());
|
||||
// identityContext.SaveChanges();
|
||||
// }
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,120 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Kyoo.Controllers;
|
||||
using Kyoo.Models;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Kyoo.Tasks
|
||||
{
|
||||
public class ExtractMetadata : ITask
|
||||
{
|
||||
public string Slug => "extract";
|
||||
public string Name => "Metadata Extractor";
|
||||
public string Description => "Extract subtitles or download thumbnails for a show/episode.";
|
||||
public string HelpMessage => null;
|
||||
public bool RunOnStartup => false;
|
||||
public int Priority => 0;
|
||||
|
||||
|
||||
private ILibraryManager _library;
|
||||
private IThumbnailsManager _thumbnails;
|
||||
private ITranscoder _transcoder;
|
||||
|
||||
public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null)
|
||||
{
|
||||
string[] args = arguments?.Split('/');
|
||||
|
||||
if (args == null || args.Length < 2)
|
||||
return;
|
||||
|
||||
string slug = args[1];
|
||||
bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase);
|
||||
bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase);
|
||||
|
||||
using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
_library = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
_thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
_transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>();
|
||||
int id;
|
||||
|
||||
switch (args[0].ToLowerInvariant())
|
||||
{
|
||||
case "show":
|
||||
case "shows":
|
||||
Show show = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Show>(id)
|
||||
: _library!.Get<Show>(slug));
|
||||
await ExtractShow(show, thumbs, subs, token);
|
||||
break;
|
||||
case "season":
|
||||
case "seasons":
|
||||
Season season = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Season>(id)
|
||||
: _library!.Get<Season>(slug));
|
||||
await ExtractSeason(season, thumbs, subs, token);
|
||||
break;
|
||||
case "episode":
|
||||
case "episodes":
|
||||
Episode episode = await (int.TryParse(slug, out id)
|
||||
? _library!.Get<Episode>(id)
|
||||
: _library!.Get<Episode>(slug));
|
||||
await ExtractEpisode(episode, thumbs, subs);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(show, true);
|
||||
await _library.Load(show, x => x.Seasons);
|
||||
foreach (Season season in show.Seasons)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await ExtractSeason(season, thumbs, subs, token);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(season, true);
|
||||
await _library.Load(season, x => x.Episodes);
|
||||
foreach (Episode episode in season.Episodes)
|
||||
{
|
||||
if (token.IsCancellationRequested)
|
||||
return;
|
||||
await ExtractEpisode(episode, thumbs, subs);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs)
|
||||
{
|
||||
if (thumbs)
|
||||
await _thumbnails!.Validate(episode, true);
|
||||
if (subs)
|
||||
{
|
||||
await _library.Load(episode, x => x.Tracks);
|
||||
episode.Tracks = (await _transcoder!.ExtractInfos(episode, true))
|
||||
.Where(x => x.Type != StreamType.Attachment)
|
||||
.Concat(episode.Tracks.Where(x => x.IsExternal))
|
||||
.ToList();
|
||||
await _library.Edit(episode, false);
|
||||
}
|
||||
}
|
||||
|
||||
public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
{
|
||||
return Task.FromResult<IEnumerable<string>>(null);
|
||||
}
|
||||
|
||||
public int? Progress()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
// using System;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Linq;
|
||||
// using System.Threading;
|
||||
// using System.Threading.Tasks;
|
||||
// using Kyoo.Controllers;
|
||||
// using Kyoo.Models;
|
||||
// using Microsoft.Extensions.DependencyInjection;
|
||||
//
|
||||
// namespace Kyoo.Tasks
|
||||
// {
|
||||
// public class ExtractMetadata : ITask
|
||||
// {
|
||||
// public string Slug => "extract";
|
||||
// public string Name => "Metadata Extractor";
|
||||
// public string Description => "Extract subtitles or download thumbnails for a show/episode.";
|
||||
// public string HelpMessage => null;
|
||||
// public bool RunOnStartup => false;
|
||||
// public int Priority => 0;
|
||||
//
|
||||
//
|
||||
// private ILibraryManager _library;
|
||||
// private IThumbnailsManager _thumbnails;
|
||||
// private ITranscoder _transcoder;
|
||||
//
|
||||
// public async Task Run(IServiceProvider serviceProvider, CancellationToken token, string arguments = null)
|
||||
// {
|
||||
// string[] args = arguments?.Split('/');
|
||||
//
|
||||
// if (args == null || args.Length < 2)
|
||||
// return;
|
||||
//
|
||||
// string slug = args[1];
|
||||
// bool thumbs = args.Length < 3 || string.Equals(args[2], "thumbnails", StringComparison.InvariantCultureIgnoreCase);
|
||||
// bool subs = args.Length < 3 || string.Equals(args[2], "subs", StringComparison.InvariantCultureIgnoreCase);
|
||||
//
|
||||
// using IServiceScope serviceScope = serviceProvider.CreateScope();
|
||||
// _library = serviceScope.ServiceProvider.GetService<ILibraryManager>();
|
||||
// _thumbnails = serviceScope.ServiceProvider.GetService<IThumbnailsManager>();
|
||||
// _transcoder = serviceScope.ServiceProvider.GetService<ITranscoder>();
|
||||
// int id;
|
||||
//
|
||||
// switch (args[0].ToLowerInvariant())
|
||||
// {
|
||||
// case "show":
|
||||
// case "shows":
|
||||
// Show show = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Show>(id)
|
||||
// : _library!.Get<Show>(slug));
|
||||
// await ExtractShow(show, thumbs, subs, token);
|
||||
// break;
|
||||
// case "season":
|
||||
// case "seasons":
|
||||
// Season season = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Season>(id)
|
||||
// : _library!.Get<Season>(slug));
|
||||
// await ExtractSeason(season, thumbs, subs, token);
|
||||
// break;
|
||||
// case "episode":
|
||||
// case "episodes":
|
||||
// Episode episode = await (int.TryParse(slug, out id)
|
||||
// ? _library!.Get<Episode>(id)
|
||||
// : _library!.Get<Episode>(slug));
|
||||
// await ExtractEpisode(episode, thumbs, subs);
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractShow(Show show, bool thumbs, bool subs, CancellationToken token)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(show, true);
|
||||
// await _library.Load(show, x => x.Seasons);
|
||||
// foreach (Season season in show.Seasons)
|
||||
// {
|
||||
// if (token.IsCancellationRequested)
|
||||
// return;
|
||||
// await ExtractSeason(season, thumbs, subs, token);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractSeason(Season season, bool thumbs, bool subs, CancellationToken token)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(season, true);
|
||||
// await _library.Load(season, x => x.Episodes);
|
||||
// foreach (Episode episode in season.Episodes)
|
||||
// {
|
||||
// if (token.IsCancellationRequested)
|
||||
// return;
|
||||
// await ExtractEpisode(episode, thumbs, subs);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private async Task ExtractEpisode(Episode episode, bool thumbs, bool subs)
|
||||
// {
|
||||
// if (thumbs)
|
||||
// await _thumbnails!.Validate(episode, true);
|
||||
// if (subs)
|
||||
// {
|
||||
// await _library.Load(episode, x => x.Tracks);
|
||||
// episode.Tracks = (await _transcoder!.ExtractInfos(episode, true))
|
||||
// .Where(x => x.Type != StreamType.Attachment)
|
||||
// .Concat(episode.Tracks.Where(x => x.IsExternal))
|
||||
// .ToList();
|
||||
// await _library.Edit(episode, false);
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public Task<IEnumerable<string>> GetPossibleParameters()
|
||||
// {
|
||||
// return Task.FromResult<IEnumerable<string>>(null);
|
||||
// }
|
||||
//
|
||||
// public int? Progress()
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user