mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
Refactored DB to be Series->Volumes->Chapters instead. All functionality that previously worked still works. Cleanup still needed.
This commit is contained in:
parent
a42e54a078
commit
53e85317f9
1
.gitignore
vendored
1
.gitignore
vendored
@ -450,3 +450,4 @@ appsettings.json
|
|||||||
/API/Hangfire-log.db
|
/API/Hangfire-log.db
|
||||||
cache/
|
cache/
|
||||||
/API/wwwroot/
|
/API/wwwroot/
|
||||||
|
/API/cache/
|
@ -62,37 +62,37 @@ namespace API.Tests.Services
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void GetOrderedChaptersTest()
|
public void GetOrderedChaptersTest()
|
||||||
{
|
{
|
||||||
var files = new List<MangaFile>()
|
// var files = new List<Chapter>()
|
||||||
{
|
// {
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 1
|
// Number = "1"
|
||||||
},
|
// },
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 2
|
// Chapter = 2
|
||||||
},
|
// },
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 0
|
// Chapter = 0
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
var expected = new List<MangaFile>()
|
// var expected = new List<MangaFile>()
|
||||||
{
|
// {
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 1
|
// Chapter = 1
|
||||||
},
|
// },
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 2
|
// Chapter = 2
|
||||||
},
|
// },
|
||||||
new()
|
// new()
|
||||||
{
|
// {
|
||||||
Chapter = 0
|
// Chapter = 0
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
Assert.NotStrictEqual(expected, _cacheService.GetOrderedChapters(files));
|
// Assert.NotStrictEqual(expected, _cacheService.GetOrderedChapters(files));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,27 +31,29 @@ namespace API.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("image")]
|
[HttpGet("image")]
|
||||||
public async Task<ActionResult<ImageDto>> GetImage(int volumeId, int page)
|
public async Task<ActionResult<ImageDto>> GetImage(int chapterId, int page)
|
||||||
{
|
{
|
||||||
// Temp let's iterate the directory each call to get next image
|
// Temp let's iterate the directory each call to get next image
|
||||||
var volume = await _cacheService.Ensure(volumeId);
|
var chapter = await _cacheService.Ensure(chapterId);
|
||||||
|
|
||||||
var (path, mangaFile) = _cacheService.GetCachedPagePath(volume, page);
|
if (chapter == null) return BadRequest("There was an issue finding image file for reading.");
|
||||||
|
|
||||||
|
var (path, mangaFile) = await _cacheService.GetCachedPagePath(chapter, page);
|
||||||
if (string.IsNullOrEmpty(path)) return BadRequest($"No such image for page {page}");
|
if (string.IsNullOrEmpty(path)) return BadRequest($"No such image for page {page}");
|
||||||
var file = await _directoryService.ReadImageAsync(path);
|
var file = await _directoryService.ReadImageAsync(path);
|
||||||
file.Page = page;
|
file.Page = page;
|
||||||
file.Chapter = mangaFile.Chapter;
|
//file.Chapter = chapter.Number;
|
||||||
file.MangaFileName = mangaFile.FilePath;
|
file.MangaFileName = mangaFile.FilePath;
|
||||||
|
|
||||||
return Ok(file);
|
return Ok(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("get-bookmark")]
|
[HttpGet("get-bookmark")]
|
||||||
public async Task<ActionResult<int>> GetBookmark(int volumeId)
|
public async Task<ActionResult<int>> GetBookmark(int chapterId)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
if (user.Progresses == null) return Ok(0);
|
if (user.Progresses == null) return Ok(0);
|
||||||
var progress = user.Progresses.SingleOrDefault(x => x.AppUserId == user.Id && x.VolumeId == volumeId);
|
var progress = user.Progresses.SingleOrDefault(x => x.AppUserId == user.Id && x.ChapterId == chapterId);
|
||||||
|
|
||||||
if (progress != null) return Ok(progress.PagesRead);
|
if (progress != null) return Ok(progress.PagesRead);
|
||||||
|
|
||||||
@ -62,12 +64,12 @@ namespace API.Controllers
|
|||||||
public async Task<ActionResult> Bookmark(BookmarkDto bookmarkDto)
|
public async Task<ActionResult> Bookmark(BookmarkDto bookmarkDto)
|
||||||
{
|
{
|
||||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||||
_logger.LogInformation($"Saving {user.UserName} progress for {bookmarkDto.VolumeId} to page {bookmarkDto.PageNum}");
|
_logger.LogInformation($"Saving {user.UserName} progress for Chapter {bookmarkDto.ChapterId} to page {bookmarkDto.PageNum}");
|
||||||
|
|
||||||
// TODO: Don't let user bookmark past total pages.
|
// TODO: Don't let user bookmark past total pages.
|
||||||
|
|
||||||
user.Progresses ??= new List<AppUserProgress>();
|
user.Progresses ??= new List<AppUserProgress>();
|
||||||
var userProgress = user.Progresses.SingleOrDefault(x => x.VolumeId == bookmarkDto.VolumeId && x.AppUserId == user.Id);
|
var userProgress = user.Progresses.SingleOrDefault(x => x.ChapterId == bookmarkDto.ChapterId && x.AppUserId == user.Id);
|
||||||
|
|
||||||
if (userProgress == null)
|
if (userProgress == null)
|
||||||
{
|
{
|
||||||
@ -77,13 +79,14 @@ namespace API.Controllers
|
|||||||
PagesRead = bookmarkDto.PageNum,
|
PagesRead = bookmarkDto.PageNum,
|
||||||
VolumeId = bookmarkDto.VolumeId,
|
VolumeId = bookmarkDto.VolumeId,
|
||||||
SeriesId = bookmarkDto.SeriesId,
|
SeriesId = bookmarkDto.SeriesId,
|
||||||
|
ChapterId = bookmarkDto.ChapterId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
userProgress.PagesRead = bookmarkDto.PageNum;
|
userProgress.PagesRead = bookmarkDto.PageNum;
|
||||||
userProgress.SeriesId = bookmarkDto.SeriesId;
|
userProgress.SeriesId = bookmarkDto.SeriesId;
|
||||||
|
userProgress.VolumeId = bookmarkDto.VolumeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
_unitOfWork.UserRepository.Update(user);
|
_unitOfWork.UserRepository.Update(user);
|
||||||
@ -93,7 +96,6 @@ namespace API.Controllers
|
|||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return BadRequest("Could not save progress");
|
return BadRequest("Could not save progress");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,11 @@ namespace API.Controllers
|
|||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns All volumes for a series with progress information and Chapters
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seriesId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
[HttpGet("volumes")]
|
[HttpGet("volumes")]
|
||||||
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
public async Task<ActionResult<IEnumerable<VolumeDto>>> GetVolumes(int seriesId)
|
||||||
{
|
{
|
||||||
@ -61,12 +66,11 @@ namespace API.Controllers
|
|||||||
return Ok(await _unitOfWork.SeriesRepository.GetVolumeDtoAsync(volumeId, user.Id));
|
return Ok(await _unitOfWork.SeriesRepository.GetVolumeDtoAsync(volumeId, user.Id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// [HttpGet("volume-files")]
|
[HttpGet("chapter")]
|
||||||
// public async Task<ActionResult<IEnumerable<MangaFileDto>>> GetMangaFiles(int volumeId)
|
public async Task<ActionResult<VolumeDto>> GetChapter(int chapterId)
|
||||||
// {
|
{
|
||||||
// var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
return Ok(await _unitOfWork.VolumeRepository.GetChapterDtoAsync(chapterId));
|
||||||
// return Ok(await _unitOfWork.SeriesRepository.GetVolumeMangaFileDtos(volumeId));
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
[Authorize(Policy = "RequireAdminRole")]
|
[Authorize(Policy = "RequireAdminRole")]
|
||||||
[HttpPost("scan")]
|
[HttpPost("scan")]
|
||||||
|
31
API/DTOs/ChapterDto.cs
Normal file
31
API/DTOs/ChapterDto.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace API.DTOs
|
||||||
|
{
|
||||||
|
public class ChapterDto
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||||
|
/// </summary>
|
||||||
|
public string Range { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Smallest number of the Range.
|
||||||
|
/// </summary>
|
||||||
|
public string Number { get; set; }
|
||||||
|
public byte[] CoverImage { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of pages in all MangaFiles
|
||||||
|
/// </summary>
|
||||||
|
public int Pages { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The files that represent this Chapter
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<MangaFileDto> Files { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Calculated at API time. Number of pages read for this Chapter for logged in user.
|
||||||
|
/// </summary>
|
||||||
|
public int PagesRead { get; set; }
|
||||||
|
public int VolumeId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,6 @@ namespace API.DTOs
|
|||||||
public class MangaFileDto
|
public class MangaFileDto
|
||||||
{
|
{
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; }
|
||||||
public int Chapter { get; set; }
|
|
||||||
public int NumberOfPages { get; set; }
|
public int NumberOfPages { get; set; }
|
||||||
public MangaFormat Format { get; set; }
|
public MangaFormat Format { get; set; }
|
||||||
|
|
||||||
|
@ -11,6 +11,6 @@ namespace API.DTOs
|
|||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
public int PagesRead { get; set; }
|
public int PagesRead { get; set; }
|
||||||
public ICollection<MangaFileDto> Files { get; set; }
|
public ICollection<ChapterDto> Chapters { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@
|
|||||||
public class BookmarkDto
|
public class BookmarkDto
|
||||||
{
|
{
|
||||||
public int VolumeId { get; init; }
|
public int VolumeId { get; init; }
|
||||||
|
public int ChapterId { get; init; }
|
||||||
public int PageNum { get; init; }
|
public int PageNum { get; init; }
|
||||||
public int SeriesId { get; init; }
|
public int SeriesId { get; init; }
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,11 @@ namespace API.Data
|
|||||||
}
|
}
|
||||||
public DbSet<Library> Library { get; set; }
|
public DbSet<Library> Library { get; set; }
|
||||||
public DbSet<Series> Series { get; set; }
|
public DbSet<Series> Series { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Chapter> Chapter { get; set; }
|
||||||
public DbSet<Volume> Volume { get; set; }
|
public DbSet<Volume> Volume { get; set; }
|
||||||
public DbSet<AppUser> AppUser { get; set; }
|
public DbSet<AppUser> AppUser { get; set; }
|
||||||
|
public DbSet<MangaFile> MangaFile { get; set; }
|
||||||
public DbSet<AppUserProgress> AppUserProgresses { get; set; }
|
public DbSet<AppUserProgress> AppUserProgresses { get; set; }
|
||||||
public DbSet<AppUserRating> AppUserRating { get; set; }
|
public DbSet<AppUserRating> AppUserRating { get; set; }
|
||||||
public DbSet<ServerSetting> ServerSetting { get; set; }
|
public DbSet<ServerSetting> ServerSetting { get; set; }
|
||||||
|
688
API/Data/Migrations/20210128143348_SeriesVolumeChapterChange.Designer.cs
generated
Normal file
688
API/Data/Migrations/20210128143348_SeriesVolumeChapterChange.Designer.cs
generated
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
// <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("20210128143348_SeriesVolumeChapterChange")]
|
||||||
|
partial class SeriesVolumeChapterChange
|
||||||
|
{
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PagesRead")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.ToTable("AppUserProgresses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Review")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.ToTable("AppUserRating");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Number")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Range")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastScanned")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
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<int>("Chapter")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int?>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumberOfPages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("MangaFile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Series", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
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<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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.ServerSetting", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ServerSetting");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSpecial")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Progresses")
|
||||||
|
.HasForeignKey("AppUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AppUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Ratings")
|
||||||
|
.HasForeignKey("AppUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AppUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.Volume", "Volume")
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", null)
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ChapterId");
|
||||||
|
|
||||||
|
b.HasOne("API.Entities.Volume", "Volume")
|
||||||
|
.WithMany()
|
||||||
|
.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("Progresses");
|
||||||
|
|
||||||
|
b.Navigation("Ratings");
|
||||||
|
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Chapters");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
111
API/Data/Migrations/20210128143348_SeriesVolumeChapterChange.cs
Normal file
111
API/Data/Migrations/20210128143348_SeriesVolumeChapterChange.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class SeriesVolumeChapterChange : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "IsSpecial",
|
||||||
|
table: "Volume",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: false);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "LastScanned",
|
||||||
|
table: "FolderPath",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "AppUserProgresses",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "Chapter",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
Range = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Number = table.Column<string>(type: "TEXT", nullable: true),
|
||||||
|
Created = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
LastModified = table.Column<DateTime>(type: "TEXT", nullable: false),
|
||||||
|
CoverImage = table.Column<byte[]>(type: "BLOB", nullable: true),
|
||||||
|
Pages = table.Column<int>(type: "INTEGER", nullable: false),
|
||||||
|
VolumeId = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_Chapter", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_Chapter_Volume_VolumeId",
|
||||||
|
column: x => x.VolumeId,
|
||||||
|
principalTable: "Volume",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MangaFile_ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "ChapterId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Chapter_VolumeId",
|
||||||
|
table: "Chapter",
|
||||||
|
column: "VolumeId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "ChapterId",
|
||||||
|
principalTable: "Chapter",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "Chapter");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_MangaFile_ChapterId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "IsSpecial",
|
||||||
|
table: "Volume");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "LastScanned",
|
||||||
|
table: "FolderPath");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "AppUserProgresses");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
676
API/Data/Migrations/20210128201832_MangaFileChapterRelationship.Designer.cs
generated
Normal file
676
API/Data/Migrations/20210128201832_MangaFileChapterRelationship.Designer.cs
generated
Normal file
@ -0,0 +1,676 @@
|
|||||||
|
// <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("20210128201832_MangaFileChapterRelationship")]
|
||||||
|
partial class MangaFileChapterRelationship
|
||||||
|
{
|
||||||
|
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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("PagesRead")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.ToTable("AppUserProgresses");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("AppUserId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Rating")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Review")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AppUserId");
|
||||||
|
|
||||||
|
b.ToTable("AppUserRating");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Number")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Range")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastScanned")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
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<int>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("FilePath")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Format")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("NumberOfPages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
|
b.ToTable("MangaFile");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Series", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
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<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
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.ServerSetting", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Key")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<uint>("RowVersion")
|
||||||
|
.IsConcurrencyToken()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Value")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Key");
|
||||||
|
|
||||||
|
b.ToTable("ServerSetting");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSpecial")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Number")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.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.AppUserProgress", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Progresses")
|
||||||
|
.HasForeignKey("AppUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AppUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.AppUserRating", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||||
|
.WithMany("Ratings")
|
||||||
|
.HasForeignKey("AppUserId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("AppUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.Volume", "Volume")
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
|
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.Chapter", "Chapter")
|
||||||
|
.WithMany("Files")
|
||||||
|
.HasForeignKey("ChapterId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Progresses");
|
||||||
|
|
||||||
|
b.Navigation("Ratings");
|
||||||
|
|
||||||
|
b.Navigation("UserRoles");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
|
||||||
|
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("Chapters");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace API.Data.Migrations
|
||||||
|
{
|
||||||
|
public partial class MangaFileChapterRelationship : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_MangaFile_Volume_VolumeId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_MangaFile_VolumeId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Chapter",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "VolumeId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER",
|
||||||
|
oldNullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "ChapterId",
|
||||||
|
principalTable: "Chapter",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<int>(
|
||||||
|
name: "ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: true,
|
||||||
|
oldClrType: typeof(int),
|
||||||
|
oldType: "INTEGER");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Chapter",
|
||||||
|
table: "MangaFile",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "VolumeId",
|
||||||
|
table: "MangaFile",
|
||||||
|
type: "INTEGER",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_MangaFile_VolumeId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "VolumeId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_MangaFile_Chapter_ChapterId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "ChapterId",
|
||||||
|
principalTable: "Chapter",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_MangaFile_Volume_VolumeId",
|
||||||
|
table: "MangaFile",
|
||||||
|
column: "VolumeId",
|
||||||
|
principalTable: "Volume",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -127,6 +127,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<int>("AppUserId")
|
b.Property<int>("AppUserId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ChapterId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("PagesRead")
|
b.Property<int>("PagesRead")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -183,12 +186,49 @@ namespace API.Data.Migrations
|
|||||||
b.ToTable("AspNetUserRoles");
|
b.ToTable("AspNetUserRoles");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte[]>("CoverImage")
|
||||||
|
.HasColumnType("BLOB");
|
||||||
|
|
||||||
|
b.Property<DateTime>("Created")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastModified")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Number")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("Pages")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Range")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("VolumeId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("VolumeId");
|
||||||
|
|
||||||
|
b.ToTable("Chapter");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime>("LastScanned")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<int>("LibraryId")
|
b.Property<int>("LibraryId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
@ -234,7 +274,7 @@ namespace API.Data.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("Chapter")
|
b.Property<int>("ChapterId")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<string>("FilePath")
|
b.Property<string>("FilePath")
|
||||||
@ -246,12 +286,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<int>("NumberOfPages")
|
b.Property<int>("NumberOfPages")
|
||||||
.HasColumnType("INTEGER");
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<int>("VolumeId")
|
|
||||||
.HasColumnType("INTEGER");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("VolumeId");
|
b.HasIndex("ChapterId");
|
||||||
|
|
||||||
b.ToTable("MangaFile");
|
b.ToTable("MangaFile");
|
||||||
});
|
});
|
||||||
@ -325,6 +362,9 @@ namespace API.Data.Migrations
|
|||||||
b.Property<DateTime>("Created")
|
b.Property<DateTime>("Created")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsSpecial")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
b.Property<DateTime>("LastModified")
|
b.Property<DateTime>("LastModified")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
@ -487,6 +527,17 @@ namespace API.Data.Migrations
|
|||||||
b.Navigation("User");
|
b.Navigation("User");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("API.Entities.Volume", "Volume")
|
||||||
|
.WithMany("Chapters")
|
||||||
|
.HasForeignKey("VolumeId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Volume");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
modelBuilder.Entity("API.Entities.FolderPath", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Library", "Library")
|
b.HasOne("API.Entities.Library", "Library")
|
||||||
@ -500,13 +551,13 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("API.Entities.MangaFile", b =>
|
modelBuilder.Entity("API.Entities.MangaFile", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("API.Entities.Volume", "Volume")
|
b.HasOne("API.Entities.Chapter", "Chapter")
|
||||||
.WithMany("Files")
|
.WithMany("Files")
|
||||||
.HasForeignKey("VolumeId")
|
.HasForeignKey("ChapterId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("Volume");
|
b.Navigation("Chapter");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Series", b =>
|
modelBuilder.Entity("API.Entities.Series", b =>
|
||||||
@ -596,6 +647,11 @@ namespace API.Data.Migrations
|
|||||||
b.Navigation("UserRoles");
|
b.Navigation("UserRoles");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("API.Entities.Chapter", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Files");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Library", b =>
|
modelBuilder.Entity("API.Entities.Library", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Folders");
|
b.Navigation("Folders");
|
||||||
@ -610,7 +666,7 @@ namespace API.Data.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("API.Entities.Volume", b =>
|
modelBuilder.Entity("API.Entities.Volume", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("Files");
|
b.Navigation("Chapters");
|
||||||
});
|
});
|
||||||
#pragma warning restore 612, 618
|
#pragma warning restore 612, 618
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,7 @@ namespace API.Data
|
|||||||
{
|
{
|
||||||
var volumes = await _context.Volume
|
var volumes = await _context.Volume
|
||||||
.Where(vol => vol.SeriesId == seriesId)
|
.Where(vol => vol.SeriesId == seriesId)
|
||||||
|
.Include(vol => vol.Chapters)
|
||||||
.OrderBy(volume => volume.Number)
|
.OrderBy(volume => volume.Number)
|
||||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@ -98,7 +99,8 @@ namespace API.Data
|
|||||||
{
|
{
|
||||||
return _context.Volume
|
return _context.Volume
|
||||||
.Where(vol => vol.SeriesId == seriesId)
|
.Where(vol => vol.SeriesId == seriesId)
|
||||||
.Include(vol => vol.Files)
|
.Include(vol => vol.Chapters)
|
||||||
|
.ThenInclude(c => c.Files)
|
||||||
.OrderBy(vol => vol.Number)
|
.OrderBy(vol => vol.Number)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
@ -118,7 +120,8 @@ namespace API.Data
|
|||||||
public async Task<Volume> GetVolumeAsync(int volumeId)
|
public async Task<Volume> GetVolumeAsync(int volumeId)
|
||||||
{
|
{
|
||||||
return await _context.Volume
|
return await _context.Volume
|
||||||
.Include(vol => vol.Files)
|
.Include(vol => vol.Chapters)
|
||||||
|
.ThenInclude(c => c.Files)
|
||||||
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
.SingleOrDefaultAsync(vol => vol.Id == volumeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,14 +129,15 @@ namespace API.Data
|
|||||||
{
|
{
|
||||||
var volume = await _context.Volume
|
var volume = await _context.Volume
|
||||||
.Where(vol => vol.Id == volumeId)
|
.Where(vol => vol.Id == volumeId)
|
||||||
.Include(vol => vol.Files)
|
.Include(vol => vol.Chapters)
|
||||||
|
.ThenInclude(c => c.Files)
|
||||||
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
.ProjectTo<VolumeDto>(_mapper.ConfigurationProvider)
|
||||||
.SingleAsync(vol => vol.Id == volumeId);
|
.SingleAsync(vol => vol.Id == volumeId);
|
||||||
|
|
||||||
var volumeList = new List<VolumeDto>() {volume};
|
var volumeList = new List<VolumeDto>() {volume};
|
||||||
await AddVolumeModifiers(userId, volumeList);
|
await AddVolumeModifiers(userId, volumeList);
|
||||||
|
|
||||||
volumeList[0].Files = volumeList[0].Files.OrderBy(f => f.Chapter).ToList();
|
//TODO: volumeList[0].Files = volumeList[0].Files.OrderBy(f => f.Chapter).ToList();
|
||||||
|
|
||||||
return volumeList[0];
|
return volumeList[0];
|
||||||
}
|
}
|
||||||
@ -199,6 +203,11 @@ namespace API.Data
|
|||||||
|
|
||||||
foreach (var v in volumes)
|
foreach (var v in volumes)
|
||||||
{
|
{
|
||||||
|
foreach (var c in v.Chapters)
|
||||||
|
{
|
||||||
|
c.PagesRead = userProgress.Where(p => p.ChapterId == c.Id).Sum(p => p.PagesRead);
|
||||||
|
}
|
||||||
|
|
||||||
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
v.PagesRead = userProgress.Where(p => p.VolumeId == v.Id).Sum(p => p.PagesRead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ namespace API.Data
|
|||||||
public IUserRepository UserRepository => new UserRepository(_context, _userManager);
|
public IUserRepository UserRepository => new UserRepository(_context, _userManager);
|
||||||
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
public ILibraryRepository LibraryRepository => new LibraryRepository(_context, _mapper);
|
||||||
|
|
||||||
|
public IVolumeRepository VolumeRepository => new VolumeRepository(_context, _mapper);
|
||||||
|
|
||||||
public async Task<bool> Complete()
|
public async Task<bool> Complete()
|
||||||
{
|
{
|
||||||
return await _context.SaveChangesAsync() > 0;
|
return await _context.SaveChangesAsync() > 0;
|
||||||
|
61
API/Data/VolumeRepository.cs
Normal file
61
API/Data/VolumeRepository.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.DTOs;
|
||||||
|
using API.Entities;
|
||||||
|
using API.Interfaces;
|
||||||
|
using AutoMapper;
|
||||||
|
using AutoMapper.QueryableExtensions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace API.Data
|
||||||
|
{
|
||||||
|
public class VolumeRepository : IVolumeRepository
|
||||||
|
{
|
||||||
|
private readonly DataContext _context;
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
public VolumeRepository(DataContext context, IMapper mapper)
|
||||||
|
{
|
||||||
|
_context = context;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Volume volume)
|
||||||
|
{
|
||||||
|
_context.Entry(volume).State = EntityState.Modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a Chapter for an Id. Includes linked <see cref="MangaFile"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chapterId"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<Chapter> GetChapterAsync(int chapterId)
|
||||||
|
{
|
||||||
|
return await _context.Chapter
|
||||||
|
.Include(c => c.Files)
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ChapterDto> GetChapterDtoAsync(int chapterId)
|
||||||
|
{
|
||||||
|
var chapter = await _context.Chapter
|
||||||
|
.Include(c => c.Files)
|
||||||
|
.ProjectTo<ChapterDto>(_mapper.ConfigurationProvider)
|
||||||
|
.AsNoTracking()
|
||||||
|
.SingleOrDefaultAsync(c => c.Id == chapterId);
|
||||||
|
|
||||||
|
return chapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IList<MangaFile>> GetFilesForChapter(int chapterId)
|
||||||
|
{
|
||||||
|
return await _context.MangaFile
|
||||||
|
.Where(c => chapterId == c.Id)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents the progress a single user has on a given Volume.
|
/// Represents the progress a single user has on a given Volume. Progress is realistically tracked against the Volume's chapters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AppUserProgress
|
public class AppUserProgress
|
||||||
{
|
{
|
||||||
@ -11,6 +11,8 @@ namespace API.Entities
|
|||||||
public int VolumeId { get; set; }
|
public int VolumeId { get; set; }
|
||||||
public int SeriesId { get; set; }
|
public int SeriesId { get; set; }
|
||||||
|
|
||||||
|
public int ChapterId { get; set; }
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
public AppUser AppUser { get; set; }
|
public AppUser AppUser { get; set; }
|
||||||
public int AppUserId { get; set; }
|
public int AppUserId { get; set; }
|
||||||
|
35
API/Entities/Chapter.cs
Normal file
35
API/Entities/Chapter.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using API.Entities.Interfaces;
|
||||||
|
|
||||||
|
namespace API.Entities
|
||||||
|
{
|
||||||
|
public class Chapter : IEntityDate
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Range of numbers. Chapter 2-4 -> "2-4". Chapter 2 -> "2".
|
||||||
|
/// </summary>
|
||||||
|
public string Range { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Smallest number of the Range. Can be a partial like Chapter 4.5
|
||||||
|
/// </summary>
|
||||||
|
public string Number { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// The files that represent this Chapter
|
||||||
|
/// </summary>
|
||||||
|
public ICollection<MangaFile> Files { get; set; }
|
||||||
|
public DateTime Created { get; set; }
|
||||||
|
public DateTime LastModified { get; set; }
|
||||||
|
public byte[] CoverImage { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of pages in all MangaFiles
|
||||||
|
/// </summary>
|
||||||
|
public int Pages { get; set; }
|
||||||
|
|
||||||
|
// Relationships
|
||||||
|
public Volume Volume { get; set; }
|
||||||
|
public int VolumeId { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,18 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace API.Entities
|
namespace API.Entities
|
||||||
{
|
{
|
||||||
public class FolderPath
|
public class FolderPath
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Path { get; set; }
|
public string Path { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Used when scanning to see if we can skip if nothing has changed.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime LastScanned { get; set; }
|
||||||
|
|
||||||
|
// Relationship
|
||||||
public Library Library { get; set; }
|
public Library Library { get; set; }
|
||||||
public int LibraryId { get; set; }
|
public int LibraryId { get; set; }
|
||||||
}
|
}
|
||||||
|
@ -9,18 +9,14 @@ namespace API.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string FilePath { get; set; }
|
public string FilePath { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to track if multiple MangaFiles (archives) represent a single Volume. If only one volume file, this will be 0.
|
|
||||||
/// </summary>
|
|
||||||
public int Chapter { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// Number of pages for the given file
|
/// Number of pages for the given file
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int NumberOfPages { get; set; }
|
public int NumberOfPages { get; set; }
|
||||||
public MangaFormat Format { get; set; }
|
public MangaFormat Format { get; set; }
|
||||||
|
|
||||||
// Relationship Mapping
|
// Relationship Mapping
|
||||||
public Volume Volume { get; set; }
|
public Chapter Chapter { get; set; }
|
||||||
public int VolumeId { get; set; }
|
public int ChapterId { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,14 +12,14 @@ namespace API.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Original Japanese Name
|
|
||||||
/// </summary>
|
|
||||||
public string OriginalName { get; set; }
|
|
||||||
/// <summary>
|
|
||||||
/// The name used to sort the Series. By default, will be the same as Name.
|
/// The name used to sort the Series. By default, will be the same as Name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string SortName { get; set; }
|
public string SortName { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Original Name on disk. Not exposed to UI.
|
||||||
|
/// </summary>
|
||||||
|
public string OriginalName { get; set; }
|
||||||
|
/// <summary>
|
||||||
/// Summary information related to the Series
|
/// Summary information related to the Series
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Summary { get; set; }
|
public string Summary { get; set; }
|
||||||
|
@ -9,12 +9,17 @@ namespace API.Entities
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public int Number { get; set; }
|
public int Number { get; set; }
|
||||||
public ICollection<MangaFile> Files { get; set; }
|
public ICollection<Chapter> Chapters { get; set; }
|
||||||
public DateTime Created { get; set; }
|
public DateTime Created { get; set; }
|
||||||
public DateTime LastModified { get; set; }
|
public DateTime LastModified { get; set; }
|
||||||
public byte[] CoverImage { get; set; }
|
public byte[] CoverImage { get; set; }
|
||||||
public int Pages { get; set; }
|
public int Pages { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Side story that is linked to the original Series. Omake, One Shot, etc.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsSpecial { get; set; } = false;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Relationships
|
// Relationships
|
||||||
|
@ -51,7 +51,8 @@ namespace API.Extensions
|
|||||||
if (file.Directory == null) continue;
|
if (file.Directory == null) continue;
|
||||||
var newName = $"{file.Directory.Name}_{file.Name}";
|
var newName = $"{file.Directory.Name}_{file.Name}";
|
||||||
var newPath = Path.Join(root.FullName, newName);
|
var newPath = Path.Join(root.FullName, newName);
|
||||||
file.MoveTo(newPath);
|
if (!File.Exists(newPath)) file.MoveTo(newPath);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ namespace API.Helpers
|
|||||||
|
|
||||||
CreateMap<MangaFile, MangaFileDto>();
|
CreateMap<MangaFile, MangaFileDto>();
|
||||||
|
|
||||||
|
CreateMap<Chapter, ChapterDto>();
|
||||||
|
|
||||||
CreateMap<Series, SeriesDto>();
|
CreateMap<Series, SeriesDto>();
|
||||||
|
|
||||||
CreateMap<Library, LibraryDto>()
|
CreateMap<Library, LibraryDto>()
|
||||||
|
@ -6,12 +6,12 @@ namespace API.Interfaces
|
|||||||
public interface ICacheService
|
public interface ICacheService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ensures the cache is created for the given volume and if not, will create it. Should be called before any other
|
/// Ensures the cache is created for the given chapter and if not, will create it. Should be called before any other
|
||||||
/// cache operations (except cleanup).
|
/// cache operations (except cleanup).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="volumeId"></param>
|
/// <param name="chapterId"></param>
|
||||||
/// <returns>Volume for the passed volumeId. Side-effect from ensuring cache.</returns>
|
/// <returns>Chapter for the passed chapterId. Side-effect from ensuring cache.</returns>
|
||||||
Task<Volume> Ensure(int volumeId);
|
Task<Chapter> Ensure(int chapterId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Clears cache directory of all folders and files.
|
/// Clears cache directory of all folders and files.
|
||||||
@ -28,11 +28,11 @@ namespace API.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the absolute path of a cached page.
|
/// Returns the absolute path of a cached page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="volume"></param>
|
/// <param name="chapter">Chapter entity with Files populated.</param>
|
||||||
/// <param name="page">Page number to look for</param>
|
/// <param name="page">Page number to look for</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
(string path, MangaFile file) GetCachedPagePath(Volume volume, int page);
|
Task<(string path, MangaFile file)> GetCachedPagePath(Chapter chapter, int page);
|
||||||
|
|
||||||
bool CacheDirectoryIsAccessible();
|
void EnsureCacheDirectory();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -7,6 +7,7 @@ namespace API.Interfaces
|
|||||||
ISeriesRepository SeriesRepository { get; }
|
ISeriesRepository SeriesRepository { get; }
|
||||||
IUserRepository UserRepository { get; }
|
IUserRepository UserRepository { get; }
|
||||||
ILibraryRepository LibraryRepository { get; }
|
ILibraryRepository LibraryRepository { get; }
|
||||||
|
IVolumeRepository VolumeRepository { get; }
|
||||||
Task<bool> Complete();
|
Task<bool> Complete();
|
||||||
bool HasChanges();
|
bool HasChanges();
|
||||||
}
|
}
|
||||||
|
15
API/Interfaces/IVolumeRepository.cs
Normal file
15
API/Interfaces/IVolumeRepository.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using API.DTOs;
|
||||||
|
using API.Entities;
|
||||||
|
|
||||||
|
namespace API.Interfaces
|
||||||
|
{
|
||||||
|
public interface IVolumeRepository
|
||||||
|
{
|
||||||
|
void Update(Volume volume);
|
||||||
|
Task<Chapter> GetChapterAsync(int chapterId);
|
||||||
|
Task<ChapterDto> GetChapterDtoAsync(int chapterId);
|
||||||
|
Task<IList<MangaFile>> GetFilesForChapter(int chapterId);
|
||||||
|
}
|
||||||
|
}
|
@ -161,7 +161,7 @@ namespace API.Services
|
|||||||
var needsFlattening = ArchiveNeedsFlattening(archive);
|
var needsFlattening = ArchiveNeedsFlattening(archive);
|
||||||
if (!archive.HasFiles() && !needsFlattening) return;
|
if (!archive.HasFiles() && !needsFlattening) return;
|
||||||
|
|
||||||
archive.ExtractToDirectory(extractPath);
|
archive.ExtractToDirectory(extractPath, true);
|
||||||
_logger.LogDebug($"Extracted archive to {extractPath} in {sw.ElapsedMilliseconds} milliseconds.");
|
_logger.LogDebug($"Extracted archive to {extractPath} in {sw.ElapsedMilliseconds} milliseconds.");
|
||||||
|
|
||||||
if (needsFlattening)
|
if (needsFlattening)
|
||||||
|
@ -18,7 +18,7 @@ namespace API.Services
|
|||||||
private readonly IArchiveService _archiveService;
|
private readonly IArchiveService _archiveService;
|
||||||
private readonly IDirectoryService _directoryService;
|
private readonly IDirectoryService _directoryService;
|
||||||
private readonly NumericComparer _numericComparer;
|
private readonly NumericComparer _numericComparer;
|
||||||
public static readonly string CacheDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "../cache/"));
|
public static readonly string CacheDirectory = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), "cache/"));
|
||||||
|
|
||||||
public CacheService(ILogger<CacheService> logger, IUnitOfWork unitOfWork, IArchiveService archiveService, IDirectoryService directoryService)
|
public CacheService(ILogger<CacheService> logger, IUnitOfWork unitOfWork, IArchiveService archiveService, IDirectoryService directoryService)
|
||||||
{
|
{
|
||||||
@ -29,39 +29,35 @@ namespace API.Services
|
|||||||
_numericComparer = new NumericComparer();
|
_numericComparer = new NumericComparer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CacheDirectoryIsAccessible()
|
public void EnsureCacheDirectory()
|
||||||
{
|
{
|
||||||
_logger.LogDebug($"Checking if valid Cache directory: {CacheDirectory}");
|
_logger.LogDebug($"Checking if valid Cache directory: {CacheDirectory}");
|
||||||
var di = new DirectoryInfo(CacheDirectory);
|
var di = new DirectoryInfo(CacheDirectory);
|
||||||
return di.Exists;
|
if (!di.Exists)
|
||||||
|
{
|
||||||
|
_logger.LogError($"Cache directory {CacheDirectory} is not accessible or does not exist. Creating...");
|
||||||
|
Directory.CreateDirectory(CacheDirectory);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Volume> Ensure(int volumeId)
|
public async Task<Chapter> Ensure(int chapterId)
|
||||||
{
|
{
|
||||||
if (!CacheDirectoryIsAccessible())
|
EnsureCacheDirectory();
|
||||||
{
|
Chapter chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Volume volume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId);
|
|
||||||
|
|
||||||
foreach (var file in volume.Files)
|
foreach (var file in chapter.Files)
|
||||||
{
|
{
|
||||||
var extractPath = GetVolumeCachePath(volumeId, file);
|
var extractPath = GetCachePath(chapterId, file);
|
||||||
_archiveService.ExtractArchive(file.FilePath, extractPath);
|
_archiveService.ExtractArchive(file.FilePath, extractPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return volume;
|
return chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup()
|
public void Cleanup()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Performing cleanup of Cache directory");
|
_logger.LogInformation("Performing cleanup of Cache directory");
|
||||||
|
EnsureCacheDirectory();
|
||||||
if (!CacheDirectoryIsAccessible())
|
|
||||||
{
|
|
||||||
_logger.LogError($"Cache directory {CacheDirectory} is not accessible or does not exist.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DirectoryInfo di = new DirectoryInfo(CacheDirectory);
|
DirectoryInfo di = new DirectoryInfo(CacheDirectory);
|
||||||
|
|
||||||
@ -79,6 +75,7 @@ namespace API.Services
|
|||||||
|
|
||||||
public void CleanupVolumes(int[] volumeIds)
|
public void CleanupVolumes(int[] volumeIds)
|
||||||
{
|
{
|
||||||
|
// TODO: Fix this code to work with chapters
|
||||||
_logger.LogInformation($"Running Cache cleanup on Volumes");
|
_logger.LogInformation($"Running Cache cleanup on Volumes");
|
||||||
|
|
||||||
foreach (var volume in volumeIds)
|
foreach (var volume in volumeIds)
|
||||||
@ -96,13 +93,19 @@ namespace API.Services
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
public string GetVolumeCachePath(int volumeId, MangaFile file)
|
/// <summary>
|
||||||
|
/// Returns the cache path for a given Chapter. Should be cacheDirectory/{chapterId}/
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="chapterId"></param>
|
||||||
|
/// <param name="file"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string GetCachePath(int chapterId, MangaFile file)
|
||||||
{
|
{
|
||||||
var extractPath = Path.GetFullPath(Path.Join(Directory.GetCurrentDirectory(), $"../cache/{volumeId}/"));
|
var extractPath = Path.GetFullPath(Path.Join(CacheDirectory, $"{chapterId}/"));
|
||||||
if (file.Chapter > 0)
|
// if (file.Chapter != null)
|
||||||
{
|
// {
|
||||||
extractPath = Path.Join(extractPath, file.Chapter + "");
|
// extractPath = Path.Join(extractPath, chapterId + "");
|
||||||
}
|
// }
|
||||||
return extractPath;
|
return extractPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,20 +113,20 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
// BUG: This causes a problem because total pages on a volume assumes "specials" to be there
|
// BUG: This causes a problem because total pages on a volume assumes "specials" to be there
|
||||||
//return files.OrderBy(f => f.Chapter).Where(f => f.Chapter > 0 || f.Volume.Number != 0);
|
//return files.OrderBy(f => f.Chapter).Where(f => f.Chapter > 0 || f.Volume.Number != 0);
|
||||||
return files.OrderBy(f => f.Chapter, new ChapterSortComparer());
|
return files;
|
||||||
|
//return files.OrderBy(f => f.Chapter, new ChapterSortComparer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public (string path, MangaFile file) GetCachedPagePath(Volume volume, int page)
|
public async Task<(string path, MangaFile file)> GetCachedPagePath(Chapter chapter, int page)
|
||||||
{
|
{
|
||||||
// Calculate what chapter the page belongs to
|
// Calculate what chapter the page belongs to
|
||||||
var pagesSoFar = 0;
|
var pagesSoFar = 0;
|
||||||
// Do not allow chapters with 0, as those are specials and break ordering for reading.
|
var chapterFiles = chapter.Files ?? await _unitOfWork.VolumeRepository.GetFilesForChapter(chapter.Id);
|
||||||
var orderedChapters = GetOrderedChapters(volume.Files);
|
foreach (var mangaFile in chapterFiles)
|
||||||
foreach (var mangaFile in orderedChapters)
|
|
||||||
{
|
{
|
||||||
if (page + 1 < (mangaFile.NumberOfPages + pagesSoFar))
|
if (page + 1 < (mangaFile.NumberOfPages + pagesSoFar))
|
||||||
{
|
{
|
||||||
var path = GetVolumeCachePath(volume.Id, mangaFile);
|
var path = GetCachePath(chapter.Id, mangaFile);
|
||||||
var files = _directoryService.GetFiles(path);
|
var files = _directoryService.GetFiles(path);
|
||||||
Array.Sort(files, _numericComparer);
|
Array.Sort(files, _numericComparer);
|
||||||
|
|
||||||
@ -132,6 +135,7 @@ namespace API.Services
|
|||||||
|
|
||||||
pagesSoFar += mangaFile.NumberOfPages;
|
pagesSoFar += mangaFile.NumberOfPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ("", null);
|
return ("", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,12 +8,19 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using API.DTOs;
|
using API.DTOs;
|
||||||
using API.Interfaces;
|
using API.Interfaces;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using NetVips;
|
using NetVips;
|
||||||
|
|
||||||
namespace API.Services
|
namespace API.Services
|
||||||
{
|
{
|
||||||
public class DirectoryService : IDirectoryService
|
public class DirectoryService : IDirectoryService
|
||||||
{
|
{
|
||||||
|
private readonly ILogger<DirectoryService> _logger;
|
||||||
|
|
||||||
|
public DirectoryService(ILogger<DirectoryService> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Given a set of regex search criteria, get files in the given path.
|
/// Given a set of regex search criteria, get files in the given path.
|
||||||
@ -52,6 +59,11 @@ namespace API.Services
|
|||||||
|
|
||||||
public async Task<ImageDto> ReadImageAsync(string imagePath)
|
public async Task<ImageDto> ReadImageAsync(string imagePath)
|
||||||
{
|
{
|
||||||
|
if (!File.Exists(imagePath))
|
||||||
|
{
|
||||||
|
_logger.LogError("Image does not exist on disk.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
using var image = Image.NewFromFile(imagePath);
|
using var image = Image.NewFromFile(imagePath);
|
||||||
|
|
||||||
return new ImageDto
|
return new ImageDto
|
||||||
|
@ -61,6 +61,12 @@ namespace API.Services
|
|||||||
var totalFiles = 0;
|
var totalFiles = 0;
|
||||||
foreach (var folderPath in library.Folders)
|
foreach (var folderPath in library.Folders)
|
||||||
{
|
{
|
||||||
|
// if (!forceUpdate && Directory.GetLastWriteTime(folderPath.Path) <= folderPath.LastScanned)
|
||||||
|
// {
|
||||||
|
// _logger.LogDebug($"{folderPath.Path} hasn't been updated since last scan. Skipping.");
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
|
||||||
try {
|
try {
|
||||||
totalFiles += DirectoryService.TraverseTreeParallelForEach(folderPath.Path, (f) =>
|
totalFiles += DirectoryService.TraverseTreeParallelForEach(folderPath.Path, (f) =>
|
||||||
{
|
{
|
||||||
@ -88,10 +94,12 @@ namespace API.Services
|
|||||||
// Remove series that are no longer on disk
|
// Remove series that are no longer on disk
|
||||||
RemoveSeriesNotOnDisk(allSeries, series, library);
|
RemoveSeriesNotOnDisk(allSeries, series, library);
|
||||||
|
|
||||||
|
foreach (var folder in library.Folders) folder.LastScanned = DateTime.Now;
|
||||||
_unitOfWork.LibraryRepository.Update(library);
|
_unitOfWork.LibraryRepository.Update(library);
|
||||||
|
|
||||||
if (Task.Run(() => _unitOfWork.Complete()).Result)
|
if (Task.Run(() => _unitOfWork.Complete()).Result)
|
||||||
{
|
{
|
||||||
|
|
||||||
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
|
_logger.LogInformation($"Scan completed on {library.Name}. Parsed {series.Keys.Count()} series.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -188,10 +196,10 @@ namespace API.Services
|
|||||||
|
|
||||||
private Series UpdateSeries(Series series, ParserInfo[] infos, bool forceUpdate)
|
private Series UpdateSeries(Series series, ParserInfo[] infos, bool forceUpdate)
|
||||||
{
|
{
|
||||||
var volumes = UpdateVolumes(series, infos, forceUpdate);
|
var volumes = UpdateVolumesWithChapters(series, infos, forceUpdate);
|
||||||
series.Volumes = volumes;
|
series.Volumes = volumes;
|
||||||
series.Pages = volumes.Sum(v => v.Pages);
|
series.Pages = volumes.Sum(v => v.Pages);
|
||||||
if (series.CoverImage == null || forceUpdate)
|
if (ShouldFindCoverImage(forceUpdate, series.CoverImage))
|
||||||
{
|
{
|
||||||
var firstCover = volumes.OrderBy(x => x.Number).FirstOrDefault(x => x.Number != 0);
|
var firstCover = volumes.OrderBy(x => x.Number).FirstOrDefault(x => x.Number != 0);
|
||||||
if (firstCover == null && volumes.Any())
|
if (firstCover == null && volumes.Any())
|
||||||
@ -213,17 +221,21 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
_logger.LogDebug($"Creating File Entry for {info.FullFilePath}");
|
_logger.LogDebug($"Creating File Entry for {info.FullFilePath}");
|
||||||
|
|
||||||
int.TryParse(info.Chapters, out var chapter);
|
|
||||||
_logger.LogDebug($"Found Chapter: {chapter}");
|
|
||||||
return new MangaFile()
|
return new MangaFile()
|
||||||
{
|
{
|
||||||
FilePath = info.FullFilePath,
|
FilePath = info.FullFilePath,
|
||||||
Chapter = chapter,
|
|
||||||
Format = info.Format,
|
Format = info.Format,
|
||||||
NumberOfPages = info.Format == MangaFormat.Archive ? _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath): 1
|
NumberOfPages = info.Format == MangaFormat.Archive ? _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath): 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ShouldFindCoverImage(bool forceUpdate, byte[] coverImage)
|
||||||
|
{
|
||||||
|
return forceUpdate || coverImage == null || !coverImage.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -238,15 +250,19 @@ namespace API.Services
|
|||||||
ICollection<Volume> volumes = new List<Volume>();
|
ICollection<Volume> volumes = new List<Volume>();
|
||||||
IList<Volume> existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList();
|
IList<Volume> existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList();
|
||||||
|
|
||||||
|
//var justVolumes = infos.Select(pi => pi.Chapters == "0");
|
||||||
|
|
||||||
|
|
||||||
foreach (var info in infos)
|
foreach (var info in infos)
|
||||||
{
|
{
|
||||||
var existingVolume = existingVolumes.SingleOrDefault(v => v.Name == info.Volumes);
|
var existingVolume = existingVolumes.SingleOrDefault(v => v.Name == info.Volumes);
|
||||||
if (existingVolume != null)
|
if (existingVolume != null)
|
||||||
{
|
{
|
||||||
var existingFile = existingVolume.Files.SingleOrDefault(f => f.FilePath == info.FullFilePath);
|
//var existingFile = existingVolume.Files.SingleOrDefault(f => f.FilePath == info.FullFilePath);
|
||||||
|
var existingFile = new MangaFile();
|
||||||
if (existingFile != null)
|
if (existingFile != null)
|
||||||
{
|
{
|
||||||
existingFile.Chapter = Parser.Parser.MinimumNumberFromRange(info.Chapters);
|
//existingFile.Chapter = Parser.Parser.MinimumNumberFromRange(info.Chapters);
|
||||||
existingFile.Format = info.Format;
|
existingFile.Format = info.Format;
|
||||||
existingFile.NumberOfPages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath);
|
existingFile.NumberOfPages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath);
|
||||||
}
|
}
|
||||||
@ -254,7 +270,7 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
if (info.Format == MangaFormat.Archive)
|
if (info.Format == MangaFormat.Archive)
|
||||||
{
|
{
|
||||||
existingVolume.Files.Add(CreateMangaFile(info));
|
// existingVolume.Files.Add(CreateMangaFile(info));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -271,7 +287,7 @@ namespace API.Services
|
|||||||
existingVolume = volumes.SingleOrDefault(v => v.Name == info.Volumes);
|
existingVolume = volumes.SingleOrDefault(v => v.Name == info.Volumes);
|
||||||
if (existingVolume != null)
|
if (existingVolume != null)
|
||||||
{
|
{
|
||||||
existingVolume.Files.Add(CreateMangaFile(info));
|
//existingVolume.Files.Add(CreateMangaFile(info));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -279,10 +295,10 @@ namespace API.Services
|
|||||||
{
|
{
|
||||||
Name = info.Volumes,
|
Name = info.Volumes,
|
||||||
Number = Parser.Parser.MinimumNumberFromRange(info.Volumes),
|
Number = Parser.Parser.MinimumNumberFromRange(info.Volumes),
|
||||||
Files = new List<MangaFile>()
|
// Files = new List<MangaFile>()
|
||||||
{
|
// {
|
||||||
CreateMangaFile(info)
|
// CreateMangaFile(info)
|
||||||
}
|
// }
|
||||||
};
|
};
|
||||||
volumes.Add(vol);
|
volumes.Add(vol);
|
||||||
}
|
}
|
||||||
@ -293,19 +309,117 @@ namespace API.Services
|
|||||||
|
|
||||||
foreach (var volume in volumes)
|
foreach (var volume in volumes)
|
||||||
{
|
{
|
||||||
if (forceUpdate || volume.CoverImage == null || !volume.Files.Any())
|
// if (forceUpdate || volume.CoverImage == null || !volume.Files.Any())
|
||||||
{
|
// {
|
||||||
var firstFile = volume.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
// var firstFile = volume.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||||
if (firstFile != null) volume.CoverImage = _archiveService.GetCoverImage(firstFile.FilePath, true);
|
// if (firstFile != null) volume.CoverImage = _archiveService.GetCoverImage(firstFile.FilePath, true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
volume.Pages = volume.Files.Sum(x => x.NumberOfPages);
|
//volume.Pages = volume.Files.Sum(x => x.NumberOfPages);
|
||||||
}
|
}
|
||||||
|
|
||||||
return volumes;
|
return volumes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="volume"></param>
|
||||||
|
/// <param name="infos"></param>
|
||||||
|
/// <param name="forceUpdate"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private ICollection<Chapter> UpdateChapters(Volume volume, IEnumerable<ParserInfo> infos, bool forceUpdate)
|
||||||
|
{
|
||||||
|
var chapters = new List<Chapter>();
|
||||||
|
|
||||||
|
foreach (var info in infos)
|
||||||
|
{
|
||||||
|
volume.Chapters ??= new List<Chapter>();
|
||||||
|
var chapter = volume.Chapters.SingleOrDefault(c => c.Range == info.Chapters) ??
|
||||||
|
chapters.SingleOrDefault(v => v.Range == info.Chapters) ??
|
||||||
|
new Chapter()
|
||||||
|
{
|
||||||
|
Number = Parser.Parser.MinimumNumberFromRange(info.Chapters) + "",
|
||||||
|
Range = info.Chapters,
|
||||||
|
};
|
||||||
|
|
||||||
|
chapter.Files ??= new List<MangaFile>();
|
||||||
|
var existingFile = chapter?.Files.SingleOrDefault(f => f.FilePath == info.FullFilePath);
|
||||||
|
if (existingFile != null)
|
||||||
|
{
|
||||||
|
existingFile.Format = info.Format;
|
||||||
|
existingFile.NumberOfPages = _archiveService.GetNumberOfPagesFromArchive(info.FullFilePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (info.Format == MangaFormat.Archive)
|
||||||
|
{
|
||||||
|
chapter.Files.Add(CreateMangaFile(info));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogDebug($"Ignoring {info.Filename} as it is not an archive.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
chapter.Number = Parser.Parser.MinimumNumberFromRange(info.Chapters) + "";
|
||||||
|
chapter.Range = info.Chapters;
|
||||||
|
|
||||||
|
chapters.Add(chapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var chapter in chapters)
|
||||||
|
{
|
||||||
|
chapter.Pages = chapter.Files.Sum(f => f.NumberOfPages);
|
||||||
|
|
||||||
|
if (ShouldFindCoverImage(forceUpdate, chapter.CoverImage))
|
||||||
|
{
|
||||||
|
var firstFile = chapter?.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||||
|
if (firstFile != null) chapter.CoverImage = _archiveService.GetCoverImage(firstFile.FilePath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapters;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private ICollection<Volume> UpdateVolumesWithChapters(Series series, ParserInfo[] infos, bool forceUpdate)
|
||||||
|
{
|
||||||
|
ICollection<Volume> volumes = new List<Volume>();
|
||||||
|
IList<Volume> existingVolumes = _unitOfWork.SeriesRepository.GetVolumes(series.Id).ToList();
|
||||||
|
|
||||||
|
foreach (var info in infos)
|
||||||
|
{
|
||||||
|
var volume = (existingVolumes.SingleOrDefault(v => v.Name == info.Volumes) ??
|
||||||
|
volumes.SingleOrDefault(v => v.Name == info.Volumes)) ?? new Volume
|
||||||
|
{
|
||||||
|
Name = info.Volumes,
|
||||||
|
Number = Parser.Parser.MinimumNumberFromRange(info.Volumes),
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
var chapters = UpdateChapters(volume, infos.Where(pi => pi.Volumes == volume.Name).ToArray(), forceUpdate);
|
||||||
|
volume.Chapters = chapters;
|
||||||
|
volume.Pages = chapters.Sum(c => c.Pages);
|
||||||
|
volumes.Add(volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var volume in volumes)
|
||||||
|
{
|
||||||
|
if (ShouldFindCoverImage(forceUpdate, volume.CoverImage))
|
||||||
|
{
|
||||||
|
// TODO: Create a custom sorter for Chapters so it's consistent across the application
|
||||||
|
var firstChapter = volume.Chapters.OrderBy(x => Double.Parse(x.Number)).FirstOrDefault();
|
||||||
|
var firstFile = firstChapter?.Files.OrderBy(x => x.Chapter).FirstOrDefault();
|
||||||
|
if (firstFile != null) volume.CoverImage = _archiveService.GetCoverImage(firstFile.FilePath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return volumes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void ScanSeries(int libraryId, int seriesId)
|
public void ScanSeries(int libraryId, int seriesId)
|
||||||
|
@ -3,6 +3,7 @@ using API.Middleware;
|
|||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.AspNetCore.StaticFiles;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
@ -57,7 +58,11 @@ namespace API
|
|||||||
app.UseAuthorization();
|
app.UseAuthorization();
|
||||||
|
|
||||||
app.UseDefaultFiles();
|
app.UseDefaultFiles();
|
||||||
app.UseStaticFiles();
|
|
||||||
|
app.UseStaticFiles(new StaticFileOptions
|
||||||
|
{
|
||||||
|
ContentTypeProvider = new FileExtensionContentTypeProvider() // this is not set by default
|
||||||
|
});
|
||||||
|
|
||||||
app.UseEndpoints(endpoints =>
|
app.UseEndpoints(endpoints =>
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user