Merge pull request #18 from Kareadita/feature/ui-cleanup

UI Cleanup
This commit is contained in:
Joseph Milazzo 2021-01-03 17:04:01 -06:00 committed by GitHub
commit 2806beaae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1883 additions and 141 deletions

View File

@ -78,6 +78,8 @@ namespace API.Controllers
user.LastActive = DateTime.Now; user.LastActive = DateTime.Now;
_userRepository.Update(user); _userRepository.Update(user);
await _userRepository.SaveAllAsync(); await _userRepository.SaveAllAsync();
_logger.LogInformation($"{user.UserName} logged in at {user.LastActive}");
return new UserDto return new UserDto
{ {

View File

@ -1,5 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs;
using API.Entities; using API.Entities;
@ -35,6 +36,48 @@ namespace API.Controllers
_taskScheduler = taskScheduler; _taskScheduler = taskScheduler;
_seriesRepository = seriesRepository; _seriesRepository = seriesRepository;
} }
/// <summary>
/// Creates a new Library. Upon library creation, adds new library to all Admin accounts.
/// </summary>
/// <param name="createLibraryDto"></param>
/// <returns></returns>
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("create")]
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
{
if (await _libraryRepository.LibraryExists(createLibraryDto.Name))
{
return BadRequest("Library name already exists. Please choose a unique name to the server.");
}
var admins = (await _userRepository.GetAdminUsersAsync()).ToList();
var library = new Library
{
Name = createLibraryDto.Name,
Type = createLibraryDto.Type,
AppUsers = admins,
Folders = createLibraryDto.Folders.Select(x => new FolderPath {Path = x}).ToList()
};
foreach (var admin in admins)
{
// If user is null, then set it
admin.Libraries ??= new List<Library>();
admin.Libraries.Add(library);
}
if (await _userRepository.SaveAllAsync())
{
var createdLibrary = await _libraryRepository.GetLibraryForNameAsync(library.Name);
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(createdLibrary.Id));
return Ok();
}
return BadRequest("There was a critical issue. Please try again.");
}
/// <summary> /// <summary>
/// Returns a list of directories for a given path. If path is empty, returns root drives. /// Returns a list of directories for a given path. If path is empty, returns root drives.
@ -85,28 +128,30 @@ namespace API.Controllers
} }
[Authorize(Policy = "RequireAdminRole")] [Authorize(Policy = "RequireAdminRole")]
[HttpGet("scan")] [HttpPost("scan")]
public async Task<ActionResult> ScanLibrary(int libraryId) public ActionResult ScanLibrary(int libraryId)
{ {
var library = await _libraryRepository.GetLibraryDtoForIdAsync(libraryId); BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(libraryId));
// We have to send a json encoded Library (aka a DTO) to the Background Job thread.
// Because we use EF, we have circular dependencies back to Library and it will crap out
BackgroundJob.Enqueue(() => _directoryService.ScanLibrary(library));
return Ok(); return Ok();
} }
[HttpGet("libraries-for")] [HttpGet("libraries-for")]
public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser(string username) public async Task<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser(string username)
{ {
return Ok(await _libraryRepository.GetLibrariesForUsernameAysnc(username)); return Ok(await _libraryRepository.GetLibrariesDtoForUsernameAsync(username));
} }
[HttpGet("series")] [HttpGet("series")]
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId) public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId)
{ {
return Ok(await _seriesRepository.GetSeriesForLibraryIdAsync(libraryId)); return Ok(await _seriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId));
} }
[Authorize(Policy = "RequireAdminRole")]
[HttpDelete("delete")]
public async Task<ActionResult<bool>> DeleteLibrary(int libraryId)
{
return Ok(await _libraryRepository.DeleteLibrary(libraryId));
}
} }
} }

View File

@ -27,13 +27,13 @@ namespace API.Controllers
[HttpGet("{seriesId}")] [HttpGet("{seriesId}")]
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId) public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
{ {
return Ok(await _seriesRepository.GetSeriesByIdAsync(seriesId)); return Ok(await _seriesRepository.GetSeriesDtoByIdAsync(seriesId));
} }
[HttpGet("volumes")] [HttpGet("volumes")]
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId) public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
{ {
return Ok(await _seriesRepository.GetVolumesAsync(seriesId)); return Ok(await _seriesRepository.GetVolumesDtoAsync(seriesId));
} }
} }
} }

