mirror of
https://github.com/Kareadita/Kavita.git
synced 2025-07-09 03:04:19 -04:00
v0.4 merge to stable (#201)
* Fix directory issue when building all the packages where directory got skewed. (#98) * Bump version for patch release due to bug in continue fuctionality. (#104) * Chore/version bump (#106) * Bump version for patch release due to bug in continue fuctionality. * Added develop branch for github actions * Updated readme to have an image and support link. (#107) * Feature/readme (#109) * Updated readme to have an image and support link. * Updated readme * Fixed a bug where if a chapter had multiple archive files, they wouldn't all be extracted due to short circuit in ExtractArchive. Now I add the file id then flatten afterwards. (#113) * Bugfix/multiple file extract (#116) * Fixed a bug where if a chapter had multiple archive files, they wouldn't all be extracted due to short circuit in ExtractArchive. Now I add the file id then flatten afterwards. * Fixed a bug where due to how we were extracting for multiple files, the single file extractions failed. * Bumped release for 3.5 release * Comic Support (#119) * Implemented some basic regex for comic support * Implemented support for comics * empty filenames, like .test.jpg shouldn't be counted as image types. * Fixed some regex for Manga's with commas or version tags in parenthesis. * More cases for parsing regex * Lots of Parsing Enhancements (#120) * More cases for parsing regex * Implemented the ability to parse "Special" keywords. * Commented out some unit tests * More parsing cases * Fixed unit tests * Fixed typo in build script * Parsing Enhancements (#126) * More cases for parsing regex * Implemented the ability to parse "Special" keywords. * Commented out some unit tests * More parsing cases * Fixed unit tests * Fixed typo in build script * Fixed a bug where if there was a series with same name, but different capitalization, we wouldn't process it's infos. * Tons of regex updates to handle more cases. * More regex tweaking to handle as many cases as possible. * Bad merge caused the comic parser to break. Fixed with some better regex. * Parser Enhancement: Fallback to Folder name (#129) * More cases for parsing regex * Implemented GetFoldersTillRoot for falling back on parsing when we can't get anything from the filename. * Implemented a fallback strategy. Not tested on large libraries yet. * Fallback tested and working great. * Removed a test case that won't pass and added some trims * Update README.md Added build steps * Update README.md (#130) Added docker link * Special Grouping (#134) * More cases for parsing regex * Implemented a change to fix old special grouping. Added some TODOs as well for a future enhancement * Don't go to archive file if it hasn't updated since last scan (#135) * Skip archive work unless the file has actually changed since last scan. * In Progress Activity Stream Fixes (#136) * Fixed a bug in In-Progress where it wasn't properly fetching series. * Fixed a bug where chapter cover images weren't being updated due to a missed not. * Removed a piece of code that was needed for upgrading, since all beta users agreed to wipe db. * Fixed InProgress to properly respect order and show more recent activity first. Issue is with IEntityDate LastModified not updating in DataContext. * Updated dependencies to lastest stable. * LastModified on Volumes wasn't updating, validated it does update when data is changed. * In Progress Query Update (#145) * Fixed a bug where chapter cover images weren't being updated due to a missed not. * Removed a piece of code that was needed for upgrading, since all beta users agreed to wipe db. * Fixed InProgress to properly respect order and show more recent activity first. Issue is with IEntityDate LastModified not updating in DataContext. * Updated dependencies to lastest stable. * LastModified on Volumes wasn't updating, validated it does update when data is changed. * Performance, Scan Loop, Specials, and cleanup (#150) * More cases for parsing regex * Fixed a bug where chapter cover images weren't being updated due to a missed not. * Removed a piece of code that was needed for upgrading, since all beta users agreed to wipe db. * Fixed InProgress to properly respect order and show more recent activity first. Issue is with IEntityDate LastModified not updating in DataContext. * Updated dependencies to lastest stable. * LastModified on Volumes wasn't updating, validated it does update when data is changed. * Rewrote a check to avoid a small heap object warning. * Ensure UpdateSeries checks all libraries for unique name. * Took care of some todos, removed unused imports, on dev go ahead and schedule reoocuring jobs since LiteDB caused the locking issue. * No Tracking when we aren't using entities. * Added code to remove abandoned progress rows after a chapter gets deleted. * RefreshMetadata uses one large query rather than many trips to DB for updating metadata. Significantly faster. * Fixed a bug where UpdateSeries would always complain about a unique name even when we weren't updating name. * Files that are linked to a series but can't parse out Vol/Chapter information are properly grouped like other Specials. * Refresh metadata on UI should call the task directly * Fixed a bug on updating series to make sure we don't complain if we aren't trying to update the name to an existing name. * Fixed #142 - Library cards should be sorted. * Refactored the name of some variables to be more agnostic to comics. * Implemented ScanLibrary but abandoning it. * Code Cleanup & removing ScanSeries code. * Some more tests and new Comparators for natural sorting. * Fixed #137 - When performing I/O on archives, ignore __MACOSX folders completely. * Fixed #137 - When performing I/O on archives, ignore __MACOSX folders completely. * All entities that will show under specials tab should be marked special, rather than just what has a special keyword. * Don't let specials generate cover images * Don't let specials generate cover images * SearchResults should send LocalizedName back since we are searching against it. * Added some tests around macosx folders found from my actual server. * Put extra notes about a case where duplicates come about, logger will now tell user about this issue. * Missed a build issue somehow... * Some code smells * Bugfixes! (#157) * More cases for parsing regex * Fixed a bug where chapter cover images weren't being updated due to a missed not. * Removed a piece of code that was needed for upgrading, since all beta users agreed to wipe db. * Fixed InProgress to properly respect order and show more recent activity first. Issue is with IEntityDate LastModified not updating in DataContext. * Updated dependencies to lastest stable. * LastModified on Volumes wasn't updating, validated it does update when data is changed. * Fixed #152 - Sorting issue when finding cover image. * Fixed #151 - Sort files during scan. * Fixed #161 - Remove files that don't exist from chapters during scan. * Fixed #155 - Ignore images that start with !, expand cover detection by checking for the word cover as well as folder, and some code cleanup to make code more concise. * Fixed #153 - Ensure that we persist series name changes and don't override on scanning. * Fixed a broken unit test * Version bump * I keep fixing this but it keeps reverting (#158) * Fixed #165 - Login and Registration will allow case-insensitive usernames now. (#169) * Cover Image - First and tests (#170) * Changed how natural sort works to cover more cases * Changed the name of CoverImage regex for Parser and added more cases. * Changed how we get result from Task.Run() * Defer execution of a loop till we really need it and added another TODO for later this iteration. * Big refactor to cover image code to unify between IOCompression and SharpCompress. Both use methods to find the correct file. This results in one extra loop through entries, but simplifies code signficantly. In addition, new unit tests for the methods that actually do the logic on choosing cover file and first file. * Removed dead code * Added missing doc * Feature/unit tests (#171) * Removed a duplicate loop that was already done earlier in method. * Normalize now replaces underscores * Added more Parser cases, Added test case for SeriesExtension (Name in List), and added MergeNameTest and some TODOs for where tests should go * Added a test for removal * Fixed bad merge Co-authored-by: Andrew Song <asong641@gmail.com> * Feature/bugfix and regex (#174) * Fixed #172 * Fixes #164 * Added a parse test for [Hidoi]_Amaenaideyo_MS_vol01_chp02.rar * Fix annoying warning about SplitQuery on GetLibraryDtosForUsernameAsync * Scan Bugfixes (#177) * Added way more logging for debugging issue #163. Fixed #175 * Removed some comment that isn't needed * Fixed a enumeration issue due to removing while enumerating * EPUB Support (#178) * Added book filetype detection and reorganized tests due to size of file * Added ability to get basic Parse Info from Book and Pages. * We can now scan books and get them in a library with cover images. * Take the first image in the epub if the cover isn't set. * Implemented the ability to unzip the ebup to cache. Implemented a test api to load html files. * Just some test code to figure out how to approach this. * Fixed some merge conflicts * Removed some dead code from merge * Snapshot: I can now load everything properly into the UI by rewriting the urls before I send them back. I don't notice any lag from this method. It can be optimized further. * Implemented a way to load the content in the browser not via an iframe. * Added a note * Anchor mappings is complete. New anchors are updated so references now resolve to javascript:void() for UI to take care of internally loading and the appropriate page is mapped to it. Anchors that are external have target="_blank" added so they don't force you out of the app and styles are of course inlined. * Oops i need this * Table of contents api implemented (rough) and some small enhancements to codebase for books. * GetBookPageResources now only loads files from within the book. Nested chapter list support and images now use html parsing instead of string parsing. * Fonts now are remapped to load from endpoint. * book-resources now uses a key, ensuring the file is in proper format for lookup. Changed chapter list based on structure with one HEADER and nested chapters. * Properly handle svg resource requests and when there are part anchors that are clickable, make sure we handle them in the UI by adding a kavita-page handler. * Add Chapter group page even if one isn't set by using first page (without part) from nestedChildren. * Added extra debug code for issue #163. * Added new user preferences for books and updated the css so we scope it to our reading section. * Cleaned up style code * Implemented ability to save book preferences and some cleanup on existing apis. * Added an api for checking if a user has read something in a library type before. * Forgot to make sure the has reading progress is against a user lol. * Remove cacheservice code for books, sine we use an in-memory method * Handle svg images as well * Enhanced cover image extraction to check for a "cover" image if the cover image wasn't set in OPF before falling back to the first image. * Fixed an issue with special books not properly generating metadata due to not having filename set. * Cleanup, removed warmup task code from statup/program and changed taskscheduler to schedule tasks on startup only (or if tasks are changed from UI). * Code cleanup * Code cleanup * So much code. Lots of refactors to try to test scanner service. Moved a lot of the queries into Extensions to allow to easier test, even though it's hacky. Support @font-face src:url swaps with ' and ". Source summary information from epubs. * Well...baseURL needs to come from BE and not from UI lol. * Adjusted migrations so default values match Entity * Removed comment * I think I finally fixed #163! The issue was that when i checked if it had a parserInfo, i wasn't considering that the chapter range might have a - in it (0-6) and so when the code to check if range could parse out a number failed, it treated it like a special and checked range against info's filename. * Some bugfixes * Lots of testing, extracting code to make it easier to test. This code is buggy, but fixed a bug where 1) If we changed the normalization code, we would remove the whole db during a scan and 2) We weren't actually removing series properly. Other than that, code is being extracted to remove duplication and centralize logic. * More code cleanup and test cleanup to ensure scan loop is working as expected and matches expectaions from tests. * Cleaned up the code and made it so if I change normalization, which I do in this branch, it wont break existing DBs. * Some comic parser changes for partial chapter support. * Added some code for directory service and scanner service along with python code to generate test files (not used yet). Fixed up all the tests. * Code smells * Book Feedback and small bugs (#183) * Remove automatic retry for scanLibraries as if something fails, it wont pass magically. Catch exceptions when opening books for parsing and swallow to ignore the file. * Delete extra attempts * Switched to using FirstOrDefault for finding existing series. This will help avoid pointless crashes. * Updated message when duplicate series are found (not sure how this happens) * Fixed a negation for deleting volumes where files still exist. * Implemented the ability to automatically scale the manga reader based on screen size. * Feature/feedback (#185) * Remove automatic retry for scanLibraries as if something fails, it wont pass magically. Catch exceptions when opening books for parsing and swallow to ignore the file. * Delete extra attempts * Switched to using FirstOrDefault for finding existing series. This will help avoid pointless crashes. * Updated message when duplicate series are found (not sure how this happens) * Fixed a negation for deleting volumes where files still exist. * Implemented the ability to automatically scale the manga reader based on screen size. * Default to automatic scaling * Fix an issue where malformed epubs wouldn't be readable due to incorrect keys in the OPF. We now check if key is valid and if not, try to correct it. This makes a page load about a second on malformed books. * Fixed #176. Refactored the recently added query to be restricted to user's access to libraries. * Fixed a one off bug with In Progress series * Implemented the ability to refresh metadata of just a single series directly * Book Feedback (#190) * Remove automatic retry for scanLibraries as if something fails, it wont pass magically. Catch exceptions when opening books for parsing and swallow to ignore the file. * Delete extra attempts * Switched to using FirstOrDefault for finding existing series. This will help avoid pointless crashes. * Updated message when duplicate series are found (not sure how this happens) * Fixed a negation for deleting volumes where files still exist. * Implemented the ability to automatically scale the manga reader based on screen size. * Default to automatic scaling * Fix an issue where malformed epubs wouldn't be readable due to incorrect keys in the OPF. We now check if key is valid and if not, try to correct it. This makes a page load about a second on malformed books. * Fixed #176. Refactored the recently added query to be restricted to user's access to libraries. * Fixed a one off bug with In Progress series * Implemented the ability to refresh metadata of just a single series directly * Fixed a parser case where Series c000 (v01) would fail to parse the series * Fixed #189. In Progress now returns data properly for library access and in multiple libraries. * Fixed #188 by adding an extra message for bad login and updating UI * Generate a fallback for table of contents by parsing the toc file (if we can find one) * Bugfixes/misc (#196) * Removed an error log statment which wasn't valid. Was showing error when a comicinfo.xml was not found in a directory. * Fixed #191. Don't overwrite summary information if we already have something set from UI. * Fixes #192 * Fixed #194 by moving the Take to after the query runs, so we take only distinct series. * Added another case for Regex parsing for VanDread-v01-c01.zip * Tap to Paginate User Pref (#197) * Fixed In Progress and removed comments * Tap to Paginate user setting is implemented. Fixes #193 * Implemented the ability to move between volumes (reading) automatically without existing the app. (#198) * Feature/tech debt (#199) * Added an icon for building the exe * Technical debt * Updated Readme for recruitment * Regex addition (#200) Co-authored-by: Andrew Song <asong641@gmail.com>
This commit is contained in:
parent
816f4d2926
commit
9c61c26807
@ -7,14 +7,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.5" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.4" />
|
||||
<PackageReference Include="NSubstitute" Version="4.2.2" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="1.3.0">
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
@ -26,6 +27,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\Test Data\ArchiveService\ComicInfos" />
|
||||
<Folder Include="Services\Test Data\ScannerService\Manga" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -2,7 +2,7 @@
|
||||
using API.Comparators;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests
|
||||
namespace API.Tests.Comparers
|
||||
{
|
||||
public class ChapterSortComparerTest
|
||||
{
|
99
API.Tests/Comparers/NaturalSortComparerTest.cs
Normal file
99
API.Tests/Comparers/NaturalSortComparerTest.cs
Normal file
@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using API.Comparators;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Comparers
|
||||
{
|
||||
public class NaturalSortComparerTest
|
||||
{
|
||||
private readonly NaturalSortComparer _nc = new NaturalSortComparer();
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
new[] {"x1.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"},
|
||||
new[] {"x1.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Beelzebub_153b_RHS.zip", "Beelzebub_01_[Noodles].zip",},
|
||||
new[] {"Beelzebub_01_[Noodles].zip", "Beelzebub_153b_RHS.zip"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"[SCX-Scans]_Vandread_v02_Act02.zip", "[SCX-Scans]_Vandread_v02_Act01.zip",},
|
||||
new[] {"[SCX-Scans]_Vandread_v02_Act01.zip", "[SCX-Scans]_Vandread_v02_Act02.zip",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Frogman v01 001.jpg", "Frogman v01 ch01 p00 Credits.jpg",},
|
||||
new[] {"Frogman v01 001.jpg", "Frogman v01 ch01 p00 Credits.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"001.jpg", "10.jpg",},
|
||||
new[] {"001.jpg", "10.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"10/001.jpg", "10.jpg",},
|
||||
new[] {"10.jpg", "10/001.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Batman - Black white vol 1 #04.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr"},
|
||||
new[] {"Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #04.cbr"}
|
||||
)]
|
||||
public void TestNaturalSortComparer(string[] input, string[] expected)
|
||||
{
|
||||
Array.Sort(input, _nc);
|
||||
|
||||
var i = 0;
|
||||
foreach (var s in input)
|
||||
{
|
||||
Assert.Equal(s, expected[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
new[] {"x1.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"},
|
||||
new[] {"x1.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"x2.jpg", "x10.jpg", "x3.jpg", "x4.jpg", "x11.jpg"},
|
||||
new[] {"x2.jpg", "x3.jpg", "x4.jpg", "x10.jpg", "x11.jpg"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Beelzebub_153b_RHS.zip", "Beelzebub_01_[Noodles].zip",},
|
||||
new[] {"Beelzebub_01_[Noodles].zip", "Beelzebub_153b_RHS.zip"}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"[SCX-Scans]_Vandread_v02_Act02.zip", "[SCX-Scans]_Vandread_v02_Act01.zip","[SCX-Scans]_Vandread_v02_Act07.zip",},
|
||||
new[] {"[SCX-Scans]_Vandread_v02_Act01.zip", "[SCX-Scans]_Vandread_v02_Act02.zip","[SCX-Scans]_Vandread_v02_Act07.zip",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Frogman v01 001.jpg", "Frogman v01 ch01 p00 Credits.jpg",},
|
||||
new[] {"Frogman v01 001.jpg", "Frogman v01 ch01 p00 Credits.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"001.jpg", "10.jpg",},
|
||||
new[] {"001.jpg", "10.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"10/001.jpg", "10.jpg",},
|
||||
new[] {"10.jpg", "10/001.jpg",}
|
||||
)]
|
||||
[InlineData(
|
||||
new[] {"Batman - Black white vol 1 #04.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr"},
|
||||
new[] {"Batman - Black white vol 1 #01.cbr", "Batman - Black white vol 1 #02.cbr", "Batman - Black white vol 1 #03.cbr", "Batman - Black white vol 1 #04.cbr"}
|
||||
)]
|
||||
public void TestNaturalSortComparerLinq(string[] input, string[] expected)
|
||||
{
|
||||
var output = input.OrderBy(c => c, _nc);
|
||||
|
||||
var i = 0;
|
||||
foreach (var s in output)
|
||||
{
|
||||
Assert.Equal(s, expected[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
using API.Comparators;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Services
|
||||
namespace API.Tests.Comparers
|
||||
{
|
||||
public class StringLogicalComparerTest
|
||||
{
|
27
API.Tests/Entities/SeriesTest.cs
Normal file
27
API.Tests/Entities/SeriesTest.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using API.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Entities
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests for <see cref="API.Entities.Series"/>
|
||||
/// </summary>
|
||||
public class SeriesTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Darker than Black")]
|
||||
public void CreateSeries(string name)
|
||||
{
|
||||
var key = API.Parser.Parser.Normalize(name);
|
||||
var series = DbFactory.Series(name);
|
||||
Assert.Equal(0, series.Id);
|
||||
Assert.Equal(0, series.Pages);
|
||||
Assert.Equal(name, series.Name);
|
||||
Assert.Null(series.CoverImage);
|
||||
Assert.Equal(name, series.LocalizedName);
|
||||
Assert.Equal(name, series.SortName);
|
||||
Assert.Equal(name, series.OriginalName);
|
||||
Assert.Equal(key, series.NormalizedName);
|
||||
}
|
||||
}
|
||||
}
|
86
API.Tests/Extensions/ChapterListExtensionsTests.cs
Normal file
86
API.Tests/Extensions/ChapterListExtensionsTests.cs
Normal file
@ -0,0 +1,86 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Parser;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Extensions
|
||||
{
|
||||
public class ChapterListExtensionsTests
|
||||
{
|
||||
private Chapter CreateChapter(string range, string number, MangaFile file, bool isSpecial)
|
||||
{
|
||||
return new Chapter()
|
||||
{
|
||||
Range = range,
|
||||
Number = number,
|
||||
Files = new List<MangaFile>() {file},
|
||||
IsSpecial = isSpecial
|
||||
};
|
||||
}
|
||||
|
||||
private MangaFile CreateFile(string file, MangaFormat format)
|
||||
{
|
||||
return new MangaFile()
|
||||
{
|
||||
FilePath = file,
|
||||
Format = format
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAnyChapterByRange_Test_ShouldBeNull()
|
||||
{
|
||||
var info = new ParserInfo()
|
||||
{
|
||||
Chapters = "0",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
Filename = "darker than black.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "0"
|
||||
};
|
||||
|
||||
var chapterList = new List<Chapter>()
|
||||
{
|
||||
CreateChapter("darker than black - Some special", "0", CreateFile("/manga/darker than black - special.cbz", MangaFormat.Archive), true)
|
||||
};
|
||||
|
||||
var actualChapter = chapterList.GetChapterByRange(info);
|
||||
|
||||
Assert.NotEqual(chapterList[0], actualChapter);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAnyChapterByRange_Test_ShouldBeNotNull()
|
||||
{
|
||||
var info = new ParserInfo()
|
||||
{
|
||||
Chapters = "0",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
Filename = "darker than black.cbz",
|
||||
IsSpecial = true,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "0"
|
||||
};
|
||||
|
||||
var chapterList = new List<Chapter>()
|
||||
{
|
||||
CreateChapter("darker than black", "0", CreateFile("/manga/darker than black.cbz", MangaFormat.Archive), true)
|
||||
};
|
||||
|
||||
var actualChapter = chapterList.GetChapterByRange(info);
|
||||
|
||||
Assert.Equal(chapterList[0], actualChapter);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
21
API.Tests/Extensions/FileInfoExtensionsTests.cs
Normal file
21
API.Tests/Extensions/FileInfoExtensionsTests.cs
Normal file
@ -0,0 +1,21 @@
|
||||
namespace API.Tests.Extensions
|
||||
{
|
||||
public class FileInfoExtensionsTests
|
||||
{
|
||||
// [Fact]
|
||||
// public void DoesLastWriteMatchTest()
|
||||
// {
|
||||
// var fi = Substitute.For<FileInfo>();
|
||||
// fi.LastWriteTime = DateTime.Now;
|
||||
//
|
||||
// var deltaTime = DateTime.Today.Subtract(TimeSpan.FromDays(1));
|
||||
// Assert.False(fi.DoesLastWriteMatch(deltaTime));
|
||||
// }
|
||||
//
|
||||
// [Fact]
|
||||
// public void IsLastWriteLessThanTest()
|
||||
// {
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
41
API.Tests/Extensions/ParserInfoListExtensionsTests.cs
Normal file
41
API.Tests/Extensions/ParserInfoListExtensionsTests.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Parser;
|
||||
using API.Tests.Helpers;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Extensions
|
||||
{
|
||||
public class ParserInfoListExtensions
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(new[] {"1", "1", "3-5", "5", "8", "0", "0"}, new[] {"1", "3-5", "5", "8", "0"})]
|
||||
public void DistinctVolumesTest(string[] volumeNumbers, string[] expectedNumbers)
|
||||
{
|
||||
var infos = volumeNumbers.Select(n => new ParserInfo() {Volumes = n}).ToList();
|
||||
Assert.Equal(expectedNumbers, infos.DistinctVolumes());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new[] {@"Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, true)]
|
||||
[InlineData(new[] {@"Cynthia The Mission - c000-006 (v06-07) [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, true)]
|
||||
[InlineData(new[] {@"Cynthia The Mission v20 c12-20 [Desudesu&Brolen].zip"}, new[] {@"E:\Manga\Cynthia the Mission\Cynthia The Mission - c000-006 (v06) [Desudesu&Brolen].zip"}, false)]
|
||||
public void HasInfoTest(string[] inputInfos, string[] inputChapters, bool expectedHasInfo)
|
||||
{
|
||||
var infos = new List<ParserInfo>();
|
||||
foreach (var filename in inputInfos)
|
||||
{
|
||||
infos.Add(API.Parser.Parser.Parse(
|
||||
filename,
|
||||
string.Empty));
|
||||
}
|
||||
|
||||
var files = inputChapters.Select(s => EntityFactory.CreateMangaFile(s, MangaFormat.Archive, 199)).ToList();
|
||||
var chapter = EntityFactory.CreateChapter("0-6", false, files);
|
||||
|
||||
Assert.Equal(expectedHasInfo, infos.HasInfo(chapter));
|
||||
}
|
||||
}
|
||||
}
|
31
API.Tests/Extensions/SeriesExtensionsTests.cs
Normal file
31
API.Tests/Extensions/SeriesExtensionsTests.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Extensions
|
||||
{
|
||||
public class SeriesExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(new [] {"Darker than Black", "Darker Than Black", "Darker than Black"}, new [] {"Darker than Black"}, true)]
|
||||
[InlineData(new [] {"Darker than Black", "Darker Than Black", "Darker than Black"}, new [] {"Darker_than_Black"}, true)]
|
||||
[InlineData(new [] {"Darker than Black", "Darker Than Black", "Darker than Black"}, new [] {"Darker then Black!"}, false)]
|
||||
[InlineData(new [] {"Salem's Lot", "Salem's Lot", "Salem's Lot"}, new [] {"Salem's Lot"}, true)]
|
||||
[InlineData(new [] {"Salem's Lot", "Salem's Lot", "Salem's Lot"}, new [] {"salems lot"}, true)]
|
||||
[InlineData(new [] {"Salem's Lot", "Salem's Lot", "Salem's Lot"}, new [] {"salem's lot"}, true)]
|
||||
// Different normalizations pass as we check normalization against an on-the-fly calculation so we don't delete series just because we change how normalization works
|
||||
[InlineData(new [] {"Salem's Lot", "Salem's Lot", "Salem's Lot", "salems lot"}, new [] {"salem's lot"}, true)]
|
||||
public void NameInListTest(string[] seriesInput, string[] list, bool expected)
|
||||
{
|
||||
var series = new Series()
|
||||
{
|
||||
Name = seriesInput[0],
|
||||
LocalizedName = seriesInput[1],
|
||||
OriginalName = seriesInput[2],
|
||||
NormalizedName = seriesInput.Length == 4 ? seriesInput[3] : API.Parser.Parser.Normalize(seriesInput[0])
|
||||
};
|
||||
|
||||
Assert.Equal(expected, series.NameInList(list));
|
||||
}
|
||||
}
|
||||
}
|
57
API.Tests/Helpers/EntityFactory.cs
Normal file
57
API.Tests/Helpers/EntityFactory.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
|
||||
namespace API.Tests.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to help quickly create DB entities for Unit Testing
|
||||
/// </summary>
|
||||
public static class EntityFactory
|
||||
{
|
||||
public static Series CreateSeries(string name)
|
||||
{
|
||||
return new Series()
|
||||
{
|
||||
Name = name,
|
||||
SortName = name,
|
||||
LocalizedName = name,
|
||||
NormalizedName = API.Parser.Parser.Normalize(name),
|
||||
Volumes = new List<Volume>()
|
||||
};
|
||||
}
|
||||
|
||||
public static Volume CreateVolume(string volumeNumber, List<Chapter> chapters = null)
|
||||
{
|
||||
return new Volume()
|
||||
{
|
||||
Name = volumeNumber,
|
||||
Pages = 0,
|
||||
Chapters = chapters ?? new List<Chapter>()
|
||||
};
|
||||
}
|
||||
|
||||
public static Chapter CreateChapter(string range, bool isSpecial, List<MangaFile> files = null)
|
||||
{
|
||||
return new Chapter()
|
||||
{
|
||||
IsSpecial = isSpecial,
|
||||
Range = range,
|
||||
Number = API.Parser.Parser.MinimumNumberFromRange(range) + string.Empty,
|
||||
Files = files ?? new List<MangaFile>(),
|
||||
Pages = 0,
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
public static MangaFile CreateMangaFile(string filename, MangaFormat format, int pages)
|
||||
{
|
||||
return new MangaFile()
|
||||
{
|
||||
FilePath = filename,
|
||||
Format = format,
|
||||
Pages = pages
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
25
API.Tests/Helpers/ParserInfoFactory.cs
Normal file
25
API.Tests/Helpers/ParserInfoFactory.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System.IO;
|
||||
using API.Entities.Enums;
|
||||
using API.Parser;
|
||||
|
||||
namespace API.Tests.Helpers
|
||||
{
|
||||
public static class ParserInfoFactory
|
||||
{
|
||||
public static ParserInfo CreateParsedInfo(string series, string volumes, string chapters, string filename, bool isSpecial)
|
||||
{
|
||||
return new ParserInfo()
|
||||
{
|
||||
Chapters = chapters,
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = Path.Join(@"/manga/", filename),
|
||||
Filename = filename,
|
||||
IsSpecial = isSpecial,
|
||||
Title = Path.GetFileNameWithoutExtension(filename),
|
||||
Series = series,
|
||||
Volumes = volumes
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
53
API.Tests/Helpers/TestCaseGenerator.cs
Normal file
53
API.Tests/Helpers/TestCaseGenerator.cs
Normal file
@ -0,0 +1,53 @@
|
||||
using System.IO;
|
||||
|
||||
namespace API.Tests.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Given a -testcase.txt file, will generate a folder with fake archive or book files. These files are just renamed txt files.
|
||||
/// <remarks>This currently is broken - you cannot create files from a unit test it seems</remarks>
|
||||
/// </summary>
|
||||
public static class TestCaseGenerator
|
||||
{
|
||||
public static string GenerateFiles(string directory, string fileToExpand)
|
||||
{
|
||||
//var files = Directory.GetFiles(directory, fileToExpand);
|
||||
var file = new FileInfo(fileToExpand);
|
||||
if (!file.Exists && file.Name.EndsWith("-testcase.txt")) return string.Empty;
|
||||
|
||||
var baseDirectory = TestCaseGenerator.CreateTestBase(fileToExpand, directory);
|
||||
var filesToCreate = File.ReadLines(file.FullName);
|
||||
foreach (var fileToCreate in filesToCreate)
|
||||
{
|
||||
// var folders = DirectoryService.GetFoldersTillRoot(directory, fileToCreate);
|
||||
// foreach (var VARIABLE in COLLECTION)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
File.Create(fileToCreate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return baseDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and returns a new base directory for data creation for a given testcase
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <param name="rootDirectory"></param>
|
||||
/// <returns></returns>
|
||||
private static string CreateTestBase(string file, string rootDirectory)
|
||||
{
|
||||
var baseDir = file.Split("-testcase.txt")[0];
|
||||
var newDirectory = Path.Join(rootDirectory, baseDir);
|
||||
if (!Directory.Exists(newDirectory))
|
||||
{
|
||||
new DirectoryInfo(newDirectory).Create();
|
||||
}
|
||||
|
||||
return newDirectory;
|
||||
}
|
||||
}
|
||||
}
|
14
API.Tests/Parser/BookParserTests.cs
Normal file
14
API.Tests/Parser/BookParserTests.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parser
|
||||
{
|
||||
public class BookParserTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", "Gifting The Wonderful World With Blessings!")]
|
||||
public void ParseSeriesTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||
}
|
||||
}
|
||||
}
|
69
API.Tests/Parser/ComicParserTests.cs
Normal file
69
API.Tests/Parser/ComicParserTests.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parser
|
||||
{
|
||||
public class ComicParserTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("01 Spider-Man & Wolverine 01.cbr", "Spider-Man & Wolverine")]
|
||||
[InlineData("04 - Asterix the Gladiator (1964) (Digital-Empire) (WebP by Doc MaKS)", "Asterix the Gladiator")]
|
||||
[InlineData("The First Asterix Frieze (WebP by Doc MaKS)", "The First Asterix Frieze")]
|
||||
[InlineData("Batman & Catwoman - Trail of the Gun 01", "Batman & Catwoman - Trail of the Gun")]
|
||||
[InlineData("Batman & Daredevil - King of New York", "Batman & Daredevil - King of New York")]
|
||||
[InlineData("Batman & Grendel (1996) 01 - Devil's Bones", "Batman & Grendel")]
|
||||
[InlineData("Batman & Robin the Teen Wonder #0", "Batman & Robin the Teen Wonder")]
|
||||
[InlineData("Batman & Wildcat (1 of 3)", "Batman & Wildcat")]
|
||||
[InlineData("Batman And Superman World's Finest #01", "Batman And Superman World's Finest")]
|
||||
[InlineData("Babe 01", "Babe")]
|
||||
[InlineData("Scott Pilgrim 01 - Scott Pilgrim's Precious Little Life (2004)", "Scott Pilgrim")]
|
||||
[InlineData("Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)", "Teen Titans")]
|
||||
[InlineData("Scott Pilgrim 02 - Scott Pilgrim vs. The World (2005)", "Scott Pilgrim")]
|
||||
[InlineData("Wolverine - Origins 003 (2006) (digital) (Minutemen-PhD)", "Wolverine - Origins")]
|
||||
[InlineData("Invincible Vol 01 Family matters (2005) (Digital).cbr", "Invincible")]
|
||||
public void ParseComicSeriesTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseComicSeries(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("01 Spider-Man & Wolverine 01.cbr", "1")]
|
||||
[InlineData("04 - Asterix the Gladiator (1964) (Digital-Empire) (WebP by Doc MaKS)", "4")]
|
||||
[InlineData("The First Asterix Frieze (WebP by Doc MaKS)", "0")]
|
||||
[InlineData("Batman & Catwoman - Trail of the Gun 01", "1")]
|
||||
[InlineData("Batman & Daredevil - King of New York", "0")]
|
||||
[InlineData("Batman & Grendel (1996) 01 - Devil's Bones", "1")]
|
||||
[InlineData("Batman & Robin the Teen Wonder #0", "0")]
|
||||
[InlineData("Batman & Wildcat (1 of 3)", "0")]
|
||||
[InlineData("Batman And Superman World's Finest #01", "1")]
|
||||
[InlineData("Babe 01", "1")]
|
||||
[InlineData("Scott Pilgrim 01 - Scott Pilgrim's Precious Little Life (2004)", "1")]
|
||||
[InlineData("Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)", "1")]
|
||||
[InlineData("Scott Pilgrim 02 - Scott Pilgrim vs. The World (2005)", "2")]
|
||||
[InlineData("Superman v1 024 (09-10 1943)", "1")]
|
||||
public void ParseComicVolumeTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseComicVolume(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("01 Spider-Man & Wolverine 01.cbr", "0")]
|
||||
[InlineData("04 - Asterix the Gladiator (1964) (Digital-Empire) (WebP by Doc MaKS)", "0")]
|
||||
[InlineData("The First Asterix Frieze (WebP by Doc MaKS)", "0")]
|
||||
[InlineData("Batman & Catwoman - Trail of the Gun 01", "0")]
|
||||
[InlineData("Batman & Daredevil - King of New York", "0")]
|
||||
[InlineData("Batman & Grendel (1996) 01 - Devil's Bones", "1")]
|
||||
[InlineData("Batman & Robin the Teen Wonder #0", "0")]
|
||||
[InlineData("Batman & Wildcat (1 of 3)", "1")]
|
||||
[InlineData("Batman & Wildcat (2 of 3)", "2")]
|
||||
[InlineData("Batman And Superman World's Finest #01", "0")]
|
||||
[InlineData("Babe 01", "0")]
|
||||
[InlineData("Scott Pilgrim 01 - Scott Pilgrim's Precious Little Life (2004)", "1")]
|
||||
[InlineData("Teen Titans v1 001 (1966-02) (digital) (OkC.O.M.P.U.T.O.-Novus)", "1")]
|
||||
[InlineData("Superman v1 024 (09-10 1943)", "24")]
|
||||
[InlineData("Invincible 070.5 - Invincible Returns 1 (2010) (digital) (Minutemen-InnerDemons).cbr", "70.5")]
|
||||
public void ParseComicChapterTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseComicChapter(filename));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using API.Entities.Enums;
|
||||
using API.Parser;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using static API.Parser.Parser;
|
||||
|
||||
namespace API.Tests
|
||||
namespace API.Tests.Parser
|
||||
{
|
||||
public class ParserTests
|
||||
public class MangaParserTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
|
||||
|
||||
public ParserTests(ITestOutputHelper testOutputHelper)
|
||||
public MangaParserTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
_testOutputHelper = testOutputHelper;
|
||||
}
|
||||
@ -52,9 +50,21 @@ namespace API.Tests
|
||||
[InlineData("Mob Psycho 100 v02 (2019) (Digital) (Shizu).cbz", "2")]
|
||||
[InlineData("Kodomo no Jikan vol. 1.cbz", "1")]
|
||||
[InlineData("Kodomo no Jikan vol. 10.cbz", "10")]
|
||||
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 12 [Dametrans][v2]", "0")]
|
||||
[InlineData("Vagabond_v03", "3")]
|
||||
[InlineData("Mujaki No Rakune Volume 10.cbz", "10")]
|
||||
[InlineData("Umineko no Naku Koro ni - Episode 3 - Banquet of the Golden Witch #02.cbz", "3")]
|
||||
[InlineData("Volume 12 - Janken Boy is Coming!.cbz", "12")]
|
||||
[InlineData("[dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz", "20")]
|
||||
[InlineData("Gantz.V26.cbz", "26")]
|
||||
[InlineData("NEEDLESS_Vol.4_-Simeon_6_v2[SugoiSugoi].rar", "4")]
|
||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "1")]
|
||||
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "4")]
|
||||
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "1")]
|
||||
|
||||
public void ParseVolumeTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ParseVolume(filename));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@ -103,11 +113,35 @@ namespace API.Tests
|
||||
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 09", "Kedouin Makoto - Corpse Party Musume")]
|
||||
[InlineData("Goblin Slayer Side Story - Year One 025.5", "Goblin Slayer Side Story - Year One")]
|
||||
[InlineData("Goblin Slayer - Brand New Day 006.5 (2019) (Digital) (danke-Empire)", "Goblin Slayer - Brand New Day")]
|
||||
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 01 [Dametrans][v2]", "Kedouin Makoto - Corpse Party Musume")]
|
||||
[InlineData("Vagabond_v03", "Vagabond")]
|
||||
[InlineData("[AN] Mahoutsukai to Deshi no Futekisetsu na Kankei Chp. 1", "Mahoutsukai to Deshi no Futekisetsu na Kankei")]
|
||||
[InlineData("Beelzebub_Side_Story_02_RHS.zip", "Beelzebub Side Story")]
|
||||
[InlineData("[BAA]_Darker_than_Black_Omake-1.zip", "Darker than Black")]
|
||||
[InlineData("Baketeriya ch01-05.zip", "Baketeriya")]
|
||||
[InlineData("[PROzess]Kimi_ha_midara_na_Boku_no_Joou_-_Ch01", "Kimi ha midara na Boku no Joou")]
|
||||
[InlineData("[SugoiSugoi]_NEEDLESS_Vol.2_-_Disk_The_Informant_5_[ENG].rar", "NEEDLESS")]
|
||||
[InlineData("Fullmetal Alchemist chapters 101-108.cbz", "Fullmetal Alchemist")]
|
||||
[InlineData("To Love Ru v09 Uncensored (Ch.071-079).cbz", "To Love Ru")]
|
||||
[InlineData("[dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz", "One Piece - Digital Colored Comics")]
|
||||
//[InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter", "Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U")]
|
||||
[InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Chapter 01", "Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U")]
|
||||
[InlineData("Vol03_ch15-22.rar", "")]
|
||||
[InlineData("Love Hina - Special.cbz", "")] // This has to be a fallback case
|
||||
[InlineData("Ani-Hina Art Collection.cbz", "")] // This has to be a fallback case
|
||||
[InlineData("Magi - Ch.252-005.cbz", "Magi")]
|
||||
[InlineData("Umineko no Naku Koro ni - Episode 1 - Legend of the Golden Witch #1", "Umineko no Naku Koro ni")]
|
||||
[InlineData("Kimetsu no Yaiba - Digital Colored Comics c162 Three Victorious Stars.cbz", "Kimetsu no Yaiba - Digital Colored Comics")]
|
||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "Amaenaideyo MS")]
|
||||
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "NEEDLESS")]
|
||||
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "Okusama wa Shougakusei")]
|
||||
[InlineData("VanDread-v01-c001[MD].zip", "VanDread")]
|
||||
[InlineData("Momo The Blood Taker - Chapter 027 Violent Emotion.cbz", "Momo The Blood Taker")]
|
||||
public void ParseSeriesTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ParseSeries(filename));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)", "1")]
|
||||
[InlineData("My Girlfriend Is Shobitch v01 - ch. 09 - pg. 008.png", "9")]
|
||||
@ -143,83 +177,75 @@ namespace API.Tests
|
||||
[InlineData("Vol 1", "0")]
|
||||
[InlineData("VanDread-v01-c001[MD].zip", "1")]
|
||||
[InlineData("Goblin Slayer Side Story - Year One 025.5", "25.5")]
|
||||
[InlineData("Kedouin Makoto - Corpse Party Musume, Chapter 01", "1")]
|
||||
[InlineData("To Love Ru v11 Uncensored (Ch.089-097+Omake)", "89-97")]
|
||||
[InlineData("To Love Ru v18 Uncensored (Ch.153-162.5)", "153-162.5")]
|
||||
[InlineData("[AN] Mahoutsukai to Deshi no Futekisetsu na Kankei Chp. 1", "1")]
|
||||
[InlineData("Beelzebub_Side_Story_02_RHS.zip", "2")]
|
||||
[InlineData("[PROzess]Kimi_ha_midara_na_Boku_no_Joou_-_Ch01", "1")]
|
||||
[InlineData("Fullmetal Alchemist chapters 101-108.cbz", "101-108")]
|
||||
[InlineData("Umineko no Naku Koro ni - Episode 3 - Banquet of the Golden Witch #02.cbz", "2")]
|
||||
[InlineData("To Love Ru v09 Uncensored (Ch.071-079).cbz", "71-79")]
|
||||
[InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter.rar", "0")]
|
||||
[InlineData("Beelzebub_153b_RHS.zip", "153.5")]
|
||||
[InlineData("Beelzebub_150-153b_RHS.zip", "150-153.5")]
|
||||
[InlineData("Transferred to another world magical swordsman v1.1", "1")]
|
||||
[InlineData("Transferred to another world magical swordsman v1.2", "2")]
|
||||
[InlineData("Kiss x Sis - Ch.15 - The Angst of a 15 Year Old Boy.cbz", "15")]
|
||||
[InlineData("Kiss x Sis - Ch.12 - 1 , 2 , 3P!.cbz", "12")]
|
||||
[InlineData("Umineko no Naku Koro ni - Episode 1 - Legend of the Golden Witch #1", "1")]
|
||||
[InlineData("Kiss x Sis - Ch.00 - Let's Start from 0.cbz", "0")]
|
||||
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "2")]
|
||||
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "3")]
|
||||
public void ParseChaptersTest(string filename, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ParseChapter(filename));
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("0001", "1")]
|
||||
[InlineData("1", "1")]
|
||||
[InlineData("0013", "13")]
|
||||
public void RemoveLeadingZeroesTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, RemoveLeadingZeroes(input));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseChapter(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1", "001")]
|
||||
[InlineData("10", "010")]
|
||||
[InlineData("100", "100")]
|
||||
public void PadZerosTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, PadZeros(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello_I_am_here", "Hello I am here")]
|
||||
[InlineData("Hello_I_am_here ", "Hello I am here")]
|
||||
[InlineData("[ReleaseGroup] The Title", "The Title")]
|
||||
[InlineData("[ReleaseGroup]_The_Title", "The Title")]
|
||||
[InlineData("[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1", "Kasumi Otoko no Ko v1.1")]
|
||||
public void CleanTitleTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, CleanTitle(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test.cbz", true)]
|
||||
[InlineData("test.cbr", true)]
|
||||
[InlineData("test.zip", true)]
|
||||
[InlineData("test.rar", true)]
|
||||
[InlineData("test.rar.!qb", false)]
|
||||
[InlineData("[shf-ma-khs-aqs]negi_pa_vol15007.jpg", false)]
|
||||
public void IsArchiveTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsArchive(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Tenjou Tenge Omnibus", "Omnibus")]
|
||||
[InlineData("Tenjou Tenge {Full Contact Edition}", "Full Contact Edition")]
|
||||
[InlineData("Tenjo Tenge {Full Contact Edition} v01 (2011) (Digital) (ASTC).cbz", "Full Contact Edition")]
|
||||
[InlineData("Wotakoi - Love is Hard for Otaku Omnibus v01 (2018) (Digital) (danke-Empire)", "Omnibus")]
|
||||
[InlineData("To Love Ru v01 Uncensored (Ch.001-007)", "Uncensored")]
|
||||
[InlineData("Chobits Omnibus Edition v01 [Dark Horse]", "Omnibus Edition")]
|
||||
[InlineData("[dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz", "")]
|
||||
[InlineData("AKIRA - c003 (v01) [Full Color] [Darkhorse].cbz", "Full Color")]
|
||||
public void ParseEditionTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, ParseEdition(input));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseEdition(input));
|
||||
}
|
||||
[Theory]
|
||||
[InlineData("Beelzebub Special OneShot - Minna no Kochikame x Beelzebub (2016) [Mangastream].cbz", true)]
|
||||
[InlineData("Beelzebub_Omake_June_2012_RHS", true)]
|
||||
[InlineData("Beelzebub_Side_Story_02_RHS.zip", false)]
|
||||
[InlineData("Darker than Black Shikkoku no Hana Special [Simple Scans].zip", true)]
|
||||
[InlineData("Darker than Black Shikkoku no Hana Fanbook Extra [Simple Scans].zip", true)]
|
||||
[InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter", true)]
|
||||
[InlineData("Ani-Hina Art Collection.cbz", true)]
|
||||
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", true)]
|
||||
public void ParseMangaSpecialTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, !string.IsNullOrEmpty(API.Parser.Parser.ParseMangaSpecial(input)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("12-14", 12)]
|
||||
[InlineData("24", 24)]
|
||||
[InlineData("18-04", 4)]
|
||||
[InlineData("18-04.5", 4.5)]
|
||||
[InlineData("40", 40)]
|
||||
public void MinimumNumberFromRangeTest(string input, float expected)
|
||||
[InlineData("image.png", MangaFormat.Image)]
|
||||
[InlineData("image.cbz", MangaFormat.Archive)]
|
||||
[InlineData("image.txt", MangaFormat.Unknown)]
|
||||
public void ParseFormatTest(string inputFile, MangaFormat expected)
|
||||
{
|
||||
Assert.Equal(expected, MinimumNumberFromRange(input));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseFormat(inputFile));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Darker Than Black", "darkerthanblack")]
|
||||
[InlineData("Darker Than Black - Something", "darkerthanblacksomething")]
|
||||
[InlineData("", "")]
|
||||
public void NormalizeTest(string input, string expected)
|
||||
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown].epub", "Side Stories")]
|
||||
public void ParseSpecialTest(string inputFile, string expected)
|
||||
{
|
||||
Assert.Equal(expected, Normalize(input));
|
||||
Assert.Equal(expected, API.Parser.Parser.ParseMangaSpecial(inputFile));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void ParseInfoTest()
|
||||
{
|
||||
@ -309,7 +335,7 @@ namespace API.Tests
|
||||
foreach (var file in expected.Keys)
|
||||
{
|
||||
var expectedInfo = expected[file];
|
||||
var actual = Parse(file, rootPath);
|
||||
var actual = API.Parser.Parser.Parse(file, rootPath);
|
||||
if (expectedInfo == null)
|
||||
{
|
||||
Assert.Null(actual);
|
110
API.Tests/Parser/ParserInfoTests.cs
Normal file
110
API.Tests/Parser/ParserInfoTests.cs
Normal file
@ -0,0 +1,110 @@
|
||||
using API.Entities.Enums;
|
||||
using API.Parser;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Parser
|
||||
{
|
||||
public class ParserInfoTests
|
||||
{
|
||||
[Fact]
|
||||
public void MergeFromTest()
|
||||
{
|
||||
var p1 = new ParserInfo()
|
||||
{
|
||||
Chapters = "0",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "0"
|
||||
};
|
||||
|
||||
var p2 = new ParserInfo()
|
||||
{
|
||||
Chapters = "1",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "darker than black",
|
||||
Title = "Darker Than Black",
|
||||
Volumes = "0"
|
||||
};
|
||||
|
||||
var expected = new ParserInfo()
|
||||
{
|
||||
Chapters = "1",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "0"
|
||||
};
|
||||
p1.Merge(p2);
|
||||
|
||||
AssertSame(expected, p1);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MergeFromTest2()
|
||||
{
|
||||
var p1 = new ParserInfo()
|
||||
{
|
||||
Chapters = "1",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = true,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "0"
|
||||
};
|
||||
|
||||
var p2 = new ParserInfo()
|
||||
{
|
||||
Chapters = "0",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = false,
|
||||
Series = "darker than black",
|
||||
Title = "Darker Than Black",
|
||||
Volumes = "1"
|
||||
};
|
||||
|
||||
var expected = new ParserInfo()
|
||||
{
|
||||
Chapters = "1",
|
||||
Edition = "",
|
||||
Format = MangaFormat.Archive,
|
||||
FullFilePath = "/manga/darker than black.cbz",
|
||||
IsSpecial = true,
|
||||
Series = "darker than black",
|
||||
Title = "darker than black",
|
||||
Volumes = "1"
|
||||
};
|
||||
p1.Merge(p2);
|
||||
|
||||
AssertSame(expected, p1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void AssertSame(ParserInfo expected, ParserInfo actual)
|
||||
{
|
||||
Assert.Equal(expected.Chapters, actual.Chapters);
|
||||
Assert.Equal(expected.Volumes, actual.Volumes);
|
||||
Assert.Equal(expected.Edition, actual.Edition);
|
||||
Assert.Equal(expected.Filename, actual.Filename);
|
||||
Assert.Equal(expected.Format, actual.Format);
|
||||
Assert.Equal(expected.Series, actual.Series);
|
||||
Assert.Equal(expected.IsSpecial, actual.IsSpecial);
|
||||
Assert.Equal(expected.FullFilePath, actual.FullFilePath);
|
||||
}
|
||||
}
|
||||
}
|
192
API.Tests/Parser/ParserTest.cs
Normal file
192
API.Tests/Parser/ParserTest.cs
Normal file
@ -0,0 +1,192 @@
|
||||
using Xunit;
|
||||
using static API.Parser.Parser;
|
||||
|
||||
namespace API.Tests.Parser
|
||||
{
|
||||
public class ParserTests
|
||||
{
|
||||
|
||||
[Theory]
|
||||
[InlineData("0001", "1")]
|
||||
[InlineData("1", "1")]
|
||||
[InlineData("0013", "13")]
|
||||
public void RemoveLeadingZeroesTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, RemoveLeadingZeroes(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("1", "001")]
|
||||
[InlineData("10", "010")]
|
||||
[InlineData("100", "100")]
|
||||
public void PadZerosTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, PadZeros(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Hello_I_am_here", "Hello I am here")]
|
||||
[InlineData("Hello_I_am_here ", "Hello I am here")]
|
||||
[InlineData("[ReleaseGroup] The Title", "The Title")]
|
||||
[InlineData("[ReleaseGroup]_The_Title", "The Title")]
|
||||
[InlineData("[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1", "Kasumi Otoko no Ko v1.1")]
|
||||
public void CleanTitleTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, CleanTitle(input));
|
||||
}
|
||||
|
||||
|
||||
// [Theory]
|
||||
// //[InlineData("@font-face{font-family:\"PaytoneOne\";src:url(\"..\\/Fonts\\/PaytoneOne.ttf\")}", "@font-face{font-family:\"PaytoneOne\";src:url(\"PaytoneOne.ttf\")}")]
|
||||
// [InlineData("@font-face{font-family:\"PaytoneOne\";src:url(\"..\\/Fonts\\/PaytoneOne.ttf\")}", "..\\/Fonts\\/PaytoneOne.ttf")]
|
||||
// //[InlineData("@font-face{font-family:'PaytoneOne';src:url('..\\/Fonts\\/PaytoneOne.ttf')}", "@font-face{font-family:'PaytoneOne';src:url('PaytoneOne.ttf')}")]
|
||||
// //[InlineData("@font-face{\r\nfont-family:'PaytoneOne';\r\nsrc:url('..\\/Fonts\\/PaytoneOne.ttf')\r\n}", "@font-face{font-family:'PaytoneOne';src:url('PaytoneOne.ttf')}")]
|
||||
// public void ReplaceStyleUrlTest(string input, string expected)
|
||||
// {
|
||||
// var replacementStr = "PaytoneOne.ttf";
|
||||
// // TODO: Use Match to validate since replace is weird
|
||||
// //Assert.Equal(expected, FontSrcUrlRegex.Replace(input, "$1" + replacementStr + "$2" + "$3"));
|
||||
// var match = FontSrcUrlRegex.Match(input);
|
||||
// Assert.Equal(!string.IsNullOrEmpty(expected), FontSrcUrlRegex.Match(input).Success);
|
||||
// }
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("test.cbz", true)]
|
||||
[InlineData("test.cbr", true)]
|
||||
[InlineData("test.zip", true)]
|
||||
[InlineData("test.rar", true)]
|
||||
[InlineData("test.rar.!qb", false)]
|
||||
[InlineData("[shf-ma-khs-aqs]negi_pa_vol15007.jpg", false)]
|
||||
public void IsArchiveTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsArchive(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test.epub", true)]
|
||||
[InlineData("test.pdf", false)]
|
||||
[InlineData("test.mobi", false)]
|
||||
[InlineData("test.djvu", false)]
|
||||
[InlineData("test.zip", false)]
|
||||
[InlineData("test.rar", false)]
|
||||
[InlineData("test.epub.!qb", false)]
|
||||
[InlineData("[shf-ma-khs-aqs]negi_pa_vol15007.ebub", false)]
|
||||
public void IsBookTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsBook(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("test.epub", true)]
|
||||
[InlineData("test.EPUB", true)]
|
||||
[InlineData("test.mobi", false)]
|
||||
[InlineData("test.epub.!qb", false)]
|
||||
[InlineData("[shf-ma-khs-aqs]negi_pa_vol15007.ebub", false)]
|
||||
public void IsEpubTest(string input, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsEpub(input));
|
||||
}
|
||||
|
||||
// [Theory]
|
||||
// [InlineData("Tenjou Tenge Omnibus", "Omnibus")]
|
||||
// [InlineData("Tenjou Tenge {Full Contact Edition}", "Full Contact Edition")]
|
||||
// [InlineData("Tenjo Tenge {Full Contact Edition} v01 (2011) (Digital) (ASTC).cbz", "Full Contact Edition")]
|
||||
// [InlineData("Wotakoi - Love is Hard for Otaku Omnibus v01 (2018) (Digital) (danke-Empire)", "Omnibus")]
|
||||
// [InlineData("To Love Ru v01 Uncensored (Ch.001-007)", "Uncensored")]
|
||||
// [InlineData("Chobits Omnibus Edition v01 [Dark Horse]", "Omnibus Edition")]
|
||||
// [InlineData("[dmntsf.net] One Piece - Digital Colored Comics Vol. 20 Ch. 177 - 30 Million vs 81 Million.cbz", "Digital Colored Comics")]
|
||||
// [InlineData("AKIRA - c003 (v01) [Full Color] [Darkhorse].cbz", "Full Color")]
|
||||
// public void ParseEditionTest(string input, string expected)
|
||||
// {
|
||||
// Assert.Equal(expected, ParseEdition(input));
|
||||
// }
|
||||
|
||||
// [Theory]
|
||||
// [InlineData("Beelzebub Special OneShot - Minna no Kochikame x Beelzebub (2016) [Mangastream].cbz", true)]
|
||||
// [InlineData("Beelzebub_Omake_June_2012_RHS", true)]
|
||||
// [InlineData("Beelzebub_Side_Story_02_RHS.zip", false)]
|
||||
// [InlineData("Darker than Black Shikkoku no Hana Special [Simple Scans].zip", true)]
|
||||
// [InlineData("Darker than Black Shikkoku no Hana Fanbook Extra [Simple Scans].zip", true)]
|
||||
// [InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter", true)]
|
||||
// [InlineData("Ani-Hina Art Collection.cbz", true)]
|
||||
// public void ParseMangaSpecialTest(string input, bool expected)
|
||||
// {
|
||||
// Assert.Equal(expected, ParseMangaSpecial(input) != "");
|
||||
// }
|
||||
|
||||
[Theory]
|
||||
[InlineData("12-14", 12)]
|
||||
[InlineData("24", 24)]
|
||||
[InlineData("18-04", 4)]
|
||||
[InlineData("18-04.5", 4.5)]
|
||||
[InlineData("40", 40)]
|
||||
public void MinimumNumberFromRangeTest(string input, float expected)
|
||||
{
|
||||
Assert.Equal(expected, MinimumNumberFromRange(input));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Darker Than Black", "darkerthanblack")]
|
||||
[InlineData("Darker Than Black - Something", "darkerthanblacksomething")]
|
||||
[InlineData("Darker Than_Black", "darkerthanblack")]
|
||||
[InlineData("", "")]
|
||||
public void NormalizeTest(string input, string expected)
|
||||
{
|
||||
Assert.Equal(expected, Normalize(input));
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("test.jpg", true)]
|
||||
[InlineData("test.jpeg", true)]
|
||||
[InlineData("test.png", true)]
|
||||
[InlineData(".test.jpg", false)]
|
||||
[InlineData("!test.jpg", false)]
|
||||
public void IsImageTest(string filename, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsImage(filename));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("C:/", "C:/Love Hina/Love Hina - Special.cbz", "Love Hina")]
|
||||
[InlineData("C:/", "C:/Love Hina/Specials/Ani-Hina Art Collection.cbz", "Love Hina")]
|
||||
[InlineData("C:/", "C:/Mujaki no Rakuen Something/Mujaki no Rakuen Vol12 ch76.cbz", "Mujaki no Rakuen")]
|
||||
public void FallbackTest(string rootDir, string inputPath, string expectedSeries)
|
||||
{
|
||||
var actual = Parse(inputPath, rootDir);
|
||||
if (actual == null)
|
||||
{
|
||||
Assert.NotNull(actual);
|
||||
return;
|
||||
}
|
||||
|
||||
Assert.Equal(expectedSeries, actual.Series);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Love Hina - Special.jpg", false)]
|
||||
[InlineData("folder.jpg", true)]
|
||||
[InlineData("DearS_v01_cover.jpg", true)]
|
||||
[InlineData("DearS_v01_covers.jpg", false)]
|
||||
[InlineData("!cover.jpg", true)]
|
||||
[InlineData("cover.jpg", true)]
|
||||
[InlineData("cover.png", true)]
|
||||
[InlineData("ch1/cover.png", true)]
|
||||
public void IsCoverImageTest(string inputPath, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, IsCoverImage(inputPath));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("__MACOSX/Love Hina - Special.jpg", true)]
|
||||
[InlineData("TEST/Love Hina - Special.jpg", false)]
|
||||
[InlineData("__macosx/Love Hina/", false)]
|
||||
[InlineData("MACOSX/Love Hina/", false)]
|
||||
public void HasBlacklistedFolderInPathTest(string inputPath, bool expected)
|
||||
{
|
||||
Assert.Equal(expected, HasBlacklistedFolderInPath(inputPath));
|
||||
}
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using API.Archive;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NSubstitute.Extensions;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@ -14,7 +14,7 @@ namespace API.Tests.Services
|
||||
public class ArchiveServiceTests
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly IArchiveService _archiveService;
|
||||
private readonly ArchiveService _archiveService;
|
||||
private readonly ILogger<ArchiveService> _logger = Substitute.For<ILogger<ArchiveService>>();
|
||||
|
||||
public ArchiveServiceTests(ITestOutputHelper testOutputHelper)
|
||||
@ -58,6 +58,9 @@ namespace API.Tests.Services
|
||||
[InlineData("file in folder in folder.zip", 1)]
|
||||
[InlineData("file in folder.zip", 1)]
|
||||
[InlineData("file in folder_alt.zip", 1)]
|
||||
[InlineData("macos_none.zip", 0)]
|
||||
[InlineData("macos_one.zip", 1)]
|
||||
[InlineData("macos_native.zip", 21)]
|
||||
public void GetNumberOfPagesFromArchiveTest(string archivePath, int expected)
|
||||
{
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
||||
@ -110,6 +113,34 @@ namespace API.Tests.Services
|
||||
|
||||
DirectoryService.ClearAndDeleteDirectory(extractDirectory);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(new [] {"folder.jpg"}, "folder.jpg")]
|
||||
[InlineData(new [] {"vol1/"}, "")]
|
||||
[InlineData(new [] {"folder.jpg", "vol1/folder.jpg"}, "folder.jpg")]
|
||||
[InlineData(new [] {"cover.jpg", "vol1/folder.jpg"}, "cover.jpg")]
|
||||
[InlineData(new [] {"__MACOSX/cover.jpg", "vol1/page 01.jpg"}, "")]
|
||||
[InlineData(new [] {"Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c060 (v10) - p200 [Digital] [LuCaZ].jpg", "folder.jpg"}, "folder.jpg")]
|
||||
public void FindFolderEntry(string[] files, string expected)
|
||||
{
|
||||
var foundFile = _archiveService.FindFolderEntry(files);
|
||||
Assert.Equal(expected, string.IsNullOrEmpty(foundFile) ? "" : foundFile);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new [] {"folder.jpg"}, "folder.jpg")]
|
||||
[InlineData(new [] {"vol1/"}, "")]
|
||||
[InlineData(new [] {"folder.jpg", "vol1/folder.jpg"}, "folder.jpg")]
|
||||
[InlineData(new [] {"cover.jpg", "vol1/folder.jpg"}, "cover.jpg")]
|
||||
[InlineData(new [] {"page 2.jpg", "page 10.jpg"}, "page 2.jpg")]
|
||||
[InlineData(new [] {"__MACOSX/cover.jpg", "vol1/page 01.jpg"}, "vol1/page 01.jpg")]
|
||||
[InlineData(new [] {"Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg", "Akame ga KILL! ZERO - c060 (v10) - p200 [Digital] [LuCaZ].jpg", "folder.jpg"}, "Akame ga KILL! ZERO - c055 (v10) - p000 [Digital] [LuCaZ].jpg")]
|
||||
public void FindFirstEntry(string[] files, string expected)
|
||||
{
|
||||
var foundFile = _archiveService.FirstFileEntry(files);
|
||||
Assert.Equal(expected, string.IsNullOrEmpty(foundFile) ? "" : foundFile);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -118,20 +149,47 @@ namespace API.Tests.Services
|
||||
[InlineData("v10 - with folder.cbz", "v10 - with folder.expected.jpg")]
|
||||
[InlineData("v10 - nested folder.cbz", "v10 - nested folder.expected.jpg")]
|
||||
//[InlineData("png.zip", "png.PNG")]
|
||||
public void GetCoverImageTest(string inputFile, string expectedOutputFile)
|
||||
[InlineData("macos_native.zip", "macos_native.jpg")]
|
||||
[InlineData("v10 - duplicate covers.cbz", "v10 - duplicate covers.expected.jpg")]
|
||||
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
||||
public void GetCoverImage_Default_Test(string inputFile, string expectedOutputFile)
|
||||
{
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger);
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages");
|
||||
var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile));
|
||||
archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.Default);
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
Assert.Equal(expectedBytes, _archiveService.GetCoverImage(Path.Join(testDirectory, inputFile)));
|
||||
Assert.Equal(expectedBytes, archiveService.GetCoverImage(Path.Join(testDirectory, inputFile)));
|
||||
_testOutputHelper.WriteLine($"Processed in {sw.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData("v10.cbz", "v10.expected.jpg")]
|
||||
[InlineData("v10 - with folder.cbz", "v10 - with folder.expected.jpg")]
|
||||
[InlineData("v10 - nested folder.cbz", "v10 - nested folder.expected.jpg")]
|
||||
//[InlineData("png.zip", "png.PNG")]
|
||||
[InlineData("macos_native.zip", "macos_native.jpg")]
|
||||
[InlineData("v10 - duplicate covers.cbz", "v10 - duplicate covers.expected.jpg")]
|
||||
[InlineData("sorting.zip", "sorting.expected.jpg")]
|
||||
public void GetCoverImage_SharpCompress_Test(string inputFile, string expectedOutputFile)
|
||||
{
|
||||
var archiveService = Substitute.For<ArchiveService>(_logger);
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/CoverImages");
|
||||
var expectedBytes = File.ReadAllBytes(Path.Join(testDirectory, expectedOutputFile));
|
||||
|
||||
archiveService.Configure().CanOpen(Path.Join(testDirectory, inputFile)).Returns(ArchiveLibrary.SharpCompress);
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
Assert.Equal(expectedBytes, archiveService.GetCoverImage(Path.Join(testDirectory, inputFile)));
|
||||
_testOutputHelper.WriteLine($"Processed in {sw.ElapsedMilliseconds} ms");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("06_v01[DMM].zip")]
|
||||
[InlineData("Archives/macos_native.zip")]
|
||||
[InlineData("Formats/One File with DB_Supported.zip")]
|
||||
public void CanParseCoverImage(string inputFile)
|
||||
{
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/Archives");
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ArchiveService/");
|
||||
Assert.NotEmpty(_archiveService.GetCoverImage(Path.Join(testDirectory, inputFile)));
|
||||
}
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
|
||||
namespace API.Tests.Services
|
||||
{
|
||||
public class BackupServiceTests
|
||||
{
|
||||
private readonly DirectoryService _directoryService;
|
||||
private readonly BackupService _backupService;
|
||||
private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
|
||||
private readonly ILogger<DirectoryService> _directoryLogger = Substitute.For<ILogger<DirectoryService>>();
|
||||
private readonly ILogger<BackupService> _logger = Substitute.For<ILogger<BackupService>>();
|
||||
private readonly IConfiguration _config;
|
||||
|
||||
// public BackupServiceTests()
|
||||
// {
|
||||
// var inMemorySettings = new Dictionary<string, string> {
|
||||
// {"Logging:File:MaxRollingFiles", "0"},
|
||||
// {"Logging:File:Path", "file.log"},
|
||||
// };
|
||||
//
|
||||
// _config = new ConfigurationBuilder()
|
||||
// .AddInMemoryCollection(inMemorySettings)
|
||||
// .Build();
|
||||
//
|
||||
// //_config.GetMaxRollingFiles().Returns(0);
|
||||
// //_config.GetLoggingFileName().Returns("file.log");
|
||||
// //var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BackupService/");
|
||||
// //Directory.GetCurrentDirectory().Returns(testDirectory);
|
||||
//
|
||||
// _directoryService = new DirectoryService(_directoryLogger);
|
||||
// _backupService = new BackupService(_unitOfWork, _logger, _directoryService, _config);
|
||||
// }
|
||||
//
|
||||
// [Fact]
|
||||
// public void Test()
|
||||
// {
|
||||
// _backupService.BackupDatabase();
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
}
|
31
API.Tests/Services/BookServiceTests.cs
Normal file
31
API.Tests/Services/BookServiceTests.cs
Normal file
@ -0,0 +1,31 @@
|
||||
using System.IO;
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace API.Tests.Services
|
||||
{
|
||||
public class BookServiceTests
|
||||
{
|
||||
private readonly IBookService _bookService;
|
||||
private readonly ILogger<BookService> _logger = Substitute.For<ILogger<BookService>>();
|
||||
|
||||
public BookServiceTests()
|
||||
{
|
||||
_bookService = new BookService(_logger);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("The Golden Harpoon; Or, Lost Among the Floes A Story of the Whaling Grounds.epub", 16)]
|
||||
[InlineData("Non-existent file.epub", 0)]
|
||||
[InlineData("Non an ebub.pdf", 0)]
|
||||
public void GetNumberOfPagesTest(string filePath, int expectedPages)
|
||||
{
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/BookService/EPUB");
|
||||
Assert.Equal(expectedPages, _bookService.GetNumberOfPages(Path.Join(testDirectory, filePath)));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -41,7 +41,7 @@
|
||||
// //[InlineData("", 0, "")]
|
||||
// public void GetCachedPagePathTest_Should()
|
||||
// {
|
||||
// // TODO: Figure out how to test this
|
||||
//
|
||||
// // string archivePath = "flat file.zip";
|
||||
// // int pageNum = 0;
|
||||
// // string expected = "cache/1/pexels-photo-6551949.jpg";
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using API.Services;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -18,6 +19,17 @@ namespace API.Tests.Services
|
||||
_directoryService = new DirectoryService(_logger);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFilesTest_Should_Be28()
|
||||
{
|
||||
var testDirectory = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Manga");
|
||||
var files = new List<string>();
|
||||
var fileCount = DirectoryService.TraverseTreeParallelForEach(testDirectory, s => files.Add(s),
|
||||
API.Parser.Parser.ArchiveFileExtensions, _logger);
|
||||
|
||||
Assert.Equal(28, fileCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetFiles_WithCustomRegex_ShouldPass_Test()
|
||||
{
|
||||
@ -74,5 +86,17 @@ namespace API.Tests.Services
|
||||
Assert.DoesNotContain(dirs, s => s.Contains("regex"));
|
||||
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("C:/Manga/", "C:/Manga/Love Hina/Specials/Omake/", "Omake,Specials,Love Hina")]
|
||||
[InlineData("C:/Manga/", "C:/Manga/Love Hina/Specials/Omake", "Omake,Specials,Love Hina")]
|
||||
[InlineData("C:/Manga", "C:/Manga/Love Hina/Specials/Omake/", "Omake,Specials,Love Hina")]
|
||||
[InlineData("C:/Manga", @"C:\Manga\Love Hina\Specials\Omake\", "Omake,Specials,Love Hina")]
|
||||
[InlineData(@"/manga/", @"/manga/Love Hina/Specials/Omake/", "Omake,Specials,Love Hina")]
|
||||
public void GetFoldersTillRoot_Test(string rootPath, string fullpath, string expectedArray)
|
||||
{
|
||||
var expected = expectedArray.Split(",");
|
||||
Assert.Equal(expected, DirectoryService.GetFoldersTillRoot(rootPath, fullpath));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.Entities;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Parser;
|
||||
using API.Services;
|
||||
using API.Services.Tasks;
|
||||
using API.Tests.Helpers;
|
||||
using AutoMapper;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
@ -12,48 +24,180 @@ using Xunit.Abstractions;
|
||||
|
||||
namespace API.Tests.Services
|
||||
{
|
||||
public class ScannerServiceTests
|
||||
public class ScannerServiceTests : IDisposable
|
||||
{
|
||||
private readonly ITestOutputHelper _testOutputHelper;
|
||||
private readonly ScannerService _scannerService;
|
||||
private readonly ILogger<ScannerService> _logger = Substitute.For<ILogger<ScannerService>>();
|
||||
private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
|
||||
private readonly IArchiveService _archiveService = Substitute.For<IArchiveService>();
|
||||
private readonly IMetadataService _metadataService;
|
||||
private readonly IBookService _bookService = Substitute.For<IBookService>();
|
||||
private readonly ILogger<MetadataService> _metadataLogger = Substitute.For<ILogger<MetadataService>>();
|
||||
private Library _libraryMock;
|
||||
|
||||
private readonly DbConnection _connection;
|
||||
private readonly DataContext _context;
|
||||
|
||||
|
||||
public ScannerServiceTests(ITestOutputHelper testOutputHelper)
|
||||
{
|
||||
var contextOptions = new DbContextOptionsBuilder()
|
||||
.UseSqlite(CreateInMemoryDatabase())
|
||||
.Options;
|
||||
_connection = RelationalOptionsExtension.Extract(contextOptions).Connection;
|
||||
|
||||
_context = new DataContext(contextOptions);
|
||||
Task.Run(SeedDb).GetAwaiter().GetResult();
|
||||
|
||||
|
||||
//BackgroundJob.Enqueue is what I need to mock or something (it's static...)
|
||||
// ICacheService cacheService, ILogger<TaskScheduler> logger, IScannerService scannerService,
|
||||
// IUnitOfWork unitOfWork, IMetadataService metadataService, IBackupService backupService, ICleanupService cleanupService,
|
||||
// IBackgroundJobClient jobClient
|
||||
//var taskScheduler = new TaskScheduler(Substitute.For<ICacheService>(), Substitute.For<ILogger<TaskScheduler>>(), Substitute.For<)
|
||||
|
||||
|
||||
// Substitute.For<UserManager<AppUser>>() - Not needed because only for UserService
|
||||
IUnitOfWork unitOfWork = new UnitOfWork(_context, Substitute.For<IMapper>(), null);
|
||||
|
||||
|
||||
_testOutputHelper = testOutputHelper;
|
||||
_scannerService = new ScannerService(_unitOfWork, _logger, _archiveService, _metadataService);
|
||||
_metadataService= Substitute.For<MetadataService>(_unitOfWork, _metadataLogger, _archiveService);
|
||||
_libraryMock = new Library()
|
||||
IMetadataService metadataService = Substitute.For<MetadataService>(unitOfWork, _metadataLogger, _archiveService, _bookService);
|
||||
_scannerService = new ScannerService(unitOfWork, _logger, _archiveService, metadataService, _bookService);
|
||||
}
|
||||
|
||||
private async Task<bool> SeedDb()
|
||||
{
|
||||
await _context.Database.MigrateAsync();
|
||||
await Seed.SeedSettings(_context);
|
||||
|
||||
_context.Library.Add(new Library()
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Manga",
|
||||
Folders = new List<FolderPath>()
|
||||
{
|
||||
new FolderPath()
|
||||
{
|
||||
Id = 1,
|
||||
LastScanned = DateTime.Now,
|
||||
LibraryId = 1,
|
||||
Path = "E:/Manga"
|
||||
}
|
||||
},
|
||||
LastModified = DateTime.Now,
|
||||
Series = new List<Series>()
|
||||
{
|
||||
new Series()
|
||||
{
|
||||
Id = 0,
|
||||
Name = "Darker Than Black"
|
||||
Path = Path.Join(Directory.GetCurrentDirectory(), "../../../Services/Test Data/ScannerService/Manga")
|
||||
}
|
||||
}
|
||||
});
|
||||
return await _context.SaveChangesAsync() > 0;
|
||||
}
|
||||
|
||||
// [Fact]
|
||||
// public void Test()
|
||||
// {
|
||||
// _scannerService.ScanLibrary(1, false);
|
||||
//
|
||||
// var series = _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1).Result.Series;
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public void FindSeriesNotOnDisk_Should_RemoveNothing_Test()
|
||||
{
|
||||
var infos = new Dictionary<string, List<ParserInfo>>();
|
||||
|
||||
AddToParsedInfo(infos, new ParserInfo() {Series = "Darker than Black"});
|
||||
AddToParsedInfo(infos, new ParserInfo() {Series = "Cage of Eden", Volumes = "1"});
|
||||
AddToParsedInfo(infos, new ParserInfo() {Series = "Cage of Eden", Volumes = "10"});
|
||||
|
||||
var existingSeries = new List<Series>();
|
||||
existingSeries.Add(new Series()
|
||||
{
|
||||
Name = "Cage of Eden",
|
||||
LocalizedName = "Cage of Eden",
|
||||
OriginalName = "Cage of Eden",
|
||||
NormalizedName = API.Parser.Parser.Normalize("Cage of Eden")
|
||||
});
|
||||
existingSeries.Add(new Series()
|
||||
{
|
||||
Name = "Darker Than Black",
|
||||
LocalizedName = "Darker Than Black",
|
||||
OriginalName = "Darker Than Black",
|
||||
NormalizedName = API.Parser.Parser.Normalize("Darker Than Black")
|
||||
});
|
||||
|
||||
|
||||
|
||||
Assert.Empty(_scannerService.FindSeriesNotOnDisk(existingSeries, infos));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(new [] {"Darker than Black"}, "Darker than Black", "Darker than Black")]
|
||||
[InlineData(new [] {"Darker than Black"}, "Darker Than Black", "Darker than Black")]
|
||||
[InlineData(new [] {"Darker than Black"}, "Darker Than Black!", "Darker than Black")]
|
||||
[InlineData(new [] {""}, "Runaway Jack", "Runaway Jack")]
|
||||
public void MergeNameTest(string[] existingSeriesNames, string parsedInfoName, string expected)
|
||||
{
|
||||
var collectedSeries = new ConcurrentDictionary<string, List<ParserInfo>>();
|
||||
foreach (var seriesName in existingSeriesNames)
|
||||
{
|
||||
AddToParsedInfo(collectedSeries, new ParserInfo() {Series = seriesName});
|
||||
}
|
||||
|
||||
var actualName = _scannerService.MergeName(collectedSeries, new ParserInfo()
|
||||
{
|
||||
Series = parsedInfoName
|
||||
});
|
||||
|
||||
Assert.Equal(expected, actualName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveMissingSeries_Should_RemoveSeries()
|
||||
{
|
||||
var existingSeries = new List<Series>()
|
||||
{
|
||||
EntityFactory.CreateSeries("Darker than Black Vol 1"),
|
||||
EntityFactory.CreateSeries("Darker than Black"),
|
||||
EntityFactory.CreateSeries("Beastars"),
|
||||
};
|
||||
var missingSeries = new List<Series>()
|
||||
{
|
||||
EntityFactory.CreateSeries("Darker than Black Vol 1"),
|
||||
};
|
||||
existingSeries = ScannerService.RemoveMissingSeries(existingSeries, missingSeries, out var removeCount).ToList();
|
||||
|
||||
Assert.DoesNotContain(missingSeries[0].Name, existingSeries.Select(s => s.Name));
|
||||
Assert.Equal(missingSeries.Count, removeCount);
|
||||
}
|
||||
|
||||
private void AddToParsedInfo(IDictionary<string, List<ParserInfo>> collectedSeries, ParserInfo info)
|
||||
{
|
||||
if (collectedSeries.GetType() == typeof(ConcurrentDictionary<,>))
|
||||
{
|
||||
((ConcurrentDictionary<string, List<ParserInfo>>) collectedSeries).AddOrUpdate(info.Series, new List<ParserInfo>() {info}, (_, oldValue) =>
|
||||
{
|
||||
oldValue ??= new List<ParserInfo>();
|
||||
if (!oldValue.Contains(info))
|
||||
{
|
||||
oldValue.Add(info);
|
||||
}
|
||||
|
||||
return oldValue;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!collectedSeries.ContainsKey(info.Series))
|
||||
{
|
||||
collectedSeries.Add(info.Series, new List<ParserInfo>() {info});
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = collectedSeries[info.Series];
|
||||
if (!list.Contains(info))
|
||||
{
|
||||
list.Add(info);
|
||||
}
|
||||
|
||||
collectedSeries[info.Series] = list;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// [Fact]
|
||||
// public void ExistingOrDefault_Should_BeFromLibrary()
|
||||
@ -111,5 +255,16 @@ namespace API.Tests.Services
|
||||
// _testOutputHelper.WriteLine(_libraryMock.ToString());
|
||||
Assert.True(true);
|
||||
}
|
||||
|
||||
private static DbConnection CreateInMemoryDatabase()
|
||||
{
|
||||
var connection = new SqliteConnection("Filename=:memory:");
|
||||
|
||||
connection.Open();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
public void Dispose() => _connection.Dispose();
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 53 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 122 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 385 KiB |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,153 @@
|
||||
\A Town Where You Live\A Town Where You Live Vol. 01.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 02.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 03.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 04.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 05.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 06.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 07.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 08.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 09.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 10.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 11.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 12.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 13.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 14.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 15.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 16.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 17.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 18.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 19.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 20.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 21.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 22.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 23.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 24.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 25.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 26.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 27.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Bonus Chapter.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Princess Lucia Collaboration.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Special Fantasy.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Special Youth's Acne.zip
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v01 (2018) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v02 (2018) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v03 (2019) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v04 (2019) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v05 (2019) (Digital) (danke-Empire).cbz
|
||||
\Aiki\Aiki V01.cbz
|
||||
\Aiki\Aiki V02.cbz
|
||||
\Aiki\Aiki V03.cbz
|
||||
\Aiki\Aiki V04.cbz
|
||||
\Aiki\Aiki V05.cbz
|
||||
\Aiki\Aiki V06.cbz
|
||||
\Aiki\Aiki V07.cbz
|
||||
\Aiki\Aiki V08.cbz
|
||||
\Aiki\Aiki V09.cbz
|
||||
\Aiki\Aiki V10.cbz
|
||||
\Aiki\Aiki V11.cbz
|
||||
\Aiki\Aiki V12.cbz
|
||||
\Aiki\Aiki V13.cbz
|
||||
\Aiki\Aiki V14.cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 074 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 074.5 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 075 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 075.5 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 076 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 077 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 078 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 079 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 080 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 081 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 082 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 083 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 083.5 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 084 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 085 (2021) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 086 (2021) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v01 (2014) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v02 (2014) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v03 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v04 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v05 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v06 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v07 (2016) (Digital) (Hexer-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v08 (2016) (Digital) (Hexer-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v09 (2017) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v10 (2017) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v11 (2018) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v12 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v13 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v14 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v15 (2020) (Digital) (danke-Empire).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v01 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v02 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v03 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v04 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v05 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v06 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v07 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v08 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v09 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v10 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v11 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v12 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v13 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v14 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v15 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v01 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v02 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v03 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v04 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v05 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v06 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v07 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v08 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v09 (2019) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v10 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v01 (2019) (F) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v02 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v03 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v04 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v05 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v06 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v07 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v08 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v09.cbz
|
||||
\Beastars\BEASTARS v10.cbz
|
||||
\Beastars\BEASTARS v11.cbz
|
||||
\Beastars\BEASTARS v12.cbz
|
||||
\Beastars\BEASTARS v13.cbz
|
||||
\Beastars\BEASTARS v14.cbz
|
||||
\Beastars\BEASTARS v15.cbz
|
||||
\Beastars\BEASTARS v16.cbz
|
||||
\Beastars\BEASTARS v17.cbz
|
||||
\Beastars\BEASTARS v18.cbz
|
||||
\Beastars\BEASTARS v19.cbz
|
||||
\Beastars\BEASTARS v20.cbz
|
||||
\Beastars\BEASTARS v21.cbz
|
||||
\Black Bullet\Black Bullet - v4 c17 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c17.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c18 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c18.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c19 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c19.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c20 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c20.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet v01 c01.rar
|
||||
\Black Bullet\Black Bullet v01 c02.rar
|
||||
\Black Bullet\Black Bullet v01 c03.rar
|
||||
\Black Bullet\Black Bullet v01 c04.rar
|
||||
\Black Bullet\Black Bullet v01 c05.rar
|
||||
\Black Bullet\Black Bullet v01 c06.rar
|
||||
\Black Bullet\Black Bullet v01 c07.rar
|
||||
\Black Bullet\Black Bullet v01 c08.rar
|
||||
\Black Bullet\Black Bullet v01 c09.5.rar
|
||||
\Black Bullet\Black Bullet v01 c09.rar
|
||||
\Black Bullet\Black Bullet v01 c10.rar
|
||||
\Black Bullet\Black Bullet v01 c11.zip
|
||||
\Black Bullet\Black Bullet v01 c12.5.rar
|
||||
\Black Bullet\Black Bullet v01 c12.rar
|
||||
\Black Bullet\Black Bullet v01 c13.rar
|
||||
\Black Bullet\Black Bullet v01 c14.rar
|
||||
\Black Bullet\Black Bullet v01 c15.rar
|
||||
\Black Bullet\Black Bullet v01 c16.rar
|
@ -0,0 +1,153 @@
|
||||
\A Town Where You Live\A Town Where You Live Vol. 01.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 02.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 03.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 04.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 05.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 06.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 07.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 08.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 09.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 10.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 11.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 12.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 13.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 14.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 15.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 16.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 17.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 18.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 19.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 20.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 21.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 22.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 23.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 24.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 25.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 26.zip
|
||||
\A Town Where You Live\A Town Where You Live Vol. 27.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Bonus Chapter.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Princess Lucia Collaboration.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Special Fantasy.zip
|
||||
\A Town Where You Live\A Town Where You Live - Post Volume 27\A Town Where You Live - Special Youth's Acne.zip
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v01 (2018) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v02 (2018) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v03 (2019) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v04 (2019) (Digital) (danke-Empire).cbz
|
||||
\Accomplishments of the Duke's Daughter\Accomplishments of the Duke's Daughter v05 (2019) (Digital) (danke-Empire).cbz
|
||||
\Aiki\Aiki V01.cbz
|
||||
\Aiki\Aiki V02.cbz
|
||||
\Aiki\Aiki V03.cbz
|
||||
\Aiki\Aiki V04.cbz
|
||||
\Aiki\Aiki V05.cbz
|
||||
\Aiki\Aiki V06.cbz
|
||||
\Aiki\Aiki V07.cbz
|
||||
\Aiki\Aiki V08.cbz
|
||||
\Aiki\Aiki V09.cbz
|
||||
\Aiki\Aiki V10.cbz
|
||||
\Aiki\Aiki V11.cbz
|
||||
\Aiki\Aiki V12.cbz
|
||||
\Aiki\Aiki V13.cbz
|
||||
\Aiki\Aiki V14.cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 074 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 074.5 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 075 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 075.5 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 076 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 077 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 078 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 079 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 080 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 081 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 082 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 083 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 083.5 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 084 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 085 (2021) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human 086 (2021) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v01 (2014) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v02 (2014) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v03 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v04 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v05 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v06 (2015) (Digital) (LostNerevarine-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v07 (2016) (Digital) (Hexer-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v08 (2016) (Digital) (Hexer-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v09 (2017) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v10 (2017) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v11 (2018) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v12 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v13 (2019) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v14 (2020) (Digital) (danke-Empire).cbz
|
||||
\Ajin - Demi-Human\Ajin - Demi-Human v15 (2020) (Digital) (danke-Empire).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v01 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v02 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v03 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v04 (2015) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v05 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v06 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v07 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v08 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v09 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v10 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v11 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v12 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v13 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v14 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL!\Akame ga KILL! v15 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v01 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v02 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v03 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v04 (2016) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v05 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v06 (2017) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v07 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v08 (2018) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v09 (2019) (Digital) (LuCaZ).cbz
|
||||
\Akame ga KILL! ZERO (2016-2019) (Digital) (LuCaZ)\Akame ga KILL! ZERO v10 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v01 (2019) (F) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v02 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v03 (2019) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v04 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v05 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v06 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v07 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v08 (2020) (Digital) (LuCaZ).cbz
|
||||
\Beastars\BEASTARS v09.cbz
|
||||
\Beastars\BEASTARS v10.cbz
|
||||
\Beastars\BEASTARS v11.cbz
|
||||
\Beastars\BEASTARS v12.cbz
|
||||
\Beastars\BEASTARS v13.cbz
|
||||
\Beastars\BEASTARS v14.cbz
|
||||
\Beastars\BEASTARS v15.cbz
|
||||
\Beastars\BEASTARS v16.cbz
|
||||
\Beastars\BEASTARS v17.cbz
|
||||
\Beastars\BEASTARS v18.cbz
|
||||
\Beastars\BEASTARS v19.cbz
|
||||
\Beastars\BEASTARS v20.cbz
|
||||
\Beastars\BEASTARS v21.cbz
|
||||
\Black Bullet\Black Bullet - v4 c17 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c17.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c18 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c18.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c19 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c19.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c20 [batoto].zip
|
||||
\Black Bullet\Black Bullet - v4 c20.5 [batoto].zip
|
||||
\Black Bullet\Black Bullet v01 c01.rar
|
||||
\Black Bullet\Black Bullet v01 c02.rar
|
||||
\Black Bullet\Black Bullet v01 c03.rar
|
||||
\Black Bullet\Black Bullet v01 c04.rar
|
||||
\Black Bullet\Black Bullet v01 c05.rar
|
||||
\Black Bullet\Black Bullet v01 c06.rar
|
||||
\Black Bullet\Black Bullet v01 c07.rar
|
||||
\Black Bullet\Black Bullet v01 c08.rar
|
||||
\Black Bullet\Black Bullet v01 c09.5.rar
|
||||
\Black Bullet\Black Bullet v01 c09.rar
|
||||
\Black Bullet\Black Bullet v01 c10.rar
|
||||
\Black Bullet\Black Bullet v01 c11.zip
|
||||
\Black Bullet\Black Bullet v01 c12.5.rar
|
||||
\Black Bullet\Black Bullet v01 c12.rar
|
||||
\Black Bullet\Black Bullet v01 c13.rar
|
||||
\Black Bullet\Black Bullet v01 c14.rar
|
||||
\Black Bullet\Black Bullet v01 c15.rar
|
||||
\Black Bullet\Black Bullet v01 c16.rar
|
80
API.Tests/generate_test_data.py
Normal file
80
API.Tests/generate_test_data.py
Normal file
@ -0,0 +1,80 @@
|
||||
""" This script should be run on a directory which will generate a test case file
|
||||
that can be loaded into the renametest.py"""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
|
||||
verbose = False
|
||||
|
||||
def print_log(val):
|
||||
if verbose:
|
||||
print(val)
|
||||
|
||||
|
||||
def create_test_base(file, root_dir):
|
||||
""" Creates and returns a new base directory for data creation for a given testcase."""
|
||||
base_dir = os.path.split(file.split('-testcase.txt')[0])[-1]
|
||||
print_log('base_dir: {0}'.format(base_dir))
|
||||
new_dir = os.path.join(root_dir, base_dir)
|
||||
print_log('new dir: {0}'.format(new_dir))
|
||||
p = Path(new_dir)
|
||||
if not p.exists():
|
||||
os.mkdir(new_dir)
|
||||
|
||||
return new_dir
|
||||
|
||||
|
||||
|
||||
def generate_data(file, root_dir):
|
||||
''' Generates directories and fake files for testing against '''
|
||||
|
||||
base_dir = ''
|
||||
if file.endswith('-testcase.txt'):
|
||||
base_dir = create_test_base(file, root_dir)
|
||||
|
||||
files_to_create = []
|
||||
with open(file, 'r') as in_file:
|
||||
files_to_create = in_file.read().splitlines()
|
||||
|
||||
for filepath in files_to_create:
|
||||
for part in os.path.split(filepath):
|
||||
part_path = os.path.join(base_dir, part)
|
||||
print_log('Checking if {0} exists '.format(part_path))
|
||||
p = Path(part_path)
|
||||
|
||||
if not p.exists():
|
||||
print_log('Creating: {0}'.format(part))
|
||||
|
||||
if p.suffix != '':
|
||||
with open(os.path.join(root_dir, base_dir + '/' + filepath), 'w+') as f:
|
||||
f.write('')
|
||||
else:
|
||||
os.mkdir(part_path)
|
||||
|
||||
def clean_up_generated_data(root_dir):
|
||||
for root, dirs, files in os.walk(root_dir):
|
||||
for dir in dirs:
|
||||
shutil.rmtree(os.path.join(root, dir))
|
||||
for file in files:
|
||||
if not file.endswith('-testcase.txt'):
|
||||
print_log('Removing {0}'.format(os.path.join(root, file)))
|
||||
os.remove(os.path.join(root, file))
|
||||
|
||||
|
||||
def generate_test_file():
|
||||
root_dir = os.path.abspath('.')
|
||||
current_folder = os.path.split(root_dir)[-1]
|
||||
out_files = []
|
||||
for root, _, files in os.walk(root_dir):
|
||||
for file in files:
|
||||
if not file.endswith('-testcase.txt'):
|
||||
filename = os.path.join(root.replace(root_dir, ''), file) # root_dir or root_dir + '//'?
|
||||
out_files.append(filename)
|
||||
|
||||
with open(os.path.join(root_dir, current_folder + '-testcase.txt'), 'w+') as f:
|
||||
for filename in out_files:
|
||||
f.write(filename + '\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
verbose = True
|
||||
generate_test_file()
|
25
API/.dockerignore
Normal file
25
API/.dockerignore
Normal file
@ -0,0 +1,25 @@
|
||||
**/.dockerignore
|
||||
**/.env
|
||||
**/.git
|
||||
**/.gitignore
|
||||
**/.project
|
||||
**/.settings
|
||||
**/.toolstarget
|
||||
**/.vs
|
||||
**/.vscode
|
||||
**/.idea
|
||||
**/*.*proj.user
|
||||
**/*.dbmdl
|
||||
**/*.jfm
|
||||
**/azds.yaml
|
||||
**/bin
|
||||
**/charts
|
||||
**/docker-compose*
|
||||
**/Dockerfile*
|
||||
**/node_modules
|
||||
**/npm-debug.log
|
||||
**/obj
|
||||
**/secrets.dev.yaml
|
||||
**/values.dev.yaml
|
||||
LICENSE
|
||||
README.md
|
@ -4,37 +4,43 @@
|
||||
<AnalysisMode>Default</AnalysisMode>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<ApplicationIcon>../favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.0" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.18" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.18" />
|
||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
|
||||
<PackageReference Include="ExCSS" Version="4.1.0" />
|
||||
<PackageReference Include="Hangfire" Version="1.7.20" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.20" />
|
||||
<PackageReference Include="Hangfire.MaximumConcurrentExecutions" Version="1.1.0" />
|
||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.1"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.1">
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.32" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
|
||||
<PackageReference Include="NetVips" Version="1.2.4" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.10.5.1" />
|
||||
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="2.0.0" />
|
||||
<PackageReference Include="NetVips" Version="2.0.0" />
|
||||
<PackageReference Include="NetVips.Native" Version="8.10.6" />
|
||||
<PackageReference Include="NReco.Logging.File" Version="1.1.1" />
|
||||
<PackageReference Include="SharpCompress" Version="0.28.1" />
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.20.0.28934">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.6.3" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.1.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.10.0" />
|
||||
<PackageReference Include="VersOne.Epub" Version="3.0.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -55,4 +61,8 @@
|
||||
<Content Remove="obj\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="logs\kavita.json" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -2,20 +2,9 @@
|
||||
|
||||
namespace API.Comparators
|
||||
{
|
||||
public class ChapterSortComparer : IComparer<float>
|
||||
public class ChapterSortComparer : IComparer<double>
|
||||
{
|
||||
// public int Compare(int x, int y)
|
||||
// {
|
||||
// if (x == 0 && y == 0) return 0;
|
||||
// // if x is 0, it comes second
|
||||
// if (x == 0) return 1;
|
||||
// // if y is 0, it comes second
|
||||
// if (y == 0) return -1;
|
||||
//
|
||||
// return x.CompareTo(y);
|
||||
// }
|
||||
|
||||
public int Compare(float x, float y)
|
||||
public int Compare(double x, double y)
|
||||
{
|
||||
if (x == 0.0 && y == 0.0) return 0;
|
||||
// if x is 0, it comes second
|
||||
|
102
API/Comparators/NaturalSortComparer.cs
Normal file
102
API/Comparators/NaturalSortComparer.cs
Normal file
@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using static System.GC;
|
||||
using static System.String;
|
||||
|
||||
namespace API.Comparators
|
||||
{
|
||||
public sealed class NaturalSortComparer : IComparer<string>, IDisposable
|
||||
{
|
||||
private readonly bool _isAscending;
|
||||
private Dictionary<string, string[]> _table = new();
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
|
||||
public NaturalSortComparer(bool inAscendingOrder = true)
|
||||
{
|
||||
_isAscending = inAscendingOrder;
|
||||
}
|
||||
|
||||
int IComparer<string>.Compare(string x, string y)
|
||||
{
|
||||
if (x == y) return 0;
|
||||
|
||||
if (!_table.TryGetValue(x ?? Empty, out var x1))
|
||||
{
|
||||
// .Replace(" ", Empty)
|
||||
x1 = Regex.Split(x ?? Empty, "([0-9]+)");
|
||||
_table.Add(x ?? Empty, x1);
|
||||
}
|
||||
|
||||
if (!_table.TryGetValue(y ?? Empty, out var y1))
|
||||
{
|
||||
y1 = Regex.Split(y ?? Empty, "([0-9]+)");
|
||||
_table.Add(y ?? Empty, y1);
|
||||
}
|
||||
|
||||
int returnVal;
|
||||
|
||||
for (var i = 0; i < x1.Length && i < y1.Length; i++)
|
||||
{
|
||||
if (x1[i] == y1[i]) continue;
|
||||
returnVal = PartCompare(x1[i], y1[i]);
|
||||
return _isAscending ? returnVal : -returnVal;
|
||||
}
|
||||
|
||||
if (y1.Length > x1.Length)
|
||||
{
|
||||
returnVal = 1;
|
||||
}
|
||||
else if (x1.Length > y1.Length)
|
||||
{
|
||||
returnVal = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
returnVal = 0;
|
||||
}
|
||||
|
||||
return _isAscending ? returnVal : -returnVal;
|
||||
}
|
||||
|
||||
private static int PartCompare(string left, string right)
|
||||
{
|
||||
if (!int.TryParse(left, out var x))
|
||||
return Compare(left, right, StringComparison.Ordinal);
|
||||
|
||||
if (!int.TryParse(right, out var y))
|
||||
return Compare(left, right, StringComparison.Ordinal);
|
||||
|
||||
return x.CompareTo(y);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
// called via myClass.Dispose().
|
||||
_table.Clear();
|
||||
_table = null;
|
||||
}
|
||||
// Release unmanaged resources.
|
||||
// Set large fields to null.
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~NaturalSortComparer() // the finalizer
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
{
|
||||
public static class PolicyConstants
|
||||
{
|
||||
public static readonly string AdminRole = "Admin";
|
||||
public static readonly string PlebRole = "Pleb";
|
||||
public const string AdminRole = "Admin";
|
||||
public const string PlebRole = "Pleb";
|
||||
}
|
||||
}
|
@ -82,14 +82,14 @@ namespace API.Controllers
|
||||
[HttpPost("register")]
|
||||
public async Task<ActionResult<UserDto>> Register(RegisterDto registerDto)
|
||||
{
|
||||
if (await _userManager.Users.AnyAsync(x => x.UserName == registerDto.Username))
|
||||
if (await _userManager.Users.AnyAsync(x => x.NormalizedUserName == registerDto.Username.ToUpper()))
|
||||
{
|
||||
return BadRequest("Username is taken.");
|
||||
}
|
||||
|
||||
var user = _mapper.Map<AppUser>(registerDto);
|
||||
user.UserPreferences ??= new AppUserPreferences();
|
||||
|
||||
|
||||
var result = await _userManager.CreateAsync(user, registerDto.Password);
|
||||
|
||||
if (!result.Succeeded) return BadRequest(result.Errors);
|
||||
@ -132,7 +132,7 @@ namespace API.Controllers
|
||||
var result = await _signInManager
|
||||
.CheckPasswordSignInAsync(user, loginDto.Password, false);
|
||||
|
||||
if (!result.Succeeded) return Unauthorized();
|
||||
if (!result.Succeeded) return Unauthorized("Your credentials are not correct.");
|
||||
|
||||
// Update LastActive on account
|
||||
user.LastActive = DateTime.Now;
|
||||
|
295
API/Controllers/BookController.cs
Normal file
295
API/Controllers/BookController.cs
Normal file
@ -0,0 +1,295 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Services;
|
||||
using HtmlAgilityPack;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using VersOne.Epub;
|
||||
|
||||
namespace API.Controllers
|
||||
{
|
||||
public class BookController : BaseApiController
|
||||
{
|
||||
private readonly ILogger<BookController> _logger;
|
||||
private readonly IBookService _bookService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private static readonly string BookApiUrl = "book-resources?file=";
|
||||
|
||||
|
||||
public BookController(ILogger<BookController> logger, IBookService bookService, IUnitOfWork unitOfWork)
|
||||
{
|
||||
_logger = logger;
|
||||
_bookService = bookService;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
[HttpGet("{chapterId}/book-info")]
|
||||
public async Task<ActionResult<string>> GetBookInfo(int chapterId)
|
||||
{
|
||||
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
|
||||
var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath);
|
||||
|
||||
return book.Title;
|
||||
}
|
||||
|
||||
[HttpGet("{chapterId}/book-resources")]
|
||||
public async Task<ActionResult> GetBookPageResources(int chapterId, [FromQuery] string file)
|
||||
{
|
||||
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
|
||||
var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath);
|
||||
|
||||
var key = BookService.CleanContentKeys(file);
|
||||
if (!book.Content.AllFiles.ContainsKey(key)) return BadRequest("File was not found in book");
|
||||
|
||||
var bookFile = book.Content.AllFiles[key];
|
||||
var content = await bookFile.ReadContentAsBytesAsync();
|
||||
Response.AddCacheHeader(content);
|
||||
var contentType = BookService.GetContentType(bookFile.ContentType);
|
||||
return File(content, contentType, $"{chapterId}-{file}");
|
||||
}
|
||||
|
||||
[HttpGet("{chapterId}/chapters")]
|
||||
public async Task<ActionResult<ICollection<BookChapterItem>>> GetBookChapters(int chapterId)
|
||||
{
|
||||
// This will return a list of mappings from ID -> pagenum. ID will be the xhtml key and pagenum will be the reading order
|
||||
// this is used to rewrite anchors in the book text so that we always load properly in FE
|
||||
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
|
||||
var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath);
|
||||
var mappings = await _bookService.CreateKeyToPageMappingAsync(book);
|
||||
|
||||
var navItems = await book.GetNavigationAsync();
|
||||
var chaptersList = new List<BookChapterItem>();
|
||||
|
||||
foreach (var navigationItem in navItems)
|
||||
{
|
||||
if (navigationItem.NestedItems.Count > 0)
|
||||
{
|
||||
_logger.LogDebug("Header: {Header}", navigationItem.Title);
|
||||
var nestedChapters = new List<BookChapterItem>();
|
||||
|
||||
foreach (var nestedChapter in navigationItem.NestedItems)
|
||||
{
|
||||
if (nestedChapter.Link == null) continue;
|
||||
var key = BookService.CleanContentKeys(nestedChapter.Link.ContentFileName);
|
||||
if (mappings.ContainsKey(key))
|
||||
{
|
||||
nestedChapters.Add(new BookChapterItem()
|
||||
{
|
||||
Title = nestedChapter.Title,
|
||||
Page = mappings[key],
|
||||
Part = nestedChapter.Link.Anchor ?? string.Empty,
|
||||
Children = new List<BookChapterItem>()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (navigationItem.Link == null)
|
||||
{
|
||||
var item = new BookChapterItem()
|
||||
{
|
||||
Title = navigationItem.Title,
|
||||
Children = nestedChapters
|
||||
};
|
||||
if (nestedChapters.Count > 0)
|
||||
{
|
||||
item.Page = nestedChapters[0].Page;
|
||||
}
|
||||
chaptersList.Add(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var groupKey = BookService.CleanContentKeys(navigationItem.Link.ContentFileName);
|
||||
if (mappings.ContainsKey(groupKey))
|
||||
{
|
||||
chaptersList.Add(new BookChapterItem()
|
||||
{
|
||||
Title = navigationItem.Title,
|
||||
Page = mappings[groupKey],
|
||||
Children = nestedChapters
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chaptersList.Count == 0)
|
||||
{
|
||||
// Generate from TOC
|
||||
var tocPage = book.Content.Html.Keys.FirstOrDefault(k => k.ToUpper().Contains("TOC"));
|
||||
if (tocPage == null) return Ok(chaptersList);
|
||||
|
||||
// Find all anchor tags, for each anchor we get inner text, to lower then titlecase on UI. Get href and generate page content
|
||||
var doc = new HtmlDocument();
|
||||
var content = await book.Content.Html[tocPage].ReadContentAsync();
|
||||
doc.LoadHtml(content);
|
||||
var anchors = doc.DocumentNode.SelectNodes("//a");
|
||||
if (anchors == null) return Ok(chaptersList);
|
||||
|
||||
foreach (var anchor in anchors)
|
||||
{
|
||||
if (anchor.Attributes.Contains("href"))
|
||||
{
|
||||
var key = BookService.CleanContentKeys(anchor.Attributes["href"].Value).Split("#")[0];
|
||||
if (!mappings.ContainsKey(key))
|
||||
{
|
||||
// Fallback to searching for key (bad epub metadata)
|
||||
var correctedKey = book.Content.Html.Keys.SingleOrDefault(s => s.EndsWith(key));
|
||||
if (!string.IsNullOrEmpty(correctedKey))
|
||||
{
|
||||
key = correctedKey;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(key) && mappings.ContainsKey(key))
|
||||
{
|
||||
var part = string.Empty;
|
||||
if (anchor.Attributes["href"].Value.Contains("#"))
|
||||
{
|
||||
part = anchor.Attributes["href"].Value.Split("#")[1];
|
||||
}
|
||||
chaptersList.Add(new BookChapterItem()
|
||||
{
|
||||
Title = anchor.InnerText,
|
||||
Page = mappings[key],
|
||||
Part = part,
|
||||
Children = new List<BookChapterItem>()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return Ok(chaptersList);
|
||||
}
|
||||
|
||||
[HttpGet("{chapterId}/book-page")]
|
||||
public async Task<ActionResult<string>> GetBookPage(int chapterId, [FromQuery] int page)
|
||||
{
|
||||
var chapter = await _unitOfWork.VolumeRepository.GetChapterAsync(chapterId);
|
||||
|
||||
var book = await EpubReader.OpenBookAsync(chapter.Files.ElementAt(0).FilePath);
|
||||
var mappings = await _bookService.CreateKeyToPageMappingAsync(book);
|
||||
|
||||
var counter = 0;
|
||||
var doc = new HtmlDocument();
|
||||
var baseUrl = Request.Scheme + "://" + Request.Host + Request.PathBase + "/api/";
|
||||
var apiBase = baseUrl + "book/" + chapterId + "/" + BookApiUrl;
|
||||
var bookPages = await book.GetReadingOrderAsync();
|
||||
foreach (var contentFileRef in bookPages)
|
||||
{
|
||||
if (page == counter)
|
||||
{
|
||||
var content = await contentFileRef.ReadContentAsync();
|
||||
if (contentFileRef.ContentType != EpubContentType.XHTML_1_1) return Ok(content);
|
||||
|
||||
doc.LoadHtml(content);
|
||||
var body = doc.DocumentNode.SelectSingleNode("/html/body");
|
||||
|
||||
var inlineStyles = doc.DocumentNode.SelectNodes("//style");
|
||||
if (inlineStyles != null)
|
||||
{
|
||||
foreach (var inlineStyle in inlineStyles)
|
||||
{
|
||||
var styleContent = await _bookService.ScopeStyles(inlineStyle.InnerHtml, apiBase);
|
||||
body.PrependChild(HtmlNode.CreateNode($"<style>{styleContent}</style>"));
|
||||
}
|
||||
}
|
||||
|
||||
var styleNodes = doc.DocumentNode.SelectNodes("/html/head/link");
|
||||
if (styleNodes != null)
|
||||
{
|
||||
foreach (var styleLinks in styleNodes)
|
||||
{
|
||||
var key = BookService.CleanContentKeys(styleLinks.Attributes["href"].Value);
|
||||
// Some epubs are malformed the key in content.opf might be: content/resources/filelist_0_0.xml but the actual html links to resources/filelist_0_0.xml
|
||||
// In this case, we will do a search for the key that ends with
|
||||
if (!book.Content.Css.ContainsKey(key))
|
||||
{
|
||||
var correctedKey = book.Content.Css.Keys.SingleOrDefault(s => s.EndsWith(key));
|
||||
if (correctedKey == null)
|
||||
{
|
||||
_logger.LogError("Epub is Malformed, key: {Key} is not matching OPF file", key);
|
||||
continue;
|
||||
}
|
||||
|
||||
key = correctedKey;
|
||||
}
|
||||
var styleContent = await _bookService.ScopeStyles(await book.Content.Css[key].ReadContentAsync(), apiBase);
|
||||
body.PrependChild(HtmlNode.CreateNode($"<style>{styleContent}</style>"));
|
||||
}
|
||||
}
|
||||
|
||||
var anchors = doc.DocumentNode.SelectNodes("//a");
|
||||
if (anchors != null)
|
||||
{
|
||||
foreach (var anchor in anchors)
|
||||
{
|
||||
BookService.UpdateLinks(anchor, mappings, page);
|
||||
}
|
||||
}
|
||||
|
||||
var images = doc.DocumentNode.SelectNodes("//img");
|
||||
if (images != null)
|
||||
{
|
||||
foreach (var image in images)
|
||||
{
|
||||
if (image.Name != "img") continue;
|
||||
|
||||
// Need to do for xlink:href
|
||||
if (image.Attributes["src"] != null)
|
||||
{
|
||||
var imageFile = image.Attributes["src"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
image.Attributes.Remove("src");
|
||||
image.Attributes.Add("src", $"{apiBase}" + imageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
images = doc.DocumentNode.SelectNodes("//image");
|
||||
if (images != null)
|
||||
{
|
||||
foreach (var image in images)
|
||||
{
|
||||
if (image.Name != "image") continue;
|
||||
|
||||
if (image.Attributes["xlink:href"] != null)
|
||||
{
|
||||
var imageFile = image.Attributes["xlink:href"].Value;
|
||||
if (!book.Content.Images.ContainsKey(imageFile))
|
||||
{
|
||||
var correctedKey = book.Content.Images.Keys.SingleOrDefault(s => s.EndsWith(imageFile));
|
||||
if (correctedKey != null)
|
||||
{
|
||||
imageFile = correctedKey;
|
||||
}
|
||||
}
|
||||
image.Attributes.Remove("xlink:href");
|
||||
image.Attributes.Add("xlink:href", $"{apiBase}" + imageFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
return Ok(body.InnerHtml);
|
||||
}
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
return BadRequest("Could not find the appropriate html for that page");
|
||||
}
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ namespace API.Controllers
|
||||
public class FallbackController : Controller
|
||||
{
|
||||
// ReSharper disable once S4487
|
||||
// ReSharper disable once NotAccessedField.Local
|
||||
private readonly ITaskScheduler _taskScheduler;
|
||||
|
||||
public FallbackController(ITaskScheduler taskScheduler)
|
||||
|
@ -22,7 +22,7 @@ namespace API.Controllers
|
||||
const string format = "jpeg";
|
||||
|
||||
Response.AddCacheHeader(content);
|
||||
return File(content, "image/" + format);
|
||||
return File(content, "image/" + format, $"chapterId");
|
||||
}
|
||||
|
||||
[HttpGet("volume-cover")]
|
||||
@ -33,7 +33,7 @@ namespace API.Controllers
|
||||
const string format = "jpeg";
|
||||
|
||||
Response.AddCacheHeader(content);
|
||||
return File(content, "image/" + format);
|
||||
return File(content, "image/" + format, $"volumeId");
|
||||
}
|
||||
|
||||
[HttpGet("series-cover")]
|
||||
@ -44,7 +44,7 @@ namespace API.Controllers
|
||||
const string format = "jpeg";
|
||||
|
||||
Response.AddCacheHeader(content);
|
||||
return File(content, "image/" + format);
|
||||
return File(content, "image/" + format, $"seriesId");
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using AutoMapper;
|
||||
@ -155,7 +155,7 @@ namespace API.Controllers
|
||||
[HttpPost("refresh-metadata")]
|
||||
public ActionResult RefreshMetadata(int libraryId)
|
||||
{
|
||||
_taskScheduler.ScanLibrary(libraryId, true);
|
||||
_taskScheduler.RefreshMetadata(libraryId);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -164,23 +164,7 @@ namespace API.Controllers
|
||||
{
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryDtosForUsernameAsync(User.GetUsername()));
|
||||
}
|
||||
|
||||
[HttpGet("series")]
|
||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
// TODO: Move this to SeriesController
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id, userParams);
|
||||
|
||||
// Apply progress/rating information (I can't work out how to do this in initial query)
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpDelete("delete")]
|
||||
public async Task<ActionResult<bool>> DeleteLibrary(int libraryId)
|
||||
@ -239,5 +223,11 @@ namespace API.Controllers
|
||||
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
[HttpGet("type")]
|
||||
public async Task<ActionResult<LibraryType>> GetLibraryType(int libraryId)
|
||||
{
|
||||
return Ok(await _unitOfWork.LibraryRepository.GetLibraryTypeAsync(libraryId));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Data;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
@ -46,7 +46,7 @@ namespace API.Controllers
|
||||
|
||||
return File(content, "image/" + format);
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("chapter-path")]
|
||||
public async Task<ActionResult<string>> GetImagePath(int chapterId)
|
||||
{
|
||||
@ -218,7 +218,8 @@ namespace API.Controllers
|
||||
PagesRead = bookmarkDto.PageNum,
|
||||
VolumeId = bookmarkDto.VolumeId,
|
||||
SeriesId = bookmarkDto.SeriesId,
|
||||
ChapterId = bookmarkDto.ChapterId
|
||||
ChapterId = bookmarkDto.ChapterId,
|
||||
LastModified = DateTime.Now
|
||||
});
|
||||
}
|
||||
else
|
||||
@ -226,8 +227,9 @@ namespace API.Controllers
|
||||
userProgress.PagesRead = bookmarkDto.PageNum;
|
||||
userProgress.SeriesId = bookmarkDto.SeriesId;
|
||||
userProgress.VolumeId = bookmarkDto.VolumeId;
|
||||
userProgress.LastModified = DateTime.Now;
|
||||
}
|
||||
|
||||
|
||||
_unitOfWork.UserRepository.Update(user);
|
||||
|
||||
if (await _unitOfWork.Complete())
|
||||
@ -237,5 +239,81 @@ namespace API.Controllers
|
||||
|
||||
return BadRequest("Could not save progress");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next logical volume from the series.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <param name="currentChapterId"></param>
|
||||
/// <returns>chapter id for next manga</returns>
|
||||
[HttpGet("next-chapter")]
|
||||
public async Task<ActionResult<int>> GetNextChapter(int seriesId, int volumeId, int currentChapterId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var volumes = await _unitOfWork.SeriesRepository.GetVolumesDtoAsync(seriesId, user.Id);
|
||||
var currentVolume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId);
|
||||
|
||||
var next = false;
|
||||
if (currentVolume.Number == 0)
|
||||
{
|
||||
foreach (var chapter in currentVolume.Chapters)
|
||||
{
|
||||
if (next)
|
||||
{
|
||||
return Ok(chapter.Id);
|
||||
}
|
||||
if (currentChapterId == chapter.Id) next = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var volume in volumes)
|
||||
{
|
||||
if (volume.Number == currentVolume.Number + 1)
|
||||
{
|
||||
return Ok(volume.Chapters.FirstOrDefault()?.Id);
|
||||
}
|
||||
}
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the previous logical volume from the series.
|
||||
/// </summary>
|
||||
/// <param name="seriesId"></param>
|
||||
/// <param name="volumeId"></param>
|
||||
/// <param name="currentChapterId"></param>
|
||||
/// <returns>chapter id for next manga</returns>
|
||||
[HttpGet("prev-chapter")]
|
||||
public async Task<ActionResult<int>> GetPreviousChapter(int seriesId, int volumeId, int currentChapterId)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var volumes = await _unitOfWork.SeriesRepository.GetVolumesDtoAsync(seriesId, user.Id);
|
||||
var currentVolume = await _unitOfWork.SeriesRepository.GetVolumeAsync(volumeId);
|
||||
|
||||
var next = false;
|
||||
if (currentVolume.Number == 0)
|
||||
{
|
||||
var chapters = currentVolume.Chapters.Reverse();
|
||||
foreach (var chapter in chapters)
|
||||
{
|
||||
if (next)
|
||||
{
|
||||
return Ok(chapter.Id);
|
||||
}
|
||||
if (currentChapterId == chapter.Id) next = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var volume in volumes.Reverse())
|
||||
{
|
||||
if (volume.Number == currentVolume.Number - 1)
|
||||
{
|
||||
return Ok(volume.Chapters.LastOrDefault()?.Id);
|
||||
}
|
||||
}
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Extensions;
|
||||
using API.Helpers;
|
||||
using API.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -23,6 +24,23 @@ namespace API.Controllers
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<Series>>> GetSeriesForLibrary(int libraryId, [FromQuery] UserParams userParams)
|
||||
{
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
var series =
|
||||
await _unitOfWork.SeriesRepository.GetSeriesDtoForLibraryIdAsync(libraryId, user.Id, userParams);
|
||||
|
||||
// Apply progress/rating information (I can't work out how to do this in initial query)
|
||||
if (series == null) return BadRequest("Could not get series for library");
|
||||
|
||||
await _unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, series);
|
||||
|
||||
Response.AddPaginationHeader(series.CurrentPage, series.PageSize, series.TotalCount, series.TotalPages);
|
||||
|
||||
return Ok(series);
|
||||
}
|
||||
|
||||
[HttpGet("{seriesId}")]
|
||||
public async Task<ActionResult<SeriesDto>> GetSeries(int seriesId)
|
||||
{
|
||||
@ -70,6 +88,8 @@ namespace API.Controllers
|
||||
{
|
||||
return Ok(await _unitOfWork.VolumeRepository.GetChapterDtoAsync(chapterId));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpPost("update-rating")]
|
||||
@ -105,11 +125,9 @@ namespace API.Controllers
|
||||
|
||||
if (series == null) return BadRequest("Series does not exist");
|
||||
|
||||
// TODO: Ensure we check against Library for Series Name change
|
||||
var existingSeries = await _unitOfWork.SeriesRepository.GetSeriesByNameAsync(updateSeries.Name);
|
||||
if (existingSeries != null && existingSeries.Id != series.Id )
|
||||
if (series.Name != updateSeries.Name && await _unitOfWork.SeriesRepository.DoesSeriesNameExistInLibrary(updateSeries.Name))
|
||||
{
|
||||
return BadRequest("A series already exists with this name. Name must be unique.");
|
||||
return BadRequest("A series already exists in this library with this name. Series Names must be unique to a library.");
|
||||
}
|
||||
series.Name = updateSeries.Name;
|
||||
series.LocalizedName = updateSeries.LocalizedName;
|
||||
@ -129,7 +147,8 @@ namespace API.Controllers
|
||||
[HttpGet("recently-added")]
|
||||
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0, int limit = 20)
|
||||
{
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(libraryId, limit));
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(user.Id, libraryId, limit));
|
||||
}
|
||||
|
||||
[HttpGet("in-progress")]
|
||||
@ -138,5 +157,13 @@ namespace API.Controllers
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit));
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpPost("refresh-metadata")]
|
||||
public ActionResult RefreshSeriesMetadata(RefreshSeriesDto refreshSeriesDto)
|
||||
{
|
||||
_taskScheduler.RefreshSeriesMetadata(refreshSeriesDto.LibraryId, refreshSeriesDto.SeriesId);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Threading.Tasks;
|
||||
using API.Extensions;
|
||||
using API.Interfaces;
|
||||
using API.Interfaces.Services;
|
||||
using API.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -105,6 +105,13 @@ namespace API.Controllers
|
||||
return Ok(CronConverter.Options);
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpGet("library-types")]
|
||||
public ActionResult<IEnumerable<string>> GetLibraryTypes()
|
||||
{
|
||||
return Ok(Enum.GetNames(typeof(LibraryType)));
|
||||
}
|
||||
|
||||
[Authorize(Policy = "RequireAdminRole")]
|
||||
[HttpGet("log-levels")]
|
||||
public ActionResult<IEnumerable<string>> GetLogLevels()
|
||||
|
@ -38,6 +38,14 @@ namespace API.Controllers
|
||||
return Ok(await _unitOfWork.UserRepository.GetMembersAsync());
|
||||
}
|
||||
|
||||
[HttpGet("has-reading-progress")]
|
||||
public async Task<ActionResult<bool>> HasReadingProgress(int libraryId)
|
||||
{
|
||||
var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(libraryId);
|
||||
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
|
||||
return Ok(await _unitOfWork.AppUserProgressRepository.UserHasProgress(library.Type, user.Id));
|
||||
}
|
||||
|
||||
[HttpGet("has-library-access")]
|
||||
public async Task<ActionResult<bool>> HasLibraryAccess(int libraryId)
|
||||
{
|
||||
@ -53,7 +61,12 @@ namespace API.Controllers
|
||||
existingPreferences.ReadingDirection = preferencesDto.ReadingDirection;
|
||||
existingPreferences.ScalingOption = preferencesDto.ScalingOption;
|
||||
existingPreferences.PageSplitOption = preferencesDto.PageSplitOption;
|
||||
existingPreferences.HideReadOnDetails = preferencesDto.HideReadOnDetails;
|
||||
existingPreferences.BookReaderMargin = preferencesDto.BookReaderMargin;
|
||||
existingPreferences.BookReaderLineSpacing = preferencesDto.BookReaderLineSpacing;
|
||||
existingPreferences.BookReaderFontFamily = preferencesDto.BookReaderFontFamily;
|
||||
existingPreferences.BookReaderDarkMode = preferencesDto.BookReaderDarkMode;
|
||||
existingPreferences.BookReaderFontSize = preferencesDto.BookReaderFontSize;
|
||||
existingPreferences.BookReaderTapToPaginate = preferencesDto.BookReaderTapToPaginate;
|
||||
|
||||
_unitOfWork.UserRepository.Update(existingPreferences);
|
||||
|
||||
|
21
API/DTOs/BookChapterItem.cs
Normal file
21
API/DTOs/BookChapterItem.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class BookChapterItem
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the Chapter
|
||||
/// </summary>
|
||||
public string Title { get; set; }
|
||||
/// <summary>
|
||||
/// A part represents the id of the anchor so we can scroll to it. 01_values.xhtml#h_sVZPaxUSy/
|
||||
/// </summary>
|
||||
public string Part { get; set; }
|
||||
/// <summary>
|
||||
/// Page Number to load for the chapter
|
||||
/// </summary>
|
||||
public int Page { get; set; }
|
||||
public ICollection<BookChapterItem> Children { get; set; }
|
||||
}
|
||||
}
|
@ -18,6 +18,14 @@ namespace API.DTOs
|
||||
/// </summary>
|
||||
public int Pages { get; init; }
|
||||
/// <summary>
|
||||
/// If this Chapter contains files that could only be identified as Series or has Special Identifier from filename
|
||||
/// </summary>
|
||||
public bool IsSpecial { get; init; }
|
||||
/// <summary>
|
||||
/// Used for books/specials to display custom title. For non-specials/books, will be set to <see cref="Range"/>
|
||||
/// </summary>
|
||||
public string Title { get; init; }
|
||||
/// <summary>
|
||||
/// The files that represent this Chapter
|
||||
/// </summary>
|
||||
public ICollection<MangaFileDto> Files { get; init; }
|
||||
|
8
API/DTOs/RefreshSeriesDto.cs
Normal file
8
API/DTOs/RefreshSeriesDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class RefreshSeriesDto
|
||||
{
|
||||
public int LibraryId { get; set; }
|
||||
public int SeriesId { get; set; }
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@
|
||||
public string Name { get; init; }
|
||||
public string OriginalName { get; init; }
|
||||
public string SortName { get; init; }
|
||||
public string LocalizedName { get; init; }
|
||||
|
||||
// Grouping information
|
||||
public string LibraryName { get; set; }
|
||||
|
@ -1,4 +1,6 @@
|
||||
namespace API.DTOs
|
||||
using System;
|
||||
|
||||
namespace API.DTOs
|
||||
{
|
||||
public class SeriesDto
|
||||
{
|
||||
@ -21,7 +23,10 @@
|
||||
/// Review from logged in user. Calculated at API-time.
|
||||
/// </summary>
|
||||
public string UserReview { get; set; }
|
||||
|
||||
public DateTime Created { get; set; }
|
||||
|
||||
public int LibraryId { get; set; }
|
||||
public string LibraryName { get; set; }
|
||||
}
|
||||
}
|
0
API/DTOs/SeriesFilterDto.cs
Normal file
0
API/DTOs/SeriesFilterDto.cs
Normal file
@ -7,9 +7,11 @@ namespace API.DTOs
|
||||
public ReadingDirection ReadingDirection { get; set; }
|
||||
public ScalingOption ScalingOption { get; set; }
|
||||
public PageSplitOption PageSplitOption { get; set; }
|
||||
/// <summary>
|
||||
/// Whether UI hides read Volumes on Details page
|
||||
/// </summary>
|
||||
public bool HideReadOnDetails { get; set; }
|
||||
public bool BookReaderDarkMode { get; set; } = false;
|
||||
public int BookReaderMargin { get; set; }
|
||||
public int BookReaderLineSpacing { get; set; }
|
||||
public int BookReaderFontSize { get; set; }
|
||||
public string BookReaderFontFamily { get; set; }
|
||||
public bool BookReaderTapToPaginate { get; set; }
|
||||
}
|
||||
}
|
56
API/Data/AppUserProgressRepository.cs
Normal file
56
API/Data/AppUserProgressRepository.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace API.Data
|
||||
{
|
||||
public class AppUserProgressRepository : IAppUserProgressRepository
|
||||
{
|
||||
private readonly DataContext _context;
|
||||
|
||||
public AppUserProgressRepository(DataContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will remove any entries that have chapterIds that no longer exists. This will execute the save as well.
|
||||
/// </summary>
|
||||
public async Task<int> CleanupAbandonedChapters()
|
||||
{
|
||||
var chapterIds = _context.Chapter.Select(c => c.Id);
|
||||
|
||||
var rowsToRemove = await _context.AppUserProgresses
|
||||
.Where(progress => !chapterIds.Contains(progress.ChapterId))
|
||||
.ToListAsync();
|
||||
|
||||
_context.RemoveRange(rowsToRemove);
|
||||
return await _context.SaveChangesAsync() > 0 ? rowsToRemove.Count : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if user has any progress against a library of passed type
|
||||
/// </summary>
|
||||
/// <param name="libraryType"></param>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> UserHasProgress(LibraryType libraryType, int userId)
|
||||
{
|
||||
var seriesIds = await _context.AppUserProgresses
|
||||
.Where(aup => aup.PagesRead > 0 && aup.AppUserId == userId)
|
||||
.AsNoTracking()
|
||||
.Select(aup => aup.SeriesId)
|
||||
.ToListAsync();
|
||||
|
||||
if (seriesIds.Count == 0) return false;
|
||||
|
||||
return await _context.Series
|
||||
.Include(s => s.Library)
|
||||
.Where(s => seriesIds.Contains(s.Id) && s.Library.Type == libraryType)
|
||||
.AsNoTracking()
|
||||
.AnyAsync();
|
||||
}
|
||||
}
|
||||
}
|
@ -47,11 +47,16 @@ namespace API.Data
|
||||
.HasForeignKey(ur => ur.RoleId)
|
||||
.IsRequired();
|
||||
}
|
||||
|
||||
|
||||
void OnEntityTracked(object sender, EntityTrackedEventArgs e)
|
||||
{
|
||||
if (!e.FromQuery && e.Entry.State == EntityState.Added && e.Entry.Entity is IEntityDate entity)
|
||||
{
|
||||
entity.Created = DateTime.Now;
|
||||
entity.LastModified = DateTime.Now;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
|
||||
|
54
API/Data/DbFactory.cs
Normal file
54
API/Data/DbFactory.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using System.Collections.Generic;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Parser;
|
||||
using API.Services.Tasks;
|
||||
|
||||
namespace API.Data
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for creating Series, Volume, Chapter, MangaFiles for use in <see cref="ScannerService"/>
|
||||
/// </summary>
|
||||
public static class DbFactory
|
||||
{
|
||||
public static Series Series(string name)
|
||||
{
|
||||
return new ()
|
||||
{
|
||||
Name = name,
|
||||
OriginalName = name,
|
||||
LocalizedName = name,
|
||||
NormalizedName = Parser.Parser.Normalize(name),
|
||||
SortName = name,
|
||||
Summary = string.Empty,
|
||||
Volumes = new List<Volume>()
|
||||
};
|
||||
}
|
||||
|
||||
public static Volume Volume(string volumeNumber)
|
||||
{
|
||||
return new Volume()
|
||||
{
|
||||
Name = volumeNumber,
|
||||
Number = (int) Parser.Parser.MinimumNumberFromRange(volumeNumber),
|
||||
Chapters = new List<Chapter>()
|
||||
};
|
||||
}
|
||||
|
||||
public static Chapter Chapter(ParserInfo info)
|
||||
{
|
||||
var specialTreatment = info.IsSpecialInfo();
|
||||
var specialTitle = specialTreatment ? info.Filename : info.Chapters;
|
||||
return new Chapter()
|
||||
{
|
||||
Number = specialTreatment ? "0" : Parser.Parser.MinimumNumberFromRange(info.Chapters) + string.Empty,
|
||||
Range = specialTreatment ? info.Filename : info.Chapters,
|
||||
Title = (specialTreatment && info.Format == MangaFormat.Book)
|
||||
? info.Title
|
||||
: specialTitle,
|
||||
Files = new List<MangaFile>(),
|
||||
IsSpecial = specialTreatment,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using API.DTOs;
|
||||
using API.Entities;
|
||||
using API.Entities.Enums;
|
||||
using API.Interfaces;
|
||||
using AutoMapper;
|
||||
using AutoMapper.QueryableExtensions;
|
||||
@ -35,15 +34,14 @@ namespace API.Data
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosForUsernameAsync(string userName)
|
||||
{
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
var libs = await _context.Library
|
||||
return await _context.Library
|
||||
.Include(l => l.AppUsers)
|
||||
.Where(library => library.AppUsers.Any(x => x.UserName == userName))
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.AsSingleQuery()
|
||||
.ToListAsync();
|
||||
Console.WriteLine("Processed GetLibraryDtosForUsernameAsync in {0} milliseconds", sw.ElapsedMilliseconds);
|
||||
return libs;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Library>> GetLibrariesAsync()
|
||||
@ -69,11 +67,23 @@ namespace API.Data
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LibraryType> GetLibraryTypeAsync(int libraryId)
|
||||
{
|
||||
return await _context.Library
|
||||
.Where(l => l.Id == libraryId)
|
||||
.AsNoTracking()
|
||||
.Select(l => l.Type)
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibraryDtosAsync()
|
||||
{
|
||||
return await _context.Library
|
||||
.Include(f => f.Folders)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
|
||||
.OrderBy(l => l.Name)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Library> GetLibraryForIdAsync(int libraryId)
|
||||
@ -91,6 +101,7 @@ namespace API.Data
|
||||
/// <returns></returns>
|
||||
public async Task<Library> GetFullLibraryForIdAsync(int libraryId)
|
||||
{
|
||||
|
||||
return await _context.Library
|
||||
.Where(x => x.Id == libraryId)
|
||||
.Include(f => f.Folders)
|
||||
@ -98,19 +109,25 @@ namespace API.Data
|
||||
.ThenInclude(s => s.Volumes)
|
||||
.ThenInclude(v => v.Chapters)
|
||||
.ThenInclude(c => c.Files)
|
||||
.AsSplitQuery()
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> LibraryExists(string libraryName)
|
||||
{
|
||||
return await _context.Library.AnyAsync(x => x.Name == libraryName);
|
||||
return await _context.Library
|
||||
.AsNoTracking()
|
||||
.AnyAsync(x => x.Name == libraryName);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LibraryDto>> GetLibrariesForUserAsync(AppUser user)
|
||||
{
|
||||
return await _context.Library.Where(library => library.AppUsers.Contains(user))
|
||||
return await _context.Library
|
||||
.Where(library => library.AppUsers.Contains(user))
|
||||
.Include(l => l.Folders)
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider).ToListAsync();
|
||||
.AsNoTracking()
|
||||
.ProjectTo<LibraryDto>(_mapper.ConfigurationProvider)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
|
739
API/Data/Migrations/20210330134414_IsSpecialOnChapters.Designer.cs
generated
Normal file
739
API/Data/Migrations/20210330134414_IsSpecialOnChapters.Designer.cs
generated
Normal file
@ -0,0 +1,739 @@
|
||||
// <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("20210330134414_IsSpecialOnChapters")]
|
||||
partial class IsSpecialOnChapters
|
||||
{
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("HideReadOnDetails")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
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<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.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>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.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.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ServerSetting", b =>
|
||||
{
|
||||
b.Property<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
.WithOne("UserPreferences")
|
||||
.HasForeignKey("API.Entities.AppUserPreferences", "AppUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AppUser");
|
||||
});
|
||||
|
||||
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("UserPreferences");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
24
API/Data/Migrations/20210330134414_IsSpecialOnChapters.cs
Normal file
24
API/Data/Migrations/20210330134414_IsSpecialOnChapters.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class IsSpecialOnChapters : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsSpecial",
|
||||
table: "Chapter",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsSpecial",
|
||||
table: "Chapter");
|
||||
}
|
||||
}
|
||||
}
|
748
API/Data/Migrations/20210419222000_BookReaderPreferences.Designer.cs
generated
Normal file
748
API/Data/Migrations/20210419222000_BookReaderPreferences.Designer.cs
generated
Normal file
@ -0,0 +1,748 @@
|
||||
// <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("20210419222000_BookReaderPreferences")]
|
||||
partial class BookReaderPreferences
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.4");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("BookReaderLineSpacing")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderMargin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
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<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.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>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.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.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ServerSetting", b =>
|
||||
{
|
||||
b.Property<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
.WithOne("UserPreferences")
|
||||
.HasForeignKey("API.Entities.AppUserPreferences", "AppUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AppUser");
|
||||
});
|
||||
|
||||
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("UserPreferences");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
56
API/Data/Migrations/20210419222000_BookReaderPreferences.cs
Normal file
56
API/Data/Migrations/20210419222000_BookReaderPreferences.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class BookReaderPreferences : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "HideReadOnDetails",
|
||||
table: "AppUserPreferences",
|
||||
newName: "BookReaderMargin");
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "BookReaderDarkMode",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BookReaderFontFamily",
|
||||
table: "AppUserPreferences",
|
||||
type: "TEXT",
|
||||
nullable: true,
|
||||
defaultValue: "default");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BookReaderLineSpacing",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 100);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookReaderDarkMode",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookReaderFontFamily",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookReaderLineSpacing",
|
||||
table: "AppUserPreferences");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "BookReaderMargin",
|
||||
table: "AppUserPreferences",
|
||||
newName: "HideReadOnDetails");
|
||||
}
|
||||
}
|
||||
}
|
751
API/Data/Migrations/20210419234652_BookReaderPreferencesFontSize.Designer.cs
generated
Normal file
751
API/Data/Migrations/20210419234652_BookReaderPreferencesFontSize.Designer.cs
generated
Normal file
@ -0,0 +1,751 @@
|
||||
// <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("20210419234652_BookReaderPreferencesFontSize")]
|
||||
partial class BookReaderPreferencesFontSize
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.4");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("BookReaderFontSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderLineSpacing")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderMargin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
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<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.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>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.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.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ServerSetting", b =>
|
||||
{
|
||||
b.Property<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
.WithOne("UserPreferences")
|
||||
.HasForeignKey("API.Entities.AppUserPreferences", "AppUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AppUser");
|
||||
});
|
||||
|
||||
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("UserPreferences");
|
||||
|
||||
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,24 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace API.Data.Migrations
|
||||
{
|
||||
public partial class BookReaderPreferencesFontSize : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BookReaderFontSize",
|
||||
table: "AppUserPreferences",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 100);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BookReaderFontSize",
|
||||
table: "AppUserPreferences");
|
||||
}
|
||||
}
|
||||
}
|
751
API/Data/Migrations/20210423132900_CustomChapterTitle.Designer.cs
generated
Normal file
751
API/Data/Migrations/20210423132900_CustomChapterTitle.Designer.cs
generated
Normal file
@ -0,0 +1,751 @@
|
||||
// <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("20210423132900_CustomChapterTitle")]
|
||||
partial class CustomChapterTitle
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "5.0.4");
|
||||
|
||||
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.AppUserPreferences", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("AppUserId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<bool>("BookReaderDarkMode")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("BookReaderFontFamily")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("BookReaderFontSize")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderLineSpacing")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("BookReaderMargin")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("PageSplitOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ReadingDirection")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<int>("ScalingOption")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AppUserId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("AppUserPreferences");
|
||||
});
|
||||
|
||||
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<DateTime>("Created")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<bool>("IsSpecial")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<string>("Title")
|
||||
.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<DateTime>("LastModified")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("Pages")
|
||||
.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>("LocalizedName")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.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.HasIndex("Name", "NormalizedName", "LocalizedName", "LibraryId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Series");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("API.Entities.ServerSetting", b =>
|
||||
{
|
||||
b.Property<int>("Key")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
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<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.AppUserPreferences", b =>
|
||||
{
|
||||
b.HasOne("API.Entities.AppUser", "AppUser")
|
||||
.WithOne("UserPreferences")
|
||||
.HasForeignKey("API.Entities.AppUserPreferences", "AppUserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("AppUser");
|
||||
});
|
||||
|
||||
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("UserPreferences");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user