diff --git a/API.Benchmark/API.Benchmark.csproj b/API.Benchmark/API.Benchmark.csproj
index d6fd4eb9f..ec9c1884f 100644
--- a/API.Benchmark/API.Benchmark.csproj
+++ b/API.Benchmark/API.Benchmark.csproj
@@ -10,8 +10,8 @@
-
-
+
+
diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj
index 73b886e13..a571a6e72 100644
--- a/API.Tests/API.Tests.csproj
+++ b/API.Tests/API.Tests.csproj
@@ -6,13 +6,13 @@
-
-
+
+
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/API.Tests/Parsers/ComicVineParserTests.cs b/API.Tests/Parsers/ComicVineParserTests.cs
index f01e98afd..2f4fd568e 100644
--- a/API.Tests/Parsers/ComicVineParserTests.cs
+++ b/API.Tests/Parsers/ComicVineParserTests.cs
@@ -36,7 +36,7 @@ public class ComicVineParserTests
public void Parse_SeriesWithComicInfo()
{
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
- RootDirectory, LibraryType.ComicVine, new ComicInfo()
+ RootDirectory, LibraryType.ComicVine, true, new ComicInfo()
{
Series = "Birds of Prey",
Volume = "2002"
@@ -54,7 +54,7 @@ public class ComicVineParserTests
public void Parse_SeriesWithDirectoryNameAsSeriesYear()
{
var actual = _parser.Parse("C:/Comics/Birds of Prey (2002)/Birds of Prey 001 (2002).cbz", "C:/Comics/Birds of Prey (2002)/",
- RootDirectory, LibraryType.ComicVine, null);
+ RootDirectory, LibraryType.ComicVine, true, null);
Assert.NotNull(actual);
Assert.Equal("Birds of Prey (2002)", actual.Series);
@@ -69,7 +69,7 @@ public class ComicVineParserTests
public void Parse_SeriesWithADirectoryNameAsSeriesYear()
{
var actual = _parser.Parse("C:/Comics/DC Comics/Birds of Prey (1999)/Birds of Prey 001 (1999).cbz", "C:/Comics/DC Comics/",
- RootDirectory, LibraryType.ComicVine, null);
+ RootDirectory, LibraryType.ComicVine, true, null);
Assert.NotNull(actual);
Assert.Equal("Birds of Prey (1999)", actual.Series);
@@ -84,7 +84,7 @@ public class ComicVineParserTests
public void Parse_FallbackToDirectoryNameOnly()
{
var actual = _parser.Parse("C:/Comics/DC Comics/Blood Syndicate/Blood Syndicate 001 (1999).cbz", "C:/Comics/DC Comics/",
- RootDirectory, LibraryType.ComicVine, null);
+ RootDirectory, LibraryType.ComicVine, true, null);
Assert.NotNull(actual);
Assert.Equal("Blood Syndicate", actual.Series);
diff --git a/API.Tests/Parsers/DefaultParserTests.cs b/API.Tests/Parsers/DefaultParserTests.cs
index 733b55d62..244c08b97 100644
--- a/API.Tests/Parsers/DefaultParserTests.cs
+++ b/API.Tests/Parsers/DefaultParserTests.cs
@@ -33,7 +33,7 @@ public class DefaultParserTests
[InlineData("C:/", "C:/Something Random/Mujaki no Rakuen SP01.cbz", "Something Random")]
public void ParseFromFallbackFolders_FallbackShouldParseSeries(string rootDir, string inputPath, string expectedSeries)
{
- var actual = _defaultParser.Parse(inputPath, rootDir, rootDir, LibraryType.Manga, null);
+ var actual = _defaultParser.Parse(inputPath, rootDir, rootDir, LibraryType.Manga, true, null);
if (actual == null)
{
Assert.NotNull(actual);
@@ -74,7 +74,7 @@ public class DefaultParserTests
fs.AddFile(inputFile, new MockFileData(""));
var ds = new DirectoryService(Substitute.For>(), fs);
var parser = new BasicParser(ds, new ImageParser(ds));
- var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
+ var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, true, null);
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
Assert.Equal(expectedParseInfo, actual.Series);
}
@@ -90,7 +90,7 @@ public class DefaultParserTests
fs.AddFile(inputFile, new MockFileData(""));
var ds = new DirectoryService(Substitute.For>(), fs);
var parser = new BasicParser(ds, new ImageParser(ds));
- var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, null);
+ var actual = parser.Parse(inputFile, rootDirectory, rootDirectory, LibraryType.Manga, true, null);
_defaultParser.ParseFromFallbackFolders(inputFile, rootDirectory, LibraryType.Manga, ref actual);
Assert.Equal(expectedParseInfo, actual.Series);
}
@@ -251,7 +251,7 @@ public class DefaultParserTests
foreach (var file in expected.Keys)
{
var expectedInfo = expected[file];
- var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Manga, null);
+ var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Manga, true, null);
if (expectedInfo == null)
{
Assert.Null(actual);
@@ -289,7 +289,7 @@ public class DefaultParserTests
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
FullFilePath = filepath, IsSpecial = false
};
- var actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Monster #8", "E:/Manga", LibraryType.Manga, null);
+ var actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Monster #8", "E:/Manga", LibraryType.Manga, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
@@ -315,7 +315,7 @@ public class DefaultParserTests
FullFilePath = filepath, IsSpecial = false
};
- actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga",LibraryType.Manga, null);
+ actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga",LibraryType.Manga, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
@@ -341,7 +341,7 @@ public class DefaultParserTests
FullFilePath = filepath, IsSpecial = false
};
- actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga", LibraryType.Manga, null);
+ actual2 = _defaultParser.Parse(filepath, @"E:/Manga/Extra layer for no reason/", "E:/Manga", LibraryType.Manga, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
@@ -383,7 +383,7 @@ public class DefaultParserTests
FullFilePath = filepath
};
- var actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
+ var actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, true, null);
Assert.NotNull(actual);
_testOutputHelper.WriteLine($"Validating {filepath}");
@@ -412,7 +412,7 @@ public class DefaultParserTests
FullFilePath = filepath
};
- actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, null);
+ actual = parser.Parse(filepath, rootPath, rootPath, LibraryType.Manga, true, null);
Assert.NotNull(actual);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expected.Format, actual.Format);
@@ -475,7 +475,7 @@ public class DefaultParserTests
foreach (var file in expected.Keys)
{
var expectedInfo = expected[file];
- var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Comic, null);
+ var actual = _defaultParser.Parse(file, rootPath, rootPath, LibraryType.Comic, true, null);
if (expectedInfo == null)
{
Assert.Null(actual);
diff --git a/API.Tests/Parsers/ImageParserTests.cs b/API.Tests/Parsers/ImageParserTests.cs
index f95c98ddf..63df1926e 100644
--- a/API.Tests/Parsers/ImageParserTests.cs
+++ b/API.Tests/Parsers/ImageParserTests.cs
@@ -34,7 +34,7 @@ public class ImageParserTests
public void Parse_SeriesWithDirectoryName()
{
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01/01.jpg", "C:/Comics/Birds of Prey/",
- RootDirectory, LibraryType.Image, null);
+ RootDirectory, LibraryType.Image, true, null);
Assert.NotNull(actual);
Assert.Equal("Birds of Prey", actual.Series);
@@ -48,7 +48,7 @@ public class ImageParserTests
public void Parse_SeriesWithNoNestedChapter()
{
var actual = _parser.Parse("C:/Comics/Birds of Prey/Chapter 01 page 01.jpg", "C:/Comics/",
- RootDirectory, LibraryType.Image, null);
+ RootDirectory, LibraryType.Image, true, null);
Assert.NotNull(actual);
Assert.Equal("Birds of Prey", actual.Series);
@@ -62,7 +62,7 @@ public class ImageParserTests
public void Parse_SeriesWithLooseImages()
{
var actual = _parser.Parse("C:/Comics/Birds of Prey/page 01.jpg", "C:/Comics/",
- RootDirectory, LibraryType.Image, null);
+ RootDirectory, LibraryType.Image, true, null);
Assert.NotNull(actual);
Assert.Equal("Birds of Prey", actual.Series);
diff --git a/API.Tests/Parsers/PdfParserTests.cs b/API.Tests/Parsers/PdfParserTests.cs
index 72088526d..08bf9f25d 100644
--- a/API.Tests/Parsers/PdfParserTests.cs
+++ b/API.Tests/Parsers/PdfParserTests.cs
@@ -35,7 +35,7 @@ public class PdfParserTests
{
var actual = _parser.Parse("C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/A Dictionary of Japanese Food - Ingredients and Culture.pdf",
"C:/Books/A Dictionary of Japanese Food - Ingredients and Culture/",
- RootDirectory, LibraryType.Book, null);
+ RootDirectory, LibraryType.Book, true, null);
Assert.NotNull(actual);
Assert.Equal("A Dictionary of Japanese Food - Ingredients and Culture", actual.Series);
diff --git a/API.Tests/Parsing/ImageParsingTests.cs b/API.Tests/Parsing/ImageParsingTests.cs
index 3d78d9372..362b4b08c 100644
--- a/API.Tests/Parsing/ImageParsingTests.cs
+++ b/API.Tests/Parsing/ImageParsingTests.cs
@@ -34,7 +34,7 @@ public class ImageParsingTests
Chapters = "8", Filename = "13.jpg", Format = MangaFormat.Image,
FullFilePath = filepath, IsSpecial = false
};
- var actual2 = _parser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Image, null);
+ var actual2 = _parser.Parse(filepath, @"E:\Manga\Monster #8", "E:/Manga", LibraryType.Image, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
@@ -60,7 +60,7 @@ public class ImageParsingTests
FullFilePath = filepath, IsSpecial = false
};
- actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
+ actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
@@ -86,7 +86,7 @@ public class ImageParsingTests
FullFilePath = filepath, IsSpecial = false
};
- actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, null);
+ actual2 = _parser.Parse(filepath, @"E:\Manga\Extra layer for no reason\", "E:/Manga", LibraryType.Image, true, null);
Assert.NotNull(actual2);
_testOutputHelper.WriteLine($"Validating {filepath}");
Assert.Equal(expectedInfo2.Format, actual2.Format);
diff --git a/API.Tests/Parsing/MangaParsingTests.cs b/API.Tests/Parsing/MangaParsingTests.cs
index 8b93c5f90..53f2bc4c9 100644
--- a/API.Tests/Parsing/MangaParsingTests.cs
+++ b/API.Tests/Parsing/MangaParsingTests.cs
@@ -68,10 +68,8 @@ public class MangaParsingTests
[InlineData("Манга Тома 1-4", "1-4")]
[InlineData("Манга Том 1-4", "1-4")]
[InlineData("조선왕조실톡 106화", "106")]
- [InlineData("죽음 13회", "13")]
[InlineData("동의보감 13장", "13")]
[InlineData("몰?루 아카이브 7.5권", "7.5")]
- [InlineData("주술회전 1.5권", "1.5")]
[InlineData("63권#200", "63")]
[InlineData("시즌34삽화2", "34")]
[InlineData("Accel World Chapter 001 Volume 002", "2")]
diff --git a/API.Tests/Services/BookServiceTests.cs b/API.Tests/Services/BookServiceTests.cs
index a80c1ca01..5848c74ba 100644
--- a/API.Tests/Services/BookServiceTests.cs
+++ b/API.Tests/Services/BookServiceTests.cs
@@ -137,7 +137,7 @@ public class BookServiceTests
var comicInfo = _bookService.GetComicInfo(filePath);
Assert.NotNull(comicInfo);
- var parserInfo = pdfParser.Parse(filePath, testDirectory, ds.GetParentDirectoryName(testDirectory), LibraryType.Book, comicInfo);
+ var parserInfo = pdfParser.Parse(filePath, testDirectory, ds.GetParentDirectoryName(testDirectory), LibraryType.Book, true, comicInfo);
Assert.NotNull(parserInfo);
Assert.Equal(parserInfo.Title, comicInfo.Title);
Assert.Equal(parserInfo.Series, comicInfo.Title);
diff --git a/API.Tests/Services/CacheServiceTests.cs b/API.Tests/Services/CacheServiceTests.cs
index 5c1752cd8..caf1ae393 100644
--- a/API.Tests/Services/CacheServiceTests.cs
+++ b/API.Tests/Services/CacheServiceTests.cs
@@ -50,12 +50,12 @@ internal class MockReadingItemServiceForCacheService : IReadingItemService
throw new System.NotImplementedException();
}
- public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
+ public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true)
{
throw new System.NotImplementedException();
}
- public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
+ public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata = true)
{
throw new System.NotImplementedException();
}
diff --git a/API.Tests/Services/ExternalMetadataServiceTests.cs b/API.Tests/Services/ExternalMetadataServiceTests.cs
index 833e8fe5f..8278f3b1a 100644
--- a/API.Tests/Services/ExternalMetadataServiceTests.cs
+++ b/API.Tests/Services/ExternalMetadataServiceTests.cs
@@ -42,7 +42,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest
_externalMetadataService = new ExternalMetadataService(UnitOfWork, Substitute.For>(),
Mapper, Substitute.For(), Substitute.For(), Substitute.For(),
- Substitute.For());
+ Substitute.For(), Substitute.For());
}
#region Gloabl
diff --git a/API.Tests/Services/ParseScannedFilesTests.cs b/API.Tests/Services/ParseScannedFilesTests.cs
index f8714f69a..a732b2526 100644
--- a/API.Tests/Services/ParseScannedFilesTests.cs
+++ b/API.Tests/Services/ParseScannedFilesTests.cs
@@ -58,35 +58,35 @@ public class MockReadingItemService : IReadingItemService
throw new NotImplementedException();
}
- public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type)
+ public ParserInfo Parse(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
{
if (_comicVineParser.IsApplicable(path, type))
{
- return _comicVineParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
+ return _comicVineParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
}
if (_imageParser.IsApplicable(path, type))
{
- return _imageParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
+ return _imageParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
}
if (_bookParser.IsApplicable(path, type))
{
- return _bookParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
+ return _bookParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
}
if (_pdfParser.IsApplicable(path, type))
{
- return _pdfParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
+ return _pdfParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
}
if (_basicParser.IsApplicable(path, type))
{
- return _basicParser.Parse(path, rootPath, libraryRoot, type, GetComicInfo(path));
+ return _basicParser.Parse(path, rootPath, libraryRoot, type, enableMetadata, GetComicInfo(path));
}
return null;
}
- public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type)
+ public ParserInfo ParseFile(string path, string rootPath, string libraryRoot, LibraryType type, bool enableMetadata)
{
- return Parse(path, rootPath, libraryRoot, type);
+ return Parse(path, rootPath, libraryRoot, type, enableMetadata);
}
}
diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs
index 2e812647b..acc0345b1 100644
--- a/API.Tests/Services/ScannerServiceTests.cs
+++ b/API.Tests/Services/ScannerServiceTests.cs
@@ -483,7 +483,7 @@ public class ScannerServiceTests : AbstractDbTest
var infos = new Dictionary();
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
- library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**/Extra/*"}];
+ library.LibraryExcludePatterns = [new LibraryExcludePattern() { Pattern = "**/Extra/*" }];
UnitOfWork.LibraryRepository.Update(library);
await UnitOfWork.CommitAsync();
@@ -507,7 +507,7 @@ public class ScannerServiceTests : AbstractDbTest
var infos = new Dictionary();
var library = await _scannerHelper.GenerateScannerData(testcase, infos);
- library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**\\Extra\\*"}];
+ library.LibraryExcludePatterns = [new LibraryExcludePattern() { Pattern = "**\\Extra\\*" }];
UnitOfWork.LibraryRepository.Update(library);
await UnitOfWork.CommitAsync();
@@ -938,4 +938,38 @@ public class ScannerServiceTests : AbstractDbTest
Assert.True(sortedChapters[1].SortOrder.Is(4f));
Assert.True(sortedChapters[2].SortOrder.Is(5f));
}
+
+
+ [Fact]
+ public async Task ScanLibrary_MetadataDisabled_NoOverrides()
+ {
+ const string testcase = "Series with Localized No Metadata - Manga.json";
+
+ // Get the first file and generate a ComicInfo
+ var infos = new Dictionary();
+ infos.Add("Immoral Guild v01.cbz", new ComicInfo()
+ {
+ Series = "Immoral Guild",
+ LocalizedSeries = "Futoku no Guild" // Filename has a capital N and localizedSeries has lowercase
+ });
+
+ var library = await _scannerHelper.GenerateScannerData(testcase, infos);
+
+ // Disable metadata
+ library.EnableMetadata = false;
+ UnitOfWork.LibraryRepository.Update(library);
+ await UnitOfWork.CommitAsync();
+
+ var scanner = _scannerHelper.CreateServices();
+ await scanner.ScanLibrary(library.Id);
+
+ var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series);
+
+ // Validate that there are 2 series
+ Assert.NotNull(postLib);
+ Assert.Equal(2, postLib.Series.Count);
+
+ Assert.Contains(postLib.Series, x => x.Name == "Immoral Guild");
+ Assert.Contains(postLib.Series, x => x.Name == "Futoku No Guild");
+ }
}
diff --git a/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Localized No Metadata - Manga.json b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Localized No Metadata - Manga.json
new file mode 100644
index 000000000..d6e91183b
--- /dev/null
+++ b/API.Tests/Services/Test Data/ScannerService/TestCases/Series with Localized No Metadata - Manga.json
@@ -0,0 +1,5 @@
+[
+ "Immoral Guild/Immoral Guild v01.cbz",
+ "Immoral Guild/Immoral Guild v02.cbz",
+ "Immoral Guild/Futoku No Guild - Vol. 12 Ch. 67 - Take Responsibility.cbz"
+]
diff --git a/API/API.csproj b/API/API.csproj
index 4eed66f22..a7d1177dc 100644
--- a/API/API.csproj
+++ b/API/API.csproj
@@ -50,9 +50,9 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -62,25 +62,25 @@
-
+
-
+
-
-
-
-
-
+
+
+
+
+
-
-
-
+
+
+
@@ -89,16 +89,16 @@
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
-
-
+
+
diff --git a/API/Controllers/LibraryController.cs b/API/Controllers/LibraryController.cs
index 2f12aa1fe..c09011b77 100644
--- a/API/Controllers/LibraryController.cs
+++ b/API/Controllers/LibraryController.cs
@@ -623,6 +623,7 @@ public class LibraryController : BaseApiController
library.ManageReadingLists = dto.ManageReadingLists;
library.AllowScrobbling = dto.AllowScrobbling;
library.AllowMetadataMatching = dto.AllowMetadataMatching;
+ library.EnableMetadata = dto.EnableMetadata;
library.LibraryFileTypes = dto.FileGroupTypes
.Select(t => new LibraryFileTypeGroup() {FileTypeGroup = t, LibraryId = library.Id})
.Distinct()
diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs
index 2b7dea8e6..c05ff0567 100644
--- a/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs
+++ b/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs
@@ -6,7 +6,7 @@ namespace API.DTOs.KavitaPlus.ExternalMetadata;
///
/// Used for matching and fetching metadata on a series
///
-internal sealed record ExternalMetadataIdsDto
+public sealed record ExternalMetadataIdsDto
{
public long? MalId { get; set; }
public int? AniListId { get; set; }
diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs
index fae674ded..a7359d69b 100644
--- a/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs
+++ b/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs
@@ -7,7 +7,7 @@ namespace API.DTOs.KavitaPlus.ExternalMetadata;
///
/// Represents a request to match some series from Kavita to an external id which K+ uses.
///
-internal sealed record MatchSeriesRequestDto
+public sealed record MatchSeriesRequestDto
{
public required string SeriesName { get; set; }
public ICollection AlternativeNames { get; set; } = [];
diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs
index d0cbb7bd3..84e9bbf3e 100644
--- a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs
+++ b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs
@@ -6,7 +6,7 @@ using API.DTOs.SeriesDetail;
namespace API.DTOs.KavitaPlus.ExternalMetadata;
-internal sealed record SeriesDetailPlusApiDto
+public sealed record SeriesDetailPlusApiDto
{
public IEnumerable Recommendations { get; set; }
public IEnumerable Reviews { get; set; }
diff --git a/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs b/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs
index 1dcd8494c..add9ca723 100644
--- a/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs
+++ b/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using API.DTOs.SeriesDetail;
namespace API.DTOs.KavitaPlus.Metadata;
+#nullable enable
///
/// Information about an individual issue/chapter/book from Kavita+
diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs
index 8ba687346..7b38379c9 100644
--- a/API/DTOs/LibraryDto.cs
+++ b/API/DTOs/LibraryDto.cs
@@ -66,4 +66,8 @@ public sealed record LibraryDto
/// This does not exclude the library from being linked to wrt Series Relationships
/// Requires a valid LicenseKey
public bool AllowMetadataMatching { get; set; } = true;
+ ///
+ /// Allow Kavita to read metadata (ComicInfo.xml, Epub, PDF)
+ ///
+ public bool EnableMetadata { get; set; } = true;
}
diff --git a/API/DTOs/UpdateLibraryDto.cs b/API/DTOs/UpdateLibraryDto.cs
index 9bd47fd39..68d2417ec 100644
--- a/API/DTOs/UpdateLibraryDto.cs
+++ b/API/DTOs/UpdateLibraryDto.cs
@@ -28,6 +28,8 @@ public sealed record UpdateLibraryDto
public bool AllowScrobbling { get; init; }
[Required]
public bool AllowMetadataMatching { get; init; }
+ [Required]
+ public bool EnableMetadata { get; init; }
///
/// What types of files to allow the scanner to pickup
///
diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs
index 3bbf45e23..aa8b67283 100644
--- a/API/Data/DataContext.cs
+++ b/API/Data/DataContext.cs
@@ -147,6 +147,9 @@ public sealed class DataContext : IdentityDbContext()
.Property(b => b.AllowMetadataMatching)
.HasDefaultValue(true);
+ builder.Entity()
+ .Property(b => b.EnableMetadata)
+ .HasDefaultValue(true);
builder.Entity()
.Property(b => b.WebLinks)
diff --git a/API/Data/Migrations/20250620215058_EnableMetadataLibrary.Designer.cs b/API/Data/Migrations/20250620215058_EnableMetadataLibrary.Designer.cs
new file mode 100644
index 000000000..c15f9f77b
--- /dev/null
+++ b/API/Data/Migrations/20250620215058_EnableMetadataLibrary.Designer.cs
@@ -0,0 +1,3709 @@
+//
+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("20250620215058_EnableMetadataLibrary")]
+ partial class EnableMetadataLibrary
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
+
+ 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("AgeRestriction")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRestrictionIncludeUnknowns")
+ .HasColumnType("INTEGER");
+
+ b.Property("AniListAccessToken")
+ .HasColumnType("TEXT");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("ConcurrencyStamp")
+ .IsConcurrencyToken()
+ .HasColumnType("TEXT");
+
+ b.Property("ConfirmationToken")
+ .HasColumnType("TEXT");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("Email")
+ .HasMaxLength(256)
+ .HasColumnType("TEXT");
+
+ b.Property("EmailConfirmed")
+ .HasColumnType("INTEGER");
+
+ b.Property("HasRunScrobbleEventGeneration")
+ .HasColumnType("INTEGER");
+
+ b.Property("LastActive")
+ .HasColumnType("TEXT");
+
+ b.Property("LastActiveUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LockoutEnabled")
+ .HasColumnType("INTEGER");
+
+ b.Property("LockoutEnd")
+ .HasColumnType("TEXT");
+
+ b.Property("MalAccessToken")
+ .HasColumnType("TEXT");
+
+ b.Property("MalUserName")
+ .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("ScrobbleEventGenerationRan")
+ .HasColumnType("TEXT");
+
+ 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("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("FileName")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .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.AppUserChapterRating", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ChapterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("HasBeenRated")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("REAL");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserChapterRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserCollection", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastSyncUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("MissingSeriesFromSource")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedTitle")
+ .HasColumnType("TEXT");
+
+ b.Property("PrimaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("Promoted")
+ .HasColumnType("INTEGER");
+
+ b.Property("SecondaryColor")
+ .HasColumnType("TEXT");
+
+ b.Property("Source")
+ .HasColumnType("INTEGER");
+
+ b.Property("SourceUrl")
+ .HasColumnType("TEXT");
+
+ b.Property("Summary")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("TotalSourceCount")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserCollection");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserDashboardStream", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsProvided")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("SmartFilterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StreamType")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(4);
+
+ b.Property("Visible")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SmartFilterId");
+
+ b.HasIndex("Visible");
+
+ b.ToTable("AppUserDashboardStream");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserExternalSource", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("ApiKey")
+ .HasColumnType("TEXT");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Host")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserExternalSource");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserOnDeckRemoval");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserPreferences", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AllowAutomaticWebtoonReaderDetection")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("AniListScrobblingEnabled")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BackgroundColor")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("#000000");
+
+ b.Property("BlurUnreadSummaries")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderImmersiveMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderWritingStyle")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("BookThemeName")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("Dark");
+
+ b.Property("CollapseSeriesRelationships")
+ .HasColumnType("INTEGER");
+
+ b.Property("EmulateBook")
+ .HasColumnType("INTEGER");
+
+ b.Property("GlobalPageLayoutMode")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("LayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("Locale")
+ .IsRequired()
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("en");
+
+ b.Property("NoTransitions")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfScrollMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfSpreadMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfTheme")
+ .HasColumnType("INTEGER");
+
+ b.Property("PromptForDownloadSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShareReviews")
+ .HasColumnType("INTEGER");
+
+ b.Property("ShowScreenHints")
+ .HasColumnType("INTEGER");
+
+ b.Property("SwipeToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("ThemeId")
+ .HasColumnType("INTEGER");
+
+ b.Property("WantToReadSync")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ 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("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PagesRead")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ 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("HasBeenRated")
+ .HasColumnType("INTEGER");
+
+ b.Property("Rating")
+ .HasColumnType("REAL");
+
+ b.Property("Review")
+ .HasColumnType("TEXT");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Tagline")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserRating");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserReadingProfile", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AllowAutomaticWebtoonReaderDetection")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(true);
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("AutoCloseMenu")
+ .HasColumnType("INTEGER");
+
+ b.Property("BackgroundColor")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("#000000");
+
+ b.Property("BookReaderFontFamily")
+ .HasColumnType("TEXT");
+
+ b.Property("BookReaderFontSize")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderImmersiveMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderLineSpacing")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderMargin")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderTapToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookReaderWritingStyle")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0);
+
+ b.Property("BookThemeName")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("Dark");
+
+ b.Property("DisableWidthOverride")
+ .HasColumnType("INTEGER");
+
+ b.Property("EmulateBook")
+ .HasColumnType("INTEGER");
+
+ b.Property("Kind")
+ .HasColumnType("INTEGER");
+
+ b.Property("LayoutMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryIds")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("NormalizedName")
+ .HasColumnType("TEXT");
+
+ b.Property("PageSplitOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfScrollMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfSpreadMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("PdfTheme")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReaderMode")
+ .HasColumnType("INTEGER");
+
+ b.Property("ReadingDirection")
+ .HasColumnType("INTEGER");
+
+ b.Property("ScalingOption")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesIds")
+ .HasColumnType("TEXT");
+
+ b.Property("ShowScreenHints")
+ .HasColumnType("INTEGER");
+
+ b.Property("SwipeToPaginate")
+ .HasColumnType("INTEGER");
+
+ b.Property("WidthOverride")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserReadingProfiles");
+ });
+
+ 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.AppUserSideNavStream", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ExternalSourceId")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsProvided")
+ .HasColumnType("INTEGER");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.Property("SmartFilterId")
+ .HasColumnType("INTEGER");
+
+ b.Property("StreamType")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(5);
+
+ b.Property("Visible")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SmartFilterId");
+
+ b.HasIndex("Visible");
+
+ b.ToTable("AppUserSideNavStream");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserSmartFilter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Filter")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.ToTable("AppUserSmartFilter");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserTableOfContent", 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("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModified")
+ .HasColumnType("TEXT");
+
+ b.Property("LastModifiedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("LibraryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("PageNumber")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("VolumeId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("ChapterId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserTableOfContent");
+ });
+
+ modelBuilder.Entity("API.Entities.AppUserWantToRead", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AppUserId")
+ .HasColumnType("INTEGER");
+
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("Id");
+
+ b.HasIndex("AppUserId");
+
+ b.HasIndex("SeriesId");
+
+ b.ToTable("AppUserWantToRead");
+ });
+
+ modelBuilder.Entity("API.Entities.Chapter", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRating")
+ .HasColumnType("INTEGER");
+
+ b.Property("AgeRatingLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("AlternateCount")
+ .HasColumnType("INTEGER");
+
+ b.Property("AlternateNumber")
+ .HasColumnType("TEXT");
+
+ b.Property("AlternateSeries")
+ .HasColumnType("TEXT");
+
+ b.Property("AverageExternalRating")
+ .HasColumnType("REAL");
+
+ b.Property("AvgHoursToRead")
+ .HasColumnType("REAL");
+
+ b.Property("CharacterLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ColoristLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Count")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverArtistLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("CoverImage")
+ .HasColumnType("TEXT");
+
+ b.Property("CoverImageLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("Created")
+ .HasColumnType("TEXT");
+
+ b.Property("CreatedUtc")
+ .HasColumnType("TEXT");
+
+ b.Property("EditorLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("GenresLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ISBN")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasDefaultValue("");
+
+ b.Property("ISBNLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("ImprintLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("InkerLocked")
+ .HasColumnType("INTEGER");
+
+ b.Property("IsSpecial")
+ .HasColumnType("INTEGER");
+
+ b.Property