diff --git a/API/API.csproj b/API/API.csproj
index 784cff6b4..52664396f 100644
--- a/API/API.csproj
+++ b/API/API.csproj
@@ -8,6 +8,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/API/Constants/PolicyConstants.cs b/API/Constants/PolicyConstants.cs
new file mode 100644
index 000000000..d64a2bab6
--- /dev/null
+++ b/API/Constants/PolicyConstants.cs
@@ -0,0 +1,8 @@
+namespace API.Constants
+{
+ public static class PolicyConstants
+ {
+ public static readonly string AdminRole = "Admin";
+ public static readonly string PlebRole = "Pleb";
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/AccountController.cs b/API/Controllers/AccountController.cs
index a621c1086..3610925e9 100644
--- a/API/Controllers/AccountController.cs
+++ b/API/Controllers/AccountController.cs
@@ -1,11 +1,11 @@
using System;
-using System.Security.Cryptography;
-using System.Text;
using System.Threading.Tasks;
-using API.Data;
+using API.Constants;
using API.DTOs;
using API.Entities;
using API.Interfaces;
+using AutoMapper;
+using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -14,81 +14,83 @@ namespace API.Controllers
{
public class AccountController : BaseApiController
{
- private readonly DataContext _context;
+ private readonly UserManager _userManager;
+ private readonly SignInManager _signInManager;
private readonly ITokenService _tokenService;
private readonly IUserRepository _userRepository;
private readonly ILogger _logger;
+ private readonly IMapper _mapper;
- public AccountController(DataContext context, ITokenService tokenService, IUserRepository userRepository, ILogger logger)
+ public AccountController(UserManager userManager,
+ SignInManager signInManager,
+ ITokenService tokenService, IUserRepository userRepository,
+ ILogger logger,
+ IMapper mapper)
{
- _context = context;
+ _userManager = userManager;
+ _signInManager = signInManager;
_tokenService = tokenService;
_userRepository = userRepository;
_logger = logger;
+ _mapper = mapper;
}
[HttpPost("register")]
public async Task> Register(RegisterDto registerDto)
{
- _logger.LogInformation("Username: " + registerDto.Password);
if (await UserExists(registerDto.Username))
{
return BadRequest("Username is taken.");
}
+
+ var user = _mapper.Map(registerDto);
+
+ var result = await _userManager.CreateAsync(user, registerDto.Password);
+
+ if (!result.Succeeded) return BadRequest(result.Errors);
- using var hmac = new HMACSHA512();
- var user = new AppUser
- {
- UserName = registerDto.Username.ToLower(),
- PasswordHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(registerDto.Password)),
- PasswordSalt = hmac.Key,
- IsAdmin = registerDto.IsAdmin,
- LastActive = DateTime.Now
- };
- _context.Users.Add(user);
- await _context.SaveChangesAsync();
+ // TODO: Need a way to store Roles in enum and configure from there
+ var role = registerDto.IsAdmin ? PolicyConstants.AdminRole : PolicyConstants.PlebRole;
+ var roleResult = await _userManager.AddToRoleAsync(user, role);
- return new UserDto()
+ if (!roleResult.Succeeded) return BadRequest(result.Errors);
+
+ return new UserDto
{
Username = user.UserName,
- Token = _tokenService.CreateToken(user),
- IsAdmin = user.IsAdmin
+ Token = await _tokenService.CreateToken(user),
};
}
[HttpPost("login")]
public async Task> Login(LoginDto loginDto)
{
- var user = await _userRepository.GetUserByUsernameAsync(loginDto.Username);
+ var user = await _userManager.Users
+ .SingleOrDefaultAsync(x => x.UserName == loginDto.Username.ToLower());
if (user == null) return Unauthorized("Invalid username");
-
- using var hmac = new HMACSHA512(user.PasswordSalt);
- var computedHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(loginDto.Password));
+ var result = await _signInManager
+ .CheckPasswordSignInAsync(user, loginDto.Password, false);
- for (int i = 0; i < computedHash.Length; i++)
- {
- if (computedHash[i] != user.PasswordHash[i]) return Unauthorized("Invalid password");
- }
+ if (!result.Succeeded) return Unauthorized();
// Update LastActive on account
user.LastActive = DateTime.Now;
_userRepository.Update(user);
await _userRepository.SaveAllAsync();
- return new UserDto()
+ return new UserDto
{
Username = user.UserName,
- Token = _tokenService.CreateToken(user),
- IsAdmin = user.IsAdmin
+ Token = await _tokenService.CreateToken(user)
};
}
private async Task UserExists(string username)
{
- return await _context.Users.AnyAsync(user => user.UserName == username.ToLower());
+ return await _userManager.Users.AnyAsync(user => user.UserName == username.ToLower());
}
}
}
\ No newline at end of file
diff --git a/API/Controllers/AdminController.cs b/API/Controllers/AdminController.cs
new file mode 100644
index 000000000..173961a48
--- /dev/null
+++ b/API/Controllers/AdminController.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using API.DTOs;
+using API.Entities;
+using API.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+
+namespace API.Controllers
+{
+ public class AdminController : BaseApiController
+ {
+ private readonly IUserRepository _userRepository;
+ private readonly UserManager _userManager;
+
+ public AdminController(IUserRepository userRepository, UserManager userManager)
+ {
+ _userRepository = userRepository;
+ _userManager = userManager;
+ }
+
+ [HttpGet("exists")]
+ public async Task> AdminExists()
+ {
+ var users = await _userManager.GetUsersInRoleAsync("Admin");
+ return users.Count > 0;
+ }
+
+
+
+
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs
new file mode 100644
index 000000000..0c6e45d99
--- /dev/null
+++ b/API/Controllers/LibraryController.cs
@@ -0,0 +1,103 @@
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using API.Data;
+using API.DTOs;
+using API.Entities;
+using API.Extensions;
+using API.Interfaces;
+using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+
+namespace API.Controllers
+{
+ [Authorize]
+ public class LibraryController : BaseApiController
+ {
+ private readonly DataContext _context;
+ private readonly IDirectoryService _directoryService;
+ private readonly ILibraryRepository _libraryRepository;
+ private readonly ILogger _logger;
+ private readonly IUserRepository _userRepository;
+ private readonly IMapper _mapper;
+
+ public LibraryController(DataContext context, IDirectoryService directoryService,
+ ILibraryRepository libraryRepository, ILogger logger, IUserRepository userRepository,
+ IMapper mapper)
+ {
+ _context = context;
+ _directoryService = directoryService;
+ _libraryRepository = libraryRepository;
+ _logger = logger;
+ _userRepository = userRepository;
+ _mapper = mapper;
+ }
+
+ ///
+ /// Returns a list of directories for a given path. If path is empty, returns root drives.
+ ///
+ ///
+ ///
+ [HttpGet("list")]
+ public ActionResult> GetDirectories(string path)
+ {
+ // TODO: We need some sort of validation other than our auth layer
+ _logger.Log(LogLevel.Debug, "Listing Directories for " + path);
+
+ if (string.IsNullOrEmpty(path))
+ {
+ return Ok(Directory.GetLogicalDrives());
+ }
+
+ if (!Directory.Exists(@path)) return BadRequest("This is not a valid path");
+
+ return Ok(_directoryService.ListDirectory(path));
+ }
+
+ [HttpGet]
+ public async Task>> GetLibraries()
+ {
+ return Ok(await _libraryRepository.GetLibrariesAsync());
+ }
+
+
+ // Do I need this method?
+ // [HttpGet("library/{username}")]
+ // public async Task>> GetLibrariesForUser(string username)
+ // {
+ // _logger.LogDebug("Method hit");
+ // var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
+ //
+ // if (user == null) return BadRequest("Could not validate user");
+ //
+ // return Ok(await _libraryRepository.GetLibrariesForUserAsync(user));
+ // }
+
+ [Authorize(Policy = "RequireAdminRole")]
+ [HttpPut("update-for")]
+ public async Task> UpdateLibrary(UpdateLibraryDto updateLibraryDto)
+ {
+ var user = await _userRepository.GetUserByUsernameAsync(updateLibraryDto.Username);
+
+ if (user == null) return BadRequest("Could not validate user");
+
+ user.Libraries = new List();
+
+ foreach (var selectedLibrary in updateLibraryDto.SelectedLibraries)
+ {
+ user.Libraries.Add(_mapper.Map(selectedLibrary));
+ }
+
+ if (await _userRepository.SaveAllAsync())
+ {
+ return Ok(user);
+ }
+
+ return BadRequest("Not Implemented");
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs
index dd27d3f15..3b546b21f 100644
--- a/API/Controllers/UsersController.cs
+++ b/API/Controllers/UsersController.cs
@@ -1,24 +1,88 @@
-using System.Collections;
-using System.Collections.Generic;
+using System.Collections.Generic;
+using System.Linq;
using System.Threading.Tasks;
using API.Data;
using API.DTOs;
+using API.Entities;
+using API.Extensions;
using API.Interfaces;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace API.Controllers
{
+ [Authorize]
public class UsersController : BaseApiController
{
private readonly DataContext _context;
private readonly IUserRepository _userRepository;
+ private readonly ILibraryRepository _libraryRepository;
- public UsersController(DataContext context, IUserRepository userRepository)
+ public UsersController(DataContext context, IUserRepository userRepository, ILibraryRepository libraryRepository)
{
_context = context;
_userRepository = userRepository;
+ _libraryRepository = libraryRepository;
}
+
+ [HttpPost("add-library")]
+ public async Task AddLibrary(CreateLibraryDto createLibraryDto)
+ {
+ // NOTE: I think we should move this into library controller because it gets added to all admins
+
+ //_logger.Log(LogLevel.Debug, "Creating a new " + createLibraryDto.Type + " library");
+ var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
+ if (user == null) return BadRequest("Could not validate user");
+
+
+ if (await _libraryRepository.LibraryExists(createLibraryDto.Name))
+ {
+ return BadRequest("Library name already exists. Please choose a unique name to the server.");
+ }
+
+ // TODO: We probably need to clean the folders before we insert
+ var library = new Library
+ {
+ Name = createLibraryDto.Name, // TODO: Ensure code handles Library name always being lowercase
+ Type = createLibraryDto.Type,
+ AppUsers = new List() { user }
+ };
+
+ library.Folders = createLibraryDto.Folders.Select(x => new FolderPath
+ {
+ Path = x,
+ Library = library
+ }).ToList();
+
+ user.Libraries ??= new List(); // If user is null, then set it
+
+ user.Libraries.Add(library);
+
+ if (await _userRepository.SaveAllAsync())
+ {
+ return Ok();
+ }
+
+ return BadRequest("Not implemented");
+ }
+
+ [Authorize(Policy = "RequireAdminRole")]
+ [HttpDelete("delete-user")]
+ public async Task DeleteUser(string username)
+ {
+ var user = await _userRepository.GetUserByUsernameAsync(username);
+ _userRepository.Delete(user);
+
+ if (await _userRepository.SaveAllAsync())
+ {
+ return Ok();
+ }
+
+ return BadRequest("Could not delete the user.");
+ }
+
+ [Authorize(Policy = "RequireAdminRole")]
[HttpGet]
public async Task>> GetUsers()
{
diff --git a/API/Controllers/WeatherForecastController.cs b/API/Controllers/WeatherForecastController.cs
deleted file mode 100644
index 28c1c42a8..000000000
--- a/API/Controllers/WeatherForecastController.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-
-namespace API.Controllers
-{
- [ApiController]
- [Route("[controller]")]
- public class WeatherForecastController : ControllerBase
- {
- private static readonly string[] Summaries = new[]
- {
- "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
- };
-
- private readonly ILogger _logger;
-
- public WeatherForecastController(ILogger logger)
- {
- _logger = logger;
- }
-
- [HttpGet]
- public IEnumerable Get()
- {
- var rng = new Random();
- return Enumerable.Range(1, 5).Select(index => new WeatherForecast
- {
- Date = DateTime.Now.AddDays(index),
- TemperatureC = rng.Next(-20, 55),
- Summary = Summaries[rng.Next(Summaries.Length)]
- })
- .ToArray();
- }
- }
-}
diff --git a/API/DTOs/CreateLibraryDto.cs b/API/DTOs/CreateLibraryDto.cs
new file mode 100644
index 000000000..3e3263d48
--- /dev/null
+++ b/API/DTOs/CreateLibraryDto.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using API.Entities;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion.Internal;
+
+namespace API.DTOs
+{
+ public class CreateLibraryDto
+ {
+ [Required]
+ public string Name { get; set; }
+ [Required]
+ public LibraryType Type { get; set; }
+ [Required]
+ [MinLength(1)]
+ public IEnumerable Folders { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs
new file mode 100644
index 000000000..31e51e173
--- /dev/null
+++ b/API/DTOs/LibraryDto.cs
@@ -0,0 +1,13 @@
+using System.Collections.Generic;
+using API.Entities;
+
+namespace API.DTOs
+{
+ public class LibraryDto
+ {
+ public string Name { get; set; }
+ public string CoverImage { get; set; }
+ public LibraryType Type { get; set; }
+ public ICollection Folders { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/API/DTOs/MemberDto.cs b/API/DTOs/MemberDto.cs
index a1f8b377b..6f09f1fc3 100644
--- a/API/DTOs/MemberDto.cs
+++ b/API/DTOs/MemberDto.cs
@@ -1,4 +1,7 @@
using System;
+using System.Collections;
+using System.Collections.Generic;
+using API.Entities;
namespace API.DTOs
{
@@ -11,6 +14,7 @@ namespace API.DTOs
public string Username { get; set; }
public DateTime Created { get; set; }
public DateTime LastActive { get; set; }
- public bool IsAdmin { get; set; }
+ public IEnumerable Libraries { get; set; }
+ public IEnumerable Roles { get; set; }
}
}
\ No newline at end of file
diff --git a/API/DTOs/UpdateLibraryDto.cs b/API/DTOs/UpdateLibraryDto.cs
new file mode 100644
index 000000000..c664b1df6
--- /dev/null
+++ b/API/DTOs/UpdateLibraryDto.cs
@@ -0,0 +1,11 @@
+using System.Collections.Generic;
+
+namespace API.DTOs
+{
+ // NOTE: Should this be a Record? https://www.youtube.com/watch?v=9Byvwa9yF-I
+ public class UpdateLibraryDto
+ {
+ public string Username { get; init; }
+ public IEnumerable SelectedLibraries { get; init; }
+ }
+}
\ No newline at end of file
diff --git a/API/DTOs/UserDto.cs b/API/DTOs/UserDto.cs
index c8b97abb6..eb0bf6bf2 100644
--- a/API/DTOs/UserDto.cs
+++ b/API/DTOs/UserDto.cs
@@ -2,8 +2,7 @@
{
public class UserDto
{
- public string Username { get; set; }
- public string Token { get; set; }
- public bool IsAdmin { get; set; }
+ public string Username { get; init; }
+ public string Token { get; init; }
}
}
\ No newline at end of file
diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs
index bca4e24be..e29f5758a 100644
--- a/API/Data/DataContext.cs
+++ b/API/Data/DataContext.cs
@@ -1,15 +1,36 @@
-using API.Entities;
+using System;
+using API.Entities;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace API.Data
{
- public class DataContext : DbContext
+ public class DataContext : IdentityDbContext, AppUserRole, IdentityUserLogin,
+ IdentityRoleClaim, IdentityUserToken>
{
public DataContext(DbContextOptions options) : base(options)
{
}
-
- public DbSet Users { get; set; }
+ public DbSet Library { get; set; }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ builder.Entity()
+ .HasMany(ur => ur.UserRoles)
+ .WithOne(u => u.User)
+ .HasForeignKey(ur => ur.UserId)
+ .IsRequired();
+
+ builder.Entity()
+ .HasMany(ur => ur.UserRoles)
+ .WithOne(u => u.Role)
+ .HasForeignKey(ur => ur.RoleId)
+ .IsRequired();
+ }
}
}
\ No newline at end of file
diff --git a/API/Data/LibraryRepository.cs b/API/Data/LibraryRepository.cs
new file mode 100644
index 000000000..68e6371bd
--- /dev/null
+++ b/API/Data/LibraryRepository.cs
@@ -0,0 +1,53 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using API.DTOs;
+using API.Entities;
+using API.Interfaces;
+using AutoMapper;
+using AutoMapper.QueryableExtensions;
+using Microsoft.EntityFrameworkCore;
+
+namespace API.Data
+{
+ public class LibraryRepository : ILibraryRepository
+ {
+ private readonly DataContext _context;
+ private readonly IMapper _mapper;
+
+ public LibraryRepository(DataContext context, IMapper mapper)
+ {
+ _context = context;
+ _mapper = mapper;
+ }
+
+ public void Update(Library library)
+ {
+ _context.Entry(library).State = EntityState.Modified;
+ }
+
+ public async Task SaveAllAsync()
+ {
+ return await _context.SaveChangesAsync() > 0;
+ }
+
+ public async Task> GetLibrariesAsync()
+ {
+ return await _context.Library
+ .Include(f => f.Folders)
+ .ProjectTo(_mapper.ConfigurationProvider).ToListAsync();
+ }
+
+ public async Task LibraryExists(string libraryName)
+ {
+ return await _context.Library.AnyAsync(x => x.Name == libraryName);
+ }
+
+ public async Task> GetLibrariesForUserAsync(AppUser user)
+ {
+ return await _context.Library.Where(library => library.AppUsers.Contains(user))
+ .Include(l => l.Folders)
+ .ProjectTo(_mapper.ConfigurationProvider).ToListAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/API/Data/Migrations/20201215195007_AddedLibrary.Designer.cs b/API/Data/Migrations/20201215195007_AddedLibrary.Designer.cs
new file mode 100644
index 000000000..4a657771e
--- /dev/null
+++ b/API/Data/Migrations/20201215195007_AddedLibrary.Designer.cs
@@ -0,0 +1,128 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20201215195007_AddedLibrary")]
+ partial class AddedLibrary
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.1");
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("IsAdmin")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("BLOB");
+
+ b.Property("PasswordSalt")
+ .HasColumnType("BLOB");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.HasOne("API.Entities.Library", null)
+ .WithMany("Folders")
+ .HasForeignKey("LibraryId");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.HasOne("API.Entities.AppUser", "AppUser")
+ .WithMany("Libraries")
+ .HasForeignKey("AppUserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("AppUser");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Navigation("Libraries");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Navigation("Folders");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201215195007_AddedLibrary.cs b/API/Data/Migrations/20201215195007_AddedLibrary.cs
new file mode 100644
index 000000000..f1c4adf56
--- /dev/null
+++ b/API/Data/Migrations/20201215195007_AddedLibrary.cs
@@ -0,0 +1,71 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Data.Migrations
+{
+ public partial class AddedLibrary : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "Library",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Name = table.Column(type: "TEXT", nullable: true),
+ CoverImage = table.Column(type: "TEXT", nullable: true),
+ Type = table.Column(type: "INTEGER", nullable: false),
+ AppUserId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Library", x => x.Id);
+ table.ForeignKey(
+ name: "FK_Library_Users_AppUserId",
+ column: x => x.AppUserId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "FolderPath",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Path = table.Column(type: "TEXT", nullable: true),
+ LibraryId = table.Column(type: "INTEGER", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_FolderPath", x => x.Id);
+ table.ForeignKey(
+ name: "FK_FolderPath_Library_LibraryId",
+ column: x => x.LibraryId,
+ principalTable: "Library",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_FolderPath_LibraryId",
+ table: "FolderPath",
+ column: "LibraryId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Library_AppUserId",
+ table: "Library",
+ column: "AppUserId");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "FolderPath");
+
+ migrationBuilder.DropTable(
+ name: "Library");
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201218173135_ManyToManyLibraries.Designer.cs b/API/Data/Migrations/20201218173135_ManyToManyLibraries.Designer.cs
new file mode 100644
index 000000000..98af0c730
--- /dev/null
+++ b/API/Data/Migrations/20201218173135_ManyToManyLibraries.Designer.cs
@@ -0,0 +1,141 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20201218173135_ManyToManyLibraries")]
+ partial class ManyToManyLibraries
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.1");
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("IsAdmin")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("BLOB");
+
+ b.Property("PasswordSalt")
+ .HasColumnType("BLOB");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.ToTable("Users");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.Property("AppUsersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibrariesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AppUsersId", "LibrariesId");
+
+ b.HasIndex("LibrariesId");
+
+ b.ToTable("AppUserLibrary");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.HasOne("API.Entities.Library", "Library")
+ .WithMany("Folders")
+ .HasForeignKey("LibraryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("AppUsersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.Library", null)
+ .WithMany()
+ .HasForeignKey("LibrariesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Navigation("Folders");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201218173135_ManyToManyLibraries.cs b/API/Data/Migrations/20201218173135_ManyToManyLibraries.cs
new file mode 100644
index 000000000..e7d2cb39b
--- /dev/null
+++ b/API/Data/Migrations/20201218173135_ManyToManyLibraries.cs
@@ -0,0 +1,119 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Data.Migrations
+{
+ public partial class ManyToManyLibraries : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_FolderPath_Library_LibraryId",
+ table: "FolderPath");
+
+ migrationBuilder.DropForeignKey(
+ name: "FK_Library_Users_AppUserId",
+ table: "Library");
+
+ migrationBuilder.DropIndex(
+ name: "IX_Library_AppUserId",
+ table: "Library");
+
+ migrationBuilder.DropColumn(
+ name: "AppUserId",
+ table: "Library");
+
+ migrationBuilder.AlterColumn(
+ name: "LibraryId",
+ table: "FolderPath",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0,
+ oldClrType: typeof(int),
+ oldType: "INTEGER",
+ oldNullable: true);
+
+ migrationBuilder.CreateTable(
+ name: "AppUserLibrary",
+ columns: table => new
+ {
+ AppUsersId = table.Column(type: "INTEGER", nullable: false),
+ LibrariesId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AppUserLibrary", x => new { x.AppUsersId, x.LibrariesId });
+ table.ForeignKey(
+ name: "FK_AppUserLibrary_Library_LibrariesId",
+ column: x => x.LibrariesId,
+ principalTable: "Library",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AppUserLibrary_Users_AppUsersId",
+ column: x => x.AppUsersId,
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AppUserLibrary_LibrariesId",
+ table: "AppUserLibrary",
+ column: "LibrariesId");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_FolderPath_Library_LibraryId",
+ table: "FolderPath",
+ column: "LibraryId",
+ principalTable: "Library",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_FolderPath_Library_LibraryId",
+ table: "FolderPath");
+
+ migrationBuilder.DropTable(
+ name: "AppUserLibrary");
+
+ migrationBuilder.AddColumn(
+ name: "AppUserId",
+ table: "Library",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AlterColumn(
+ name: "LibraryId",
+ table: "FolderPath",
+ type: "INTEGER",
+ nullable: true,
+ oldClrType: typeof(int),
+ oldType: "INTEGER");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Library_AppUserId",
+ table: "Library",
+ column: "AppUserId");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_FolderPath_Library_LibraryId",
+ table: "FolderPath",
+ column: "LibraryId",
+ principalTable: "Library",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Restrict);
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_Library_Users_AppUserId",
+ table: "Library",
+ column: "AppUserId",
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201221141047_IdentityAdded.Designer.cs b/API/Data/Migrations/20201221141047_IdentityAdded.Designer.cs
new file mode 100644
index 000000000..0836f6f4a
--- /dev/null
+++ b/API/Data/Migrations/20201221141047_IdentityAdded.Designer.cs
@@ -0,0 +1,380 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20201221141047_IdentityAdded")]
+ partial class IdentityAdded
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.1");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsAdmin")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("BLOB");
+
+ b.Property("PasswordSalt")
+ .HasColumnType("BLOB");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.Property("AppUsersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibrariesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AppUsersId", "LibrariesId");
+
+ b.HasIndex("LibrariesId");
+
+ b.ToTable("AppUserLibrary");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.HasOne("API.Entities.AppRole", "Role")
+ .WithMany("UserRoles")
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.AppUser", "User")
+ .WithMany("UserRoles")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Role");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.HasOne("API.Entities.Library", "Library")
+ .WithMany("Folders")
+ .HasForeignKey("LibraryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("AppUsersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.Library", null)
+ .WithMany()
+ .HasForeignKey("LibrariesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("API.Entities.AppRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Navigation("Folders");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201221141047_IdentityAdded.cs b/API/Data/Migrations/20201221141047_IdentityAdded.cs
new file mode 100644
index 000000000..ee9dd15b2
--- /dev/null
+++ b/API/Data/Migrations/20201221141047_IdentityAdded.cs
@@ -0,0 +1,376 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Data.Migrations
+{
+ public partial class IdentityAdded : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_AppUserLibrary_Users_AppUsersId",
+ table: "AppUserLibrary");
+
+ migrationBuilder.DropPrimaryKey(
+ name: "PK_Users",
+ table: "Users");
+
+ migrationBuilder.RenameTable(
+ name: "Users",
+ newName: "AspNetUsers");
+
+ migrationBuilder.AddColumn(
+ name: "AccessFailedCount",
+ table: "AspNetUsers",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0);
+
+ migrationBuilder.AddColumn(
+ name: "ConcurrencyStamp",
+ table: "AspNetUsers",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "Email",
+ table: "AspNetUsers",
+ type: "TEXT",
+ maxLength: 256,
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "EmailConfirmed",
+ table: "AspNetUsers",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "LockoutEnabled",
+ table: "AspNetUsers",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "LockoutEnd",
+ table: "AspNetUsers",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "NormalizedEmail",
+ table: "AspNetUsers",
+ type: "TEXT",
+ maxLength: 256,
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "NormalizedUserName",
+ table: "AspNetUsers",
+ type: "TEXT",
+ maxLength: 256,
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "PhoneNumber",
+ table: "AspNetUsers",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "PhoneNumberConfirmed",
+ table: "AspNetUsers",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddColumn(
+ name: "SecurityStamp",
+ table: "AspNetUsers",
+ type: "TEXT",
+ nullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "TwoFactorEnabled",
+ table: "AspNetUsers",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: false);
+
+ migrationBuilder.AddPrimaryKey(
+ name: "PK_AspNetUsers",
+ table: "AspNetUsers",
+ column: "Id");
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoles",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ Name = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true),
+ ConcurrencyStamp = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoles", x => x.Id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ UserId = table.Column(type: "INTEGER", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetUserClaims_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserLogins",
+ columns: table => new
+ {
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ ProviderKey = table.Column(type: "TEXT", nullable: false),
+ ProviderDisplayName = table.Column(type: "TEXT", nullable: true),
+ UserId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey });
+ table.ForeignKey(
+ name: "FK_AspNetUserLogins_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserTokens",
+ columns: table => new
+ {
+ UserId = table.Column(type: "INTEGER", nullable: false),
+ LoginProvider = table.Column(type: "TEXT", nullable: false),
+ Name = table.Column(type: "TEXT", nullable: false),
+ Value = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ table.ForeignKey(
+ name: "FK_AspNetUserTokens_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetRoleClaims",
+ columns: table => new
+ {
+ Id = table.Column(type: "INTEGER", nullable: false)
+ .Annotation("Sqlite:Autoincrement", true),
+ RoleId = table.Column(type: "INTEGER", nullable: false),
+ ClaimType = table.Column(type: "TEXT", nullable: true),
+ ClaimValue = table.Column(type: "TEXT", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id);
+ table.ForeignKey(
+ name: "FK_AspNetRoleClaims_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "AspNetUserRoles",
+ columns: table => new
+ {
+ UserId = table.Column(type: "INTEGER", nullable: false),
+ RoleId = table.Column(type: "INTEGER", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId });
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetRoles_RoleId",
+ column: x => x.RoleId,
+ principalTable: "AspNetRoles",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_AspNetUserRoles_AspNetUsers_UserId",
+ column: x => x.UserId,
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers",
+ column: "NormalizedEmail");
+
+ migrationBuilder.CreateIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers",
+ column: "NormalizedUserName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetRoleClaims_RoleId",
+ table: "AspNetRoleClaims",
+ column: "RoleId");
+
+ migrationBuilder.CreateIndex(
+ name: "RoleNameIndex",
+ table: "AspNetRoles",
+ column: "NormalizedName",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserClaims_UserId",
+ table: "AspNetUserClaims",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserLogins_UserId",
+ table: "AspNetUserLogins",
+ column: "UserId");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_AspNetUserRoles_RoleId",
+ table: "AspNetUserRoles",
+ column: "RoleId");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_AppUserLibrary_AspNetUsers_AppUsersId",
+ table: "AppUserLibrary",
+ column: "AppUsersId",
+ principalTable: "AspNetUsers",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropForeignKey(
+ name: "FK_AppUserLibrary_AspNetUsers_AppUsersId",
+ table: "AppUserLibrary");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoleClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserClaims");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserLogins");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserRoles");
+
+ migrationBuilder.DropTable(
+ name: "AspNetUserTokens");
+
+ migrationBuilder.DropTable(
+ name: "AspNetRoles");
+
+ migrationBuilder.DropPrimaryKey(
+ name: "PK_AspNetUsers",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropIndex(
+ name: "EmailIndex",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropIndex(
+ name: "UserNameIndex",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "AccessFailedCount",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "ConcurrencyStamp",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "Email",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "EmailConfirmed",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "LockoutEnabled",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "LockoutEnd",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "NormalizedEmail",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "NormalizedUserName",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "PhoneNumber",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "PhoneNumberConfirmed",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "SecurityStamp",
+ table: "AspNetUsers");
+
+ migrationBuilder.DropColumn(
+ name: "TwoFactorEnabled",
+ table: "AspNetUsers");
+
+ migrationBuilder.RenameTable(
+ name: "AspNetUsers",
+ newName: "Users");
+
+ migrationBuilder.AddPrimaryKey(
+ name: "PK_Users",
+ table: "Users",
+ column: "Id");
+
+ migrationBuilder.AddForeignKey(
+ name: "FK_AppUserLibrary_Users_AppUsersId",
+ table: "AppUserLibrary",
+ column: "AppUsersId",
+ principalTable: "Users",
+ principalColumn: "Id",
+ onDelete: ReferentialAction.Cascade);
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs b/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs
new file mode 100644
index 000000000..8ae8c597a
--- /dev/null
+++ b/API/Data/Migrations/20201224155621_MiscCleanup.Designer.cs
@@ -0,0 +1,377 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20201224155621_MiscCleanup")]
+ partial class MiscCleanup
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "5.0.1");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsAdmin")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.Property("AppUsersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibrariesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AppUsersId", "LibrariesId");
+
+ b.HasIndex("LibrariesId");
+
+ b.ToTable("AppUserLibrary");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetRoleClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserClaims");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ProviderDisplayName")
+ .HasColumnType("TEXT");
+
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("LoginProvider", "ProviderKey");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("AspNetUserLogins");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LoginProvider")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("UserId", "LoginProvider", "Name");
+
+ b.ToTable("AspNetUserTokens");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.HasOne("API.Entities.AppRole", "Role")
+ .WithMany("UserRoles")
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.AppUser", "User")
+ .WithMany("UserRoles")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Role");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.HasOne("API.Entities.Library", "Library")
+ .WithMany("Folders")
+ .HasForeignKey("LibraryId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Library");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("AppUsersId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("API.Entities.Library", null)
+ .WithMany()
+ .HasForeignKey("LibrariesId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.HasOne("API.Entities.AppRole", null)
+ .WithMany()
+ .HasForeignKey("RoleId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b =>
+ {
+ b.HasOne("API.Entities.AppUser", null)
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+ });
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Navigation("UserRoles");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Navigation("Folders");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/API/Data/Migrations/20201224155621_MiscCleanup.cs b/API/Data/Migrations/20201224155621_MiscCleanup.cs
new file mode 100644
index 000000000..20e0a4dc9
--- /dev/null
+++ b/API/Data/Migrations/20201224155621_MiscCleanup.cs
@@ -0,0 +1,42 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace API.Data.Migrations
+{
+ public partial class MiscCleanup : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "PasswordSalt",
+ table: "AspNetUsers");
+
+ migrationBuilder.AlterColumn(
+ name: "PasswordHash",
+ table: "AspNetUsers",
+ type: "TEXT",
+ nullable: true,
+ oldClrType: typeof(byte[]),
+ oldType: "BLOB",
+ oldNullable: true);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AlterColumn(
+ name: "PasswordHash",
+ table: "AspNetUsers",
+ type: "BLOB",
+ nullable: true,
+ oldClrType: typeof(string),
+ oldType: "TEXT",
+ oldNullable: true);
+
+ migrationBuilder.AddColumn(
+ name: "PasswordSalt",
+ table: "AspNetUsers",
+ type: "BLOB",
+ nullable: true);
+ }
+ }
+}
diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs
index 9ed038826..fd6f137ac 100644
--- a/API/Data/Migrations/DataContextModelSnapshot.cs
+++ b/API/Data/Migrations/DataContextModelSnapshot.cs
@@ -16,37 +16,361 @@ namespace API.Data.Migrations
modelBuilder
.HasAnnotation("ProductVersion", "5.0.1");
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles");
+ });
+
modelBuilder.Entity("API.Entities.AppUser", b =>
{
b.Property