diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs index e65e0e2dd..e06cba530 100644 --- a/API/Controllers/LibraryController.cs +++ b/API/Controllers/LibraryController.cs @@ -21,10 +21,11 @@ namespace API.Controllers private readonly IUserRepository _userRepository; private readonly IMapper _mapper; private readonly ITaskScheduler _taskScheduler; + private readonly ISeriesRepository _seriesRepository; public LibraryController(IDirectoryService directoryService, ILibraryRepository libraryRepository, ILogger logger, IUserRepository userRepository, - IMapper mapper, ITaskScheduler taskScheduler) + IMapper mapper, ITaskScheduler taskScheduler, ISeriesRepository seriesRepository) { _directoryService = directoryService; _libraryRepository = libraryRepository; @@ -32,6 +33,7 @@ namespace API.Controllers _userRepository = userRepository; _mapper = mapper; _taskScheduler = taskScheduler; + _seriesRepository = seriesRepository; } /// @@ -79,7 +81,7 @@ namespace API.Controllers return Ok(user); } - return BadRequest("Not Implemented"); + return BadRequest("There was a critical issue. Please try again."); } [Authorize(Policy = "RequireAdminRole")] @@ -91,8 +93,20 @@ namespace API.Controllers // 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(); } + + [HttpGet("libraries-for")] + public async Task>> GetLibrariesForUser(string username) + { + return Ok(await _libraryRepository.GetLibrariesForUsernameAysnc(username)); + } + + [HttpGet("series")] + public async Task>> GetSeriesForLibrary(int libraryId) + { + return Ok(await _seriesRepository.GetSeriesForLibraryIdAsync(libraryId)); + + } } } \ No newline at end of file diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs new file mode 100644 index 000000000..dbdfaf93e --- /dev/null +++ b/API/Controllers/SeriesController.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using API.DTOs; +using API.Interfaces; +using AutoMapper; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace API.Controllers +{ + public class SeriesController : BaseApiController + { + private readonly ILogger _logger; + private readonly IMapper _mapper; + private readonly ITaskScheduler _taskScheduler; + private readonly ISeriesRepository _seriesRepository; + + public SeriesController(ILogger logger, IMapper mapper, + ITaskScheduler taskScheduler, ISeriesRepository seriesRepository) + { + _logger = logger; + _mapper = mapper; + _taskScheduler = taskScheduler; + _seriesRepository = seriesRepository; + } + + [HttpGet("{seriesId}")] + public async Task> GetSeries(int seriesId) + { + return Ok(await _seriesRepository.GetSeriesByIdAsync(seriesId)); + } + + [HttpGet("volumes")] + public async Task>> GetVolumes(int seriesId) + { + return Ok(await _seriesRepository.GetVolumesAsync(seriesId)); + } + } +} \ No newline at end of file diff --git a/API/Controllers/UsersController.cs b/API/Controllers/UsersController.cs index 337d54959..f309ef0f5 100644 --- a/API/Controllers/UsersController.cs +++ b/API/Controllers/UsersController.cs @@ -23,6 +23,7 @@ namespace API.Controllers _libraryRepository = libraryRepository; } + [Authorize(Policy = "RequireAdminRole")] [HttpPost("add-library")] public async Task AddLibrary(CreateLibraryDto createLibraryDto) { diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs index 31e51e173..e23268269 100644 --- a/API/DTOs/LibraryDto.cs +++ b/API/DTOs/LibraryDto.cs @@ -5,6 +5,7 @@ namespace API.DTOs { public class LibraryDto { + public int Id { get; init; } public string Name { get; set; } public string CoverImage { get; set; } public LibraryType Type { get; set; } diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs new file mode 100644 index 000000000..8f13061bf --- /dev/null +++ b/API/DTOs/SeriesDto.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace API.DTOs +{ + public class SeriesDto + { + public int Id { get; set; } + public string Name { get; set; } + public string OriginalName { get; set; } + public string SortName { get; set; } + public string Summary { get; set; } + } +} \ No newline at end of file diff --git a/API/DTOs/VolumeDto.cs b/API/DTOs/VolumeDto.cs new file mode 100644 index 000000000..1753d4679 --- /dev/null +++ b/API/DTOs/VolumeDto.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace API.DTOs +{ + public class VolumeDto + { + public int Id { get; set; } + public string Number { get; set; } + public string CoverImage { get; set; } + public ICollection Files { get; set; } + } +} \ No newline at end of file diff --git a/API/Data/LibraryRepository.cs b/API/Data/LibraryRepository.cs index 3f77a0e15..a218cdd98 100644 --- a/API/Data/LibraryRepository.cs +++ b/API/Data/LibraryRepository.cs @@ -6,6 +6,7 @@ using API.Entities; using API.Interfaces; using AutoMapper; using AutoMapper.QueryableExtensions; +using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace API.Data @@ -45,6 +46,14 @@ namespace API.Data .Single(); } + public async Task> GetLibrariesForUsernameAysnc(string userName) + { + return await _context.Library + .Include(l => l.AppUsers) + .Where(library => library.AppUsers.Any(x => x.UserName == userName)) + .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); + } + public async Task> GetLibrariesAsync() { return await _context.Library diff --git a/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.Designer.cs b/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.Designer.cs new file mode 100644 index 000000000..a1a54360f --- /dev/null +++ b/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.Designer.cs @@ -0,0 +1,491 @@ +// +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("20210101180935_AddedCoverImageToSeries")] + partial class AddedCoverImageToSeries + { + 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("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + 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("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", 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"); + + 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 + } + } +} diff --git a/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.cs b/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.cs new file mode 100644 index 000000000..45e0fdc41 --- /dev/null +++ b/API/Data/Migrations/20210101180935_AddedCoverImageToSeries.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace API.Data.Migrations +{ + public partial class AddedCoverImageToSeries : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "CoverImage", + table: "Series", + type: "TEXT", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "CoverImage", + table: "Series"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index d1d3553a3..110c60774 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -200,6 +200,9 @@ namespace API.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("CoverImage") + .HasColumnType("TEXT"); + b.Property("LibraryId") .HasColumnType("INTEGER"); diff --git a/API/Data/SeriesRepository.cs b/API/Data/SeriesRepository.cs index b00239beb..fd109f45f 100644 --- a/API/Data/SeriesRepository.cs +++ b/API/Data/SeriesRepository.cs @@ -1,7 +1,11 @@ -using System.Linq; +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 @@ -9,10 +13,12 @@ namespace API.Data public class SeriesRepository : ISeriesRepository { private readonly DataContext _context; + private readonly IMapper _mapper; - public SeriesRepository(DataContext context) + public SeriesRepository(DataContext context, IMapper mapper) { _context = context; + _mapper = mapper; } public void Update(Series series) @@ -39,5 +45,39 @@ namespace API.Data { return _context.Series.SingleOrDefault(x => x.Name == name); } + + public async Task> GetSeriesForLibraryIdAsync(int libraryId) + { + return await _context.Series + .Where(series => series.LibraryId == libraryId) + .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); + } + + public async Task> GetVolumesAsync(int seriesId) + { + return await _context.Volume + .Where(vol => vol.SeriesId == seriesId) + .ProjectTo(_mapper.ConfigurationProvider).ToListAsync(); + } + + public IEnumerable GetVolumesDto(int seriesId) + { + return _context.Volume + .Where(vol => vol.SeriesId == seriesId) + .ProjectTo(_mapper.ConfigurationProvider).ToList(); + } + + public IEnumerable GetVolumes(int seriesId) + { + return _context.Volume + .Where(vol => vol.SeriesId == seriesId) + .ToList(); + } + + public async Task GetSeriesByIdAsync(int seriesId) + { + return await _context.Series.Where(x => x.Id == seriesId) + .ProjectTo(_mapper.ConfigurationProvider).SingleAsync(); + } } } \ No newline at end of file diff --git a/API/Entities/MangaFile.cs b/API/Entities/MangaFile.cs index 6818bbff7..685d399e9 100644 --- a/API/Entities/MangaFile.cs +++ b/API/Entities/MangaFile.cs @@ -4,8 +4,7 @@ { public int Id { get; set; } public string FilePath { get; set; } - //public string FileExtension { get; set; } - + // Relationship Mapping public Volume Volume { get; set; } public int VolumeId { get; set; } diff --git a/API/Entities/Series.cs b/API/Entities/Series.cs index 3c475ecdd..dd47e528d 100644 --- a/API/Entities/Series.cs +++ b/API/Entities/Series.cs @@ -21,6 +21,7 @@ namespace API.Entities /// Summary information related to the Series /// public string Summary { get; set; } + public string CoverImage { get; set; } public ICollection Volumes { get; set; } public Library Library { get; set; } diff --git a/API/Entities/Volume.cs b/API/Entities/Volume.cs index 2cc36453d..bb3638323 100644 --- a/API/Entities/Volume.cs +++ b/API/Entities/Volume.cs @@ -7,7 +7,7 @@ namespace API.Entities public int Id { get; set; } public string Number { get; set; } public ICollection Files { get; set; } - + // Many-to-Many relationships public Series Series { get; set; } public int SeriesId { get; set; } diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 08e16b68a..35d7bb951 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -10,6 +10,12 @@ namespace API.Helpers public AutoMapperProfiles() { CreateMap(); + + CreateMap() + .ForMember(dest => dest.Files, + opt => opt.MapFrom(src => src.Files.Select(x => x.FilePath).ToList())); + + CreateMap(); CreateMap() .ForMember(dest => dest.Folders, diff --git a/API/Interfaces/ILibraryRepository.cs b/API/Interfaces/ILibraryRepository.cs index 112a6b7d8..41be8ef40 100644 --- a/API/Interfaces/ILibraryRepository.cs +++ b/API/Interfaces/ILibraryRepository.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using API.DTOs; using API.Entities; +using Microsoft.AspNetCore.Mvc; namespace API.Interfaces { @@ -20,5 +21,7 @@ namespace API.Interfaces Task GetLibraryForIdAsync(int libraryId); bool SaveAll(); Library GetLibraryForName(string libraryName); + Task> GetLibrariesForUsernameAysnc(string userName); + //Task> GetSeriesForIdAsync(int libraryId); } } \ No newline at end of file diff --git a/API/Interfaces/ISeriesRepository.cs b/API/Interfaces/ISeriesRepository.cs index 2853b98c1..ddd085cec 100644 --- a/API/Interfaces/ISeriesRepository.cs +++ b/API/Interfaces/ISeriesRepository.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; +using API.DTOs; using API.Entities; namespace API.Interfaces @@ -10,5 +12,11 @@ namespace API.Interfaces Task GetSeriesByNameAsync(string name); Series GetSeriesByName(string name); bool SaveAll(); + Task> GetSeriesForLibraryIdAsync(int libraryId); + Task> GetVolumesAsync(int seriesId); + IEnumerable GetVolumesDto(int seriesId); + IEnumerable GetVolumes(int seriesId); + Task GetSeriesByIdAsync(int seriesId); + } } \ No newline at end of file diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index df9dda0be..7ff5c605b 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -135,6 +135,7 @@ namespace API.Services private Series UpdateSeries(string seriesName, ParserInfo[] infos) { var series = _seriesRepository.GetSeriesByName(seriesName); + ICollection volumes = new List();; if (series == null) { @@ -146,24 +147,45 @@ namespace API.Services Summary = "", }; } + - ICollection volumes = new List(); + // BUG: This is creating new volume entries and not resetting each run. + IEnumerable existingVolumes = _seriesRepository.GetVolumes(series.Id); foreach (var info in infos) { - volumes.Add(new Volume() + var existingVolume = existingVolumes.SingleOrDefault(v => v.Number == info.Volumes); + if (existingVolume != null) { - Number = info.Volumes, - Files = new List() {new MangaFile() + // Temp let's overwrite all files (we need to enhance to update files) + existingVolume.Files = new List() { - FilePath = info.File - }} - }); + new MangaFile() + { + FilePath = info.File + } + }; + volumes.Add(existingVolume); + } + else + { + var vol = new Volume() + { + Number = info.Volumes, + Files = new List() + { + new MangaFile() + { + FilePath = info.File + } + } + }; + volumes.Add(vol); + } + + Console.WriteLine($"Adding volume {volumes.Last().Number} with File: {info.File}"); } series.Volumes = volumes; - - - //_seriesRepository.Update(series); return series; } @@ -208,48 +230,14 @@ namespace API.Services } _libraryRepository.Update(libraryEntity); - - // This is throwing a DbUpdateConcurrencyException due to multiple threads modifying Library at one time. - try + + if (_libraryRepository.SaveAll()) { - if (_libraryRepository.SaveAll()) - { - _logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series."); - } - else - { - _logger.LogError("There was a critical error that resulted in a failed scan. Please rescan."); - } + _logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series."); } - catch (DbUpdateConcurrencyException ex) + else { - foreach (var entry in ex.Entries) - { - if (entry.Entity is Series) - { - var proposedValues = entry.CurrentValues; - var databaseValues = entry.GetDatabaseValues(); - - foreach (var property in proposedValues.Properties) - { - var proposedValue = proposedValues[property]; - var databaseValue = databaseValues[property]; - - // TODO: decide which value should be written to database - // proposedValues[property] = ; - Console.WriteLine($"Proposed ({proposedValue}) vs Database ({databaseValue})"); - } - - // Refresh original values to bypass next concurrency check - entry.OriginalValues.SetValues(databaseValues); - } - else - { - throw new NotSupportedException( - "Don't know how to handle concurrency conflicts for " - + entry.Metadata.Name); - } - } + _logger.LogError("There was a critical error that resulted in a failed scan. Please rescan."); }