View File

@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs;
using API.Entities;
using API.Extensions; using API.Extensions;
using API.Interfaces; using API.Interfaces;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -22,47 +21,6 @@ namespace API.Controllers
_libraryRepository = libraryRepository; _libraryRepository = libraryRepository;
} }
[Authorize(Policy = "RequireAdminRole")]
[HttpPost("add-library")]
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
{
// NOTE: I think we should move this into library controller because it gets added to all admins
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.");
}
var library = new Library
{
Name = createLibraryDto.Name.ToLower(),
Type = createLibraryDto.Type,
AppUsers = new List<AppUser>() { user }
};
library.Folders = createLibraryDto.Folders.Select(x => new FolderPath
{
Path = x,
Library = library
}).ToList();
user.Libraries ??= new List<Library>(); // If user is null, then set it
user.Libraries.Add(library);
if (await _userRepository.SaveAllAsync())
{
return Ok();
}
return BadRequest("Not implemented");
}
[Authorize(Policy = "RequireAdminRole")] [Authorize(Policy = "RequireAdminRole")]
[HttpDelete("delete-user")] [HttpDelete("delete-user")]
public async Task<ActionResult> DeleteUser(string username) public async Task<ActionResult> DeleteUser(string username)
@ -84,5 +42,17 @@ namespace API.Controllers
{ {
return Ok(await _userRepository.GetMembersAsync()); return Ok(await _userRepository.GetMembersAsync());
} }
[HttpGet("has-library-access")]
public async Task<ActionResult<bool>> HasLibraryAccess(int libraryId)
{
var user = await _userRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return BadRequest("Could not validate user");
var libs = await _libraryRepository.GetLibrariesDtoForUsernameAsync(user.UserName);
return Ok(libs.Any(x => x.Id == libraryId));
}
} }
} }

View File

@ -5,7 +5,8 @@ namespace API.DTOs
public class VolumeDto public class VolumeDto
{ {
public int Id { get; set; } public int Id { get; set; }
public string Number { get; set; } public int Number { get; set; }
public string Name { get; set; }
public string CoverImage { get; set; } public string CoverImage { get; set; }
public ICollection<string> Files { get; set; } public ICollection<string> Files { get; set; }
} }

View File

@ -1,16 +1,21 @@
using API.Entities; using System;
using API.Entities;
using API.Entities.Interfaces;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
namespace API.Data namespace API.Data
{ {
public class DataContext : IdentityDbContext<AppUser, AppRole, int, public sealed class DataContext : IdentityDbContext<AppUser, AppRole, int,
IdentityUserClaim<int>, AppUserRole, IdentityUserLogin<int>, IdentityUserClaim<int>, AppUserRole, IdentityUserLogin<int>,
IdentityRoleClaim<int>, IdentityUserToken<int>> IdentityRoleClaim<int>, IdentityUserToken<int>>
{ {
public DataContext(DbContextOptions options) : base(options) public DataContext(DbContextOptions options) : base(options)
{ {
ChangeTracker.Tracked += OnEntityTracked;
ChangeTracker.StateChanged += OnEntityStateChanged;
} }
public DbSet<Library> Library { get; set; } public DbSet<Library> Library { get; set; }
@ -33,12 +38,18 @@ namespace API.Data
.WithOne(u => u.Role) .WithOne(u => u.Role)
.HasForeignKey(ur => ur.RoleId) .HasForeignKey(ur => ur.RoleId)
.IsRequired(); .IsRequired();
}
// builder.Entity<Library>()
// .HasMany(s => s.Series) void OnEntityTracked(object sender, EntityTrackedEventArgs e)
// .WithOne(l => l.Library) {
// .HasForeignKey(x => x.Id) if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IEntityDate entity)
entity.Created = DateTime.Now;
}
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.NewState == EntityState.Modified && e.Entry.Entity is IEntityDate entity)
entity.LastModified = DateTime.Now;
} }
} }
} }

View File

