diff --git a/API.Tests/Comparers/ChapterSortComparerZeroFirstTests.cs b/API.Tests/Comparers/ChapterSortComparerZeroFirstTests.cs
index 72d6908c6..df3934884 100644
--- a/API.Tests/Comparers/ChapterSortComparerZeroFirstTests.cs
+++ b/API.Tests/Comparers/ChapterSortComparerZeroFirstTests.cs
@@ -14,4 +14,11 @@ public class ChapterSortComparerZeroFirstTests
{
Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerZeroFirst()).ToArray());
}
+
+ [Theory]
+ [InlineData(new[] {1.0, 0.5, 0.3}, new[] {0.3, 0.5, 1.0})]
+ public void ChapterSortComparerZeroFirstTest_Doubles(double[] input, double[] expected)
+ {
+ Assert.Equal(expected, input.OrderBy(f => f, new ChapterSortComparerZeroFirst()).ToArray());
+ }
}
diff --git a/API.Tests/Comparers/NumericComparerTests.cs b/API.Tests/Comparers/NumericComparerTests.cs
index 9a66e7666..8a1f23773 100644
--- a/API.Tests/Comparers/NumericComparerTests.cs
+++ b/API.Tests/Comparers/NumericComparerTests.cs
@@ -11,6 +11,10 @@ public class NumericComparerTests
new[] {"x1.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"},
new[] {"x1.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"}
)]
+ [InlineData(
+ new[] {"x1.0.jpg", "0.5.jpg", "0.3.jpg"},
+ new[] {"0.3.jpg", "0.5.jpg", "x1.0.jpg",}
+ )]
public void NumericComparerTest(string[] input, string[] expected)
{
var nc = new NumericComparer();
diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs
index 280fe5c10..e3331bf6d 100644
--- a/API.Tests/Services/ScannerServiceTests.cs
+++ b/API.Tests/Services/ScannerServiceTests.cs
@@ -125,5 +125,6 @@ namespace API.Tests.Services
// }
+
}
}
diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs
index ee1fcd584..a473e1fd7 100644
--- a/API/Controllers/SeriesController.cs
+++ b/API/Controllers/SeriesController.cs
@@ -6,8 +6,10 @@ using API.Data;
using API.Data.Repositories;
using API.DTOs;
using API.DTOs.Filtering;
+using API.DTOs.SeriesDetail;
using API.Entities;
using API.Entities.Enums;
+using API.Entities.Metadata;
using API.Extensions;
using API.Helpers;
using API.Services;
@@ -339,5 +341,79 @@ namespace API.Controllers
var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
return await _seriesService.GetSeriesDetail(seriesId, userId);
}
+
+ ///
+ /// Fetches the related series for a given series
+ ///
+ ///
+ /// Type of Relationship to pull back
+ ///
+ [HttpGet("related")]
+ public async Task>> GetRelatedSeries(int seriesId, RelationKind relation)
+ {
+ // Send back a custom DTO with each type or maybe sorted in some way
+ var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
+ return Ok(await _unitOfWork.SeriesRepository.GetSeriesForRelationKind(userId, seriesId, relation));
+ }
+
+ [HttpGet("all-related")]
+ public async Task> GetAllRelatedSeries(int seriesId)
+ {
+ // Send back a custom DTO with each type or maybe sorted in some way
+ var userId = await _unitOfWork.UserRepository.GetUserIdByUsernameAsync(User.GetUsername());
+ return Ok(await _unitOfWork.SeriesRepository.GetRelatedSeries(userId, seriesId));
+ }
+
+ [Authorize(Policy="RequireAdminRole")]
+ [HttpPost("update-related")]
+ public async Task UpdateRelatedSeries(UpdateRelatedSeriesDto dto)
+ {
+ var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(dto.SeriesId, SeriesIncludes.Related);
+
+ UpdateRelationForKind(dto.Adaptations, series.Relations.Where(r => r.RelationKind == RelationKind.Adaptation).ToList(), series, RelationKind.Adaptation);
+ UpdateRelationForKind(dto.Characters, series.Relations.Where(r => r.RelationKind == RelationKind.Character).ToList(), series, RelationKind.Character);
+ UpdateRelationForKind(dto.Contains, series.Relations.Where(r => r.RelationKind == RelationKind.Contains).ToList(), series, RelationKind.Contains);
+ UpdateRelationForKind(dto.Others, series.Relations.Where(r => r.RelationKind == RelationKind.Other).ToList(), series, RelationKind.Other);
+ UpdateRelationForKind(dto.SideStories, series.Relations.Where(r => r.RelationKind == RelationKind.SideStory).ToList(), series, RelationKind.SideStory);
+ UpdateRelationForKind(dto.SpinOffs, series.Relations.Where(r => r.RelationKind == RelationKind.SpinOff).ToList(), series, RelationKind.SpinOff);
+ UpdateRelationForKind(dto.AlternativeSettings, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeSetting).ToList(), series, RelationKind.AlternativeSetting);
+ UpdateRelationForKind(dto.AlternativeVersions, series.Relations.Where(r => r.RelationKind == RelationKind.AlternativeVersion).ToList(), series, RelationKind.AlternativeVersion);
+ UpdateRelationForKind(dto.Doujinshis, series.Relations.Where(r => r.RelationKind == RelationKind.Doujinshi).ToList(), series, RelationKind.Doujinshi);
+ UpdateRelationForKind(dto.Prequels, series.Relations.Where(r => r.RelationKind == RelationKind.Prequel).ToList(), series, RelationKind.Prequel);
+ UpdateRelationForKind(dto.Sequels, series.Relations.Where(r => r.RelationKind == RelationKind.Sequel).ToList(), series, RelationKind.Sequel);
+
+ if (!_unitOfWork.HasChanges()) return Ok();
+ if (await _unitOfWork.CommitAsync()) return Ok();
+
+
+ return BadRequest("There was an issue updating relationships");
+ }
+
+ private void UpdateRelationForKind(IList dtoTargetSeriesIds, IEnumerable adaptations, Series series, RelationKind kind)
+ {
+ foreach (var adaptation in adaptations.Where(adaptation => !dtoTargetSeriesIds.Contains(adaptation.TargetSeriesId)))
+ {
+ // If the seriesId isn't in dto, it means we've removed or reclassified
+ series.Relations.Remove(adaptation);
+ }
+
+ // At this point, we only have things to add
+ foreach (var targetSeriesId in dtoTargetSeriesIds)
+ {
+ // This ensures we don't allow any duplicates to be added
+ if (series.Relations.SingleOrDefault(r =>
+ r.RelationKind == kind && r.TargetSeriesId == targetSeriesId) !=
+ null) continue;
+
+ series.Relations.Add(new SeriesRelation()
+ {
+ Series = series,
+ SeriesId = series.Id,
+ TargetSeriesId = targetSeriesId,
+ RelationKind = kind
+ });
+ _unitOfWork.SeriesRepository.Update(series);
+ }
+ }
}
}
diff --git a/API/DTOs/SeriesDetail/RelatedSeriesDto.cs b/API/DTOs/SeriesDetail/RelatedSeriesDto.cs
new file mode 100644
index 000000000..f3c3fd644
--- /dev/null
+++ b/API/DTOs/SeriesDetail/RelatedSeriesDto.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using API.Entities.Enums;
+
+namespace API.DTOs.SeriesDetail;
+
+public class RelatedSeriesDto
+{
+ ///
+ /// The parent relationship Series
+ ///
+ public int SourceSeriesId { get; set; }
+
+ public IEnumerable Sequels { get; set; }
+ public IEnumerable Prequels { get; set; }
+ public IEnumerable SpinOffs { get; set; }
+ public IEnumerable Adaptations { get; set; }
+ public IEnumerable SideStories { get; set; }
+ public IEnumerable Characters { get; set; }
+ public IEnumerable Contains { get; set; }
+ public IEnumerable Others { get; set; }
+ public IEnumerable AlternativeSettings { get; set; }
+ public IEnumerable AlternativeVersions { get; set; }
+ public IEnumerable Doujinshis { get; set; }
+ public IEnumerable Parent { get; set; }
+}
diff --git a/API/DTOs/SeriesDetailDto.cs b/API/DTOs/SeriesDetail/SeriesDetailDto.cs
similarity index 100%
rename from API/DTOs/SeriesDetailDto.cs
rename to API/DTOs/SeriesDetail/SeriesDetailDto.cs
diff --git a/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs b/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs
new file mode 100644
index 000000000..b39f91244
--- /dev/null
+++ b/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+
+namespace API.DTOs.SeriesDetail;
+
+public class UpdateRelatedSeriesDto
+{
+ public int SeriesId { get; set; }
+ public IList Adaptations { get; set; }
+ public IList Characters { get; set; }
+ public IList Contains { get; set; }
+ public IList Others { get; set; }
+ public IList Prequels { get; set; }
+ public IList Sequels { get; set; }
+ public IList SideStories { get; set; }
+ public IList SpinOffs { get; set; }
+ public IList AlternativeSettings { get; set; }
+ public IList AlternativeVersions { get; set; }
+ public IList Doujinshis { get; set; }
+}
diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs
index 6822467a8..90a6718f4 100644
--- a/API/Data/DataContext.cs
+++ b/API/Data/DataContext.cs
@@ -41,24 +41,36 @@ namespace API.Data
public DbSet Genre { get; set; }
public DbSet Tag { get; set; }
public DbSet SiteTheme { get; set; }
+ public DbSet SeriesRelation { get; set; }
- protected override void OnModelCreating(ModelBuilder builder)
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
{
- base.OnModelCreating(builder);
+ base.OnModelCreating(modelBuilder);
- builder.Entity()
+ modelBuilder.Entity()
.HasMany(ur => ur.UserRoles)
.WithOne(u => u.User)
.HasForeignKey(ur => ur.UserId)
.IsRequired();
- builder.Entity()
+ modelBuilder.Entity()
.HasMany(ur => ur.UserRoles)
.WithOne(u => u.Role)
.HasForeignKey(ur => ur.RoleId)
.IsRequired();
+
+ modelBuilder.Entity()
+ .HasOne(pt => pt.Series)
+ .WithMany(p => p.Relations)
+ .HasForeignKey(pt => pt.SeriesId)
+ .OnDelete(DeleteBehavior.ClientCascade);
+
+ modelBuilder.Entity()
+ .HasOne(pt => pt.TargetSeries)
+ .WithMany(t => t.RelationOf)
+ .HasForeignKey(pt => pt.TargetSeriesId);
}
diff --git a/API/Data/Migrations/20220421214448_SeriesRelations.Designer.cs b/API/Data/Migrations/20220421214448_SeriesRelations.Designer.cs
new file mode 100644
index 000000000..11937eb15
--- /dev/null
+++ b/API/Data/Migrations/20220421214448_SeriesRelations.Designer.cs
@@ -0,0 +1,1513 @@
+//
+using System;
+using API.Data;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace API.Data.Migrations
+{
+ [DbContext(typeof(DataContext))]
+ [Migration("20220421214448_SeriesRelations")]
+ partial class SeriesRelations
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.3");
+
+ modelBuilder.Entity("API.Entities.AppRole", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedName")
+ .IsUnique()
+ .HasDatabaseName("RoleNameIndex");
+
+ b.ToTable("AspNetRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUser", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AccessFailedCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedEmail")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedUserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("PasswordHash")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("PhoneNumberConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SecurityStamp")
+ .HasColumnType("TEXT");
+
+ b.Property("TwoFactorEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("UserName")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedEmail")
+ .HasDatabaseName("EmailIndex");
+
+ b.HasIndex("NormalizedUserName")
+ .IsUnique()
+ .HasDatabaseName("UserNameIndex");
+
+ b.ToTable("AspNetUsers", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserBookmark", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("FileName")
+ .HasColumnType("TEXT");
+
+ b.Property("Page")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserBookmark");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BackgroundColor")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderDarkMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("LayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowScreenHints")
+ .HasColumnType("INTEGER");
+
+ b.Property("ThemeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId")
+ .IsUnique();
+
+ b.HasIndex("ThemeId");
+
+ b.ToTable("AppUserPreferences");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserProgress", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookScrollId")
+ .HasColumnType("TEXT");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("PagesRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserProgresses");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRating", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("INTEGER");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserRole", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("RoleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("UserId", "RoleId");
+
+ b.HasIndex("RoleId");
+
+ b.ToTable("AspNetUserRoles", (string)null);
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("IsSpecial")
+ .HasColumnType("INTEGER");
+
+ b.Property("Language")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Number")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("Range")
+ .HasColumnType("TEXT");
+
+ b.Property("ReleaseDate")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("TitleName")
+ .HasColumnType("TEXT");
+
+ b.Property("TotalCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("VolumeId");
+
+ b.ToTable("Chapter");
+ });
+
+ modelBuilder.Entity("API.Entities.CollectionTag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Id", "Promoted")
+ .IsUnique();
+
+ b.ToTable("CollectionTag");
+ });
+
+ modelBuilder.Entity("API.Entities.FolderPath", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("LastScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Path")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("FolderPath");
+ });
+
+ modelBuilder.Entity("API.Entities.Genre", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ExternalTag")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedTitle", "ExternalTag")
+ .IsUnique();
+
+ b.ToTable("Genre");
+ });
+
+ modelBuilder.Entity("API.Entities.Library", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastScanned")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Type")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Library");
+ });
+
+ modelBuilder.Entity("API.Entities.MangaFile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("FilePath")
+ .HasColumnType("TEXT");
+
+ b.Property("Format")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.ToTable("MangaFile");
+ });
+
+ modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRatingLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("CharacterLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ColoristLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverArtistLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("EditorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("GenresLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("InkerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Language")
+ .HasColumnType("TEXT");
+
+ b.Property("LanguageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("LettererLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PencillerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublicationStatus")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublicationStatusLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("PublisherLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReleaseYear")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("SummaryLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("TagsLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("TranslatorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("WriterLocked")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId")
+ .IsUnique();
+
+ b.HasIndex("Id", "SeriesId")
+ .IsUnique();
+
+ b.ToTable("SeriesMetadata");
+ });
+
+ modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("RelationKind")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TargetSeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("TargetSeriesId");
+
+ b.ToTable("SeriesRelation");
+ });
+
+ modelBuilder.Entity("API.Entities.Person", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("Role")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("Person");
+ });
+
+ modelBuilder.Entity("API.Entities.ReadingList", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("ReadingList");
+ });
+
+ modelBuilder.Entity("API.Entities.ReadingListItem", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingListId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("ReadingListId");
+
+ b.HasIndex("SeriesId");
+
+ b.HasIndex("VolumeId");
+
+ b.ToTable("ReadingListItem");
+ });
+
+ modelBuilder.Entity("API.Entities.Series", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("Format")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastChapterAdded")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LocalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("LocalizedNameLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NameLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("OriginalName")
+ .HasColumnType("TEXT");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("SortName")
+ .HasColumnType("TEXT");
+
+ b.Property("SortNameLocked")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("LibraryId");
+
+ b.ToTable("Series");
+ });
+
+ modelBuilder.Entity("API.Entities.ServerSetting", b =>
+ {
+ b.Property("Key")
+ .HasColumnType("INTEGER");
+
+ b.Property("RowVersion")
+ .IsConcurrencyToken()
+ .HasColumnType("INTEGER");
+
+ b.Property("Value")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Key");
+
+ b.ToTable("ServerSetting");
+ });
+
+ modelBuilder.Entity("API.Entities.SiteTheme", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("FileName")
+ .HasColumnType("TEXT");
+
+ b.Property("IsDefault")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("Provider")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.ToTable("SiteTheme");
+ });
+
+ modelBuilder.Entity("API.Entities.Tag", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ExternalTag")
+ .HasColumnType("INTEGER");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("NormalizedTitle", "ExternalTag")
+ .IsUnique();
+
+ b.ToTable("Tag");
+ });
+
+ modelBuilder.Entity("API.Entities.Volume", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Number")
+ .HasColumnType("INTEGER");
+
+ b.Property("Pages")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("Volume");
+ });
+
+ modelBuilder.Entity("AppUserLibrary", b =>
+ {
+ b.Property("AppUsersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibrariesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("AppUsersId", "LibrariesId");
+
+ b.HasIndex("LibrariesId");
+
+ b.ToTable("AppUserLibrary");
+ });
+
+ modelBuilder.Entity("ChapterGenre", b =>
+ {
+ b.Property("ChaptersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("GenresId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ChaptersId", "GenresId");
+
+ b.HasIndex("GenresId");
+
+ b.ToTable("ChapterGenre");
+ });
+
+ modelBuilder.Entity("ChapterPerson", b =>
+ {
+ b.Property("ChapterMetadatasId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PeopleId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ChapterMetadatasId", "PeopleId");
+
+ b.HasIndex("PeopleId");
+
+ b.ToTable("ChapterPerson");
+ });
+
+ modelBuilder.Entity("ChapterTag", b =>
+ {
+ b.Property("ChaptersId")
+ .HasColumnType("INTEGER");
+
+ b.Property("TagsId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("ChaptersId", "TagsId");
+
+ b.HasIndex("TagsId");
+
+ b.ToTable("ChapterTag");
+ });
+
+ modelBuilder.Entity("CollectionTagSeriesMetadata", b =>
+ {
+ b.Property("CollectionTagsId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesMetadatasId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("CollectionTagsId", "SeriesMetadatasId");
+
+ b.HasIndex("SeriesMetadatasId");
+
+ b.ToTable("CollectionTagSeriesMetadata");
+ });
+
+ modelBuilder.Entity("GenreSeriesMetadata", b =>
+ {
+ b.Property("GenresId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesMetadatasId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("GenresId", "SeriesMetadatasId");
+
+ b.HasIndex("SeriesMetadatasId");
+
+ b.ToTable("GenreSeriesMetadata");
+ });
+
+ modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ClaimType")
+ .HasColumnType("TEXT");
+
+ b.Property("ClaimValue")
+ .HasColumnType("TEXT");
+
+ b.Property