mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-05-24 00:52:23 -04:00
Fixed a bug in ScanLibrary that caused duplicated Volumes. Implemented APIs for navigating down to Volume for webui.
This is rough code and needs to be polished and refactored.
This commit is contained in:
parent
380c3e7b3c
commit
c429c50ba2
@ -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<LibraryController> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -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<ActionResult<IEnumerable<LibraryDto>>> GetLibrariesForUser(string username)
|
||||
{
|
||||
return Ok(await _libraryRepository.GetLibrariesForUsernameAysnc(username));
|
||||
}
|
||||
|
||||
[HttpGet("series")]
|
||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId)
|
||||
{
|
||||
return Ok(await _seriesRepository.GetSeriesForLibraryIdAsync(libraryId));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
39
API/Controllers/SeriesController.cs
Normal file
39
API/Controllers/SeriesController.cs
Normal file
@ -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<SeriesController> _logger;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
private readonly ISeriesRepository _seriesRepository;
|
||||
|
||||
public SeriesController(ILogger<SeriesController> logger, IMapper mapper,
|
||||
ITaskScheduler taskScheduler, ISeriesRepository seriesRepository)
|
||||
{
|
||||
_logger = logger;
|
||||
_mapper = mapper;
|
||||
_taskScheduler = taskScheduler;
|
||||
_seriesRepository = seriesRepository;
|
||||
}
|
||||
|
||||
[HttpGet("{seriesId}")]
|
||||
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
|
||||
{
|
||||
return Ok(await _seriesRepository.GetSeriesByIdAsync(seriesId));
|
||||
}
|
||||
|
||||
[HttpGet("volumes")]
|
||||
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
||||
{
|
||||
return Ok(await _seriesRepository.GetVolumesAsync(seriesId));
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ namespace API.Controllers
|
||||
_libraryRepository = libraryRepository;
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("add-library")]
|
||||
public async Task<ActionResult> AddLibrary(CreateLibraryDto createLibraryDto)
|
||||
{
|
||||
|
@ -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; }
|
||||
|
13
API/DTOs/SeriesDto.cs
Normal file
13
API/DTOs/SeriesDto.cs
Normal file
@ -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; }
|
||||
}
|
||||
}
|
12
API/DTOs/VolumeDto.cs
Normal file
12
API/DTOs/VolumeDto.cs
Normal file
@ -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<string> Files { get; set; }
|
||||
}
|
||||
}
|
@ -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<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName)
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
|
491
API/Data/Migrations/20210101180935_AddedCoverImageToSeries.Designer.cs
generated
Normal file
491
API/Data/Migrations/20210101180935_AddedCoverImageToSeries.Designer.cs
generated
Normal file
@ -0,0 +1,491 @@
|
||||
// <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("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<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<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<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<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
|
||||
}
|
||||
}
|
||||
}
|
@ -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<string>(
|
||||
name: "CoverImage",
|
||||
table: "Series",
|
||||
type: "TEXT",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CoverImage",
|
||||
table: "Series");
|
||||
}
|
||||
}
|
||||
}
|
@ -200,6 +200,9 @@ namespace API.Data.Migrations
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("CoverImage")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("LibraryId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
|
@ -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<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId)
|
||||
{
|
||||
return await _context.Series
|
||||
.Where(series => series.LibraryId == libraryId)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId)
|
||||
{
|
||||
return await _context.Volume
|
||||
.Where(vol => vol.SeriesId == seriesId)
|
||||
.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)
|
||||
{
|
||||
return _context.Volume
|
||||
.Where(vol => vol.SeriesId == seriesId)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public async Task<SeriesDto> GetSeriesByIdAsync(int seriesId)
|
||||
{
|
||||
return await _context.Series.Where(x => x.Id == seriesId)
|
||||
.ProjectTo<SeriesDto>(_mapper.ConfigurationProvider).SingleAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -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; }
|
||||
|
@ -21,6 +21,7 @@ namespace API.Entities
|
||||
/// Summary information related to the Series
|
||||
/// </summary>
|
||||
public string Summary { get; set; }
|
||||
public string CoverImage { get; set; }
|
||||
public ICollection<Volume> Volumes { get; set; }
|
||||
|
||||
public Library Library { get; set; }
|
||||
|
@ -7,7 +7,7 @@ namespace API.Entities
|
||||
public int Id { get; set; }
|
||||
public string Number { get; set; }
|
||||
public ICollection<MangaFile> Files { get; set; }
|
||||
|
||||
|
||||
// Many-to-Many relationships
|
||||
public Series Series { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
|
@ -10,6 +10,12 @@ namespace API.Helpers
|
||||
public AutoMapperProfiles()
|
||||
{
|
||||
CreateMap<LibraryDto, Library>();
|
||||
|
||||
CreateMap<Volume, VolumeDto>()
|
||||
.ForMember(dest => dest.Files,
|
||||
opt => opt.MapFrom(src => src.Files.Select(x => x.FilePath).ToList()));
|
||||
|
||||
CreateMap<Series, SeriesDto>();
|
||||
|
||||
CreateMap<Library, LibraryDto>()
|
||||
.ForMember(dest => dest.Folders,
|
||||
|
@ -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<Library> GetLibraryForIdAsync(int libraryId);
|
||||
bool SaveAll();
|
||||
Library GetLibraryForName(string libraryName);
|
||||
Task<IEnumerable<LibraryDto>> GetLibrariesForUsernameAysnc(string userName);
|
||||
//Task<IEnumerable<Series>> GetSeriesForIdAsync(int libraryId);
|
||||
}
|
||||
}
|
@ -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<Series> GetSeriesByNameAsync(string name);
|
||||
Series GetSeriesByName(string name);
|
||||
bool SaveAll();
|
||||
Task<IEnumerable<SeriesDto>> GetSeriesForLibraryIdAsync(int libraryId);
|
||||
Task<IEnumerable<VolumeDto>> GetVolumesAsync(int seriesId);
|
||||
IEnumerable<VolumeDto> GetVolumesDto(int seriesId);
|
||||
IEnumerable<Volume> GetVolumes(int seriesId);
|
||||
Task<SeriesDto> GetSeriesByIdAsync(int seriesId);
|
||||
|
||||
}
|
||||
}
|
@ -135,6 +135,7 @@ namespace API.Services
|
||||
private Series UpdateSeries(string seriesName, ParserInfo[] infos)
|
||||
{
|
||||
var series = _seriesRepository.GetSeriesByName(seriesName);
|
||||
ICollection<Volume> volumes = new List<Volume>();;
|
||||
|
||||
if (series == null)
|
||||
{
|
||||
@ -146,24 +147,45 @@ namespace API.Services
|
||||
Summary = "",
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
ICollection<Volume> volumes = new List<Volume>();
|
||||
// BUG: This is creating new volume entries and not resetting each run.
|
||||
IEnumerable<Volume> 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<MangaFile>() {new MangaFile()
|
||||
// Temp let's overwrite all files (we need to enhance to update files)
|
||||
existingVolume.Files = new List<MangaFile>()
|
||||
{
|
||||
FilePath = info.File
|
||||
}}
|
||||
});
|
||||
new MangaFile()
|
||||
{
|
||||
FilePath = info.File
|
||||
}
|
||||
};
|
||||
volumes.Add(existingVolume);
|
||||
}
|
||||
else
|
||||
{
|
||||
var vol = new Volume()
|
||||
{
|
||||
Number = info.Volumes,
|
||||
Files = new List<MangaFile>()
|
||||
{
|
||||
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] = <value to be saved>;
|
||||
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.");
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user