@ -36,16 +36,7 @@ namespace API.Data
return _context.SaveChanges() > 0; return _context.SaveChanges() > 0;
} }
public Library GetLibraryForName(string libraryName) public async Task<IEnumerable<LibraryDto>> GetLibrariesDtoForUsernameAsync(string userName)
{
return _context.Library
.Where(x => x.Name == libraryName)
.Include(f => f.Folders)
.Include(s => s.Series)
.Single();
}
public async Task<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName)
{ {
return await _context.Library return await _context.Library
.Include(l => l.AppUsers) .Include(l => l.AppUsers)
@ -53,21 +44,29 @@ namespace API.Data
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync(); .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
public async Task<Library> GetLibraryForNameAsync(string libraryName)
{
return await _context.Library
.Where(x => x.Name == libraryName)
.Include(f => f.Folders)
.Include(s => s.Series)
.SingleAsync();
}
public async Task<bool> DeleteLibrary(int libraryId)
{
var library = await GetLibraryForIdAsync(libraryId);
_context.Library.Remove(library);
return await _context.SaveChangesAsync() > 0;
}
public async Task<IEnumerable<LibraryDto>> GetLibrariesAsync() public async Task<IEnumerable<LibraryDto>> GetLibrariesAsync()
{ {
return await _context.Library return await _context.Library
.Include(f => f.Folders) .Include(f => f.Folders)
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync(); .ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
public async Task<LibraryDto> GetLibraryDtoForIdAsync(int libraryId)
{
return await _context.Library
.Where(x => x.Id == libraryId)
.Include(f => f.Folders)
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).SingleAsync();
}
public async Task<Library> GetLibraryForIdAsync(int libraryId) public async Task<Library> GetLibraryForIdAsync(int libraryId)
{ {
return await _context.Library return await _context.Library

View File

@ -0,0 +1,509 @@
// <auto-generated />
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("20210102165536_EntityTimestamps")]
partial class EntityTimestamps
{
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastActive")
.HasColumnType("TEXT");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("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<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("FolderPath");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Library");
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FilePath")
.HasColumnType("TEXT");
b.Property<int>("VolumeId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("VolumeId");
b.ToTable("MangaFile");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("OriginalName")
.HasColumnType("TEXT");
b.Property<string>("SortName")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("Series");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Number")
.HasColumnType("TEXT");
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SeriesId");
b.ToTable("Volume");
});
modelBuilder.Entity("AppUserLibrary", b =>
{
b.Property<int>("AppUsersId")
.HasColumnType("INTEGER");
b.Property<int>("LibrariesId")
.HasColumnType("INTEGER");
b.HasKey("AppUsersId", "LibrariesId");
b.HasIndex("LibrariesId");
b.ToTable("AppUserLibrary");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("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("API.Entities.MangaFile", b =>
{
b.HasOne("API.Entities.Volume", "Volume")
.WithMany("Files")
.HasForeignKey("VolumeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Volume");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.HasOne("API.Entities.Library", "Library")
.WithMany("Series")
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Library");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.HasOne("API.Entities.Series", "Series")
.WithMany("Volumes")
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Series");
});
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<int>", b =>
{
b.HasOne("API.Entities.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", 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");
b.Navigation("Series");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Navigation("Volumes");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Navigation("Files");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,80 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace API.Data.Migrations
{
public partial class EntityTimestamps : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Volume",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "LastModified",
table: "Volume",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Series",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "LastModified",
table: "Series",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "Created",
table: "Library",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
migrationBuilder.AddColumn<DateTime>(
name: "LastModified",
table: "Library",
type: "TEXT",
nullable: false,
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Created",
table: "Volume");
migrationBuilder.DropColumn(
name: "LastModified",
table: "Volume");
migrationBuilder.DropColumn(
name: "Created",
table: "Series");
migrationBuilder.DropColumn(
name: "LastModified",
table: "Series");
migrationBuilder.DropColumn(
name: "Created",
table: "Library");
migrationBuilder.DropColumn(
name: "LastModified",
table: "Library");
}
}
}

View File

@ -0,0 +1,512 @@
// <auto-generated />
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("20210102173326_VolumeNumberRefactor")]
partial class VolumeNumberRefactor
{
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastActive")
.HasColumnType("TEXT");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("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<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("FolderPath");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Library");
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FilePath")
.HasColumnType("TEXT");
b.Property<int>("VolumeId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("VolumeId");
b.ToTable("MangaFile");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("OriginalName")
.HasColumnType("TEXT");
b.Property<string>("SortName")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("Series");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SeriesId");
b.ToTable("Volume");
});
modelBuilder.Entity("AppUserLibrary", b =>
{
b.Property<int>("AppUsersId")
.HasColumnType("INTEGER");
b.Property<int>("LibrariesId")
.HasColumnType("INTEGER");
b.HasKey("AppUsersId", "LibrariesId");
b.HasIndex("LibrariesId");
b.ToTable("AppUserLibrary");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("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("API.Entities.MangaFile", b =>
{
b.HasOne("API.Entities.Volume", "Volume")
.WithMany("Files")
.HasForeignKey("VolumeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Volume");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.HasOne("API.Entities.Library", "Library")
.WithMany("Series")
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Library");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.HasOne("API.Entities.Series", "Series")
.WithMany("Volumes")
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Series");
});
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<int>", b =>
{
b.HasOne("API.Entities.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", 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");
b.Navigation("Series");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Navigation("Volumes");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Navigation("Files");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace API.Data.Migrations
{
public partial class VolumeNumberRefactor : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<int>(
name: "Number",
table: "Volume",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(string),
oldType: "TEXT",
oldNullable: true);
migrationBuilder.AddColumn<string>(
name: "Name",
table: "Volume",
type: "TEXT",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Name",
table: "Volume");
migrationBuilder.AlterColumn<string>(
name: "Number",
table: "Volume",
type: "TEXT",
nullable: true,
oldClrType: typeof(int),
oldType: "INTEGER");
}
}
}

View File

@ -0,0 +1,509 @@
// <auto-generated />
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("20210103201043_RemoveUserIsAdmin")]
partial class RemoveUserIsAdmin
{
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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastActive")
.HasColumnType("TEXT");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<uint>("RowVersion")
.IsConcurrencyToken()
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("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<int>("UserId")
.HasColumnType("INTEGER");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles");
});
modelBuilder.Entity("API.Entities.FolderPath", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Path")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("FolderPath");
});
modelBuilder.Entity("API.Entities.Library", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.ToTable("Library");
});
modelBuilder.Entity("API.Entities.MangaFile", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("FilePath")
.HasColumnType("TEXT");
b.Property<int>("VolumeId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("VolumeId");
b.ToTable("MangaFile");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("CoverImage")
.HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("LibraryId")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("OriginalName")
.HasColumnType("TEXT");
b.Property<string>("SortName")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("LibraryId");
b.ToTable("Series");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<int>("SeriesId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SeriesId");
b.ToTable("Volume");
});
modelBuilder.Entity("AppUserLibrary", b =>
{
b.Property<int>("AppUsersId")
.HasColumnType("INTEGER");
b.Property<int>("LibrariesId")
.HasColumnType("INTEGER");
b.HasKey("AppUsersId", "LibrariesId");
b.HasIndex("LibrariesId");
b.ToTable("AppUserLibrary");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("RoleId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", b =>
{
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("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("API.Entities.MangaFile", b =>
{
b.HasOne("API.Entities.Volume", "Volume")
.WithMany("Files")
.HasForeignKey("VolumeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Volume");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.HasOne("API.Entities.Library", "Library")
.WithMany("Series")
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Library");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.HasOne("API.Entities.Series", "Series")
.WithMany("Volumes")
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Series");
});
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<int>", b =>
{
b.HasOne("API.Entities.AppRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<int>", b =>
{
b.HasOne("API.Entities.AppUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<int>", 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");
b.Navigation("Series");
});
modelBuilder.Entity("API.Entities.Series", b =>
{
b.Navigation("Volumes");
});
modelBuilder.Entity("API.Entities.Volume", b =>
{
b.Navigation("Files");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,24 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace API.Data.Migrations
{
public partial class RemoveUserIsAdmin : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsAdmin",
table: "AspNetUsers");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsAdmin",
table: "AspNetUsers",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
}
}

View File

@ -66,9 +66,6 @@ namespace API.Data.Migrations
b.Property<bool>("EmailConfirmed") b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastActive") b.Property<DateTime>("LastActive")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -164,6 +161,12 @@ namespace API.Data.Migrations
b.Property<string>("CoverImage") b.Property<string>("CoverImage")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name") b.Property<string>("Name")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
@ -203,6 +206,12 @@ namespace API.Data.Migrations
b.Property<string>("CoverImage") b.Property<string>("CoverImage")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("Created")
.HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<int>("LibraryId") b.Property<int>("LibraryId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
@ -231,9 +240,18 @@ namespace API.Data.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Number") b.Property<DateTime>("Created")
.HasColumnType("TEXT"); .HasColumnType("TEXT");
b.Property<DateTime>("LastModified")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<int>("Number")
.HasColumnType("INTEGER");
b.Property<int>("SeriesId") b.Property<int>("SeriesId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");

View File

@ -46,35 +46,31 @@ namespace API.Data
return _context.Series.SingleOrDefault(x => x.Name == name); return _context.Series.SingleOrDefault(x => x.Name == name);
} }
public async Task<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId) public async Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId)
{ {
return await _context.Series return await _context.Series
.Where(series => series.LibraryId == libraryId) .Where(series => series.LibraryId == libraryId)
.OrderBy(s => s.SortName)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).ToListAsync(); .ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
public async Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId) public async Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId)
{ {
return await _context.Volume return await _context.Volume
.Where(vol => vol.SeriesId == seriesId) .Where(vol => vol.SeriesId == seriesId)
.OrderBy(volume => volume.Number)
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToListAsync(); .ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToListAsync();
} }
public IEnumerable<VolumeDto> GetVolumesDto(int seriesId)
{
return _context.Volume
.Where(vol => vol.SeriesId == seriesId)
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider).ToList();
}
public IEnumerable<Volume> GetVolumes(int seriesId) public IEnumerable<Volume> GetVolumes(int seriesId)
{ {
return _context.Volume return _context.Volume
.Where(vol => vol.SeriesId == seriesId) .Where(vol => vol.SeriesId == seriesId)
.OrderBy(vol => vol.Number)
.ToList(); .ToList();
} }
public async Task<SeriesDto> GetSeriesByIdAsync(int seriesId) public async Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId)
{ {
return await _context.Series.Where(x => x.Id == seriesId) return await _context.Series.Where(x => x.Id == seriesId)
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync(); .ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync();

View File

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Constants;
using API.DTOs; using API.DTOs;
using API.Entities; using API.Entities;
using API.Interfaces; using API.Interfaces;
@ -55,6 +56,11 @@ namespace API.Data
.SingleOrDefaultAsync(x => x.UserName == username); .SingleOrDefaultAsync(x => x.UserName == username);
} }
public async Task<IEnumerable<AppUser>> GetAdminUsersAsync()
{
return await _userManager.GetUsersInRoleAsync(PolicyConstants.AdminRole);
}
public async Task<IEnumerable<MemberDto>> GetMembersAsync() public async Task<IEnumerable<MemberDto>> GetMembersAsync()
{ {
return await _userManager.Users return await _userManager.Users

View File

@ -1,18 +1,17 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using API.Entities.Interfaces;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
namespace API.Entities namespace API.Entities
{ {
public class AppUser : IdentityUser<int> public class AppUser : IdentityUser<int>, IHasConcurrencyToken
{ {
public DateTime Created { get; set; } = DateTime.Now; public DateTime Created { get; set; } = DateTime.Now;
public DateTime LastActive { get; set; } public DateTime LastActive { get; set; }
public bool IsAdmin { get; set; }
public ICollection<Library> Libraries { get; set; } public ICollection<Library> Libraries { get; set; }
[ConcurrencyCheck] [ConcurrencyCheck]
public uint RowVersion { get; set; } public uint RowVersion { get; set; }

View File

@ -1,4 +1,5 @@
namespace API.Entities 
namespace API.Entities
{ {
public class FolderPath public class FolderPath
{ {

View File

@ -0,0 +1,10 @@
using System;
namespace API.Entities.Interfaces
{
public interface IEntityDate
{
DateTime Created { get; set; }
DateTime LastModified { get; set; }
}
}

View File

@ -1,15 +1,20 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using API.Entities.Interfaces;
namespace API.Entities namespace API.Entities
{ {
public class Library public class Library : IEntityDate
{ {
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string CoverImage { get; set; } public string CoverImage { get; set; }
public LibraryType Type { get; set; } public LibraryType Type { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
public ICollection<FolderPath> Folders { get; set; } public ICollection<FolderPath> Folders { get; set; }
public ICollection<AppUser> AppUsers { get; set; } public ICollection<AppUser> AppUsers { get; set; }
public ICollection<Series> Series { get; set; } public ICollection<Series> Series { get; set; }
} }
} }

View File

@ -1,4 +1,5 @@
namespace API.Entities 
namespace API.Entities
{ {
public class MangaFile public class MangaFile
{ {

View File

@ -1,8 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using API.Entities.Interfaces;
namespace API.Entities namespace API.Entities
{ {
public class Series public class Series : IEntityDate
{ {
public int Id { get; set; } public int Id { get; set; }
/// <summary> /// <summary>
@ -22,6 +24,8 @@ namespace API.Entities
/// </summary> /// </summary>
public string Summary { get; set; } public string Summary { get; set; }
public string CoverImage { get; set; } public string CoverImage { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
public ICollection<Volume> Volumes { get; set; } public ICollection<Volume> Volumes { get; set; }
public Library Library { get; set; } public Library Library { get; set; }

View File

@ -1,12 +1,17 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using API.Entities.Interfaces;
namespace API.Entities namespace API.Entities
{ {
public class Volume public class Volume : IEntityDate
{ {
public int Id { get; set; } public int Id { get; set; }
public string Number { get; set; } public string Name { get; set; }
public int Number { get; set; }
public ICollection<MangaFile> Files { get; set; } public ICollection<MangaFile> Files { get; set; }
public DateTime Created { get; set; }
public DateTime LastModified { get; set; }
// Many-to-Many relationships // Many-to-Many relationships
public Series Series { get; set; } public Series Series { get; set; }

View File

@ -1,5 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using API.DTOs;
namespace API.Interfaces namespace API.Interfaces
{ {
@ -7,6 +6,6 @@ namespace API.Interfaces
{ {
IEnumerable<string> ListDirectory(string rootPath); IEnumerable<string> ListDirectory(string rootPath);
void ScanLibrary(LibraryDto library); void ScanLibrary(int libraryId);
} }
} }

View File

@ -10,16 +10,12 @@ namespace API.Interfaces
void Update(Library library); void Update(Library library);
Task<bool> SaveAllAsync(); Task<bool> SaveAllAsync();
Task<IEnumerable<LibraryDto>> GetLibrariesAsync(); Task<IEnumerable<LibraryDto>> GetLibrariesAsync();
/// <summary>
/// Checks to see if a library of the same name exists. We only allow unique library names, no duplicates per LibraryType.
/// </summary>
/// <param name="libraryName"></param>
/// <returns></returns>
Task<bool> LibraryExists(string libraryName); Task<bool> LibraryExists(string libraryName);
Task<LibraryDto> GetLibraryDtoForIdAsync(int libraryId);
Task<Library> GetLibraryForIdAsync(int libraryId); Task<Library> GetLibraryForIdAsync(int libraryId);
bool SaveAll(); bool SaveAll();
Library GetLibraryForName(string libraryName); Task<IEnumerable<LibraryDto>> GetLibrariesDtoForUsernameAsync(string userName);
Task<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName); Task<Library> GetLibraryForNameAsync(string libraryName);
Task<bool> DeleteLibrary(int libraryId);
} }
} }

View File

@ -12,11 +12,10 @@ namespace API.Interfaces
Task<Series> GetSeriesByNameAsync(string name); Task<Series> GetSeriesByNameAsync(string name);
Series GetSeriesByName(string name); Series GetSeriesByName(string name);
bool SaveAll(); bool SaveAll();
Task<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId); Task<IEnumerable<SeriesDto>> GetSeriesDtoForLibraryIdAsync(int libraryId);
Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId); Task<IEnumerable<VolumeDto>> GetVolumesDtoAsync(int seriesId);
IEnumerable<VolumeDto> GetVolumesDto(int seriesId);
IEnumerable<Volume> GetVolumes(int seriesId); IEnumerable<Volume> GetVolumes(int seriesId);
Task<SeriesDto> GetSeriesByIdAsync(int seriesId); Task<SeriesDto> GetSeriesDtoByIdAsync(int seriesId);
} }
} }

View File

@ -15,5 +15,6 @@ namespace API.Interfaces
Task<IEnumerable<MemberDto>> GetMembersAsync(); Task<IEnumerable<MemberDto>> GetMembersAsync();
Task<MemberDto> GetMemberAsync(string username); Task<MemberDto> GetMemberAsync(string username);
public void Delete(AppUser user); public void Delete(AppUser user);
Task<IEnumerable<AppUser>> GetAdminUsersAsync();
} }
} }

View File

@ -5,11 +5,9 @@ using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs;
using API.Entities; using API.Entities;
using API.Interfaces; using API.Interfaces;
using API.Parser; using API.Parser;
@ -149,9 +147,10 @@ namespace API.Services
// BUG: This is creating new volume entries and not resetting each run. // BUG: This is creating new volume entries and not resetting each run.
IList<Volume> existingVolumes = _seriesRepository.GetVolumes(series.Id).ToList(); IList<Volume> existingVolumes = _seriesRepository.GetVolumes(series.Id).ToList();
//IList<Volume> existingVolumes = Task.Run(() => _seriesRepository.GetVolumesAsync(series.Id)).Result.ToList();
foreach (var info in infos) foreach (var info in infos)
{ {
var existingVolume = existingVolumes.SingleOrDefault(v => v.Number == info.Volumes); var existingVolume = existingVolumes.SingleOrDefault(v => v.Name == info.Volumes);
if (existingVolume != null) if (existingVolume != null)
{ {
// Temp let's overwrite all files (we need to enhance to update files) // Temp let's overwrite all files (we need to enhance to update files)
@ -168,7 +167,8 @@ namespace API.Services
{ {
var vol = new Volume() var vol = new Volume()
{ {
Number = info.Volumes, Name = info.Volumes,
Number = Int32.Parse(info.Volumes),
Files = new List<MangaFile>() Files = new List<MangaFile>()
{ {
new MangaFile() new MangaFile()
@ -188,46 +188,45 @@ namespace API.Services
return series; return series;
} }
public void ScanLibrary(LibraryDto library) public void ScanLibrary(int libraryId)
{ {
var library = Task.Run(() => _libraryRepository.GetLibraryForIdAsync(libraryId)).Result;
_scannedSeries = new ConcurrentDictionary<string, ConcurrentBag<ParserInfo>>(); _scannedSeries = new ConcurrentDictionary<string, ConcurrentBag<ParserInfo>>();
_logger.LogInformation($"Beginning scan on {library.Name}"); _logger.LogInformation($"Beginning scan on {library.Name}");
foreach (var folderPath in library.Folders) foreach (var folderPath in library.Folders)
{ {
try { try {
TraverseTreeParallelForEach(folderPath, (f) => TraverseTreeParallelForEach(folderPath.Path, (f) =>
{ {
// Exceptions are no-ops.
try try
{ {
Process(f); Process(f);
} }
catch (FileNotFoundException) {} catch (FileNotFoundException exception)
catch (IOException) {} {
catch (UnauthorizedAccessException) {} _logger.LogError(exception, "The file could not be found");
catch (SecurityException) {} }
}); });
} }
catch (ArgumentException ex) { catch (ArgumentException ex) {
_logger.LogError(ex, "The directory '{folderPath}' does not exist"); _logger.LogError(ex, $"The directory '{folderPath}' does not exist");
} }
} }
var filtered = _scannedSeries.Where(kvp => !kvp.Value.IsEmpty); var filtered = _scannedSeries.Where(kvp => !kvp.Value.IsEmpty);
var series = filtered.ToImmutableDictionary(v => v.Key, v => v.Value); var series = filtered.ToImmutableDictionary(v => v.Key, v => v.Value);
// Perform DB activities on ImmutableDictionary // Perform DB activities
var libraryEntity = _libraryRepository.GetLibraryForName(library.Name); library.Series = new List<Series>(); // Temp delete everything until we can mark items Unavailable
libraryEntity.Series = new List<Series>(); // Temp delete everything for testing
foreach (var seriesKey in series.Keys) foreach (var seriesKey in series.Keys)
{ {
var s = UpdateSeries(seriesKey, series[seriesKey].ToArray()); var s = UpdateSeries(seriesKey, series[seriesKey].ToArray());
Console.WriteLine($"Created/Updated series {s.Name}"); _logger.LogInformation($"Created/Updated series {s.Name}");
libraryEntity.Series.Add(s); library.Series.Add(s);
} }
_libraryRepository.Update(libraryEntity); _libraryRepository.Update(library);
if (_libraryRepository.SaveAll()) if (_libraryRepository.SaveAll())
{ {