mirror of
https://github.com/zoriya/Kyoo.git
synced 2025-05-30 19:54:16 -04:00
A basic account system exists now
This commit is contained in:
parent
bd12c35073
commit
30da20a456
9
Kyoo.Common/Models/Account.cs
Normal file
9
Kyoo.Common/Models/Account.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Kyoo.Models
|
||||||
|
{
|
||||||
|
public class Account
|
||||||
|
{
|
||||||
|
public string Username { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string Picture { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,11 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
|
|
||||||
namespace Kyoo
|
namespace Kyoo
|
||||||
{
|
{
|
||||||
public class DatabaseContext : IdentityDbContext<Account>
|
public class DatabaseContext : IdentityDbContext<User>
|
||||||
{
|
{
|
||||||
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
|
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
|
||||||
|
|
||||||
public DbSet<Account> Accounts { get; set; }
|
public DbSet<User> Accounts { get; set; }
|
||||||
|
|
||||||
public DbSet<Library> Libraries { get; set; }
|
public DbSet<Library> Libraries { get; set; }
|
||||||
public DbSet<Collection> Collections { get; set; }
|
public DbSet<Collection> Collections { get; set; }
|
||||||
@ -66,7 +66,7 @@ namespace Kyoo
|
|||||||
modelBuilder.Entity<Show>()
|
modelBuilder.Entity<Show>()
|
||||||
.Ignore(x => x.Genres);
|
.Ignore(x => x.Genres);
|
||||||
|
|
||||||
modelBuilder.Entity<Account>().ToTable("Account");
|
modelBuilder.Entity<User>().ToTable("User");
|
||||||
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
|
modelBuilder.Entity<IdentityUserRole<string>>().ToTable("UserRole");
|
||||||
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
|
modelBuilder.Entity<IdentityUserLogin<string>>().ToTable("UserLogin");
|
||||||
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
|
modelBuilder.Entity<IdentityUserClaim<string>>().ToTable("UserClaim");
|
||||||
|
@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace Kyoo.Models.DatabaseMigrations.Internal
|
namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||||
{
|
{
|
||||||
[DbContext(typeof(DatabaseContext))]
|
[DbContext(typeof(DatabaseContext))]
|
||||||
[Migration("20200307014347_Initial")]
|
[Migration("20200307160105_Intial")]
|
||||||
partial class Initial
|
partial class Intial
|
||||||
{
|
{
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
@ -18,76 +18,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "3.1.2");
|
.HasAnnotation("ProductVersion", "3.1.2");
|
||||||
|
|
||||||
modelBuilder.Entity("Kyoo.Models.Account", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("AccessFailedCount")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("ConcurrencyStamp")
|
|
||||||
.IsConcurrencyToken()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<bool>("EmailConfirmed")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("LockoutEnabled")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("NormalizedEmail")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<string>("NormalizedUserName")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<string>("OTAC")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("OTACExpires")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("PhoneNumberConfirmed")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("SecurityStamp")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("TwoFactorEnabled")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("UserName")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NormalizedEmail")
|
|
||||||
.HasName("EmailIndex");
|
|
||||||
|
|
||||||
b.HasIndex("NormalizedUserName")
|
|
||||||
.IsUnique()
|
|
||||||
.HasName("UserNameIndex");
|
|
||||||
|
|
||||||
b.ToTable("Account");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("ID")
|
b.Property<long>("ID")
|
||||||
@ -465,6 +395,76 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
b.ToTable("Tracks");
|
b.ToTable("Tracks");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<string>("OTAC")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("OTACExpires")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
@ -700,7 +700,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -709,7 +709,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -724,7 +724,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -733,7 +733,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
@ -3,37 +3,10 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
|||||||
|
|
||||||
namespace Kyoo.Models.DatabaseMigrations.Internal
|
namespace Kyoo.Models.DatabaseMigrations.Internal
|
||||||
{
|
{
|
||||||
public partial class Initial : Migration
|
public partial class Intial : Migration
|
||||||
{
|
{
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "Account",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<string>(nullable: false),
|
|
||||||
UserName = table.Column<string>(maxLength: 256, nullable: true),
|
|
||||||
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
|
|
||||||
Email = table.Column<string>(maxLength: 256, nullable: true),
|
|
||||||
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
|
|
||||||
EmailConfirmed = table.Column<bool>(nullable: false),
|
|
||||||
PasswordHash = table.Column<string>(nullable: true),
|
|
||||||
SecurityStamp = table.Column<string>(nullable: true),
|
|
||||||
ConcurrencyStamp = table.Column<string>(nullable: true),
|
|
||||||
PhoneNumber = table.Column<string>(nullable: true),
|
|
||||||
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
|
|
||||||
TwoFactorEnabled = table.Column<bool>(nullable: false),
|
|
||||||
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
|
|
||||||
LockoutEnabled = table.Column<bool>(nullable: false),
|
|
||||||
AccessFailedCount = table.Column<int>(nullable: false),
|
|
||||||
OTAC = table.Column<string>(nullable: true),
|
|
||||||
OTACExpires = table.Column<DateTime>(nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_Account", x => x.Id);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Collections",
|
name: "Collections",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -109,6 +82,33 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
table.PrimaryKey("PK_Studios", x => x.ID);
|
table.PrimaryKey("PK_Studios", x => x.ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "User",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<string>(nullable: false),
|
||||||
|
UserName = table.Column<string>(maxLength: 256, nullable: true),
|
||||||
|
NormalizedUserName = table.Column<string>(maxLength: 256, nullable: true),
|
||||||
|
Email = table.Column<string>(maxLength: 256, nullable: true),
|
||||||
|
NormalizedEmail = table.Column<string>(maxLength: 256, nullable: true),
|
||||||
|
EmailConfirmed = table.Column<bool>(nullable: false),
|
||||||
|
PasswordHash = table.Column<string>(nullable: true),
|
||||||
|
SecurityStamp = table.Column<string>(nullable: true),
|
||||||
|
ConcurrencyStamp = table.Column<string>(nullable: true),
|
||||||
|
PhoneNumber = table.Column<string>(nullable: true),
|
||||||
|
PhoneNumberConfirmed = table.Column<bool>(nullable: false),
|
||||||
|
TwoFactorEnabled = table.Column<bool>(nullable: false),
|
||||||
|
LockoutEnd = table.Column<DateTimeOffset>(nullable: true),
|
||||||
|
LockoutEnabled = table.Column<bool>(nullable: false),
|
||||||
|
AccessFailedCount = table.Column<int>(nullable: false),
|
||||||
|
OTAC = table.Column<string>(nullable: true),
|
||||||
|
OTACExpires = table.Column<DateTime>(nullable: true)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_User", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "UserRoles",
|
name: "UserRoles",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -123,67 +123,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
table.PrimaryKey("PK_UserRoles", x => x.Id);
|
table.PrimaryKey("PK_UserRoles", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserClaim",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
Id = table.Column<int>(nullable: false)
|
|
||||||
.Annotation("Sqlite:Autoincrement", true),
|
|
||||||
UserId = table.Column<string>(nullable: false),
|
|
||||||
ClaimType = table.Column<string>(nullable: true),
|
|
||||||
ClaimValue = table.Column<string>(nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserClaim", x => x.Id);
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserClaim_Account_UserId",
|
|
||||||
column: x => x.UserId,
|
|
||||||
principalTable: "Account",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserLogin",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
LoginProvider = table.Column<string>(nullable: false),
|
|
||||||
ProviderKey = table.Column<string>(nullable: false),
|
|
||||||
ProviderDisplayName = table.Column<string>(nullable: true),
|
|
||||||
UserId = table.Column<string>(nullable: false)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserLogin", x => new { x.LoginProvider, x.ProviderKey });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserLogin_Account_UserId",
|
|
||||||
column: x => x.UserId,
|
|
||||||
principalTable: "Account",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
|
||||||
name: "UserToken",
|
|
||||||
columns: table => new
|
|
||||||
{
|
|
||||||
UserId = table.Column<string>(nullable: false),
|
|
||||||
LoginProvider = table.Column<string>(nullable: false),
|
|
||||||
Name = table.Column<string>(nullable: false),
|
|
||||||
Value = table.Column<string>(nullable: true)
|
|
||||||
},
|
|
||||||
constraints: table =>
|
|
||||||
{
|
|
||||||
table.PrimaryKey("PK_UserToken", x => new { x.UserId, x.LoginProvider, x.Name });
|
|
||||||
table.ForeignKey(
|
|
||||||
name: "FK_UserToken_Account_UserId",
|
|
||||||
column: x => x.UserId,
|
|
||||||
principalTable: "Account",
|
|
||||||
principalColumn: "Id",
|
|
||||||
onDelete: ReferentialAction.Cascade);
|
|
||||||
});
|
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Shows",
|
name: "Shows",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -218,6 +157,67 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
onDelete: ReferentialAction.Restrict);
|
onDelete: ReferentialAction.Restrict);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "UserClaim",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
UserId = table.Column<string>(nullable: false),
|
||||||
|
ClaimType = table.Column<string>(nullable: true),
|
||||||
|
ClaimValue = table.Column<string>(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>(nullable: false),
|
||||||
|
ProviderKey = table.Column<string>(nullable: false),
|
||||||
|
ProviderDisplayName = table.Column<string>(nullable: true),
|
||||||
|
UserId = table.Column<string>(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>(nullable: false),
|
||||||
|
LoginProvider = table.Column<string>(nullable: false),
|
||||||
|
Name = table.Column<string>(nullable: false),
|
||||||
|
Value = table.Column<string>(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(
|
migrationBuilder.CreateTable(
|
||||||
name: "UserRole",
|
name: "UserRole",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
@ -235,9 +235,9 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_UserRole_Account_UserId",
|
name: "FK_UserRole_User_UserId",
|
||||||
column: x => x.UserId,
|
column: x => x.UserId,
|
||||||
principalTable: "Account",
|
principalTable: "User",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
@ -462,17 +462,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
onDelete: ReferentialAction.Cascade);
|
onDelete: ReferentialAction.Cascade);
|
||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "EmailIndex",
|
|
||||||
table: "Account",
|
|
||||||
column: "NormalizedEmail");
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
|
||||||
name: "UserNameIndex",
|
|
||||||
table: "Account",
|
|
||||||
column: "NormalizedUserName",
|
|
||||||
unique: true);
|
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_CollectionLinks_CollectionID",
|
name: "IX_CollectionLinks_CollectionID",
|
||||||
table: "CollectionLinks",
|
table: "CollectionLinks",
|
||||||
@ -538,6 +527,17 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
table: "Tracks",
|
table: "Tracks",
|
||||||
column: "EpisodeID");
|
column: "EpisodeID");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "EmailIndex",
|
||||||
|
table: "User",
|
||||||
|
column: "NormalizedEmail");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "UserNameIndex",
|
||||||
|
table: "User",
|
||||||
|
column: "NormalizedUserName",
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_UserClaim_UserId",
|
name: "IX_UserClaim_UserId",
|
||||||
table: "UserClaim",
|
table: "UserClaim",
|
||||||
@ -616,7 +616,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
name: "UserRoles");
|
name: "UserRoles");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Account");
|
name: "User");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Seasons");
|
name: "Seasons");
|
@ -16,76 +16,6 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
modelBuilder
|
modelBuilder
|
||||||
.HasAnnotation("ProductVersion", "3.1.2");
|
.HasAnnotation("ProductVersion", "3.1.2");
|
||||||
|
|
||||||
modelBuilder.Entity("Kyoo.Models.Account", b =>
|
|
||||||
{
|
|
||||||
b.Property<string>("Id")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<int>("AccessFailedCount")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("ConcurrencyStamp")
|
|
||||||
.IsConcurrencyToken()
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("Email")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<bool>("EmailConfirmed")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<bool>("LockoutEnabled")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("NormalizedEmail")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<string>("NormalizedUserName")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.Property<string>("OTAC")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<DateTime?>("OTACExpires")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("PasswordHash")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<string>("PhoneNumber")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("PhoneNumberConfirmed")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("SecurityStamp")
|
|
||||||
.HasColumnType("TEXT");
|
|
||||||
|
|
||||||
b.Property<bool>("TwoFactorEnabled")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.Property<string>("UserName")
|
|
||||||
.HasColumnType("TEXT")
|
|
||||||
.HasMaxLength(256);
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.HasIndex("NormalizedEmail")
|
|
||||||
.HasName("EmailIndex");
|
|
||||||
|
|
||||||
b.HasIndex("NormalizedUserName")
|
|
||||||
.IsUnique()
|
|
||||||
.HasName("UserNameIndex");
|
|
||||||
|
|
||||||
b.ToTable("Account");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
modelBuilder.Entity("Kyoo.Models.Collection", b =>
|
||||||
{
|
{
|
||||||
b.Property<long>("ID")
|
b.Property<long>("ID")
|
||||||
@ -463,6 +393,76 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
b.ToTable("Tracks");
|
b.ToTable("Tracks");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.Property<string>("OTAC")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("OTACExpires")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasColumnType("TEXT")
|
||||||
|
.HasMaxLength(256);
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedEmail")
|
||||||
|
.HasName("EmailIndex");
|
||||||
|
|
||||||
|
b.HasIndex("NormalizedUserName")
|
||||||
|
.IsUnique()
|
||||||
|
.HasName("UserNameIndex");
|
||||||
|
|
||||||
|
b.ToTable("User");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||||
{
|
{
|
||||||
b.Property<string>("Id")
|
b.Property<string>("Id")
|
||||||
@ -698,7 +698,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -707,7 +707,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -722,7 +722,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
@ -731,7 +731,7 @@ namespace Kyoo.Models.DatabaseMigrations.Internal
|
|||||||
|
|
||||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("Kyoo.Models.Account", null)
|
b.HasOne("Kyoo.Models.User", null)
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
@ -22,11 +22,13 @@ namespace Kyoo
|
|||||||
new Client
|
new Client
|
||||||
{
|
{
|
||||||
ClientId = "kyoo.webapp",
|
ClientId = "kyoo.webapp",
|
||||||
ClientSecrets = { new Secret("secret".Sha256()) },
|
AllowedGrantTypes = GrantTypes.Code,
|
||||||
AllowedGrantTypes = GrantTypes.Implicit,
|
AllowOfflineAccess = true,
|
||||||
|
RequireClientSecret = false,
|
||||||
|
RequireConsent = false,
|
||||||
AllowedScopes = { "kyoo.admin", "kyoo.write", "kyoo.read", "openid", "profile" },
|
AllowedScopes = { "kyoo.admin", "kyoo.write", "kyoo.read", "openid", "profile" },
|
||||||
AllowAccessTokensViaBrowser = true,
|
RedirectUris = { "/logged", "/silent" },
|
||||||
RequireConsent = false
|
PostLogoutRedirectUris = { "/logout" }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
|
|
||||||
namespace Kyoo.Models
|
namespace Kyoo.Models
|
||||||
{
|
{
|
||||||
public class Account : IdentityUser
|
public class User : IdentityUser
|
||||||
{
|
{
|
||||||
public string OTAC { get; set; }
|
public string OTAC { get; set; }
|
||||||
public DateTime? OTACExpires { get; set; }
|
public DateTime? OTACExpires { get; set; }
|
@ -39,28 +39,34 @@ namespace Kyoo
|
|||||||
services.AddDbContext<DatabaseContext>(options => options.UseLazyLoadingProxies()
|
services.AddDbContext<DatabaseContext>(options => options.UseLazyLoadingProxies()
|
||||||
.UseSqlite(Configuration.GetConnectionString("Database")));
|
.UseSqlite(Configuration.GetConnectionString("Database")));
|
||||||
|
|
||||||
services.AddIdentity<Account, IdentityRole>()
|
services.AddIdentity<User, IdentityRole>()
|
||||||
.AddEntityFrameworkStores<DatabaseContext>()
|
.AddEntityFrameworkStores<DatabaseContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
services.AddIdentityServer(options =>
|
services.AddIdentityServer(options =>
|
||||||
{
|
{
|
||||||
options.UserInteraction.LoginUrl = publicUrl + "/login";
|
options.UserInteraction.LoginUrl = publicUrl + "login";
|
||||||
options.UserInteraction.ErrorUrl = publicUrl + "/error";
|
options.UserInteraction.ErrorUrl = publicUrl + "error";
|
||||||
options.UserInteraction.LogoutUrl = publicUrl + "/logout";
|
options.UserInteraction.LogoutUrl = publicUrl + "logout";
|
||||||
})
|
})
|
||||||
.AddConfigurationStore(options =>
|
.AddConfigurationStore(options =>
|
||||||
{
|
{
|
||||||
options.ConfigureDbContext = builder => builder.UseSqlite(Configuration.GetConnectionString("Database"), sql => sql.MigrationsAssembly(assemblyName));
|
options.ConfigureDbContext = builder =>
|
||||||
|
builder.UseSqlite(Configuration.GetConnectionString("Database"),
|
||||||
|
sql => sql.MigrationsAssembly(assemblyName));
|
||||||
})
|
})
|
||||||
.AddOperationalStore(options =>
|
.AddOperationalStore(options =>
|
||||||
{
|
{
|
||||||
options.ConfigureDbContext = builder => builder.UseSqlite(Configuration.GetConnectionString("Database"), sql => sql.MigrationsAssembly(assemblyName));
|
options.ConfigureDbContext = builder =>
|
||||||
|
builder.UseSqlite(Configuration.GetConnectionString("Database"),
|
||||||
|
sql => sql.MigrationsAssembly(assemblyName));
|
||||||
options.EnableTokenCleanup = true;
|
options.EnableTokenCleanup = true;
|
||||||
})
|
})
|
||||||
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
|
.AddInMemoryIdentityResources(IdentityContext.GetIdentityResources())
|
||||||
.AddInMemoryApiResources(IdentityContext.GetApis())
|
.AddInMemoryApiResources(IdentityContext.GetApis())
|
||||||
.AddAspNetIdentity<Account>();
|
.AddAspNetIdentity<User>()
|
||||||
|
.AddDeveloperSigningCredential();
|
||||||
|
|
||||||
|
|
||||||
services.AddScoped<ILibraryManager, LibraryManager>();
|
services.AddScoped<ILibraryManager, LibraryManager>();
|
||||||
services.AddScoped<ICrawler, Crawler>();
|
services.AddScoped<ICrawler, Crawler>();
|
||||||
@ -105,6 +111,7 @@ namespace Kyoo
|
|||||||
app.UseRouting();
|
app.UseRouting();
|
||||||
|
|
||||||
app.UseIdentityServer();
|
app.UseIdentityServer();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IdentityServer4.Services;
|
||||||
using Kyoo.Models;
|
using Kyoo.Models;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
|
using SignInResult = Microsoft.AspNetCore.Identity.SignInResult;
|
||||||
@ -25,12 +28,12 @@ namespace Kyoo.Api
|
|||||||
[ApiController]
|
[ApiController]
|
||||||
public class AccountController : Controller
|
public class AccountController : Controller
|
||||||
{
|
{
|
||||||
private readonly UserManager<Account> _accountManager;
|
private readonly UserManager<User> _userManager;
|
||||||
private readonly SignInManager<Account> _signInManager;
|
private readonly SignInManager<User> _signInManager;
|
||||||
|
|
||||||
public AccountController(UserManager<Account> accountManager, SignInManager<Account> siginInManager)
|
public AccountController(UserManager<User> userManager, SignInManager<User> siginInManager)
|
||||||
{
|
{
|
||||||
_accountManager = accountManager;
|
_userManager = userManager;
|
||||||
_signInManager = siginInManager;
|
_signInManager = siginInManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,12 +42,12 @@ namespace Kyoo.Api
|
|||||||
{
|
{
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return BadRequest(user);
|
return BadRequest(user);
|
||||||
Account account = new Account {UserName = user.Username, Email = user.Email};
|
User account = new User {UserName = user.Username, Email = user.Email};
|
||||||
IdentityResult result = await _accountManager.CreateAsync(account, user.Password);
|
IdentityResult result = await _userManager.CreateAsync(account, user.Password);
|
||||||
if (!result.Succeeded)
|
if (!result.Succeeded)
|
||||||
return BadRequest(result.Errors);
|
return BadRequest(result.Errors);
|
||||||
string otac = account.GenerateOTAC(TimeSpan.FromMinutes(1));
|
string otac = account.GenerateOTAC(TimeSpan.FromMinutes(1));
|
||||||
await _accountManager.UpdateAsync(account);
|
await _userManager.UpdateAsync(account);
|
||||||
return Ok(otac);
|
return Ok(otac);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,9 +57,30 @@ namespace Kyoo.Api
|
|||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return BadRequest(login);
|
return BadRequest(login);
|
||||||
SignInResult result = await _signInManager.PasswordSignInAsync(login.Username, login.Password, login.StayLoggedIn, false);
|
SignInResult result = await _signInManager.PasswordSignInAsync(login.Username, login.Password, login.StayLoggedIn, false);
|
||||||
if (result.Succeeded)
|
if (!result.Succeeded)
|
||||||
return Ok();
|
|
||||||
return BadRequest("Invalid username/password");
|
return BadRequest("Invalid username/password");
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<Account>> Index()
|
||||||
|
{
|
||||||
|
User account = await _userManager.GetUserAsync(HttpContext.User);
|
||||||
|
return new Account{
|
||||||
|
Username = account.UserName,
|
||||||
|
Email = account.Email,
|
||||||
|
Picture = "api/account/picture/" + account.UserName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("picture/{username}")]
|
||||||
|
public IActionResult Picture(string username)
|
||||||
|
{
|
||||||
|
string path = $"account/{username}.png";
|
||||||
|
if (System.IO.File.Exists(path))
|
||||||
|
return new PhysicalFileResult(path, "image");
|
||||||
|
return NotFound();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit 6240bbce05be87f576fd4326ce26e8bfa8335c73
|
Subproject commit 34f828af246b0c86c53f612bd7999a4c2b3e266d
|
Loading…
x
Reference in New Issue
Block a user