diff --git a/API.Tests/API.Tests.csproj b/API.Tests/API.Tests.csproj index 3a4867ec4..20e10e548 100644 --- a/API.Tests/API.Tests.csproj +++ b/API.Tests/API.Tests.csproj @@ -28,6 +28,7 @@ + diff --git a/API.Tests/AbstractDbTest.cs b/API.Tests/AbstractDbTest.cs index 77f978e7f..9c5f3e726 100644 --- a/API.Tests/AbstractDbTest.cs +++ b/API.Tests/AbstractDbTest.cs @@ -20,10 +20,11 @@ namespace API.Tests; public abstract class AbstractDbTest : AbstractFsTest , IDisposable { - protected readonly DbConnection _connection; - protected readonly DataContext _context; - protected readonly IUnitOfWork _unitOfWork; - protected readonly IMapper _mapper; + protected readonly DataContext Context; + protected readonly IUnitOfWork UnitOfWork; + protected readonly IMapper Mapper; + private readonly DbConnection _connection; + private bool _disposed; protected AbstractDbTest() { @@ -34,17 +35,17 @@ public abstract class AbstractDbTest : AbstractFsTest , IDisposable _connection = RelationalOptionsExtension.Extract(contextOptions).Connection; - _context = new DataContext(contextOptions); + Context = new DataContext(contextOptions); - _context.Database.EnsureCreated(); // Ensure DB schema is created + Context.Database.EnsureCreated(); // Ensure DB schema is created Task.Run(SeedDb).GetAwaiter().GetResult(); var config = new MapperConfiguration(cfg => cfg.AddProfile()); - _mapper = config.CreateMapper(); + Mapper = config.CreateMapper(); GlobalConfiguration.Configuration.UseInMemoryStorage(); - _unitOfWork = new UnitOfWork(_context, _mapper, null); + UnitOfWork = new UnitOfWork(Context, Mapper, null); } private static DbConnection CreateInMemoryDatabase() @@ -59,34 +60,34 @@ public abstract class AbstractDbTest : AbstractFsTest , IDisposable { try { - await _context.Database.EnsureCreatedAsync(); + await Context.Database.EnsureCreatedAsync(); var filesystem = CreateFileSystem(); - await Seed.SeedSettings(_context, new DirectoryService(Substitute.For>(), filesystem)); + await Seed.SeedSettings(Context, new DirectoryService(Substitute.For>(), filesystem)); - var setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync(); + var setting = await Context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync(); setting.Value = CacheDirectory; - setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync(); + setting = await Context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync(); setting.Value = BackupDirectory; - setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BookmarkDirectory).SingleAsync(); + setting = await Context.ServerSetting.Where(s => s.Key == ServerSettingKey.BookmarkDirectory).SingleAsync(); setting.Value = BookmarkDirectory; - setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.TotalLogs).SingleAsync(); + setting = await Context.ServerSetting.Where(s => s.Key == ServerSettingKey.TotalLogs).SingleAsync(); setting.Value = "10"; - _context.ServerSetting.Update(setting); + Context.ServerSetting.Update(setting); - _context.Library.Add(new LibraryBuilder("Manga") + Context.Library.Add(new LibraryBuilder("Manga") .WithAllowMetadataMatching(true) .WithFolderPath(new FolderPathBuilder(DataDirectory).Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - await Seed.SeedMetadataSettings(_context); + await Seed.SeedMetadataSettings(Context); return true; } @@ -101,8 +102,21 @@ public abstract class AbstractDbTest : AbstractFsTest , IDisposable public void Dispose() { - _context.Dispose(); - _connection.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + if (disposing) + { + Context?.Dispose(); + _connection?.Dispose(); + } + + _disposed = true; } /// @@ -114,9 +128,9 @@ public abstract class AbstractDbTest : AbstractFsTest , IDisposable { var role = new AppRole { Id = userId, Name = roleName, NormalizedName = roleName.ToUpper() }; - await _context.Roles.AddAsync(role); - await _context.UserRoles.AddAsync(new AppUserRole { UserId = userId, RoleId = userId }); + await Context.Roles.AddAsync(role); + await Context.UserRoles.AddAsync(new AppUserRole { UserId = userId, RoleId = userId }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } } diff --git a/API.Tests/AbstractFsTest.cs b/API.Tests/AbstractFsTest.cs index 3341a3a7c..965a7ad78 100644 --- a/API.Tests/AbstractFsTest.cs +++ b/API.Tests/AbstractFsTest.cs @@ -1,6 +1,7 @@ using System.IO; +using System.IO.Abstractions; using System.IO.Abstractions.TestingHelpers; using API.Services.Tasks.Scanner.Parser; diff --git a/API.Tests/Extensions/ChapterListExtensionsTests.cs b/API.Tests/Extensions/ChapterListExtensionsTests.cs index d27903ca9..f19a0cede 100644 --- a/API.Tests/Extensions/ChapterListExtensionsTests.cs +++ b/API.Tests/Extensions/ChapterListExtensionsTests.cs @@ -142,7 +142,7 @@ public class ChapterListExtensionsTests CreateChapter("darker than black", "1", CreateFile("/manga/darker than black.cbz", MangaFormat.Archive), false), }; - Assert.Equal(chapterList.First(), chapterList.GetFirstChapterWithFiles()); + Assert.Equal(chapterList[0], chapterList.GetFirstChapterWithFiles()); } [Fact] @@ -150,13 +150,13 @@ public class ChapterListExtensionsTests { var chapterList = new List() { - CreateChapter("darker than black", API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, CreateFile("/manga/darker than black.cbz", MangaFormat.Archive), true), + CreateChapter("darker than black", Parser.DefaultChapter, CreateFile("/manga/darker than black.cbz", MangaFormat.Archive), true), CreateChapter("darker than black", "1", CreateFile("/manga/darker than black.cbz", MangaFormat.Archive), false), }; - chapterList.First().Files = new List(); + chapterList[0].Files = new List(); - Assert.Equal(chapterList.Last(), chapterList.GetFirstChapterWithFiles()); + Assert.Equal(chapterList[^1], chapterList.GetFirstChapterWithFiles()); } @@ -181,7 +181,7 @@ public class ChapterListExtensionsTests CreateChapter("detective comics", API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, CreateFile("/manga/detective comics #001.cbz", MangaFormat.Archive), true) }; - chapterList[0].ReleaseDate = new DateTime(10, 1, 1); + chapterList[0].ReleaseDate = new DateTime(10, 1, 1, 0, 0, 0, DateTimeKind.Utc); chapterList[1].ReleaseDate = DateTime.MinValue; Assert.Equal(0, chapterList.MinimumReleaseYear()); @@ -196,8 +196,8 @@ public class ChapterListExtensionsTests CreateChapter("detective comics", API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, CreateFile("/manga/detective comics #001.cbz", MangaFormat.Archive), true) }; - chapterList[0].ReleaseDate = new DateTime(2002, 1, 1); - chapterList[1].ReleaseDate = new DateTime(2012, 2, 1); + chapterList[0].ReleaseDate = new DateTime(2002, 1, 1, 0, 0, 0, DateTimeKind.Utc); + chapterList[1].ReleaseDate = new DateTime(2012, 2, 1, 0, 0, 0, DateTimeKind.Utc); Assert.Equal(2002, chapterList.MinimumReleaseYear()); } diff --git a/API.Tests/Extensions/SeriesFilterTests.cs b/API.Tests/Extensions/SeriesFilterTests.cs index 577e17619..ba42be8a1 100644 --- a/API.Tests/Extensions/SeriesFilterTests.cs +++ b/API.Tests/Extensions/SeriesFilterTests.cs @@ -24,9 +24,9 @@ public class SeriesFilterTests : AbstractDbTest { protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series); - _context.AppUser.RemoveRange(_context.AppUser); - await _context.SaveChangesAsync(); + Context.Series.RemoveRange(Context.Series); + Context.AppUser.RemoveRange(Context.AppUser); + await Context.SaveChangesAsync(); } #region HasProgress @@ -54,18 +54,18 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); // Create read progress on Partial and Full - var readerService = new ReaderService(_unitOfWork, Substitute.For>(), + var readerService = new ReaderService(UnitOfWork, Substitute.For>(), Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); // Select Partial and set pages read to 5 on first chapter - var partialSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2); + var partialSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2); var partialChapter = partialSeries.Volumes.First().Chapters.First(); Assert.True(await readerService.SaveReadingProgress(new ProgressDto() @@ -78,7 +78,7 @@ public class SeriesFilterTests : AbstractDbTest }, user.Id)); // Select Full and set pages read to 10 on first chapter - var fullSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3); + var fullSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(3); var fullChapter = fullSeries.Volumes.First().Chapters.First(); Assert.True(await readerService.SaveReadingProgress(new ProgressDto() @@ -98,7 +98,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasProgress(); - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.LessThan, 50, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.LessThan, 50, user.Id) .ToListAsync(); Assert.Single(queryResult); @@ -111,7 +111,7 @@ public class SeriesFilterTests : AbstractDbTest var user = await SetupHasProgress(); // Query series with progress <= 50% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.LessThanEqual, 50, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.LessThanEqual, 50, user.Id) .ToListAsync(); Assert.Equal(2, queryResult.Count); @@ -125,7 +125,7 @@ public class SeriesFilterTests : AbstractDbTest var user = await SetupHasProgress(); // Query series with progress > 50% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.GreaterThan, 50, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.GreaterThan, 50, user.Id) .ToListAsync(); Assert.Single(queryResult); @@ -138,7 +138,7 @@ public class SeriesFilterTests : AbstractDbTest var user = await SetupHasProgress(); // Query series with progress == 100% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.Equal, 100, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.Equal, 100, user.Id) .ToListAsync(); Assert.Single(queryResult); @@ -151,7 +151,7 @@ public class SeriesFilterTests : AbstractDbTest var user = await SetupHasProgress(); // Query series with progress < 100% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.LessThan, 100, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.LessThan, 100, user.Id) .ToListAsync(); Assert.Equal(2, queryResult.Count); @@ -165,7 +165,7 @@ public class SeriesFilterTests : AbstractDbTest var user = await SetupHasProgress(); // Query series with progress <= 100% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.LessThanEqual, 100, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.LessThanEqual, 100, user.Id) .ToListAsync(); Assert.Equal(3, queryResult.Count); @@ -188,16 +188,16 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); - var readerService = new ReaderService(_unitOfWork, Substitute.For>(), + var readerService = new ReaderService(UnitOfWork, Substitute.For>(), Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); // Set progress to 99.99% (99/100 pages read) - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); var chapter = series.Volumes.First().Chapters.First(); Assert.True(await readerService.SaveReadingProgress(new ProgressDto() @@ -210,7 +210,7 @@ public class SeriesFilterTests : AbstractDbTest }, user.Id)); // Query series with progress < 100% - var queryResult = await _context.Series.HasReadingProgress(true, FilterComparison.LessThan, 100, user.Id) + var queryResult = await Context.Series.HasReadingProgress(true, FilterComparison.LessThan, 100, user.Id) .ToListAsync(); Assert.Single(queryResult); @@ -246,9 +246,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -258,7 +258,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.Equal, ["en"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.Equal, ["en"]).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("en", foundSeries[0].Metadata.Language); } @@ -268,7 +268,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.NotEqual, ["en"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.NotEqual, ["en"]).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.DoesNotContain(foundSeries, s => s.Metadata.Language == "en"); } @@ -278,7 +278,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.Contains, ["en", "fr"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.Contains, ["en", "fr"]).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Metadata.Language == "en"); Assert.Contains(foundSeries, s => s.Metadata.Language == "fr"); @@ -289,7 +289,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.NotContains, ["en", "fr"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.NotContains, ["en", "fr"]).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("es", foundSeries[0].Metadata.Language); } @@ -300,11 +300,11 @@ public class SeriesFilterTests : AbstractDbTest await SetupHasLanguage(); // Since "MustContains" matches all the provided languages, no series should match in this case. - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.MustContains, ["en", "fr"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.MustContains, ["en", "fr"]).ToListAsync(); Assert.Empty(foundSeries); // Single language should work. - foundSeries = await _context.Series.HasLanguage(true, FilterComparison.MustContains, ["en"]).ToListAsync(); + foundSeries = await Context.Series.HasLanguage(true, FilterComparison.MustContains, ["en"]).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("en", foundSeries[0].Metadata.Language); } @@ -314,7 +314,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.Matches, ["e"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.Matches, ["e"]).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains("en", foundSeries.Select(s => s.Metadata.Language)); Assert.Contains("es", foundSeries.Select(s => s.Metadata.Language)); @@ -325,7 +325,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(false, FilterComparison.Equal, ["en"]).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(false, FilterComparison.Equal, ["en"]).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -334,7 +334,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasLanguage(); - var foundSeries = await _context.Series.HasLanguage(true, FilterComparison.Equal, new List()).ToListAsync(); + var foundSeries = await Context.Series.HasLanguage(true, FilterComparison.Equal, new List()).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -345,7 +345,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasLanguage(true, FilterComparison.GreaterThan, ["en"]).ToListAsync(); + await Context.Series.HasLanguage(true, FilterComparison.GreaterThan, ["en"]).ToListAsync(); }); } @@ -379,9 +379,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -391,7 +391,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.Equal, 100).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.Equal, 100).ToListAsync(); Assert.Single(series); Assert.Equal("Full", series[0].Name); } @@ -401,7 +401,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.GreaterThan, 50).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.GreaterThan, 50).ToListAsync(); Assert.Single(series); Assert.Equal("Full", series[0].Name); } @@ -411,7 +411,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.GreaterThanEqual, 50).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.GreaterThanEqual, 50).ToListAsync(); Assert.Equal(2, series.Count); Assert.Contains(series, s => s.Name == "Partial"); Assert.Contains(series, s => s.Name == "Full"); @@ -422,7 +422,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.LessThan, 50).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.LessThan, 50).ToListAsync(); Assert.Single(series); Assert.Equal("None", series[0].Name); } @@ -432,7 +432,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.LessThanEqual, 50).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.LessThanEqual, 50).ToListAsync(); Assert.Equal(2, series.Count); Assert.Contains(series, s => s.Name == "None"); Assert.Contains(series, s => s.Name == "Partial"); @@ -443,7 +443,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.NotEqual, 100).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.NotEqual, 100).ToListAsync(); Assert.Equal(2, series.Count); Assert.DoesNotContain(series, s => s.Name == "Full"); } @@ -453,7 +453,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(false, FilterComparison.Equal, 100).ToListAsync(); + var series = await Context.Series.HasAverageRating(false, FilterComparison.Equal, 100).ToListAsync(); Assert.Equal(3, series.Count); } @@ -462,7 +462,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAverageRating(); - var series = await _context.Series.HasAverageRating(true, FilterComparison.Equal, -1).ToListAsync(); + var series = await Context.Series.HasAverageRating(true, FilterComparison.Equal, -1).ToListAsync(); Assert.Single(series); Assert.Equal("None", series[0].Name); } @@ -474,7 +474,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasAverageRating(true, FilterComparison.Contains, 50).ToListAsync(); + await Context.Series.HasAverageRating(true, FilterComparison.Contains, 50).ToListAsync(); }); } @@ -485,7 +485,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasAverageRating(true, (FilterComparison)999, 50).ToListAsync(); + await Context.Series.HasAverageRating(true, (FilterComparison)999, 50).ToListAsync(); }); } @@ -519,9 +519,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -531,7 +531,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(true, FilterComparison.Equal, new List { PublicationStatus.Cancelled }).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(true, FilterComparison.Equal, new List { PublicationStatus.Cancelled }).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("Cancelled", foundSeries[0].Name); } @@ -541,7 +541,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(true, FilterComparison.Contains, new List { PublicationStatus.Cancelled, PublicationStatus.Completed }).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(true, FilterComparison.Contains, new List { PublicationStatus.Cancelled, PublicationStatus.Completed }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "Cancelled"); Assert.Contains(foundSeries, s => s.Name == "Completed"); @@ -552,7 +552,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(true, FilterComparison.NotContains, new List { PublicationStatus.Cancelled }).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(true, FilterComparison.NotContains, new List { PublicationStatus.Cancelled }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "OnGoing"); Assert.Contains(foundSeries, s => s.Name == "Completed"); @@ -563,7 +563,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(true, FilterComparison.NotEqual, new List { PublicationStatus.OnGoing }).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(true, FilterComparison.NotEqual, new List { PublicationStatus.OnGoing }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "Cancelled"); Assert.Contains(foundSeries, s => s.Name == "Completed"); @@ -574,7 +574,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(false, FilterComparison.Equal, new List { PublicationStatus.Cancelled }).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(false, FilterComparison.Equal, new List { PublicationStatus.Cancelled }).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -583,7 +583,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasPublicationStatus(); - var foundSeries = await _context.Series.HasPublicationStatus(true, FilterComparison.Equal, new List()).ToListAsync(); + var foundSeries = await Context.Series.HasPublicationStatus(true, FilterComparison.Equal, new List()).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -594,7 +594,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasPublicationStatus(true, FilterComparison.BeginsWith, new List { PublicationStatus.Cancelled }).ToListAsync(); + await Context.Series.HasPublicationStatus(true, FilterComparison.BeginsWith, new List { PublicationStatus.Cancelled }).ToListAsync(); }); } @@ -605,7 +605,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasPublicationStatus(true, (FilterComparison)999, new List { PublicationStatus.Cancelled }).ToListAsync(); + await Context.Series.HasPublicationStatus(true, (FilterComparison)999, new List { PublicationStatus.Cancelled }).ToListAsync(); }); } #endregion @@ -637,9 +637,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -649,7 +649,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.Equal, [AgeRating.G]).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.Equal, [AgeRating.G]).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("G", foundSeries[0].Name); } @@ -659,7 +659,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.Contains, new List { AgeRating.G, AgeRating.Mature }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.Contains, new List { AgeRating.G, AgeRating.Mature }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "G"); Assert.Contains(foundSeries, s => s.Name == "Mature"); @@ -670,7 +670,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.NotContains, new List { AgeRating.Unknown }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.NotContains, new List { AgeRating.Unknown }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "G"); Assert.Contains(foundSeries, s => s.Name == "Mature"); @@ -681,7 +681,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.NotEqual, new List { AgeRating.G }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.NotEqual, new List { AgeRating.G }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "Unknown"); Assert.Contains(foundSeries, s => s.Name == "Mature"); @@ -692,7 +692,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.GreaterThan, new List { AgeRating.Unknown }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.GreaterThan, new List { AgeRating.Unknown }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "G"); Assert.Contains(foundSeries, s => s.Name == "Mature"); @@ -703,7 +703,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.GreaterThanEqual, new List { AgeRating.G }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.GreaterThanEqual, new List { AgeRating.G }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "G"); Assert.Contains(foundSeries, s => s.Name == "Mature"); @@ -714,7 +714,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.LessThan, new List { AgeRating.Mature }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.LessThan, new List { AgeRating.Mature }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "Unknown"); Assert.Contains(foundSeries, s => s.Name == "G"); @@ -725,7 +725,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.LessThanEqual, new List { AgeRating.G }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.LessThanEqual, new List { AgeRating.G }).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "Unknown"); Assert.Contains(foundSeries, s => s.Name == "G"); @@ -736,7 +736,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(false, FilterComparison.Equal, new List { AgeRating.G }).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(false, FilterComparison.Equal, new List { AgeRating.G }).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -745,7 +745,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasAgeRating(); - var foundSeries = await _context.Series.HasAgeRating(true, FilterComparison.Equal, new List()).ToListAsync(); + var foundSeries = await Context.Series.HasAgeRating(true, FilterComparison.Equal, new List()).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -756,7 +756,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasAgeRating(true, FilterComparison.BeginsWith, new List { AgeRating.G }).ToListAsync(); + await Context.Series.HasAgeRating(true, FilterComparison.BeginsWith, new List { AgeRating.G }).ToListAsync(); }); } @@ -767,7 +767,7 @@ public class SeriesFilterTests : AbstractDbTest await Assert.ThrowsAsync(async () => { - await _context.Series.HasAgeRating(true, (FilterComparison)999, new List { AgeRating.G }).ToListAsync(); + await Context.Series.HasAgeRating(true, (FilterComparison)999, new List { AgeRating.G }).ToListAsync(); }); } @@ -801,9 +801,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -813,7 +813,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.Equal, 2020).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.Equal, 2020).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("2020", foundSeries[0].Name); } @@ -823,7 +823,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.GreaterThan, 2000).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.GreaterThan, 2000).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "2020"); Assert.Contains(foundSeries, s => s.Name == "2025"); @@ -834,7 +834,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.LessThan, 2025).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.LessThan, 2025).ToListAsync(); Assert.Equal(2, foundSeries.Count); Assert.Contains(foundSeries, s => s.Name == "2000"); Assert.Contains(foundSeries, s => s.Name == "2020"); @@ -845,7 +845,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.IsInLast, 5).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.IsInLast, 5).ToListAsync(); Assert.Equal(2, foundSeries.Count); } @@ -854,7 +854,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.IsNotInLast, 5).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.IsNotInLast, 5).ToListAsync(); Assert.Single(foundSeries); Assert.Contains(foundSeries, s => s.Name == "2000"); } @@ -864,7 +864,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(false, FilterComparison.Equal, 2020).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(false, FilterComparison.Equal, 2020).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -873,7 +873,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasReleaseYear(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.Equal, null).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.Equal, null).ToListAsync(); Assert.Equal(3, foundSeries.Count); } @@ -889,10 +889,10 @@ public class SeriesFilterTests : AbstractDbTest .Build()) .Build(); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Library.Add(library); + await Context.SaveChangesAsync(); - var foundSeries = await _context.Series.HasReleaseYear(true, FilterComparison.IsEmpty, 0).ToListAsync(); + var foundSeries = await Context.Series.HasReleaseYear(true, FilterComparison.IsEmpty, 0).ToListAsync(); Assert.Single(foundSeries); Assert.Equal("EmptyYear", foundSeries[0].Name); } @@ -925,30 +925,26 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); - - var seriesService = new SeriesService(_unitOfWork, Substitute.For(), - Substitute.For(), Substitute.For>(), - Substitute.For(), Substitute.For(), - Substitute.For()); + var ratingService = new RatingService(UnitOfWork, Substitute.For(), Substitute.For>()); // Select 0 Rating - var zeroRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2); + var zeroRating = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2); Assert.NotNull(zeroRating); - Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto() + Assert.True(await ratingService.UpdateSeriesRating(user, new UpdateRatingDto() { SeriesId = zeroRating.Id, UserRating = 0 })); // Select 4.5 Rating - var partialRating = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(3); + var partialRating = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(3); - Assert.True(await seriesService.UpdateRating(user, new UpdateSeriesRatingDto() + Assert.True(await ratingService.UpdateSeriesRating(user, new UpdateRatingDto() { SeriesId = partialRating.Id, UserRating = 4.5f @@ -962,7 +958,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.Equal, 4.5f, user.Id) .ToListAsync(); @@ -975,7 +971,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.GreaterThan, 0, user.Id) .ToListAsync(); @@ -988,7 +984,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.LessThan, 4.5f, user.Id) .ToListAsync(); @@ -1001,7 +997,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.IsEmpty, 0, user.Id) .ToListAsync(); @@ -1014,7 +1010,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.GreaterThanEqual, 4.5f, user.Id) .ToListAsync(); @@ -1027,7 +1023,7 @@ public class SeriesFilterTests : AbstractDbTest { var user = await SetupHasRating(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasRating(true, FilterComparison.LessThanEqual, 0, user.Id) .ToListAsync(); @@ -1105,9 +1101,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -1117,7 +1113,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.Equal, "My Dress-Up Darling") .ToListAsync(); @@ -1130,7 +1126,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.Equal, "Ijiranaide, Nagatoro-san") .ToListAsync(); @@ -1143,7 +1139,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.BeginsWith, "My Dress") .ToListAsync(); @@ -1156,7 +1152,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.BeginsWith, "Sono Bisque") .ToListAsync(); @@ -1169,7 +1165,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.EndsWith, "Nagatoro") .ToListAsync(); @@ -1182,7 +1178,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.Matches, "Toy With Me") .ToListAsync(); @@ -1195,7 +1191,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasName(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasName(true, FilterComparison.NotEqual, "My Dress-Up Darling") .ToListAsync(); @@ -1239,9 +1235,9 @@ public class SeriesFilterTests : AbstractDbTest .WithLibrary(library) .Build(); - _context.Users.Add(user); - _context.Library.Add(library); - await _context.SaveChangesAsync(); + Context.Users.Add(user); + Context.Library.Add(library); + await Context.SaveChangesAsync(); return user; } @@ -1251,7 +1247,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.Equal, "I like hippos") .ToListAsync(); @@ -1264,7 +1260,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.BeginsWith, "I like h") .ToListAsync(); @@ -1277,7 +1273,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.EndsWith, "apples") .ToListAsync(); @@ -1290,7 +1286,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.Matches, "like ducks") .ToListAsync(); @@ -1303,7 +1299,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.NotEqual, "I like ducks") .ToListAsync(); @@ -1316,7 +1312,7 @@ public class SeriesFilterTests : AbstractDbTest { await SetupHasSummary(); - var foundSeries = await _context.Series + var foundSeries = await Context.Series .HasSummary(true, FilterComparison.IsEmpty, string.Empty) .ToListAsync(); diff --git a/API.Tests/Helpers/PersonHelperTests.cs b/API.Tests/Helpers/PersonHelperTests.cs index 1a38ccdac..66713e17c 100644 --- a/API.Tests/Helpers/PersonHelperTests.cs +++ b/API.Tests/Helpers/PersonHelperTests.cs @@ -7,8 +7,8 @@ public class PersonHelperTests : AbstractDbTest { protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series.ToList()); - await _context.SaveChangesAsync(); + Context.Series.RemoveRange(Context.Series.ToList()); + await Context.SaveChangesAsync(); } // // // 1. Test adding new people and keeping existing ones diff --git a/API.Tests/Services/CleanupServiceTests.cs b/API.Tests/Services/CleanupServiceTests.cs index 0f1e9e9da..b0610aed5 100644 --- a/API.Tests/Services/CleanupServiceTests.cs +++ b/API.Tests/Services/CleanupServiceTests.cs @@ -29,11 +29,11 @@ public class CleanupServiceTests : AbstractDbTest public CleanupServiceTests() : base() { - _context.Library.Add(new LibraryBuilder("Manga") + Context.Library.Add(new LibraryBuilder("Manga") .WithFolderPath(new FolderPathBuilder(Root + "data/").Build()) .Build()); - _readerService = new ReaderService(_unitOfWork, Substitute.For>(), Substitute.For(), + _readerService = new ReaderService(UnitOfWork, Substitute.For>(), Substitute.For(), Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem()), Substitute.For()); } @@ -43,11 +43,11 @@ public class CleanupServiceTests : AbstractDbTest protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series.ToList()); - _context.Users.RemoveRange(_context.Users.ToList()); - _context.AppUserBookmark.RemoveRange(_context.AppUserBookmark.ToList()); + Context.Series.RemoveRange(Context.Series.ToList()); + Context.Users.RemoveRange(Context.Users.ToList()); + Context.AppUserBookmark.RemoveRange(Context.AppUserBookmark.ToList()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } #endregion @@ -68,18 +68,18 @@ public class CleanupServiceTests : AbstractDbTest var s = new SeriesBuilder("Test 1").Build(); s.CoverImage = $"{ImageService.GetSeriesFormat(1)}.jpg"; s.LibraryId = 1; - _context.Series.Add(s); + Context.Series.Add(s); s = new SeriesBuilder("Test 2").Build(); s.CoverImage = $"{ImageService.GetSeriesFormat(3)}.jpg"; s.LibraryId = 1; - _context.Series.Add(s); + Context.Series.Add(s); s = new SeriesBuilder("Test 3").Build(); s.CoverImage = $"{ImageService.GetSeriesFormat(1000)}.jpg"; s.LibraryId = 1; - _context.Series.Add(s); + Context.Series.Add(s); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.DeleteSeriesCoverImages(); @@ -102,16 +102,16 @@ public class CleanupServiceTests : AbstractDbTest var s = new SeriesBuilder("Test 1").Build(); s.CoverImage = $"{ImageService.GetSeriesFormat(1)}.jpg"; s.LibraryId = 1; - _context.Series.Add(s); + Context.Series.Add(s); s = new SeriesBuilder("Test 2").Build(); s.CoverImage = $"{ImageService.GetSeriesFormat(3)}.jpg"; s.LibraryId = 1; - _context.Series.Add(s); + Context.Series.Add(s); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.DeleteSeriesCoverImages(); @@ -133,7 +133,7 @@ public class CleanupServiceTests : AbstractDbTest await ResetDb(); // Add 2 series with cover images - _context.Series.Add(new SeriesBuilder("Test 1") + Context.Series.Add(new SeriesBuilder("Test 1") .WithVolume(new VolumeBuilder("1") .WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithCoverImage("v01_c01.jpg").Build()) .WithCoverImage("v01_c01.jpg") @@ -142,7 +142,7 @@ public class CleanupServiceTests : AbstractDbTest .WithLibraryId(1) .Build()); - _context.Series.Add(new SeriesBuilder("Test 2") + Context.Series.Add(new SeriesBuilder("Test 2") .WithVolume(new VolumeBuilder("1") .WithChapter(new ChapterBuilder(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter).WithCoverImage("v01_c03.jpg").Build()) .WithCoverImage("v01_c03.jpg") @@ -152,9 +152,9 @@ public class CleanupServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.DeleteChapterCoverImages(); @@ -223,7 +223,7 @@ public class CleanupServiceTests : AbstractDbTest // Delete all Series to reset state await ResetDb(); - _context.Users.Add(new AppUser() + Context.Users.Add(new AppUser() { UserName = "Joe", ReadingLists = new List() @@ -239,9 +239,9 @@ public class CleanupServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.DeleteReadingListCoverImages(); @@ -260,7 +260,7 @@ public class CleanupServiceTests : AbstractDbTest filesystem.AddFile($"{CacheDirectory}02.jpg", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); cleanupService.CleanupCacheAndTempDirectories(); Assert.Empty(ds.GetFiles(CacheDirectory, searchOption: SearchOption.AllDirectories)); @@ -274,7 +274,7 @@ public class CleanupServiceTests : AbstractDbTest filesystem.AddFile($"{CacheDirectory}subdir/02.jpg", new MockFileData("")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); cleanupService.CleanupCacheAndTempDirectories(); Assert.Empty(ds.GetFiles(CacheDirectory, searchOption: SearchOption.AllDirectories)); @@ -297,7 +297,7 @@ public class CleanupServiceTests : AbstractDbTest filesystem.AddFile($"{BackupDirectory}randomfile.zip", filesystemFile); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.CleanupBackups(); Assert.Single(ds.GetFiles(BackupDirectory, searchOption: SearchOption.AllDirectories)); @@ -319,7 +319,7 @@ public class CleanupServiceTests : AbstractDbTest }); var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.CleanupBackups(); Assert.True(filesystem.File.Exists($"{BackupDirectory}randomfile.zip")); @@ -343,7 +343,7 @@ public class CleanupServiceTests : AbstractDbTest } var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.CleanupLogs(); Assert.Single(ds.GetFiles(LogDirectory, searchOption: SearchOption.AllDirectories)); @@ -372,7 +372,7 @@ public class CleanupServiceTests : AbstractDbTest var ds = new DirectoryService(Substitute.For>(), filesystem); - var cleanupService = new CleanupService(_logger, _unitOfWork, _messageHub, + var cleanupService = new CleanupService(_logger, UnitOfWork, _messageHub, ds); await cleanupService.CleanupLogs(); Assert.True(filesystem.File.Exists($"{LogDirectory}kavita20200911.log")); @@ -396,36 +396,36 @@ public class CleanupServiceTests : AbstractDbTest .Build(); series.Library = new LibraryBuilder("Test LIb").Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); await _readerService.MarkChaptersUntilAsRead(user, 1, 5); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Validate correct chapters have read status - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); - var cleanupService = new CleanupService(Substitute.For>(), _unitOfWork, + var cleanupService = new CleanupService(Substitute.For>(), UnitOfWork, Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem())); // Delete the Chapter - _context.Chapter.Remove(c); - await _unitOfWork.CommitAsync(); - Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1)); + Context.Chapter.Remove(c); + await UnitOfWork.CommitAsync(); + Assert.Empty(await UnitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1)); // NOTE: This may not be needed, the underlying DB structure seems fixed as of v0.7 await cleanupService.CleanupDbEntries(); - Assert.Empty(await _unitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1)); + Assert.Empty(await UnitOfWork.AppUserProgressRepository.GetUserProgressForSeriesAsync(1, 1)); } [Fact] @@ -436,7 +436,7 @@ public class CleanupServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder().Build()) .Build(); s.Library = new LibraryBuilder("Test LIb").Build(); - _context.Series.Add(s); + Context.Series.Add(s); var c = new AppUserCollection() { @@ -446,24 +446,24 @@ public class CleanupServiceTests : AbstractDbTest Items = new List() {s} }; - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007", Collections = new List() {c} }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var cleanupService = new CleanupService(Substitute.For>(), _unitOfWork, + var cleanupService = new CleanupService(Substitute.For>(), UnitOfWork, Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem())); // Delete the Chapter - _context.Series.Remove(s); - await _unitOfWork.CommitAsync(); + Context.Series.Remove(s); + await UnitOfWork.CommitAsync(); await cleanupService.CleanupDbEntries(); - Assert.Empty(await _unitOfWork.CollectionTagRepository.GetAllCollectionsAsync()); + Assert.Empty(await UnitOfWork.CollectionTagRepository.GetAllCollectionsAsync()); } #endregion @@ -480,15 +480,15 @@ public class CleanupServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test LIb").Build(); - _context.Series.Add(s); + Context.Series.Add(s); var user = new AppUser() { UserName = "CleanupWantToRead_ShouldRemoveFullyReadSeries", }; - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); // Add want to read user.WantToRead = new List() @@ -498,12 +498,12 @@ public class CleanupServiceTests : AbstractDbTest SeriesId = s.Id } }; - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); await _readerService.MarkSeriesAsRead(user, s.Id); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - var cleanupService = new CleanupService(Substitute.For>(), _unitOfWork, + var cleanupService = new CleanupService(Substitute.For>(), UnitOfWork, Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem())); @@ -511,7 +511,7 @@ public class CleanupServiceTests : AbstractDbTest await cleanupService.CleanupWantToRead(); var wantToRead = - await _unitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, new UserParams(), new FilterDto()); + await UnitOfWork.SeriesRepository.GetWantToReadForUserAsync(user.Id, new UserParams(), new FilterDto()); Assert.Equal(0, wantToRead.TotalCount); } @@ -533,15 +533,15 @@ public class CleanupServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test Lib").Build(); - _context.Series.Add(s); + Context.Series.Add(s); var user = new AppUser() { UserName = "ConsolidateProgress_ShouldRemoveDuplicates", }; - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); // Add 2 progress events user.Progresses ??= []; @@ -553,7 +553,7 @@ public class CleanupServiceTests : AbstractDbTest LibraryId = s.LibraryId, PagesRead = 1, }); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); // Add a duplicate with higher page number user.Progresses.Add(new AppUserProgress() @@ -564,18 +564,18 @@ public class CleanupServiceTests : AbstractDbTest LibraryId = s.LibraryId, PagesRead = 3, }); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - Assert.Equal(2, (await _unitOfWork.AppUserProgressRepository.GetAllProgress()).Count()); + Assert.Equal(2, (await UnitOfWork.AppUserProgressRepository.GetAllProgress()).Count()); - var cleanupService = new CleanupService(Substitute.For>(), _unitOfWork, + var cleanupService = new CleanupService(Substitute.For>(), UnitOfWork, Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem())); await cleanupService.ConsolidateProgress(); - var progress = await _unitOfWork.AppUserProgressRepository.GetAllProgress(); + var progress = await UnitOfWork.AppUserProgressRepository.GetAllProgress(); Assert.Single(progress); Assert.True(progress.First().PagesRead == 3); @@ -601,50 +601,50 @@ public class CleanupServiceTests : AbstractDbTest { new VolumeBuilder(API.Services.Tasks.Scanner.Parser.Parser.LooseLeafVolume).WithChapter(c).Build() }; - _context.Series.Add(s); + Context.Series.Add(s); var user = new AppUser() { UserName = "EnsureChapterProgressIsCapped", Progresses = new List() }; - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); await _readerService.MarkChaptersAsRead(user, s.Id, new List() {c}); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - var chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); - await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); + var chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); + await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); Assert.NotNull(chapter); Assert.Equal(2, chapter.PagesRead); // Update chapter to have 1 page c.Pages = 1; - _unitOfWork.ChapterRepository.Update(c); - await _unitOfWork.CommitAsync(); + UnitOfWork.ChapterRepository.Update(c); + await UnitOfWork.CommitAsync(); - chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); - await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); + chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); + await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); Assert.NotNull(chapter); Assert.Equal(2, chapter.PagesRead); Assert.Equal(1, chapter.Pages); - var cleanupService = new CleanupService(Substitute.For>(), _unitOfWork, + var cleanupService = new CleanupService(Substitute.For>(), UnitOfWork, Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem())); await cleanupService.EnsureChapterProgressIsCapped(); - chapter = await _unitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); - await _unitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); + chapter = await UnitOfWork.ChapterRepository.GetChapterDtoAsync(c.Id); + await UnitOfWork.ChapterRepository.AddChapterModifiers(user.Id, chapter); Assert.NotNull(chapter); Assert.Equal(1, chapter.PagesRead); - _context.AppUser.Remove(user); - await _unitOfWork.CommitAsync(); + Context.AppUser.Remove(user); + await UnitOfWork.CommitAsync(); } #endregion diff --git a/API.Tests/Services/CollectionTagServiceTests.cs b/API.Tests/Services/CollectionTagServiceTests.cs index 14ce131d8..3414dd86b 100644 --- a/API.Tests/Services/CollectionTagServiceTests.cs +++ b/API.Tests/Services/CollectionTagServiceTests.cs @@ -23,24 +23,24 @@ public class CollectionTagServiceTests : AbstractDbTest private readonly ICollectionTagService _service; public CollectionTagServiceTests() { - _service = new CollectionTagService(_unitOfWork, Substitute.For()); + _service = new CollectionTagService(UnitOfWork, Substitute.For()); } protected override async Task ResetDb() { - _context.AppUserCollection.RemoveRange(_context.AppUserCollection.ToList()); - _context.Library.RemoveRange(_context.Library.ToList()); + Context.AppUserCollection.RemoveRange(Context.AppUserCollection.ToList()); + Context.Library.RemoveRange(Context.Library.ToList()); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } private async Task SeedSeries() { - if (_context.AppUserCollection.Any()) return; + if (Context.AppUserCollection.Any()) return; var s1 = new SeriesBuilder("Series 1").WithMetadata(new SeriesMetadataBuilder().WithAgeRating(AgeRating.Mature).Build()).Build(); var s2 = new SeriesBuilder("Series 2").WithMetadata(new SeriesMetadataBuilder().WithAgeRating(AgeRating.G).Build()).Build(); - _context.Library.Add(new LibraryBuilder("Library 2", LibraryType.Manga) + Context.Library.Add(new LibraryBuilder("Library 2", LibraryType.Manga) .WithSeries(s1) .WithSeries(s2) .Build()); @@ -51,9 +51,9 @@ public class CollectionTagServiceTests : AbstractDbTest new AppUserCollectionBuilder("Tag 1").WithItems(new []{s1}).Build(), new AppUserCollectionBuilder("Tag 2").WithItems(new []{s1, s2}).WithIsPromoted(true).Build() }; - _unitOfWork.UserRepository.Add(user); + UnitOfWork.UserRepository.Add(user); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } #region DeleteTag @@ -64,7 +64,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Arrange await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Act @@ -72,7 +72,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Assert Assert.True(result); - var deletedTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var deletedTag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.Null(deletedTag); Assert.Single(user.Collections); // Only one collection should remain } @@ -82,7 +82,7 @@ public class CollectionTagServiceTests : AbstractDbTest { // Arrange await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Act - Try to delete a non-existent tag @@ -98,7 +98,7 @@ public class CollectionTagServiceTests : AbstractDbTest { // Arrange await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Act @@ -106,7 +106,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Assert Assert.True(result); - var remainingTag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2); + var remainingTag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(2); Assert.NotNull(remainingTag); Assert.Equal("Tag 2", remainingTag.Title); Assert.True(remainingTag.Promoted); @@ -121,12 +121,12 @@ public class CollectionTagServiceTests : AbstractDbTest { await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); user.Collections.Add(new AppUserCollectionBuilder("UpdateTag_ShouldUpdateFields").WithIsPromoted(true).Build()); - _unitOfWork.UserRepository.Update(user); - await _unitOfWork.CommitAsync(); + UnitOfWork.UserRepository.Update(user); + await UnitOfWork.CommitAsync(); await _service.UpdateTag(new AppUserCollectionDto() { @@ -137,7 +137,7 @@ public class CollectionTagServiceTests : AbstractDbTest AgeRating = AgeRating.Unknown }, 1); - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(3); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(3); Assert.NotNull(tag); Assert.True(tag.Promoted); Assert.False(string.IsNullOrEmpty(tag.Summary)); @@ -151,12 +151,12 @@ public class CollectionTagServiceTests : AbstractDbTest { await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); user.Collections.Add(new AppUserCollectionBuilder("UpdateTag_ShouldNotChangeTitle_WhenNotKavitaSource").WithSource(ScrobbleProvider.Mal).Build()); - _unitOfWork.UserRepository.Update(user); - await _unitOfWork.CommitAsync(); + UnitOfWork.UserRepository.Update(user); + await UnitOfWork.CommitAsync(); await _service.UpdateTag(new AppUserCollectionDto() { @@ -167,7 +167,7 @@ public class CollectionTagServiceTests : AbstractDbTest AgeRating = AgeRating.Unknown }, 1); - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(3); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(3); Assert.NotNull(tag); Assert.Equal("UpdateTag_ShouldNotChangeTitle_WhenNotKavitaSource", tag.Title); Assert.False(string.IsNullOrEmpty(tag.Summary)); @@ -198,8 +198,8 @@ public class CollectionTagServiceTests : AbstractDbTest // Create a second user var user2 = new AppUserBuilder("user2", "user2", Seed.DefaultThemes.First()).Build(); - _unitOfWork.UserRepository.Add(user2); - await _unitOfWork.CommitAsync(); + UnitOfWork.UserRepository.Add(user2); + await UnitOfWork.CommitAsync(); // Act & Assert var exception = await Assert.ThrowsAsync(() => _service.UpdateTag(new AppUserCollectionDto() @@ -261,7 +261,7 @@ public class CollectionTagServiceTests : AbstractDbTest }, 1); // Assert - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.True(tag.CoverImageLocked); @@ -273,7 +273,7 @@ public class CollectionTagServiceTests : AbstractDbTest CoverImageLocked = false }, 1); - tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.False(tag.CoverImageLocked); Assert.Equal(string.Empty, tag.CoverImage); @@ -286,7 +286,7 @@ public class CollectionTagServiceTests : AbstractDbTest await SeedSeries(); // Setup a user with admin role - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); await AddUserWithRole(user.Id, PolicyConstants.AdminRole); @@ -300,7 +300,7 @@ public class CollectionTagServiceTests : AbstractDbTest }, 1); // Assert - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.True(tag.Promoted); } @@ -312,7 +312,7 @@ public class CollectionTagServiceTests : AbstractDbTest await SeedSeries(); // Setup a user with promote role - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Mock to return promote role for the user @@ -327,7 +327,7 @@ public class CollectionTagServiceTests : AbstractDbTest }, 1); // Assert - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.True(tag.Promoted); } @@ -339,7 +339,7 @@ public class CollectionTagServiceTests : AbstractDbTest await SeedSeries(); // Setup a user with no special roles - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Act - Try to promote a tag without proper role @@ -351,7 +351,7 @@ public class CollectionTagServiceTests : AbstractDbTest }, 1); // Assert - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.False(tag.Promoted); // Should remain unpromoted } @@ -365,15 +365,15 @@ public class CollectionTagServiceTests : AbstractDbTest { await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Tag 2 has 2 series - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(2); Assert.NotNull(tag); await _service.RemoveTagFromSeries(tag, new[] {1}); - var userCollections = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var userCollections = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.Equal(2, userCollections!.Collections.Count); Assert.Single(tag.Items); Assert.Equal(2, tag.Items.First().Id); @@ -387,11 +387,11 @@ public class CollectionTagServiceTests : AbstractDbTest { await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Tag 2 has 2 series - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(2); Assert.NotNull(tag); await _service.RemoveTagFromSeries(tag, new[] {1}); @@ -407,15 +407,15 @@ public class CollectionTagServiceTests : AbstractDbTest { await SeedSeries(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Collections); Assert.NotNull(user); // Tag 1 has 1 series - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); await _service.RemoveTagFromSeries(tag, new[] {1}); - var tag2 = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag2 = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.Null(tag2); } @@ -435,7 +435,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Arrange await SeedSeries(); - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); var initialItemCount = tag.Items.Count; @@ -444,7 +444,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Assert Assert.True(result); - tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.Equal(initialItemCount, tag.Items.Count); // No items should be removed } @@ -455,7 +455,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Arrange await SeedSeries(); - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); var initialItemCount = tag.Items.Count; @@ -464,7 +464,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Assert Assert.True(result); - tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); Assert.Equal(initialItemCount, tag.Items.Count); // No items should be removed } @@ -475,13 +475,13 @@ public class CollectionTagServiceTests : AbstractDbTest // Arrange await SeedSeries(); - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.NotNull(tag); // Force null items list tag.Items = null; - _unitOfWork.CollectionTagRepository.Update(tag); - await _unitOfWork.CommitAsync(); + UnitOfWork.CollectionTagRepository.Update(tag); + await UnitOfWork.CommitAsync(); // Act var result = await _service.RemoveTagFromSeries(tag, [1]); @@ -489,7 +489,7 @@ public class CollectionTagServiceTests : AbstractDbTest // Assert Assert.True(result); // The tag should not be removed since the items list was null, not empty - var tagAfter = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(1); + var tagAfter = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(1); Assert.Null(tagAfter); } @@ -501,21 +501,21 @@ public class CollectionTagServiceTests : AbstractDbTest // Add a third series with a different age rating var s3 = new SeriesBuilder("Series 3").WithMetadata(new SeriesMetadataBuilder().WithAgeRating(AgeRating.PG).Build()).Build(); - _context.Library.First().Series.Add(s3); - await _unitOfWork.CommitAsync(); + Context.Library.First().Series.Add(s3); + await UnitOfWork.CommitAsync(); // Add series 3 to tag 2 - var tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2); + var tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(2); Assert.NotNull(tag); tag.Items.Add(s3); - _unitOfWork.CollectionTagRepository.Update(tag); - await _unitOfWork.CommitAsync(); + UnitOfWork.CollectionTagRepository.Update(tag); + await UnitOfWork.CommitAsync(); // Act - Remove the series with Mature rating await _service.RemoveTagFromSeries(tag, new[] {1}); // Assert - tag = await _unitOfWork.CollectionTagRepository.GetCollectionAsync(2); + tag = await UnitOfWork.CollectionTagRepository.GetCollectionAsync(2); Assert.NotNull(tag); Assert.Equal(2, tag.Items.Count); diff --git a/API.Tests/Services/CoverDbServiceTests.cs b/API.Tests/Services/CoverDbServiceTests.cs new file mode 100644 index 000000000..93217c3b5 --- /dev/null +++ b/API.Tests/Services/CoverDbServiceTests.cs @@ -0,0 +1,117 @@ +using System.IO; +using System.IO.Abstractions; +using System.Reflection; +using System.Threading.Tasks; +using API.Constants; +using API.Entities.Enums; +using API.Extensions; +using API.Services; +using API.Services.Tasks.Metadata; +using API.SignalR; +using EasyCaching.Core; +using Kavita.Common; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace API.Tests.Services; + +public class CoverDbServiceTests : AbstractDbTest +{ + private readonly DirectoryService _directoryService; + private readonly IEasyCachingProviderFactory _cacheFactory = Substitute.For(); + private readonly ICoverDbService _coverDbService; + + private static readonly string FaviconPath = Path.Join(Directory.GetCurrentDirectory(), + "../../../Services/Test Data/CoverDbService/Favicons"); + /// + /// Path to download files temp to. Should be empty after each test. + /// + private static readonly string TempPath = Path.Join(Directory.GetCurrentDirectory(), + "../../../Services/Test Data/CoverDbService/Temp"); + + public CoverDbServiceTests() + { + _directoryService = new DirectoryService(Substitute.For>(), CreateFileSystem()); + var imageService = new ImageService(Substitute.For>(), _directoryService); + + _coverDbService = new CoverDbService(Substitute.For>(), _directoryService, _cacheFactory, + Substitute.For(), imageService, UnitOfWork, Substitute.For()); + } + + protected override Task ResetDb() + { + throw new System.NotImplementedException(); + } + + + #region Download Favicon + + /// + /// I cannot figure out how to test this code due to the reliance on the _directoryService.FaviconDirectory and not being + /// able to redirect it to the real filesystem. + /// + public async Task DownloadFaviconAsync_ShouldDownloadAndMatchExpectedFavicon() + { + // Arrange + var testUrl = "https://anilist.co/anime/6205/Kmpfer/"; + var encodeFormat = EncodeFormat.WEBP; + var expectedFaviconPath = Path.Combine(FaviconPath, "anilist.co.webp"); + + // Ensure TempPath exists + _directoryService.ExistOrCreate(TempPath); + + var baseUrl = "https://anilist.co"; + + // Ensure there is no cache result for this URL + var provider = Substitute.For(); + provider.GetAsync(baseUrl).Returns(new CacheValue(null, false)); + _cacheFactory.GetCachingProvider(EasyCacheProfiles.Favicon).Returns(provider); + + + // // Replace favicon directory with TempPath + // var directoryService = (DirectoryService)_directoryService; + // directoryService.FaviconDirectory = TempPath; + + // Hack: Swap FaviconDirectory with TempPath for ability to download real files + typeof(DirectoryService) + .GetField("FaviconDirectory", BindingFlags.NonPublic | BindingFlags.Instance) + ?.SetValue(_directoryService, TempPath); + + + // Act + var resultFilename = await _coverDbService.DownloadFaviconAsync(testUrl, encodeFormat); + var actualFaviconPath = Path.Combine(TempPath, resultFilename); + + // Assert file exists + Assert.True(File.Exists(actualFaviconPath), "Downloaded favicon does not exist in temp path"); + + // Load and compare similarity + + var similarity = expectedFaviconPath.CalculateSimilarity(actualFaviconPath); // Assuming you have this extension + Assert.True(similarity > 0.9f, $"Image similarity too low: {similarity}"); + } + + [Fact] + public async Task DownloadFaviconAsync_ShouldThrowKavitaException_WhenPreviouslyFailedUrlExistsInCache() + { + // Arrange + var testUrl = "https://example.com"; + var encodeFormat = EncodeFormat.WEBP; + + var provider = Substitute.For(); + provider.GetAsync(Arg.Any()) + .Returns(new CacheValue(string.Empty, true)); // Simulate previous failure + + _cacheFactory.GetCachingProvider(EasyCacheProfiles.Favicon).Returns(provider); + + // Act & Assert + await Assert.ThrowsAsync(() => + _coverDbService.DownloadFaviconAsync(testUrl, encodeFormat)); + } + + #endregion + + +} diff --git a/API.Tests/Services/DeviceServiceTests.cs b/API.Tests/Services/DeviceServiceTests.cs index 1d021c76d..cbcf70f82 100644 --- a/API.Tests/Services/DeviceServiceTests.cs +++ b/API.Tests/Services/DeviceServiceTests.cs @@ -18,13 +18,13 @@ public class DeviceServiceDbTests : AbstractDbTest public DeviceServiceDbTests() : base() { - _deviceService = new DeviceService(_unitOfWork, _logger, Substitute.For()); + _deviceService = new DeviceService(UnitOfWork, _logger, Substitute.For()); } protected override async Task ResetDb() { - _context.Users.RemoveRange(_context.Users.ToList()); - await _unitOfWork.CommitAsync(); + Context.Users.RemoveRange(Context.Users.ToList()); + await UnitOfWork.CommitAsync(); } @@ -39,8 +39,8 @@ public class DeviceServiceDbTests : AbstractDbTest Devices = new List() }; - _context.Users.Add(user); - await _unitOfWork.CommitAsync(); + Context.Users.Add(user); + await UnitOfWork.CommitAsync(); var device = await _deviceService.Create(new CreateDeviceDto() { @@ -62,8 +62,8 @@ public class DeviceServiceDbTests : AbstractDbTest Devices = new List() }; - _context.Users.Add(user); - await _unitOfWork.CommitAsync(); + Context.Users.Add(user); + await UnitOfWork.CommitAsync(); var device = await _deviceService.Create(new CreateDeviceDto() { diff --git a/API.Tests/Services/ExternalMetadataServiceTests.cs b/API.Tests/Services/ExternalMetadataServiceTests.cs index 127bceb7a..c2c226538 100644 --- a/API.Tests/Services/ExternalMetadataServiceTests.cs +++ b/API.Tests/Services/ExternalMetadataServiceTests.cs @@ -40,8 +40,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest // Set up Hangfire to use in-memory storage for testing GlobalConfiguration.Configuration.UseInMemoryStorage(); - _externalMetadataService = new ExternalMetadataService(_unitOfWork, Substitute.For>(), - _mapper, Substitute.For(), Substitute.For(), Substitute.For(), + _externalMetadataService = new ExternalMetadataService(UnitOfWork, Substitute.For>(), + Mapper, Substitute.For(), Substitute.For(), Substitute.For(), Substitute.For()); } @@ -58,14 +58,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = false; metadataSettings.EnableSummary = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -75,7 +75,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(string.Empty, postSeries.Metadata.Summary); } @@ -95,14 +95,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableSummary = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -112,7 +112,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(string.Empty, postSeries.Metadata.Summary); } @@ -128,14 +128,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableSummary = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -145,7 +145,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.False(string.IsNullOrEmpty(postSeries.Metadata.Summary)); Assert.Equal(series.Metadata.Summary, postSeries.Metadata.Summary); @@ -163,14 +163,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithSummary("This summary is not locked") .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableSummary = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -180,7 +180,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.False(string.IsNullOrEmpty(postSeries.Metadata.Summary)); Assert.Equal("This summary is not locked", postSeries.Metadata.Summary); @@ -198,14 +198,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithSummary("This summary is not locked", true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableSummary = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -215,7 +215,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.False(string.IsNullOrEmpty(postSeries.Metadata.Summary)); Assert.Equal("This summary is not locked", postSeries.Metadata.Summary); @@ -233,15 +233,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithSummary("This summary is not locked", true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableSummary = true; metadataSettings.Overrides = [MetadataSettingField.Summary]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -251,7 +251,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.False(string.IsNullOrEmpty(postSeries.Metadata.Summary)); Assert.Equal("This should write", postSeries.Metadata.Summary); @@ -273,14 +273,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableStartDate = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -290,7 +290,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(0, postSeries.Metadata.ReleaseYear); } @@ -306,14 +306,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableStartDate = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -323,7 +323,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(DateTime.UtcNow.Year, postSeries.Metadata.ReleaseYear); } @@ -340,14 +340,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithReleaseYear(1990) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableStartDate = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -357,7 +357,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(1990, postSeries.Metadata.ReleaseYear); } @@ -374,14 +374,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithReleaseYear(1990, true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableStartDate = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -391,7 +391,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(1990, postSeries.Metadata.ReleaseYear); } @@ -408,15 +408,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithReleaseYear(1990, true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableStartDate = true; metadataSettings.Overrides = [MetadataSettingField.StartDate]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -426,7 +426,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(DateTime.UtcNow.Year, postSeries.Metadata.ReleaseYear); } @@ -447,14 +447,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -464,7 +464,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(string.Empty, postSeries.LocalizedName); } @@ -481,14 +481,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -498,7 +498,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal("Kimchi", postSeries.LocalizedName); } @@ -515,14 +515,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -532,7 +532,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal("Localized Name here", postSeries.LocalizedName); } @@ -549,14 +549,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -566,7 +566,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal("Localized Name here", postSeries.LocalizedName); } @@ -583,15 +583,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = true; metadataSettings.Overrides = [MetadataSettingField.LocalizedName]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -601,7 +601,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal("Kimchi", postSeries.LocalizedName); } @@ -618,14 +618,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableLocalizedName = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -635,7 +635,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.True(string.IsNullOrEmpty(postSeries.LocalizedName)); } @@ -661,14 +661,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("2").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -678,7 +678,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.OnGoing, postSeries.Metadata.PublicationStatus); } @@ -700,14 +700,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("2").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -717,7 +717,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.Completed, postSeries.Metadata.PublicationStatus); } @@ -740,14 +740,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("2").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -757,7 +757,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.Completed, postSeries.Metadata.PublicationStatus); } @@ -780,14 +780,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("2").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -797,7 +797,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.Hiatus, postSeries.Metadata.PublicationStatus); } @@ -820,15 +820,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("2").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = true; metadataSettings.Overrides = [MetadataSettingField.PublicationStatus]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -838,7 +838,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.Completed, postSeries.Metadata.PublicationStatus); } @@ -858,14 +858,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithChapter(new ChapterBuilder("1").Build()) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePublicationStatus = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -875,7 +875,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(PublicationStatus.Ended, postSeries.Metadata.PublicationStatus); } @@ -897,18 +897,18 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.AgeRatingMappings = new Dictionary() { {"Ecchi", AgeRating.Teen}, // Genre {"H", AgeRating.R18Plus}, // Tag }; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -918,7 +918,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(AgeRating.Teen, postSeries.Metadata.AgeRating); } @@ -935,18 +935,18 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithAgeRating(AgeRating.Mature) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.AgeRatingMappings = new Dictionary() { {"Ecchi", AgeRating.Teen}, // Genre {"H", AgeRating.R18Plus}, // Tag }; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -956,7 +956,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(AgeRating.Mature, postSeries.Metadata.AgeRating); } @@ -973,18 +973,18 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithAgeRating(AgeRating.Everyone) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.AgeRatingMappings = new Dictionary() { {"Ecchi", AgeRating.Teen}, // Genre {"H", AgeRating.R18Plus}, // Tag }; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -994,7 +994,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(AgeRating.Teen, postSeries.Metadata.AgeRating); } @@ -1011,18 +1011,18 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithAgeRating(AgeRating.Everyone, true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.AgeRatingMappings = new Dictionary() { {"Ecchi", AgeRating.Teen}, // Genre {"H", AgeRating.R18Plus}, // Tag }; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1032,7 +1032,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(AgeRating.Everyone, postSeries.Metadata.AgeRating); } @@ -1049,10 +1049,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithAgeRating(AgeRating.Everyone, true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.Overrides = [MetadataSettingField.AgeRating]; metadataSettings.AgeRatingMappings = new Dictionary() @@ -1060,8 +1060,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest {"Ecchi", AgeRating.Teen}, // Genre {"H", AgeRating.R18Plus}, // Tag }; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1071,7 +1071,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(AgeRating.Teen, postSeries.Metadata.AgeRating); } @@ -1091,14 +1091,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1108,7 +1108,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal([], postSeries.Metadata.Genres); } @@ -1124,14 +1124,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1141,7 +1141,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Ecchi"], postSeries.Metadata.Genres.Select(g => g.Title)); } @@ -1158,14 +1158,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithGenre(_genreLookup["Action"]) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1175,7 +1175,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Ecchi"], postSeries.Metadata.Genres.Select(g => g.Title)); } @@ -1192,14 +1192,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithGenre(_genreLookup["Action"], true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1209,7 +1209,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Action"], postSeries.Metadata.Genres.Select(g => g.Title)); } @@ -1226,15 +1226,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithGenre(_genreLookup["Action"], true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; metadataSettings.Overrides = [MetadataSettingField.Genres]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1244,7 +1244,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Ecchi"], postSeries.Metadata.Genres.Select(g => g.Title)); } @@ -1264,14 +1264,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1281,7 +1281,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal([], postSeries.Metadata.Tags); } @@ -1297,14 +1297,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1314,7 +1314,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Boxing"], postSeries.Metadata.Tags.Select(t => t.Title)); } @@ -1331,14 +1331,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithTag(_tagLookup["H"], true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1348,7 +1348,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["H"], postSeries.Metadata.Tags.Select(t => t.Title)); } @@ -1365,15 +1365,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithTag(_tagLookup["H"], true) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.Overrides = [MetadataSettingField.Tags]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1383,7 +1383,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Boxing"], postSeries.Metadata.Tags.Select(t => t.Title)); } @@ -1403,14 +1403,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1420,7 +1420,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal([], postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer)); } @@ -1436,15 +1436,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1454,7 +1454,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["John Doe"], postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer).Select(p => p.Person.Name)); } @@ -1472,15 +1472,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1490,7 +1490,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1511,17 +1511,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Writer]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1531,7 +1531,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe", "Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1554,17 +1554,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = false; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Writer]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1574,7 +1574,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1595,17 +1595,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = []; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1615,7 +1615,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1635,17 +1635,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Writer]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1655,7 +1655,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1668,7 +1668,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest Staff = [CreateStaff("John", "Doe 2", "Story")] }, 1); - postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe 2"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Writer) @@ -1691,14 +1691,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = false; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1708,7 +1708,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal([], postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character)); } @@ -1724,15 +1724,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1742,7 +1742,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["John Doe"], postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character).Select(p => p.Person.Name)); } @@ -1760,15 +1760,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.CharacterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1778,7 +1778,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1799,17 +1799,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Character]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1819,7 +1819,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe", "Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1842,17 +1842,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = false; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Character]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1862,7 +1862,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler", "Twowheeler Johnny"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1883,17 +1883,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .Build()) .Build(); series.Metadata.WriterLocked = true; - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = []; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1903,7 +1903,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"Johnny Twowheeler"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1923,17 +1923,17 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnablePeople = true; metadataSettings.FirstLastPeopleNaming = true; metadataSettings.Overrides = [MetadataSettingField.People]; metadataSettings.PersonRoles = [PersonRole.Character]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -1943,7 +1943,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1956,7 +1956,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest Characters = [CreateCharacter("John", "Doe 2", CharacterRole.Main)] }, 1); - postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[]{"John Doe 2"}.OrderBy(s => s), postSeries.Metadata.People.Where(p => p.Role == PersonRole.Character) @@ -1988,7 +1988,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); var series2 = new SeriesBuilder("Test - Relationships Side Story - Target") .WithLibraryId(1) @@ -2000,14 +2000,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest AniListId = 10 }) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() { @@ -2028,7 +2028,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var sourceSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sourceSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sourceSeries); Assert.Single(sourceSeries.Relations); Assert.Equal(series2.Name, sourceSeries.Relations.First().TargetSeries.Name); @@ -2046,7 +2046,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); var series2 = new SeriesBuilder("Test - Relationships Side Story - Target") .WithLibraryId(1) @@ -2055,14 +2055,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() { @@ -2083,7 +2083,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var sourceSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sourceSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sourceSeries); Assert.Single(sourceSeries.Relations); Assert.Equal(series2.Name, sourceSeries.Relations.First().TargetSeries.Name); @@ -2102,7 +2102,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); var series2 = new SeriesBuilder("Test - Relationships Side Story - Target") .WithLibraryId(1) @@ -2111,14 +2111,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() { @@ -2139,7 +2139,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var sourceSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sourceSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sourceSeries); Assert.Empty(sourceSeries.Relations); } @@ -2153,8 +2153,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest var existingRelationshipSeries = new SeriesBuilder("Existing") .WithLibraryId(1) .Build(); - _context.Series.Attach(existingRelationshipSeries); - await _context.SaveChangesAsync(); + Context.Series.Attach(existingRelationshipSeries); + await Context.SaveChangesAsync(); const string seriesName = "Test - Relationships Side Story"; var series = new SeriesBuilder(seriesName) @@ -2164,7 +2164,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); var series2 = new SeriesBuilder("Test - Relationships Side Story - Target") .WithLibraryId(1) @@ -2176,14 +2176,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest AniListId = 10 }) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2204,7 +2204,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 2); // Repull Series and validate what is overwritten - var sourceSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sourceSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sourceSeries); Assert.Equal(seriesName, sourceSeries.Name); @@ -2227,7 +2227,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); var series2 = new SeriesBuilder("Test - Relationships Target") .WithLibraryId(1) @@ -2235,14 +2235,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() { @@ -2262,12 +2262,12 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var sourceSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sourceSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sourceSeries); Assert.Single(sourceSeries.Relations); Assert.Equal(series2.Name, sourceSeries.Relations.First().TargetSeries.Name); - var sequel = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); + var sequel = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(sequel); Assert.Equal(seriesName, sequel.Relations.First().TargetSeries.Name); } @@ -2284,7 +2284,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); + Context.Series.Attach(series); // ID 2: Blue Lock var series2 = new SeriesBuilder("Blue Lock") @@ -2293,14 +2293,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series2); - await _context.SaveChangesAsync(); + Context.Series.Attach(series2); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableRelationships = true; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); // Apply to Blue Lock - Episode Nagi (ID 1), setting Blue Lock (ID 2) as its prequel await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2324,14 +2324,14 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Apply to series ID 1 (Nagi) // Verify Blue Lock - Episode Nagi has Blue Lock as prequel - var nagiSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); + var nagiSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(nagiSeries); Assert.Single(nagiSeries.Relations); Assert.Equal("Blue Lock", nagiSeries.Relations.First().TargetSeries.Name); Assert.Equal(RelationKind.Prequel, nagiSeries.Relations.First().RelationKind); // Verify Blue Lock has Blue Lock - Episode Nagi as sequel - var blueLockSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); + var blueLockSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Metadata | SeriesIncludes.Related); Assert.NotNull(blueLockSeries); Assert.Single(blueLockSeries.Relations); Assert.Equal("Blue Lock - Episode Nagi", blueLockSeries.Relations.First().TargetSeries.Name); @@ -2354,16 +2354,16 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.EnableGenres = true; metadataSettings.Blacklist = ["Sports", "Action"]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2373,7 +2373,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[] {"Boxing"}.OrderBy(s => s), postSeries.Metadata.Genres.Select(t => t.Title).OrderBy(s => s)); } @@ -2390,16 +2390,16 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.EnableGenres = true; metadataSettings.Blacklist = ["Sports", "Action"]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2409,7 +2409,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[] {"Boxing"}.OrderBy(s => s), postSeries.Metadata.Tags.Select(t => t.Title).OrderBy(s => s)); } @@ -2435,15 +2435,15 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.Whitelist = ["Sports", "Action"]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2453,7 +2453,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[] {"Sports", "Action"}.OrderBy(s => s), postSeries.Metadata.Tags.Select(t => t.Title).OrderBy(s => s)); } @@ -2469,10 +2469,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.FieldMappings = [new MetadataFieldMapping() @@ -2485,8 +2485,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; metadataSettings.Whitelist = ["Sports", "Action"]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2496,7 +2496,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(new[] {"Sports", "Action"}.OrderBy(s => s), postSeries.Metadata.Tags.Select(t => t.Title).OrderBy(s => s)); } @@ -2516,10 +2516,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; metadataSettings.Overrides = [MetadataSettingField.Genres]; @@ -2533,8 +2533,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2544,7 +2544,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal( new[] { "Ecchi", "Fanservice" }.OrderBy(s => s), @@ -2563,10 +2563,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; metadataSettings.Overrides = [MetadataSettingField.Genres]; @@ -2580,8 +2580,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2591,7 +2591,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Fanservice"], postSeries.Metadata.Genres.Select(g => g.Title)); } @@ -2607,10 +2607,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.FieldMappings = [new MetadataFieldMapping() @@ -2623,8 +2623,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2634,7 +2634,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal( new[] { "Ecchi", "Fanservice" }.OrderBy(s => s), @@ -2653,10 +2653,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableTags = true; metadataSettings.Overrides = [MetadataSettingField.Genres]; @@ -2670,8 +2670,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2681,7 +2681,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal(["Fanservice"], postSeries.Metadata.Tags.Select(g => g.Title)); } @@ -2697,10 +2697,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder() .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; metadataSettings.EnableTags = true; @@ -2715,8 +2715,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2726,7 +2726,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal( new[] {"Ecchi"}.OrderBy(s => s), @@ -2752,10 +2752,10 @@ public class ExternalMetadataServiceTests : AbstractDbTest .WithGenre(_genreLookup["Action"]) .Build()) .Build(); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = true; metadataSettings.EnableGenres = true; metadataSettings.EnableTags = true; @@ -2770,8 +2770,8 @@ public class ExternalMetadataServiceTests : AbstractDbTest }]; - _context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + Context.MetadataSettings.Update(metadataSettings); + await Context.SaveChangesAsync(); await _externalMetadataService.WriteExternalMetadataToSeries(new ExternalSeriesDetailDto() @@ -2780,7 +2780,7 @@ public class ExternalMetadataServiceTests : AbstractDbTest }, 1); // Repull Series and validate what is overwritten - var postSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); + var postSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Metadata); Assert.NotNull(postSeries); Assert.Equal( new[] {"Action"}.OrderBy(s => s), @@ -2794,13 +2794,13 @@ public class ExternalMetadataServiceTests : AbstractDbTest protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series); - _context.AppUser.RemoveRange(_context.AppUser); - _context.Genre.RemoveRange(_context.Genre); - _context.Tag.RemoveRange(_context.Tag); - _context.Person.RemoveRange(_context.Person); + Context.Series.RemoveRange(Context.Series); + Context.AppUser.RemoveRange(Context.AppUser); + Context.Genre.RemoveRange(Context.Genre); + Context.Tag.RemoveRange(Context.Tag); + Context.Person.RemoveRange(Context.Person); - var metadataSettings = await _unitOfWork.SettingsRepository.GetMetadataSettings(); + var metadataSettings = await UnitOfWork.SettingsRepository.GetMetadataSettings(); metadataSettings.Enabled = false; metadataSettings.EnableSummary = false; metadataSettings.EnableCoverImage = false; @@ -2811,41 +2811,41 @@ public class ExternalMetadataServiceTests : AbstractDbTest metadataSettings.EnableTags = false; metadataSettings.EnablePublicationStatus = false; metadataSettings.EnableStartDate = false; - _context.MetadataSettings.Update(metadataSettings); + Context.MetadataSettings.Update(metadataSettings); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - _context.AppUser.Add(new AppUserBuilder("Joe", "Joe") + Context.AppUser.Add(new AppUserBuilder("Joe", "Joe") .WithRole(PolicyConstants.AdminRole) - .WithLibrary(await _context.Library.FirstAsync(l => l.Id == 1)) + .WithLibrary(await Context.Library.FirstAsync(l => l.Id == 1)) .Build()); // Create a bunch of Genres for this test and store their string in _genreLookup _genreLookup.Clear(); var g1 = new GenreBuilder("Action").Build(); var g2 = new GenreBuilder("Ecchi").Build(); - _context.Genre.Add(g1); - _context.Genre.Add(g2); + Context.Genre.Add(g1); + Context.Genre.Add(g2); _genreLookup.Add("Action", g1); _genreLookup.Add("Ecchi", g2); _tagLookup.Clear(); var t1 = new TagBuilder("H").Build(); var t2 = new TagBuilder("Boxing").Build(); - _context.Tag.Add(t1); - _context.Tag.Add(t2); + Context.Tag.Add(t1); + Context.Tag.Add(t2); _tagLookup.Add("H", t1); _tagLookup.Add("Boxing", t2); _personLookup.Clear(); var p1 = new PersonBuilder("Johnny Twowheeler").Build(); var p2 = new PersonBuilder("Boxing").Build(); - _context.Person.Add(p1); - _context.Person.Add(p2); + Context.Person.Add(p1); + Context.Person.Add(p2); _personLookup.Add("Johnny Twowheeler", p1); _personLookup.Add("Batman Robin", p2); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } private static SeriesStaffDto CreateStaff(string first, string last, string role) diff --git a/API.Tests/Services/ParseScannedFilesTests.cs b/API.Tests/Services/ParseScannedFilesTests.cs index f81ebd3c4..f8714f69a 100644 --- a/API.Tests/Services/ParseScannedFilesTests.cs +++ b/API.Tests/Services/ParseScannedFilesTests.cs @@ -99,14 +99,14 @@ public class ParseScannedFilesTests : AbstractDbTest { // Since ProcessFile relies on _readingItemService, we can implement our own versions of _readingItemService so we have control over how the calls work GlobalConfiguration.Configuration.UseInMemoryStorage(); - _scannerHelper = new ScannerHelper(_unitOfWork, testOutputHelper); + _scannerHelper = new ScannerHelper(UnitOfWork, testOutputHelper); } protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series.ToList()); + Context.Series.RemoveRange(Context.Series.ToList()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } #region MergeName @@ -206,13 +206,13 @@ public class ParseScannedFilesTests : AbstractDbTest var library = - await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1, + await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1, LibraryIncludes.Folders | LibraryIncludes.FileTypes); Assert.NotNull(library); library.Type = LibraryType.Manga; var parsedSeries = await psf.ScanLibrariesForSeries(library, new List() {Root + "Data/"}, false, - await _unitOfWork.SeriesRepository.GetFolderPathMap(1)); + await UnitOfWork.SeriesRepository.GetFolderPathMap(1)); // Assert.Equal(3, parsedSeries.Values.Count); @@ -251,9 +251,9 @@ public class ParseScannedFilesTests : AbstractDbTest new MockReadingItemService(ds, Substitute.For()), Substitute.For()); var directoriesSeen = new HashSet(); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1, + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1, LibraryIncludes.Folders | LibraryIncludes.FileTypes); - var scanResults = await psf.ScanFiles("C:/Data/", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library); + var scanResults = await psf.ScanFiles("C:/Data/", true, await UnitOfWork.SeriesRepository.GetFolderPathMap(1), library); foreach (var scanResult in scanResults) { directoriesSeen.Add(scanResult.Folder); @@ -270,13 +270,13 @@ public class ParseScannedFilesTests : AbstractDbTest var psf = new ParseScannedFiles(Substitute.For>(), ds, new MockReadingItemService(ds, Substitute.For()), Substitute.For()); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1, + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1, LibraryIncludes.Folders | LibraryIncludes.FileTypes); Assert.NotNull(library); var directoriesSeen = new HashSet(); var scanResults = await psf.ScanFiles("C:/Data/", false, - await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library); + await UnitOfWork.SeriesRepository.GetFolderPathMap(1), library); foreach (var scanResult in scanResults) { @@ -305,10 +305,10 @@ public class ParseScannedFilesTests : AbstractDbTest var psf = new ParseScannedFiles(Substitute.For>(), ds, new MockReadingItemService(ds, Substitute.For()), Substitute.For()); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1, + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1, LibraryIncludes.Folders | LibraryIncludes.FileTypes); Assert.NotNull(library); - var scanResults = await psf.ScanFiles("C:/Data", true, await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library); + var scanResults = await psf.ScanFiles("C:/Data", true, await UnitOfWork.SeriesRepository.GetFolderPathMap(1), library); Assert.Equal(2, scanResults.Count); } @@ -334,11 +334,11 @@ public class ParseScannedFilesTests : AbstractDbTest var psf = new ParseScannedFiles(Substitute.For>(), ds, new MockReadingItemService(ds, Substitute.For()), Substitute.For()); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1, + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1, LibraryIncludes.Folders | LibraryIncludes.FileTypes); Assert.NotNull(library); var scanResults = await psf.ScanFiles("C:/Data", false, - await _unitOfWork.SeriesRepository.GetFolderPathMap(1), library); + await UnitOfWork.SeriesRepository.GetFolderPathMap(1), library); Assert.Single(scanResults); } @@ -357,8 +357,8 @@ public class ParseScannedFilesTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var fs = new FileSystem(); var ds = new DirectoryService(Substitute.For>(), fs); @@ -368,7 +368,7 @@ public class ParseScannedFilesTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(ds, fs); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(4, postLib.Series.Count); @@ -391,7 +391,7 @@ public class ParseScannedFilesTests : AbstractDbTest Path.Join(executionerCopyDir, "The Executioner and Her Way of Life Vol. 1 Ch. 0002.cbz")); // 4 series, of which 2 have volumes as directories - var folderMap = await _unitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id); + var folderMap = await UnitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id); Assert.Equal(6, folderMap.Count); var res = await psf.ScanFiles(testDirectoryPath, true, folderMap, postLib); @@ -409,8 +409,8 @@ public class ParseScannedFilesTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var fs = new FileSystem(); var ds = new DirectoryService(Substitute.For>(), fs); @@ -420,7 +420,7 @@ public class ParseScannedFilesTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(ds, fs); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(4, postLib.Series.Count); @@ -438,7 +438,7 @@ public class ParseScannedFilesTests : AbstractDbTest Path.Join(executionerCopyDir, "The Executioner and Her Way of Life Vol. 2.cbz")); var res = await psf.ScanFiles(testDirectoryPath, true, - await _unitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); + await UnitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); var changes = res.Count(sc => sc.HasChanged); Assert.Equal(1, changes); } @@ -452,8 +452,8 @@ public class ParseScannedFilesTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var fs = new FileSystem(); var ds = new DirectoryService(Substitute.For>(), fs); @@ -463,7 +463,7 @@ public class ParseScannedFilesTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(ds, fs); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -476,7 +476,7 @@ public class ParseScannedFilesTests : AbstractDbTest await Task.Delay(1100); // Ensure at least one second has passed since library scan var res = await psf.ScanFiles(testDirectoryPath, true, - await _unitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); + await UnitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); Assert.DoesNotContain(res, sc => sc.HasChanged); } @@ -488,8 +488,8 @@ public class ParseScannedFilesTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var fs = new FileSystem(); var ds = new DirectoryService(Substitute.For>(), fs); @@ -499,7 +499,7 @@ public class ParseScannedFilesTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(ds, fs); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -508,8 +508,8 @@ public class ParseScannedFilesTests : AbstractDbTest Assert.Equal(4, spiceAndWolf.Volumes.Sum(v => v.Chapters.Count)); spiceAndWolf.LastFolderScanned = DateTime.Now.Subtract(TimeSpan.FromMinutes(2)); - _context.Series.Update(spiceAndWolf); - await _context.SaveChangesAsync(); + Context.Series.Update(spiceAndWolf); + await Context.SaveChangesAsync(); // Add file at series root var spiceAndWolfDir = Path.Join(testDirectoryPath, "Spice and Wolf"); @@ -517,7 +517,7 @@ public class ParseScannedFilesTests : AbstractDbTest Path.Join(spiceAndWolfDir, "Spice and Wolf Vol. 4.cbz")); var res = await psf.ScanFiles(testDirectoryPath, true, - await _unitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); + await UnitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); var changes = res.Count(sc => sc.HasChanged); Assert.Equal(2, changes); } @@ -530,8 +530,8 @@ public class ParseScannedFilesTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var fs = new FileSystem(); var ds = new DirectoryService(Substitute.For>(), fs); @@ -541,7 +541,7 @@ public class ParseScannedFilesTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(ds, fs); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -550,8 +550,8 @@ public class ParseScannedFilesTests : AbstractDbTest Assert.Equal(4, spiceAndWolf.Volumes.Sum(v => v.Chapters.Count)); spiceAndWolf.LastFolderScanned = DateTime.Now.Subtract(TimeSpan.FromMinutes(2)); - _context.Series.Update(spiceAndWolf); - await _context.SaveChangesAsync(); + Context.Series.Update(spiceAndWolf); + await Context.SaveChangesAsync(); // Add file in subfolder var spiceAndWolfDir = Path.Join(Path.Join(testDirectoryPath, "Spice and Wolf"), "Spice and Wolf Vol. 3"); @@ -559,7 +559,7 @@ public class ParseScannedFilesTests : AbstractDbTest Path.Join(spiceAndWolfDir, "Spice and Wolf Vol. 3 Ch. 0013.cbz")); var res = await psf.ScanFiles(testDirectoryPath, true, - await _unitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); + await UnitOfWork.SeriesRepository.GetFolderPathMap(postLib.Id), postLib); var changes = res.Count(sc => sc.HasChanged); Assert.Equal(2, changes); } diff --git a/API.Tests/Services/RatingServiceTests.cs b/API.Tests/Services/RatingServiceTests.cs new file mode 100644 index 000000000..15f4541d7 --- /dev/null +++ b/API.Tests/Services/RatingServiceTests.cs @@ -0,0 +1,189 @@ +using System.Linq; +using System.Threading.Tasks; +using API.Data.Repositories; +using API.DTOs; +using API.Entities.Enums; +using API.Helpers.Builders; +using API.Services; +using API.Services.Plus; +using Hangfire; +using Hangfire.InMemory; +using Microsoft.Extensions.Logging; +using NSubstitute; +using Xunit; + +namespace API.Tests.Services; + +public class RatingServiceTests: AbstractDbTest +{ + private readonly RatingService _ratingService; + + public RatingServiceTests() + { + _ratingService = new RatingService(UnitOfWork, Substitute.For(), Substitute.For>()); + } + + [Fact] + public async Task UpdateRating_ShouldSetRating() + { + await ResetDb(); + + Context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + + await Context.SaveChangesAsync(); + + + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + JobStorage.Current = new InMemoryStorage(); + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 3, + }); + + Assert.True(result); + + var ratings = (await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings))! + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(3, ratings.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldUpdateExistingRating() + { + await ResetDb(); + + Context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + + await Context.SaveChangesAsync(); + + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 3, + }); + + Assert.True(result); + + JobStorage.Current = new InMemoryStorage(); + var ratings = (await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(3, ratings.First().Rating); + + // Update the DB again + + var result2 = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 5, + }); + + Assert.True(result2); + + var ratings2 = (await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) + .Ratings; + Assert.NotEmpty(ratings2); + Assert.True(ratings2.Count == 1); + Assert.Equal(5, ratings2.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldClampRatingAt5() + { + await ResetDb(); + + Context.Library.Add(new LibraryBuilder("Test LIb") + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + await Context.SaveChangesAsync(); + + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto + { + SeriesId = 1, + UserRating = 10, + }); + + Assert.True(result); + + JobStorage.Current = new InMemoryStorage(); + var ratings = (await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", + AppUserIncludes.Ratings)!) + .Ratings; + Assert.NotEmpty(ratings); + Assert.Equal(5, ratings.First().Rating); + } + + [Fact] + public async Task UpdateRating_ShouldReturnFalseWhenSeriesDoesntExist() + { + await ResetDb(); + + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) + .WithSeries(new SeriesBuilder("Test") + + .WithVolume(new VolumeBuilder("1") + .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) + .Build()) + .Build()) + .Build()); + + await Context.SaveChangesAsync(); + + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); + + var result = await _ratingService.UpdateSeriesRating(user, new UpdateRatingDto + { + SeriesId = 2, + UserRating = 5, + }); + + Assert.False(result); + + var ratings = user.Ratings; + Assert.Empty(ratings); + } + protected override async Task ResetDb() + { + Context.Series.RemoveRange(Context.Series.ToList()); + Context.AppUserRating.RemoveRange(Context.AppUserRating.ToList()); + Context.Genre.RemoveRange(Context.Genre.ToList()); + Context.CollectionTag.RemoveRange(Context.CollectionTag.ToList()); + Context.Person.RemoveRange(Context.Person.ToList()); + Context.Library.RemoveRange(Context.Library.ToList()); + + await Context.SaveChangesAsync(); + } +} diff --git a/API.Tests/Services/ReaderServiceTests.cs b/API.Tests/Services/ReaderServiceTests.cs index 102ea3b81..0e4ab2701 100644 --- a/API.Tests/Services/ReaderServiceTests.cs +++ b/API.Tests/Services/ReaderServiceTests.cs @@ -1,25 +1,19 @@ using System.Collections.Generic; -using System.Data.Common; using System.IO.Abstractions.TestingHelpers; using System.Linq; using System.Threading.Tasks; -using API.Data; using API.Data.Repositories; using API.DTOs.Progress; using API.DTOs.Reader; using API.Entities; using API.Entities.Enums; using API.Extensions; -using API.Helpers; using API.Helpers.Builders; using API.Services; using API.Services.Plus; using API.SignalR; -using AutoMapper; using Hangfire; using Hangfire.InMemory; -using Microsoft.Data.Sqlite; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using NSubstitute; using Xunit; @@ -27,25 +21,16 @@ using Xunit.Abstractions; namespace API.Tests.Services; -public class ReaderServiceTests: AbstractFsTest +public class ReaderServiceTests: AbstractDbTest { private readonly ITestOutputHelper _testOutputHelper; - private readonly IUnitOfWork _unitOfWork; - private readonly DataContext _context; private readonly ReaderService _readerService; public ReaderServiceTests(ITestOutputHelper testOutputHelper) { _testOutputHelper = testOutputHelper; - var contextOptions = new DbContextOptionsBuilder().UseSqlite(CreateInMemoryDatabase()).Options; - _context = new DataContext(contextOptions); - Task.Run(SeedDb).GetAwaiter().GetResult(); - - var config = new MapperConfiguration(cfg => cfg.AddProfile()); - var mapper = config.CreateMapper(); - _unitOfWork = new UnitOfWork(_context, mapper, null); - _readerService = new ReaderService(_unitOfWork, Substitute.For>(), + _readerService = new ReaderService(UnitOfWork, Substitute.For>(), Substitute.For(), Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem()), Substitute.For()); @@ -53,42 +38,12 @@ public class ReaderServiceTests: AbstractFsTest #region Setup - private static DbConnection CreateInMemoryDatabase() + + protected override async Task ResetDb() { - var connection = new SqliteConnection("Filename=:memory:"); + Context.Series.RemoveRange(Context.Series.ToList()); - connection.Open(); - - return connection; - } - - private async Task SeedDb() - { - await _context.Database.MigrateAsync(); - var filesystem = CreateFileSystem(); - - await Seed.SeedSettings(_context, - new DirectoryService(Substitute.For>(), filesystem)); - - var setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.CacheDirectory).SingleAsync(); - setting.Value = CacheDirectory; - - setting = await _context.ServerSetting.Where(s => s.Key == ServerSettingKey.BackupDirectory).SingleAsync(); - setting.Value = BackupDirectory; - - _context.ServerSetting.Update(setting); - - _context.Library.Add(new LibraryBuilder("Manga") - .WithFolderPath(new FolderPathBuilder("C:/data/").Build()) - .Build()); - return await _context.SaveChangesAsync() > 0; - } - - private async Task ResetDb() - { - _context.Series.RemoveRange(_context.Series.ToList()); - - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } #endregion @@ -122,10 +77,10 @@ public class ReaderServiceTests: AbstractFsTest series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); Assert.Equal(0, (await _readerService.CapPageToChapter(1, -1)).Item1); @@ -150,14 +105,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); JobStorage.Current = new InMemoryStorage(); @@ -171,7 +126,7 @@ public class ReaderServiceTests: AbstractFsTest }, 1); Assert.True(successful); - Assert.NotNull(await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)); + Assert.NotNull(await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)); } [Fact] @@ -188,14 +143,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); JobStorage.Current = new InMemoryStorage(); var successful = await _readerService.SaveReadingProgress(new ProgressDto() @@ -208,7 +163,7 @@ public class ReaderServiceTests: AbstractFsTest }, 1); Assert.True(successful); - Assert.NotNull(await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)); + Assert.NotNull(await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)); Assert.True(await _readerService.SaveReadingProgress(new ProgressDto() { @@ -219,7 +174,9 @@ public class ReaderServiceTests: AbstractFsTest BookScrollId = "/h1/" }, 1)); - Assert.Equal("/h1/", (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).BookScrollId); + var userProgress = await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1); + Assert.NotNull(userProgress); + Assert.Equal("/h1/", userProgress.BookScrollId); } @@ -245,22 +202,24 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var volumes = await _unitOfWork.VolumeRepository.GetVolumes(1); - await _readerService.MarkChaptersAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); - await _context.SaveChangesAsync(); + var volumes = await UnitOfWork.VolumeRepository.GetVolumes(1); + await _readerService.MarkChaptersAsRead(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); + await Context.SaveChangesAsync(); - Assert.Equal(2, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); + var userProgress = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); + Assert.NotNull(userProgress); + Assert.Equal(2, userProgress.Progresses.Count); } #endregion @@ -283,27 +242,27 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(1)).ToList(); - await _readerService.MarkChaptersAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); + var volumes = (await UnitOfWork.VolumeRepository.GetVolumes(1)).ToList(); + await _readerService.MarkChaptersAsRead(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes[0].Chapters); - await _context.SaveChangesAsync(); - Assert.Equal(2, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); + await Context.SaveChangesAsync(); + Assert.Equal(2, (await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); - await _readerService.MarkChaptersAsUnread(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); - await _context.SaveChangesAsync(); + await _readerService.MarkChaptersAsUnread(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes[0].Chapters); + await Context.SaveChangesAsync(); - var progresses = (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses; + var progresses = (await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses; Assert.Equal(0, progresses.Max(p => p.PagesRead)); Assert.Equal(2, progresses.Count); } @@ -336,19 +295,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 1, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("2", actualChapter.Range); } @@ -370,17 +329,17 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test Lib", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 1, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("3-4", actualChapter.Volume.Name); Assert.Equal("1", actualChapter.Range); @@ -413,19 +372,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 2, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("31", actualChapter.Range); } @@ -453,18 +412,18 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("21", actualChapter.Range); } @@ -492,19 +451,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("21", actualChapter.Range); } @@ -527,18 +486,18 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 4, 1); Assert.NotEqual(-1, nextChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("21", actualChapter.Range); } @@ -564,21 +523,21 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1); Assert.NotEqual(-1, nextChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal(API.Services.Tasks.Scanner.Parser.Parser.DefaultChapter, actualChapter.Range); } @@ -601,13 +560,13 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 4, 1); Assert.Equal(-1, nextChapter); @@ -626,13 +585,13 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); Assert.Equal(-1, nextChapter); @@ -651,13 +610,13 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); Assert.Equal(-1, nextChapter); @@ -680,13 +639,13 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); Assert.Equal(-1, nextChapter); @@ -716,13 +675,13 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1); Assert.Equal(-1, nextChapter); @@ -754,19 +713,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); Assert.NotEqual(-1, nextChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("A.cbz", actualChapter.Range); } @@ -792,19 +751,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 2, 1); Assert.NotEqual(-1, nextChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("A.cbz", actualChapter.Range); } @@ -834,14 +793,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 3, 4, 1); Assert.Equal(-1, nextChapter); @@ -871,19 +830,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 2, 3, 1); Assert.NotEqual(-1, nextChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter); Assert.NotNull(actualChapter); Assert.Equal("B.cbz", actualChapter.Range); } @@ -904,21 +863,21 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); var user = new AppUserBuilder("majora2007", "fake").Build(); - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); await _readerService.MarkChaptersAsRead(user, 1, new List() { - series.Volumes.First().Chapters.First() + series.Volumes[0].Chapters[0] }); var nextChapter = await _readerService.GetNextChapterIdAsync(1, 1, 1, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter, ChapterIncludes.Volumes); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter, ChapterIncludes.Volumes); Assert.Equal(2, actualChapter.Volume.MinNumber); } @@ -950,19 +909,19 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 1, 2, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("1", actualChapter.Range); } @@ -990,18 +949,18 @@ public class ReaderServiceTests: AbstractFsTest .Build()) .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 3, 5, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("22", actualChapter.Range); } @@ -1039,20 +998,20 @@ public class ReaderServiceTests: AbstractFsTest .Build()) .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.Series.Add(series); + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // prevChapter should be id from ch.21 from volume 2001 var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 5, 7, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("21", actualChapter.Range); } @@ -1080,20 +1039,20 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("2", actualChapter.Range); } @@ -1116,21 +1075,21 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1); Assert.Equal(2, prevChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("2", actualChapter.Range); } @@ -1148,14 +1107,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1176,14 +1135,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1209,14 +1168,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 3, 1); Assert.Equal(-1, prevChapter); @@ -1246,20 +1205,20 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2,5, 1); - var chapterInfoDto = await _unitOfWork.ChapterRepository.GetChapterInfoDtoAsync(prevChapter); + var chapterInfoDto = await UnitOfWork.ChapterRepository.GetChapterInfoDtoAsync(prevChapter); Assert.Equal(1, chapterInfoDto.ChapterNumber.AsFloat()); // This is first chapter of first volume @@ -1280,14 +1239,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1319,22 +1278,22 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 4, 1); Assert.NotEqual(-1, prevChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("A.cbz", actualChapter.Range); } @@ -1357,18 +1316,18 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var prevChapter = await _readerService.GetPrevChapterIdAsync(1, 1, 1, 1); Assert.NotEqual(-1, prevChapter); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(prevChapter); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(prevChapter); Assert.NotNull(actualChapter); Assert.Equal("22", actualChapter.Range); } @@ -1389,16 +1348,16 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); var user = new AppUserBuilder("majora2007", "fake").Build(); - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetPrevChapterIdAsync(1, 2, 2, 1); - var actualChapter = await _unitOfWork.ChapterRepository.GetChapterAsync(nextChapter, ChapterIncludes.Volumes); + var actualChapter = await UnitOfWork.ChapterRepository.GetChapterAsync(nextChapter, ChapterIncludes.Volumes); Assert.Equal(1, actualChapter.Volume.MinNumber); } @@ -1431,15 +1390,15 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1464,14 +1423,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1503,14 +1462,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1548,14 +1507,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1583,7 +1542,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 2 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1627,14 +1586,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1656,7 +1615,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 3 // Volume 2 id }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1682,14 +1641,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1715,14 +1674,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1750,7 +1709,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 2 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1777,15 +1736,15 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1815,22 +1774,22 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); var user = new AppUser() { UserName = "majora2007" }; - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Mark everything but chapter 101 as read await _readerService.MarkSeriesAsRead(user, 1); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); // Unmark last chapter as read - var vol = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(1); + var vol = await UnitOfWork.VolumeRepository.GetVolumeByIdAsync(1); foreach (var chapt in vol.Chapters) { await _readerService.SaveReadingProgress(new ProgressDto() @@ -1841,7 +1800,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 1 }, 1); } - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1869,22 +1828,22 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); var user = new AppUser() { UserName = "majora2007" }; - _context.AppUser.Add(user); + Context.AppUser.Add(user); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Mark everything but chapter 101 as read await _readerService.MarkSeriesAsRead(user, 1); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); // Unmark last chapter as read - var vol = await _unitOfWork.VolumeRepository.GetVolumeByIdAsync(1); + var vol = await UnitOfWork.VolumeRepository.GetVolumeByIdAsync(1); await _readerService.SaveReadingProgress(new ProgressDto() { PageNum = 0, @@ -1899,7 +1858,7 @@ public class ReaderServiceTests: AbstractFsTest SeriesId = 1, VolumeId = 1 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1922,14 +1881,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -1956,7 +1915,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 2 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -1982,21 +1941,21 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Save progress on first volume chapters and 1st of second volume - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); await _readerService.MarkSeriesAsRead(user, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2020,14 +1979,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); @@ -2054,7 +2013,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 1 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2083,20 +2042,20 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); await _readerService.MarkSeriesAsRead(user, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Add 2 new unread series to the Series series.Volumes[0].Chapters.Add(new ChapterBuilder("231") @@ -2105,8 +2064,8 @@ public class ReaderServiceTests: AbstractFsTest series.Volumes[2].Chapters.Add(new ChapterBuilder("14.9") .WithPages(1) .Build()); - _context.Series.Attach(series); - await _context.SaveChangesAsync(); + Context.Series.Attach(series); + await Context.SaveChangesAsync(); // This tests that if you add a series later to a volume and a loose leaf chapter, we continue from that volume, rather than loose leaf var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2146,26 +2105,26 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Save progress on first volume chapters and 1st of second volume - var user = await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress); await _readerService.MarkChaptersAsRead(user, 1, new List() { readChapter1, readChapter2 }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2202,14 +2161,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); await _readerService.SaveReadingProgress(new ProgressDto() { @@ -2267,7 +2226,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 2 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2296,14 +2255,14 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); await _readerService.SaveReadingProgress(new ProgressDto() { @@ -2313,7 +2272,7 @@ public class ReaderServiceTests: AbstractFsTest VolumeId = 1 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2328,7 +2287,7 @@ public class ReaderServiceTests: AbstractFsTest SeriesId = 1, VolumeId = 1 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2343,7 +2302,7 @@ public class ReaderServiceTests: AbstractFsTest SeriesId = 1, VolumeId = 1 }, 1); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); nextChapter = await _readerService.GetContinuePoint(1, 1); @@ -2373,26 +2332,26 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); await _readerService.MarkChaptersUntilAsRead(user, 1, 5); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Validate correct chapters have read status - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1)).PagesRead); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(4, 1))); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1)).PagesRead); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(4, 1))); } [Fact] @@ -2413,27 +2372,27 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); await _readerService.MarkChaptersUntilAsRead(user, 1, 2.5f); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Validate correct chapters have read status - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1)).PagesRead); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(4, 1))); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1))); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1)).PagesRead); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(4, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1))); } [Fact] @@ -2451,23 +2410,24 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + Assert.NotNull(user); await _readerService.MarkChaptersUntilAsRead(user, 1, 2); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Validate correct chapters have read status - Assert.True(await _unitOfWork.AppUserProgressRepository.UserHasProgress(LibraryType.Manga, 1)); + Assert.True(await UnitOfWork.AppUserProgressRepository.UserHasProgress(LibraryType.Manga, 1)); } [Fact] @@ -2502,24 +2462,24 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); const int markReadUntilNumber = 47; await _readerService.MarkChaptersUntilAsRead(user, 1, markReadUntilNumber); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var volumes = await _unitOfWork.VolumeRepository.GetVolumesDtoAsync(1, 1); + var volumes = await UnitOfWork.VolumeRepository.GetVolumesDtoAsync(1, 1); Assert.True(volumes.SelectMany(v => v.Chapters).All(c => { // Specials are ignored. @@ -2556,21 +2516,21 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - await _readerService.MarkSeriesAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); - await _context.SaveChangesAsync(); + await _readerService.MarkSeriesAsRead(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); + await Context.SaveChangesAsync(); - Assert.Equal(4, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); + Assert.Equal(4, (await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); } @@ -2591,27 +2551,27 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var volumes = (await _unitOfWork.VolumeRepository.GetVolumes(1)).ToList(); - await _readerService.MarkChaptersAsRead(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes.First().Chapters); + var volumes = (await UnitOfWork.VolumeRepository.GetVolumes(1)).ToList(); + await _readerService.MarkChaptersAsRead(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1, volumes[0].Chapters); - await _context.SaveChangesAsync(); - Assert.Equal(2, (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); + await Context.SaveChangesAsync(); + Assert.Equal(2, (await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses.Count); - await _readerService.MarkSeriesAsUnread(await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); - await _context.SaveChangesAsync(); + await _readerService.MarkSeriesAsUnread(await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress), 1); + await Context.SaveChangesAsync(); - var progresses = (await _unitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses; + var progresses = (await UnitOfWork.UserRepository.GetUserByIdAsync(1, AppUserIncludes.Progress)).Progresses; Assert.Equal(0, progresses.Max(p => p.PagesRead)); Assert.Equal(2, progresses.Count); } @@ -2679,31 +2639,32 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); await _readerService.MarkVolumesUntilAsRead(user, 1, 2002); - await _context.SaveChangesAsync(); + Assert.NotNull(user); + await Context.SaveChangesAsync(); // Validate loose leaf chapters don't get marked as read - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1))); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1))); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); // Validate that volumes 1997 and 2002 both have their respective chapter 0 marked as read - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(6, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1)).PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(6, 1)).PagesRead); // Validate that the chapter 0 of the following volume (2003) is not read - Assert.Null(await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(7, 1)); + Assert.Null(await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(7, 1)); } @@ -2734,30 +2695,31 @@ public class ReaderServiceTests: AbstractFsTest .Build(); series.Library = new LibraryBuilder("Test LIb", LibraryType.Manga).Build(); - _context.Series.Add(series); + Context.Series.Add(series); - _context.AppUser.Add(new AppUser() + Context.AppUser.Add(new AppUser() { UserName = "majora2007" }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + var user = await UnitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Progress); + Assert.NotNull(user); await _readerService.MarkVolumesUntilAsRead(user, 1, 2002); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Validate loose leaf chapters don't get marked as read - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1))); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1))); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(1, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(2, 1))); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); // Validate volumes chapter 0 have read status - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1)).PagesRead); - Assert.Equal(1, (await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(6, 1)).PagesRead); - Assert.Null((await _unitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(5, 1))?.PagesRead); + Assert.Equal(1, (await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(6, 1))?.PagesRead); + Assert.Null((await UnitOfWork.AppUserProgressRepository.GetUserProgressAsync(3, 1))); } #endregion diff --git a/API.Tests/Services/ScannerServiceTests.cs b/API.Tests/Services/ScannerServiceTests.cs index 4554820fb..2e812647b 100644 --- a/API.Tests/Services/ScannerServiceTests.cs +++ b/API.Tests/Services/ScannerServiceTests.cs @@ -28,13 +28,13 @@ public class ScannerServiceTests : AbstractDbTest // Set up Hangfire to use in-memory storage for testing GlobalConfiguration.Configuration.UseInMemoryStorage(); - _scannerHelper = new ScannerHelper(_unitOfWork, testOutputHelper); + _scannerHelper = new ScannerHelper(UnitOfWork, testOutputHelper); } protected override async Task ResetDb() { - _context.Library.RemoveRange(_context.Library); - await _context.SaveChangesAsync(); + Context.Library.RemoveRange(Context.Library); + await Context.SaveChangesAsync(); } @@ -44,18 +44,18 @@ public class ScannerServiceTests : AbstractDbTest { await SetLastScannedInThePast(series, duration, false); } - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } protected async Task SetLastScannedInThePast(Series series, TimeSpan? duration = null, bool save = true) { duration ??= TimeSpan.FromMinutes(2); series.LastFolderScanned = DateTime.Now.Subtract(duration.Value); - _context.Series.Update(series); + Context.Series.Update(series); if (save) { - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } } @@ -66,7 +66,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(4, postLib.Series.Count); @@ -79,7 +79,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -94,7 +94,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -110,7 +110,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -125,7 +125,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -141,7 +141,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -157,7 +157,7 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -188,7 +188,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -213,7 +213,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -244,7 +244,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -268,7 +268,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -288,7 +288,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -326,7 +326,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -350,7 +350,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -369,7 +369,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -399,7 +399,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -436,7 +436,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -464,7 +464,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -484,13 +484,13 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**/Extra/*"}]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -508,13 +508,13 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); library.LibraryExcludePatterns = [new LibraryExcludePattern() {Pattern = "**\\Extra\\*"}]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -541,13 +541,13 @@ public class ScannerServiceTests : AbstractDbTest new FolderPath() {Path = Path.Join(testDirectoryPath, "Root 2")} ]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(2, postLib.Series.Count); @@ -563,7 +563,7 @@ public class ScannerServiceTests : AbstractDbTest // Rescan to ensure nothing changes yet again await scanner.ScanLibrary(library.Id, true); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.Equal(2, postLib.Series.Count); s = postLib.Series.First(s => s.Name == "Plush"); Assert.Equal(3, s.Volumes.Count); @@ -594,13 +594,13 @@ public class ScannerServiceTests : AbstractDbTest new FolderPath() {Path = Path.Join(testDirectoryPath, "Root 2")} ]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(2, postLib.Series.Count); @@ -619,7 +619,7 @@ public class ScannerServiceTests : AbstractDbTest // Rescan to ensure nothing changes yet again await scanner.ScanLibrary(library.Id, false); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.Equal(2, postLib.Series.Count); s = postLib.Series.First(s => s.Name == "Plush"); Assert.Equal(3, s.Volumes.Count); @@ -647,14 +647,14 @@ public class ScannerServiceTests : AbstractDbTest new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 2") } ]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); // First Scan: Everything should be added await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Contains(postLib.Series, s => s.Name == "Accel"); @@ -662,19 +662,19 @@ public class ScannerServiceTests : AbstractDbTest // Second Scan: Remove Root 2, expect Accel to be removed library.Folders = [new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 1") }]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); // Emulate time passage by updating lastFolderScan to be a min in the past foreach (var s in postLib.Series) { s.LastFolderScanned = DateTime.Now.Subtract(TimeSpan.FromMinutes(1)); - _context.Series.Update(s); + Context.Series.Update(s); } - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.DoesNotContain(postLib.Series, s => s.Name == "Accel"); // Ensure Accel is gone Assert.Contains(postLib.Series, s => s.Name == "Plush"); @@ -685,19 +685,19 @@ public class ScannerServiceTests : AbstractDbTest new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 1") }, new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 2") } ]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); // Emulate time passage by updating lastFolderScan to be a min in the past foreach (var s in postLib.Series) { s.LastFolderScanned = DateTime.Now.Subtract(TimeSpan.FromMinutes(1)); - _context.Series.Update(s); + Context.Series.Update(s); } - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.Contains(postLib.Series, s => s.Name == "Accel"); // Accel should be back Assert.Contains(postLib.Series, s => s.Name == "Plush"); @@ -707,7 +707,7 @@ public class ScannerServiceTests : AbstractDbTest // Fourth Scan: Run again to check stability (should not remove Accel) await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.Contains(postLib.Series, s => s.Name == "Accel"); Assert.Contains(postLib.Series, s => s.Name == "Plush"); @@ -732,14 +732,14 @@ public class ScannerServiceTests : AbstractDbTest new FolderPath() { Path = Path.Combine(testDirectoryPath, "Root 2") } ]; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); // First Scan: Everything should be added await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Contains(postLib.Series, s => s.Name == "Accel"); @@ -747,14 +747,14 @@ public class ScannerServiceTests : AbstractDbTest // Second Scan: Delete the Series library.Series = []; - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Empty(postLib.Series); await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.Contains(postLib.Series, s => s.Name == "Accel"); // Ensure Accel is gone Assert.Contains(postLib.Series, s => s.Name == "Plush"); @@ -768,13 +768,13 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(4, postLib.Series.Count); @@ -800,9 +800,9 @@ public class ScannerServiceTests : AbstractDbTest Path.Join(executionerCopyDir, "The Executioner and Her Way of Life Vol. 1 Ch. 0002.cbz")); await scanner.ScanLibrary(library.Id); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(4, postLib.Series.Count); @@ -827,13 +827,13 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Equal(2, postLib.Series.Count); @@ -841,9 +841,9 @@ public class ScannerServiceTests : AbstractDbTest Directory.Delete(executionerCopyDir, true); await scanner.ScanLibrary(library.Id); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); Assert.Single(postLib.Series, s => s.Name == "Spice and Wolf"); @@ -859,13 +859,13 @@ public class ScannerServiceTests : AbstractDbTest var library = await _scannerHelper.GenerateScannerData(testcase, infos); var testDirectoryPath = library.Folders.First().Path; - _unitOfWork.LibraryRepository.Update(library); - await _unitOfWork.CommitAsync(); + UnitOfWork.LibraryRepository.Update(library); + await UnitOfWork.CommitAsync(); var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -882,7 +882,7 @@ public class ScannerServiceTests : AbstractDbTest await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -899,7 +899,7 @@ public class ScannerServiceTests : AbstractDbTest await scanner.ScanLibrary(library.Id); - postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); Assert.Single(postLib.Series); @@ -923,7 +923,7 @@ public class ScannerServiceTests : AbstractDbTest var scanner = _scannerHelper.CreateServices(); await scanner.ScanLibrary(library.Id); - var postLib = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); + var postLib = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(library.Id, LibraryIncludes.Series); Assert.NotNull(postLib); // Get the loose leaf volume and confirm each chapter aligns with expectation of Sort Order diff --git a/API.Tests/Services/ScrobblingServiceTests.cs b/API.Tests/Services/ScrobblingServiceTests.cs index b7a418d83..50398a146 100644 --- a/API.Tests/Services/ScrobblingServiceTests.cs +++ b/API.Tests/Services/ScrobblingServiceTests.cs @@ -28,17 +28,17 @@ public class ScrobblingServiceTests : AbstractDbTest _logger = Substitute.For>(); _emailService = Substitute.For(); - _service = new ScrobblingService(_unitOfWork, Substitute.For(), _logger, _licenseService, _localizationService, _emailService); + _service = new ScrobblingService(UnitOfWork, Substitute.For(), _logger, _licenseService, _localizationService, _emailService); } protected override async Task ResetDb() { - _context.ScrobbleEvent.RemoveRange(_context.ScrobbleEvent.ToList()); - _context.Series.RemoveRange(_context.Series.ToList()); - _context.Library.RemoveRange(_context.Library.ToList()); - _context.AppUser.RemoveRange(_context.AppUser.ToList()); + Context.ScrobbleEvent.RemoveRange(Context.ScrobbleEvent.ToList()); + Context.Series.RemoveRange(Context.Series.ToList()); + Context.Library.RemoveRange(Context.Library.ToList()); + Context.AppUser.RemoveRange(Context.AppUser.ToList()); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } private async Task SeedData() @@ -54,7 +54,7 @@ public class ScrobblingServiceTests : AbstractDbTest .Build(); - _context.Library.Add(library); + Context.Library.Add(library); var user = new AppUserBuilder("testuser", "testuser") //.WithPreferences(new UserPreferencesBuilder().WithAniListScrobblingEnabled(true).Build()) @@ -62,9 +62,9 @@ public class ScrobblingServiceTests : AbstractDbTest user.UserPreferences.AniListScrobblingEnabled = true; - _unitOfWork.UserRepository.Add(user); + UnitOfWork.UserRepository.Add(user); - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } #region ScrobbleWantToReadUpdate Tests @@ -83,7 +83,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, true); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.Equal(ScrobbleEventType.AddWantToRead, events[0].ScrobbleEventType); Assert.Equal(userId, events[0].AppUserId); @@ -103,7 +103,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, false); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.Equal(ScrobbleEventType.RemoveWantToRead, events[0].ScrobbleEventType); Assert.Equal(userId, events[0].AppUserId); @@ -126,7 +126,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, true); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.All(events, e => Assert.Equal(ScrobbleEventType.AddWantToRead, e.ScrobbleEventType)); @@ -149,7 +149,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, false); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.Contains(events, e => e.ScrobbleEventType == ScrobbleEventType.RemoveWantToRead); @@ -172,7 +172,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, false); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.All(events, e => Assert.Equal(ScrobbleEventType.RemoveWantToRead, e.ScrobbleEventType)); @@ -195,7 +195,7 @@ public class ScrobblingServiceTests : AbstractDbTest await _service.ScrobbleWantToReadUpdate(userId, seriesId, true); // Assert - var events = await _unitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); + var events = await UnitOfWork.ScrobbleRepository.GetAllEventsForSeries(seriesId); Assert.Single(events); Assert.Contains(events, e => e.ScrobbleEventType == ScrobbleEventType.AddWantToRead); diff --git a/API.Tests/Services/SeriesServiceTests.cs b/API.Tests/Services/SeriesServiceTests.cs index 5696bb76b..4bf0e6782 100644 --- a/API.Tests/Services/SeriesServiceTests.cs +++ b/API.Tests/Services/SeriesServiceTests.cs @@ -56,7 +56,7 @@ public class SeriesServiceTests : AbstractDbTest var locService = new LocalizationService(ds, new MockHostingEnvironment(), Substitute.For(), Substitute.For()); - _seriesService = new SeriesService(_unitOfWork, Substitute.For(), + _seriesService = new SeriesService(UnitOfWork, Substitute.For(), Substitute.For(), Substitute.For>(), Substitute.For(), locService, Substitute.For()); } @@ -65,14 +65,14 @@ public class SeriesServiceTests : AbstractDbTest protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series.ToList()); - _context.AppUserRating.RemoveRange(_context.AppUserRating.ToList()); - _context.Genre.RemoveRange(_context.Genre.ToList()); - _context.CollectionTag.RemoveRange(_context.CollectionTag.ToList()); - _context.Person.RemoveRange(_context.Person.ToList()); - _context.Library.RemoveRange(_context.Library.ToList()); + Context.Series.RemoveRange(Context.Series.ToList()); + Context.AppUserRating.RemoveRange(Context.AppUserRating.ToList()); + Context.Genre.RemoveRange(Context.Genre.ToList()); + Context.CollectionTag.RemoveRange(Context.CollectionTag.ToList()); + Context.Person.RemoveRange(Context.Person.ToList()); + Context.Library.RemoveRange(Context.Library.ToList()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } private static UpdateRelatedSeriesDto CreateRelationsDto(Series series) @@ -105,7 +105,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -126,7 +126,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var expectedRanges = new[] {"Omake", "Something SP02"}; @@ -141,7 +141,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -162,7 +162,7 @@ public class SeriesServiceTests : AbstractDbTest .Build() ); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.NotEmpty(detail.Chapters); @@ -178,7 +178,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -196,7 +196,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.NotEmpty(detail.Chapters); @@ -212,7 +212,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") .WithVolume(new VolumeBuilder(Parser.LooseLeafVolume) @@ -229,7 +229,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.NotEmpty(detail.Chapters); @@ -248,7 +248,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -263,7 +263,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.NotEmpty(detail.Volumes); @@ -277,7 +277,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -294,7 +294,7 @@ public class SeriesServiceTests : AbstractDbTest - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.NotEmpty(detail.Volumes); @@ -314,7 +314,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -332,7 +332,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); Assert.Equal("Volume 1", detail.Volumes.ElementAt(0).Name); @@ -349,7 +349,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -373,7 +373,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); @@ -400,7 +400,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Comic) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Comic) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -424,7 +424,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); @@ -450,7 +450,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.ComicVine) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.ComicVine) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -474,7 +474,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); @@ -500,7 +500,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -522,7 +522,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); @@ -548,7 +548,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.LightNovel) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.LightNovel) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -570,7 +570,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()) .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var detail = await _seriesService.GetSeriesDetail(1, 1); @@ -590,164 +590,6 @@ public class SeriesServiceTests : AbstractDbTest - #endregion - - - #region UpdateRating - - [Fact] - public async Task UpdateRating_ShouldSetRating() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - - await _context.SaveChangesAsync(); - - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - JobStorage.Current = new InMemoryStorage(); - var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto - { - SeriesId = 1, - UserRating = 3, - }); - - Assert.True(result); - - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings))! - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(3, ratings.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldUpdateExistingRating() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto - { - SeriesId = 1, - UserRating = 3, - }); - - Assert.True(result); - - JobStorage.Current = new InMemoryStorage(); - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(3, ratings.First().Rating); - - // Update the DB again - - var result2 = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto - { - SeriesId = 1, - UserRating = 5, - }); - - Assert.True(result2); - - var ratings2 = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings)) - .Ratings; - Assert.NotEmpty(ratings2); - Assert.True(ratings2.Count == 1); - Assert.Equal(5, ratings2.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldClampRatingAt5() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb") - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto - { - SeriesId = 1, - UserRating = 10, - }); - - Assert.True(result); - - JobStorage.Current = new InMemoryStorage(); - var ratings = (await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", - AppUserIncludes.Ratings)!) - .Ratings; - Assert.NotEmpty(ratings); - Assert.Equal(5, ratings.First().Rating); - } - - [Fact] - public async Task UpdateRating_ShouldReturnFalseWhenSeriesDoesntExist() - { - await ResetDb(); - - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) - .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) - .WithSeries(new SeriesBuilder("Test") - - .WithVolume(new VolumeBuilder("1") - .WithChapter(new ChapterBuilder("1").WithPages(1).Build()) - .Build()) - .Build()) - .Build()); - - await _context.SaveChangesAsync(); - - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync("majora2007", AppUserIncludes.Ratings); - - var result = await _seriesService.UpdateRating(user, new UpdateSeriesRatingDto - { - SeriesId = 2, - UserRating = 5, - }); - - Assert.False(result); - - var ratings = user.Ratings; - Assert.Empty(ratings); - } - #endregion #region UpdateSeriesMetadata @@ -760,8 +602,8 @@ public class SeriesServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); - _context.Series.Add(s); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -775,7 +617,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Contains("New Genre".SentenceCase(), series.Metadata.Genres.Select(g => g.Title)); @@ -792,10 +634,10 @@ public class SeriesServiceTests : AbstractDbTest var g = new GenreBuilder("Existing Genre").Build(); s.Metadata.Genres = new List {g}; - _context.Series.Add(s); + Context.Series.Add(s); - _context.Genre.Add(g); - await _context.SaveChangesAsync(); + Context.Genre.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -809,7 +651,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.True(series.Metadata.Genres.Select(g1 => g1.Title).All(g2 => g2 == "New Genre".SentenceCase())); @@ -821,7 +663,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); var g = new PersonBuilder("Existing Person").Build(); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var s = new SeriesBuilder("Test") .WithMetadata(new SeriesMetadataBuilder() @@ -831,10 +673,10 @@ public class SeriesServiceTests : AbstractDbTest s.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); - _context.Series.Add(s); + Context.Series.Add(s); - _context.Person.Add(g); - await _context.SaveChangesAsync(); + Context.Person.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -848,7 +690,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.True(series.Metadata.People.Select(g => g.Person.Name).All(personName => personName == "Existing Person")); @@ -871,10 +713,10 @@ public class SeriesServiceTests : AbstractDbTest new SeriesMetadataPeople() {Person = new PersonBuilder("Existing Publisher 2").Build(), Role = PersonRole.Publisher} }; - _context.Series.Add(s); + Context.Series.Add(s); - _context.Person.Add(g); - await _context.SaveChangesAsync(); + Context.Person.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -889,7 +731,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.True(series.Metadata.People.Select(g => g.Person.Name).All(personName => personName == "Existing Person")); @@ -921,9 +763,9 @@ public class SeriesServiceTests : AbstractDbTest new SeriesMetadataPeople { Person = new PersonBuilder("Existing Publisher 2").Build(), Role = PersonRole.Publisher } }; - _context.Series.Add(series); - _context.Person.Add(existingPerson); - await _context.SaveChangesAsync(); + Context.Series.Add(series); + Context.Person.Add(existingPerson); + await Context.SaveChangesAsync(); // Act: Update series metadata, attempting to update the writer to "Existing Writer" var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto @@ -940,7 +782,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); // Reload the series from the database - var updatedSeries = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(series.Id); + var updatedSeries = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(series.Id); Assert.NotNull(updatedSeries.Metadata); // Assert that the people list still contains the updated person with the new name @@ -962,10 +804,10 @@ public class SeriesServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); var g = new PersonBuilder("Existing Person").Build(); - _context.Series.Add(s); + Context.Series.Add(s); - _context.Person.Add(g); - await _context.SaveChangesAsync(); + Context.Person.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -979,7 +821,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.False(series.Metadata.People.Any()); @@ -997,10 +839,10 @@ public class SeriesServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); var g = new PersonBuilder("Existing Person").Build(); - _context.Series.Add(s); + Context.Series.Add(s); - _context.Person.Add(g); - await _context.SaveChangesAsync(); + Context.Person.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1015,7 +857,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.True(series.Metadata.People.Count != 0); @@ -1049,10 +891,10 @@ public class SeriesServiceTests : AbstractDbTest var g = new GenreBuilder("Existing Genre").Build(); s.Metadata.Genres = new List {g}; s.Metadata.GenresLocked = true; - _context.Series.Add(s); + Context.Series.Add(s); - _context.Genre.Add(g); - await _context.SaveChangesAsync(); + Context.Genre.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1067,7 +909,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.True(series.Metadata.Genres.Select(g => g.Title).All(g => g == "Existing Genre".SentenceCase())); @@ -1082,8 +924,8 @@ public class SeriesServiceTests : AbstractDbTest .WithMetadata(new SeriesMetadataBuilder().Build()) .Build(); s.Library = new LibraryBuilder("Test LIb", LibraryType.Book).Build(); - _context.Series.Add(s); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1097,7 +939,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Equal(0, series.Metadata.ReleaseYear); @@ -1116,8 +958,8 @@ public class SeriesServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test Lib", LibraryType.Book).Build(); - _context.Series.Add(s); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1130,7 +972,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Contains("New Genre".SentenceCase(), series.Metadata.Genres.Select(g => g.Title)); @@ -1149,9 +991,9 @@ public class SeriesServiceTests : AbstractDbTest var g = new GenreBuilder("Existing Genre").Build(); s.Metadata.Genres = new List { g }; - _context.Series.Add(s); - _context.Genre.Add(g); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + Context.Genre.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1164,7 +1006,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.DoesNotContain("Existing Genre".SentenceCase(), series.Metadata.Genres.Select(g => g.Title)); @@ -1183,9 +1025,9 @@ public class SeriesServiceTests : AbstractDbTest var g = new GenreBuilder("Existing Genre").Build(); s.Metadata.Genres = new List { g }; - _context.Series.Add(s); - _context.Genre.Add(g); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + Context.Genre.Add(g); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1198,7 +1040,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Empty(series.Metadata.Genres); @@ -1216,8 +1058,8 @@ public class SeriesServiceTests : AbstractDbTest .Build(); s.Library = new LibraryBuilder("Test Lib", LibraryType.Book).Build(); - _context.Series.Add(s); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1230,7 +1072,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Contains("New Tag".SentenceCase(), series.Metadata.Tags.Select(t => t.Title)); @@ -1248,9 +1090,9 @@ public class SeriesServiceTests : AbstractDbTest var t = new TagBuilder("Existing Tag").Build(); s.Metadata.Tags = new List { t }; - _context.Series.Add(s); - _context.Tag.Add(t); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + Context.Tag.Add(t); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1263,7 +1105,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.DoesNotContain("Existing Tag".SentenceCase(), series.Metadata.Tags.Select(t => t.Title)); @@ -1282,9 +1124,9 @@ public class SeriesServiceTests : AbstractDbTest var t = new TagBuilder("Existing Tag").Build(); s.Metadata.Tags = new List { t }; - _context.Series.Add(s); - _context.Tag.Add(t); - await _context.SaveChangesAsync(); + Context.Series.Add(s); + Context.Tag.Add(t); + await Context.SaveChangesAsync(); var success = await _seriesService.UpdateSeriesMetadata(new UpdateSeriesMetadataDto { @@ -1297,7 +1139,7 @@ public class SeriesServiceTests : AbstractDbTest Assert.True(success); - var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); + var series = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(s.Id); Assert.NotNull(series); Assert.NotNull(series.Metadata); Assert.Empty(series.Metadata.Tags); @@ -1434,7 +1276,7 @@ public class SeriesServiceTests : AbstractDbTest public async Task UpdateRelatedSeries_ShouldAddAllRelations() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1453,9 +1295,9 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(2); @@ -1470,7 +1312,7 @@ public class SeriesServiceTests : AbstractDbTest public async Task UpdateRelatedSeries_ShouldAddPrequelWhenAddingSequel() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1488,10 +1330,10 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); - var series2 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series2 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Sequels.Add(2); @@ -1506,7 +1348,7 @@ public class SeriesServiceTests : AbstractDbTest public async Task UpdateRelatedSeries_DeleteAllRelations() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1525,9 +1367,9 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(2); @@ -1550,7 +1392,7 @@ public class SeriesServiceTests : AbstractDbTest public async Task UpdateRelatedSeries_DeleteTargetSeries_ShouldSucceed() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1568,9 +1410,9 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(2); @@ -1579,10 +1421,10 @@ public class SeriesServiceTests : AbstractDbTest Assert.NotNull(series1); Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId); - _context.Series.Remove(await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2)); + Context.Series.Remove(await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2)); try { - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } catch (Exception) { @@ -1590,14 +1432,14 @@ public class SeriesServiceTests : AbstractDbTest } // Remove relations - Assert.Empty((await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related)).Relations); + Assert.Empty((await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related)).Relations); } [Fact] public async Task UpdateRelatedSeries_DeleteSourceSeries_ShouldSucceed() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1615,9 +1457,9 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(2); @@ -1625,12 +1467,12 @@ public class SeriesServiceTests : AbstractDbTest Assert.NotNull(series1); Assert.Equal(2, series1.Relations.Single(s => s.TargetSeriesId == 2).TargetSeriesId); - var seriesToRemove = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1); + var seriesToRemove = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1); Assert.NotNull(seriesToRemove); - _context.Series.Remove(seriesToRemove); + Context.Series.Remove(seriesToRemove); try { - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } catch (Exception) { @@ -1638,14 +1480,14 @@ public class SeriesServiceTests : AbstractDbTest } // Remove relations - Assert.Empty((await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Related)).Relations); + Assert.Empty((await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2, SeriesIncludes.Related)).Relations); } [Fact] public async Task UpdateRelatedSeries_ShouldNotAllowDuplicates() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1663,9 +1505,9 @@ public class SeriesServiceTests : AbstractDbTest } }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); var relation = new SeriesRelation { Series = series1, @@ -1690,7 +1532,7 @@ public class SeriesServiceTests : AbstractDbTest public async Task GetRelatedSeries_EditionPrequelSequel_ShouldNotHaveParent() { await ResetDb(); - _context.Library.Add(new Library + Context.Library.Add(new Library { AppUsers = new List { @@ -1710,8 +1552,8 @@ public class SeriesServiceTests : AbstractDbTest new SeriesBuilder("Test Series Adaption").Build(), } }); - await _context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + await Context.SaveChangesAsync(); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Editions.Add(2); @@ -1737,30 +1579,30 @@ public class SeriesServiceTests : AbstractDbTest .WithSeries(new SeriesBuilder("Test Series Sequels").Build()) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .Build(); - _context.Library.Add(lib); + Context.Library.Add(lib); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(2); addRelationDto.Sequels.Add(3); await _seriesService.UpdateRelatedSeries(addRelationDto); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(lib.Id); - _unitOfWork.LibraryRepository.Delete(library); + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(lib.Id); + UnitOfWork.LibraryRepository.Delete(library); try { - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } catch (Exception) { Assert.False(true); } - Assert.Null(await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1)); + Assert.Null(await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1)); } [Fact] @@ -1781,7 +1623,7 @@ public class SeriesServiceTests : AbstractDbTest .WithSeries(new SeriesBuilder("Test Series Sequels").Build()) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .Build(); - _context.Library.Add(lib1); + Context.Library.Add(lib1); var lib2 = new LibraryBuilder("Test LIb 2", LibraryType.Book) .WithSeries(new SeriesBuilder("Test Series 2").Build()) @@ -1789,29 +1631,29 @@ public class SeriesServiceTests : AbstractDbTest .WithSeries(new SeriesBuilder("Test Series Prequels 3").Build())// TODO: Is this a bug .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .Build(); - _context.Library.Add(lib2); + Context.Library.Add(lib2); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related); // Add relations var addRelationDto = CreateRelationsDto(series1); addRelationDto.Adaptations.Add(4); // cross library link await _seriesService.UpdateRelatedSeries(addRelationDto); - var library = await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(lib1.Id, LibraryIncludes.Series); - _unitOfWork.LibraryRepository.Delete(library); + var library = await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(lib1.Id, LibraryIncludes.Series); + UnitOfWork.LibraryRepository.Delete(library); try { - await _unitOfWork.CommitAsync(); + await UnitOfWork.CommitAsync(); } catch (Exception) { Assert.False(true); } - Assert.Null(await _unitOfWork.LibraryRepository.GetLibraryForIdAsync(1)); + Assert.Null(await UnitOfWork.LibraryRepository.GetLibraryForIdAsync(1)); } #endregion @@ -1833,13 +1675,13 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty) .WithLocale("en") .Build()) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); Assert.Equal(expected, await _seriesService.FormatChapterName(1, libraryType, withHash)); } @@ -2004,18 +1846,18 @@ public class SeriesServiceTests : AbstractDbTest .WithSeries(new SeriesBuilder("Test Series Sequels").Build()) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .Build(); - _context.Library.Add(lib1); + Context.Library.Add(lib1); var lib2 = new LibraryBuilder("Test LIb 2", LibraryType.Book) .WithSeries(new SeriesBuilder("Test Series 2").Build()) .WithSeries(new SeriesBuilder("Test Series Prequels 2").Build()) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .Build(); - _context.Library.Add(lib2); + Context.Library.Add(lib2); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var series1 = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1, + var series1 = await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1, SeriesIncludes.Related | SeriesIncludes.ExternalRatings); // Add relations var addRelationDto = CreateRelationsDto(series1); @@ -2061,12 +1903,12 @@ public class SeriesServiceTests : AbstractDbTest } }; - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); // Ensure we can delete the series Assert.True(await _seriesService.DeleteMultipleSeries(new[] {1, 2})); - Assert.Null(await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(1)); - Assert.Null(await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(2)); + Assert.Null(await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(1)); + Assert.Null(await UnitOfWork.SeriesRepository.GetSeriesByIdAsync(2)); } #endregion @@ -2078,7 +1920,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -2091,7 +1933,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _seriesService.GetEstimatedChapterCreationDate(1, 1); Assert.Equal(Parser.LooseLeafVolumeNumber, nextChapter.VolumeNumber); @@ -2103,7 +1945,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") .WithPublicationStatus(PublicationStatus.Completed) @@ -2116,7 +1958,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _seriesService.GetEstimatedChapterCreationDate(1, 1); Assert.Equal(Parser.LooseLeafVolumeNumber, nextChapter.VolumeNumber); @@ -2128,7 +1970,7 @@ public class SeriesServiceTests : AbstractDbTest { await ResetDb(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") @@ -2140,7 +1982,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _seriesService.GetEstimatedChapterCreationDate(1, 1); Assert.NotNull(nextChapter); @@ -2154,7 +1996,7 @@ public class SeriesServiceTests : AbstractDbTest await ResetDb(); var now = DateTime.Parse("2021-01-01", CultureInfo.InvariantCulture); // 10/31/2024 can trigger an edge case bug - _context.Library.Add(new LibraryBuilder("Test LIb") + Context.Library.Add(new LibraryBuilder("Test LIb") .WithAppUser(new AppUserBuilder("majora2007", string.Empty).Build()) .WithSeries(new SeriesBuilder("Test") .WithPublicationStatus(PublicationStatus.OnGoing) @@ -2168,7 +2010,7 @@ public class SeriesServiceTests : AbstractDbTest .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var nextChapter = await _seriesService.GetEstimatedChapterCreationDate(1, 1); Assert.NotNull(nextChapter); diff --git a/API.Tests/Services/SiteThemeServiceTests.cs b/API.Tests/Services/SiteThemeServiceTests.cs index 463d49df4..3893af1fb 100644 --- a/API.Tests/Services/SiteThemeServiceTests.cs +++ b/API.Tests/Services/SiteThemeServiceTests.cs @@ -31,24 +31,24 @@ public abstract class SiteThemeServiceTest : AbstractDbTest protected override async Task ResetDb() { - _context.SiteTheme.RemoveRange(_context.SiteTheme); - await _context.SaveChangesAsync(); + Context.SiteTheme.RemoveRange(Context.SiteTheme); + await Context.SaveChangesAsync(); // Recreate defaults - await Seed.SeedThemes(_context); + await Seed.SeedThemes(Context); } [Fact] public async Task UpdateDefault_ShouldThrowOnInvalidId() { await ResetDb(); - _testOutputHelper.WriteLine($"[UpdateDefault_ShouldThrowOnInvalidId] All Themes: {(await _unitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); + _testOutputHelper.WriteLine($"[UpdateDefault_ShouldThrowOnInvalidId] All Themes: {(await UnitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub, Substitute.For(), + var siteThemeService = new ThemeService(ds, UnitOfWork, _messageHub, Substitute.For(), Substitute.For>(), Substitute.For()); - _context.SiteTheme.Add(new SiteTheme() + Context.SiteTheme.Add(new SiteTheme() { Name = "Custom", NormalizedName = "Custom".ToNormalized(), @@ -56,7 +56,7 @@ public abstract class SiteThemeServiceTest : AbstractDbTest FileName = "custom.css", IsDefault = false }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var ex = await Assert.ThrowsAsync(() => siteThemeService.UpdateDefault(10)); Assert.Equal("Theme file missing or invalid", ex.Message); @@ -68,14 +68,14 @@ public abstract class SiteThemeServiceTest : AbstractDbTest public async Task GetContent_ShouldReturnContent() { await ResetDb(); - _testOutputHelper.WriteLine($"[GetContent_ShouldReturnContent] All Themes: {(await _unitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); + _testOutputHelper.WriteLine($"[GetContent_ShouldReturnContent] All Themes: {(await UnitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub, Substitute.For(), + var siteThemeService = new ThemeService(ds, UnitOfWork, _messageHub, Substitute.For(), Substitute.For>(), Substitute.For()); - _context.SiteTheme.Add(new SiteTheme() + Context.SiteTheme.Add(new SiteTheme() { Name = "Custom", NormalizedName = "Custom".ToNormalized(), @@ -83,9 +83,9 @@ public abstract class SiteThemeServiceTest : AbstractDbTest FileName = "custom.css", IsDefault = false }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var content = await siteThemeService.GetContent((await _unitOfWork.SiteThemeRepository.GetThemeDtoByName("Custom")).Id); + var content = await siteThemeService.GetContent((await UnitOfWork.SiteThemeRepository.GetThemeDtoByName("Custom")).Id); Assert.NotNull(content); Assert.NotEmpty(content); Assert.Equal("123", content); @@ -95,14 +95,14 @@ public abstract class SiteThemeServiceTest : AbstractDbTest public async Task UpdateDefault_ShouldHaveOneDefault() { await ResetDb(); - _testOutputHelper.WriteLine($"[UpdateDefault_ShouldHaveOneDefault] All Themes: {(await _unitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); + _testOutputHelper.WriteLine($"[UpdateDefault_ShouldHaveOneDefault] All Themes: {(await UnitOfWork.SiteThemeRepository.GetThemes()).Count(t => t.IsDefault)}"); var filesystem = CreateFileSystem(); filesystem.AddFile($"{SiteThemeDirectory}custom.css", new MockFileData("123")); var ds = new DirectoryService(Substitute.For>(), filesystem); - var siteThemeService = new ThemeService(ds, _unitOfWork, _messageHub, Substitute.For(), + var siteThemeService = new ThemeService(ds, UnitOfWork, _messageHub, Substitute.For(), Substitute.For>(), Substitute.For()); - _context.SiteTheme.Add(new SiteTheme() + Context.SiteTheme.Add(new SiteTheme() { Name = "Custom", NormalizedName = "Custom".ToNormalized(), @@ -110,16 +110,16 @@ public abstract class SiteThemeServiceTest : AbstractDbTest FileName = "custom.css", IsDefault = false }); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); - var customTheme = (await _unitOfWork.SiteThemeRepository.GetThemeDtoByName("Custom")); + var customTheme = (await UnitOfWork.SiteThemeRepository.GetThemeDtoByName("Custom")); Assert.NotNull(customTheme); await siteThemeService.UpdateDefault(customTheme.Id); - Assert.Equal(customTheme.Id, (await _unitOfWork.SiteThemeRepository.GetDefaultTheme()).Id); + Assert.Equal(customTheme.Id, (await UnitOfWork.SiteThemeRepository.GetDefaultTheme()).Id); } } diff --git a/API.Tests/Services/Test Data/CoverDbService/Existing/01.webp b/API.Tests/Services/Test Data/CoverDbService/Existing/01.webp new file mode 100644 index 000000000..0b46b66d2 Binary files /dev/null and b/API.Tests/Services/Test Data/CoverDbService/Existing/01.webp differ diff --git a/API.Tests/Services/Test Data/CoverDbService/Favicons/anilist.co.webp b/API.Tests/Services/Test Data/CoverDbService/Favicons/anilist.co.webp new file mode 100644 index 000000000..475824863 Binary files /dev/null and b/API.Tests/Services/Test Data/CoverDbService/Favicons/anilist.co.webp differ diff --git a/API.Tests/Services/VersionUpdaterServiceTests.cs b/API.Tests/Services/VersionUpdaterServiceTests.cs index c7a8a14d8..8be8f4aee 100644 --- a/API.Tests/Services/VersionUpdaterServiceTests.cs +++ b/API.Tests/Services/VersionUpdaterServiceTests.cs @@ -16,19 +16,15 @@ namespace API.Tests.Services; public class VersionUpdaterServiceTests : IDisposable { - private readonly ILogger _logger; - private readonly IEventHub _eventHub; - private readonly IDirectoryService _directoryService; + private readonly ILogger _logger = Substitute.For>(); + private readonly IEventHub _eventHub = Substitute.For(); + private readonly IDirectoryService _directoryService = Substitute.For(); private readonly VersionUpdaterService _service; private readonly string _tempPath; private readonly HttpTest _httpTest; public VersionUpdaterServiceTests() { - _logger = Substitute.For>(); - _eventHub = Substitute.For(); - _directoryService = Substitute.For(); - // Create temp directory for cache _tempPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); Directory.CreateDirectory(_tempPath); @@ -55,6 +51,7 @@ public class VersionUpdaterServiceTests : IDisposable // Reset BuildInfo.Version typeof(BuildInfo).GetProperty(nameof(BuildInfo.Version))?.SetValue(null, null); + GC.SuppressFinalize(this); } [Fact] @@ -302,7 +299,7 @@ public class VersionUpdaterServiceTests : IDisposable var result = await _service.GetAllReleases(); - Assert.Equal(1, result.Count); + Assert.Single(result); Assert.Equal("0.7.0.0", result[0].UpdateVersion); Assert.NotEmpty(_httpTest.CallLog); // HTTP call was made } diff --git a/API.Tests/Services/WordCountAnalysisTests.cs b/API.Tests/Services/WordCountAnalysisTests.cs index 8c8c4193c..57c6ec7f6 100644 --- a/API.Tests/Services/WordCountAnalysisTests.cs +++ b/API.Tests/Services/WordCountAnalysisTests.cs @@ -26,9 +26,10 @@ public class WordCountAnalysisTests : AbstractDbTest private const long MinHoursToRead = 1; private const float AvgHoursToRead = 1.66954792f; private const long MaxHoursToRead = 3; - public WordCountAnalysisTests() : base() + + public WordCountAnalysisTests() { - _readerService = new ReaderService(_unitOfWork, Substitute.For>(), + _readerService = new ReaderService(UnitOfWork, Substitute.For>(), Substitute.For(), Substitute.For(), new DirectoryService(Substitute.For>(), new MockFileSystem()), Substitute.For()); @@ -36,9 +37,9 @@ public class WordCountAnalysisTests : AbstractDbTest protected override async Task ResetDb() { - _context.Series.RemoveRange(_context.Series.ToList()); + Context.Series.RemoveRange(Context.Series.ToList()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); } [Fact] @@ -56,7 +57,7 @@ public class WordCountAnalysisTests : AbstractDbTest MangaFormat.Epub).Build()) .Build(); - _context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test LIb", LibraryType.Book) .WithSeries(series) .Build()); @@ -67,11 +68,11 @@ public class WordCountAnalysisTests : AbstractDbTest .Build(), }; - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var cacheService = new CacheHelper(new FileService()); - var service = new WordCountAnalyzerService(Substitute.For>(), _unitOfWork, + var service = new WordCountAnalyzerService(Substitute.For>(), UnitOfWork, Substitute.For(), cacheService, _readerService, Substitute.For()); @@ -83,7 +84,7 @@ public class WordCountAnalysisTests : AbstractDbTest Assert.Equal(MaxHoursToRead, series.MaxHoursToRead); // Validate the Chapter gets updated correctly - var volume = series.Volumes.First(); + var volume = series.Volumes[0]; Assert.Equal(WordCount, volume.WordCount); Assert.Equal(MinHoursToRead, volume.MinHoursToRead); Assert.Equal(AvgHoursToRead, volume.AvgHoursToRead); @@ -114,16 +115,16 @@ public class WordCountAnalysisTests : AbstractDbTest .Build()) .Build(); - _context.Library.Add(new LibraryBuilder("Test", LibraryType.Book) + Context.Library.Add(new LibraryBuilder("Test", LibraryType.Book) .WithSeries(series) .Build()); - await _context.SaveChangesAsync(); + await Context.SaveChangesAsync(); var cacheService = new CacheHelper(new FileService()); - var service = new WordCountAnalyzerService(Substitute.For>(), _unitOfWork, + var service = new WordCountAnalyzerService(Substitute.For>(), UnitOfWork, Substitute.For(), cacheService, _readerService, Substitute.For()); await service.ScanSeries(1, 1); @@ -139,21 +140,21 @@ public class WordCountAnalysisTests : AbstractDbTest .WithChapter(chapter2) .Build()); - series.Volumes.First().Chapters.Add(chapter2); - await _unitOfWork.CommitAsync(); + series.Volumes[0].Chapters.Add(chapter2); + await UnitOfWork.CommitAsync(); await service.ScanSeries(1, 1); Assert.Equal(WordCount * 2L, series.WordCount); Assert.Equal(MinHoursToRead * 2, series.MinHoursToRead); - var firstVolume = series.Volumes.ElementAt(0); + var firstVolume = series.Volumes[0]; Assert.Equal(WordCount, firstVolume.WordCount); Assert.Equal(MinHoursToRead, firstVolume.MinHoursToRead); Assert.True(series.AvgHoursToRead.Is(AvgHoursToRead * 2)); Assert.Equal(MaxHoursToRead, firstVolume.MaxHoursToRead); - var secondVolume = series.Volumes.ElementAt(1); + var secondVolume = series.Volumes[1]; Assert.Equal(WordCount, secondVolume.WordCount); Assert.Equal(MinHoursToRead, secondVolume.MinHoursToRead); Assert.Equal(AvgHoursToRead, secondVolume.AvgHoursToRead); diff --git a/API/Constants/CacheProfiles.cs b/API/Constants/CacheProfiles.cs index ccbbf2479..afc82f19c 100644 --- a/API/Constants/CacheProfiles.cs +++ b/API/Constants/CacheProfiles.cs @@ -8,6 +8,10 @@ public static class EasyCacheProfiles public const string RevokedJwt = "revokedJWT"; public const string Favicon = "favicon"; /// + /// Images for Publishers + /// + public const string Publisher = "publisherImages"; + /// /// If a user's license is valid /// public const string License = "license"; diff --git a/API/Controllers/ChapterController.cs b/API/Controllers/ChapterController.cs index 4110cd907..8de26cf97 100644 --- a/API/Controllers/ChapterController.cs +++ b/API/Controllers/ChapterController.cs @@ -6,6 +6,7 @@ using API.Constants; using API.Data; using API.Data.Repositories; using API.DTOs; +using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; using API.Entities.Person; @@ -14,8 +15,10 @@ using API.Helpers; using API.Services; using API.Services.Tasks.Scanner.Parser; using API.SignalR; +using AutoMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Nager.ArticleNumber; @@ -27,13 +30,16 @@ public class ChapterController : BaseApiController private readonly ILocalizationService _localizationService; private readonly IEventHub _eventHub; private readonly ILogger _logger; + private readonly IMapper _mapper; - public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger logger) + public ChapterController(IUnitOfWork unitOfWork, ILocalizationService localizationService, IEventHub eventHub, ILogger logger, + IMapper mapper) { _unitOfWork = unitOfWork; _localizationService = localizationService; _eventHub = eventHub; _logger = logger; + _mapper = mapper; } /// @@ -62,7 +68,8 @@ public class ChapterController : BaseApiController { if (User.IsInRole(PolicyConstants.ReadOnlyRole)) return BadRequest(await _localizationService.Translate(User.GetUserId(), "permission-denied")); - var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId); + var chapter = await _unitOfWork.ChapterRepository.GetChapterAsync(chapterId, + ChapterIncludes.Files | ChapterIncludes.ExternalReviews | ChapterIncludes.ExternalRatings); if (chapter == null) return BadRequest(_localizationService.Translate(User.GetUserId(), "chapter-doesnt-exist")); @@ -80,6 +87,15 @@ public class ChapterController : BaseApiController _unitOfWork.ChapterRepository.Remove(chapter); } + // If we removed the volume, do an additional check if we need to delete the actual series as well or not + var series = await _unitOfWork.SeriesRepository.GetSeriesByIdAsync(vol.SeriesId, SeriesIncludes.ExternalData | SeriesIncludes.Volumes); + var needToRemoveSeries = needToRemoveVolume && series != null && series.Volumes.Count <= 1; + if (needToRemoveSeries) + { + _unitOfWork.SeriesRepository.Remove(series!); + } + + if (!await _unitOfWork.CommitAsync()) return Ok(false); @@ -89,6 +105,12 @@ public class ChapterController : BaseApiController await _eventHub.SendMessageAsync(MessageFactory.VolumeRemoved, MessageFactory.VolumeRemovedEvent(chapter.VolumeId, vol.SeriesId), false); } + if (needToRemoveSeries) + { + await _eventHub.SendMessageAsync(MessageFactory.SeriesRemoved, + MessageFactory.SeriesRemovedEvent(series!.Id, series.Name, series.LibraryId), false); + } + return Ok(true); } @@ -391,6 +413,39 @@ public class ChapterController : BaseApiController return Ok(); } + /// + /// Returns Ratings and Reviews for an individual Chapter + /// + /// + /// + [HttpGet("chapter-detail-plus")] + public async Task> ChapterDetailPlus([FromQuery] int chapterId) + { + var ret = new ChapterDetailPlusDto(); + var userReviews = (await _unitOfWork.UserRepository.GetUserRatingDtosForChapterAsync(chapterId, User.GetUserId())) + .Where(r => !string.IsNullOrEmpty(r.Body)) + .OrderByDescending(review => review.Username.Equals(User.GetUsername()) ? 1 : 0) + .ToList(); + + var ownRating = await _unitOfWork.UserRepository.GetUserChapterRatingAsync(User.GetUserId(), chapterId); + if (ownRating != null) + { + ret.Rating = ownRating.Rating; + ret.HasBeenRated = ownRating.HasBeenRated; + } + + var externalReviews = await _unitOfWork.ChapterRepository.GetExternalChapterReviewDtos(chapterId); + if (externalReviews.Count > 0) + { + userReviews.AddRange(ReviewHelper.SelectSpectrumOfReviews(externalReviews)); + } + + ret.Reviews = userReviews; + + ret.Ratings = await _unitOfWork.ChapterRepository.GetExternalChapterRatingDtos(chapterId); + + return Ok(ret); + } } diff --git a/API/Controllers/MetadataController.cs b/API/Controllers/MetadataController.cs index 9757186bb..b08ac1f38 100644 --- a/API/Controllers/MetadataController.cs +++ b/API/Controllers/MetadataController.cs @@ -221,7 +221,7 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc return Ok(ret); } - private async Task PrepareSeriesDetail(List userReviews, SeriesDetailPlusDto ret) + private async Task PrepareSeriesDetail(List userReviews, SeriesDetailPlusDto? ret) { var isAdmin = User.IsInRole(PolicyConstants.AdminRole); var user = await unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId())!; @@ -235,12 +235,12 @@ public class MetadataController(IUnitOfWork unitOfWork, ILocalizationService loc ret.Recommendations.OwnedSeries = await unitOfWork.SeriesRepository.GetSeriesDtoByIdsAsync( ret.Recommendations.OwnedSeries.Select(s => s.Id), user); - ret.Recommendations.ExternalSeries = new List(); + ret.Recommendations.ExternalSeries = []; } if (ret.Recommendations != null && user != null) { - ret.Recommendations.OwnedSeries ??= new List(); + ret.Recommendations.OwnedSeries ??= []; await unitOfWork.SeriesRepository.AddSeriesModifiers(user.Id, ret.Recommendations.OwnedSeries); } } diff --git a/API/Controllers/PluginController.cs b/API/Controllers/PluginController.cs index 87cfaf2c2..c7f48cf54 100644 --- a/API/Controllers/PluginController.cs +++ b/API/Controllers/PluginController.cs @@ -30,7 +30,7 @@ public class PluginController(IUnitOfWork unitOfWork, ITokenService tokenService public async Task> Authenticate([Required] string apiKey, [Required] string pluginName) { // NOTE: In order to log information about plugins, we need some Plugin Description information for each request - // Should log into access table so we can tell the user + // Should log into the access table so we can tell the user var ipAddress = HttpContext.Connection.RemoteIpAddress?.ToString(); var userAgent = HttpContext.Request.Headers.UserAgent; var userId = await unitOfWork.UserRepository.GetUserIdByApiKeyAsync(apiKey); diff --git a/API/Controllers/RatingController.cs b/API/Controllers/RatingController.cs index a40b6680b..9283ef6d3 100644 --- a/API/Controllers/RatingController.cs +++ b/API/Controllers/RatingController.cs @@ -1,15 +1,12 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using API.Constants; using API.Data; +using API.Data.Repositories; using API.DTOs; using API.Extensions; +using API.Services; using API.Services.Plus; -using EasyCaching.Core; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; namespace API.Controllers; @@ -21,21 +18,85 @@ namespace API.Controllers; public class RatingController : BaseApiController { private readonly IUnitOfWork _unitOfWork; + private readonly IRatingService _ratingService; + private readonly ILocalizationService _localizationService; - public RatingController(IUnitOfWork unitOfWork) + public RatingController(IUnitOfWork unitOfWork, IRatingService ratingService, ILocalizationService localizationService) { _unitOfWork = unitOfWork; - + _ratingService = ratingService; + _localizationService = localizationService; } - [HttpGet("overall")] - public async Task> GetOverallRating(int seriesId) + /// + /// Update the users' rating of the given series + /// + /// + /// + /// + [HttpPost("series")] + public async Task UpdateSeriesRating(UpdateRatingDto updateRating) + { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings | AppUserIncludes.ChapterRatings); + if (user == null) throw new UnauthorizedAccessException(); + + if (await _ratingService.UpdateSeriesRating(user, updateRating)) + { + return Ok(); + } + + return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); + } + + /// + /// Update the users' rating of the given chapter + /// + /// chapterId must be set + /// + /// + [HttpPost("chapter")] + public async Task UpdateChapterRating(UpdateRatingDto updateRating) + { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings | AppUserIncludes.ChapterRatings); + if (user == null) throw new UnauthorizedAccessException(); + + if (await _ratingService.UpdateChapterRating(user, updateRating)) + { + return Ok(); + } + + return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); + } + + /// + /// Overall rating from all Kavita users for a given Series + /// + /// + /// + [HttpGet("overall-series")] + public async Task> GetOverallSeriesRating(int seriesId) { return Ok(new RatingDto() { Provider = ScrobbleProvider.Kavita, AverageScore = await _unitOfWork.SeriesRepository.GetAverageUserRating(seriesId, User.GetUserId()), - FavoriteCount = 0 + FavoriteCount = 0, + }); + } + + /// + /// Overall rating from all Kavita users for a given Chapter + /// + /// + /// + [HttpGet("overall-chapter")] + public async Task> GetOverallChapterRating(int chapterId) + { + return Ok(new RatingDto() + { + Provider = ScrobbleProvider.Kavita, + AverageScore = await _unitOfWork.ChapterRepository.GetAverageUserRating(chapterId, User.GetUserId()), + FavoriteCount = 0, }); } } diff --git a/API/Controllers/ReviewController.cs b/API/Controllers/ReviewController.cs index ae8ce02ee..d4de3db16 100644 --- a/API/Controllers/ReviewController.cs +++ b/API/Controllers/ReviewController.cs @@ -1,8 +1,11 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using API.Data; using API.Data.Repositories; using API.DTOs.SeriesDetail; +using API.Entities; +using API.Entities.Enums; using API.Extensions; using API.Helpers.Builders; using API.Services.Plus; @@ -30,17 +33,17 @@ public class ReviewController : BaseApiController /// - /// Updates the review for a given series + /// Updates the user's review for a given series /// /// /// - [HttpPost] - public async Task> UpdateReview(UpdateUserReviewDto dto) + [HttpPost("series")] + public async Task> UpdateSeriesReview(UpdateUserReviewDto dto) { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings); if (user == null) return Unauthorized(); - var ratingBuilder = new RatingBuilder(user.Ratings.FirstOrDefault(r => r.SeriesId == dto.SeriesId)); + var ratingBuilder = new RatingBuilder(await _unitOfWork.UserRepository.GetUserRatingAsync(dto.SeriesId, user.Id)); var rating = ratingBuilder .WithBody(dto.Body) @@ -52,22 +55,58 @@ public class ReviewController : BaseApiController { user.Ratings.Add(rating); } + _unitOfWork.UserRepository.Update(user); await _unitOfWork.CommitAsync(); - BackgroundJob.Enqueue(() => _scrobblingService.ScrobbleReviewUpdate(user.Id, dto.SeriesId, string.Empty, dto.Body)); return Ok(_mapper.Map(rating)); } + /// + /// Update the user's review for a given chapter + /// + /// chapterId must be set + /// + [HttpPost("chapter")] + public async Task> UpdateChapterReview(UpdateUserReviewDto dto) + { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings); + if (user == null) return Unauthorized(); + + if (dto.ChapterId == null) return BadRequest(); + + int chapterId = dto.ChapterId.Value; + + var ratingBuilder = new ChapterRatingBuilder(await _unitOfWork.UserRepository.GetUserChapterRatingAsync(user.Id, chapterId)); + + var rating = ratingBuilder + .WithBody(dto.Body) + .WithSeriesId(dto.SeriesId) + .WithChapterId(chapterId) + .Build(); + + if (rating.Id == 0) + { + user.ChapterRatings.Add(rating); + } + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + + return Ok(_mapper.Map(rating)); + } + + /// /// Deletes the user's review for the given series /// /// - [HttpDelete] - public async Task DeleteReview(int seriesId) + [HttpDelete("series")] + public async Task DeleteSeriesReview([FromQuery] int seriesId) { var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.Ratings); if (user == null) return Unauthorized(); @@ -80,4 +119,23 @@ public class ReviewController : BaseApiController return Ok(); } + + /// + /// Deletes the user's review for the given chapter + /// + /// + [HttpDelete("chapter")] + public async Task DeleteChapterReview([FromQuery] int chapterId) + { + var user = await _unitOfWork.UserRepository.GetUserByIdAsync(User.GetUserId(), AppUserIncludes.ChapterRatings); + if (user == null) return Unauthorized(); + + user.ChapterRatings = user.ChapterRatings.Where(r => r.ChapterId != chapterId).ToList(); + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + + return Ok(); + } } diff --git a/API/Controllers/SeriesController.cs b/API/Controllers/SeriesController.cs index 94f9c084f..7cd897c32 100644 --- a/API/Controllers/SeriesController.cs +++ b/API/Controllers/SeriesController.cs @@ -191,21 +191,6 @@ public class SeriesController : BaseApiController return Ok(await _unitOfWork.ChapterRepository.GetChapterMetadataDtoAsync(chapterId)); } - - /// - /// Update the user rating for the given series - /// - /// - /// - [HttpPost("update-rating")] - public async Task UpdateSeriesRating(UpdateSeriesRatingDto updateSeriesRatingDto) - { - var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername(), AppUserIncludes.Ratings); - if (!await _seriesService.UpdateRating(user!, updateSeriesRatingDto)) - return BadRequest(await _localizationService.Translate(User.GetUserId(), "generic-error")); - return Ok(); - } - /// /// Updates the Series /// diff --git a/API/DTOs/Account/AgeRestrictionDto.cs b/API/DTOs/Account/AgeRestrictionDto.cs index 0aaec9b97..6505bdbff 100644 --- a/API/DTOs/Account/AgeRestrictionDto.cs +++ b/API/DTOs/Account/AgeRestrictionDto.cs @@ -2,15 +2,15 @@ namespace API.DTOs.Account; -public class AgeRestrictionDto +public sealed record AgeRestrictionDto { /// /// The maximum age rating a user has access to. -1 if not applicable /// - public required AgeRating AgeRating { get; set; } = AgeRating.NotApplicable; + public required AgeRating AgeRating { get; init; } = AgeRating.NotApplicable; /// /// Are Unknowns explicitly allowed against age rating /// /// Unknown is always lowest and default age rating. Setting this to false will ensure Teen age rating applies and unknowns are still filtered - public required bool IncludeUnknowns { get; set; } = false; + public required bool IncludeUnknowns { get; init; } = false; } diff --git a/API/DTOs/Account/ConfirmEmailDto.cs b/API/DTOs/Account/ConfirmEmailDto.cs index 2f5849e74..413f9f34a 100644 --- a/API/DTOs/Account/ConfirmEmailDto.cs +++ b/API/DTOs/Account/ConfirmEmailDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Account; -public class ConfirmEmailDto +public sealed record ConfirmEmailDto { [Required] public string Email { get; set; } = default!; diff --git a/API/DTOs/Account/ConfirmEmailUpdateDto.cs b/API/DTOs/Account/ConfirmEmailUpdateDto.cs index 42abb1295..2a0738e35 100644 --- a/API/DTOs/Account/ConfirmEmailUpdateDto.cs +++ b/API/DTOs/Account/ConfirmEmailUpdateDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Account; -public class ConfirmEmailUpdateDto +public sealed record ConfirmEmailUpdateDto { [Required] public string Email { get; set; } = default!; diff --git a/API/DTOs/Account/ConfirmMigrationEmailDto.cs b/API/DTOs/Account/ConfirmMigrationEmailDto.cs index efb42b8fd..cdfc1505c 100644 --- a/API/DTOs/Account/ConfirmMigrationEmailDto.cs +++ b/API/DTOs/Account/ConfirmMigrationEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Account; -public class ConfirmMigrationEmailDto +public sealed record ConfirmMigrationEmailDto { public string Email { get; set; } = default!; public string Token { get; set; } = default!; diff --git a/API/DTOs/Account/ConfirmPasswordResetDto.cs b/API/DTOs/Account/ConfirmPasswordResetDto.cs index 16dd86f9a..00aff301b 100644 --- a/API/DTOs/Account/ConfirmPasswordResetDto.cs +++ b/API/DTOs/Account/ConfirmPasswordResetDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Account; -public class ConfirmPasswordResetDto +public sealed record ConfirmPasswordResetDto { [Required] public string Email { get; set; } = default!; diff --git a/API/DTOs/Account/InviteUserDto.cs b/API/DTOs/Account/InviteUserDto.cs index 112013053..c12bebc2b 100644 --- a/API/DTOs/Account/InviteUserDto.cs +++ b/API/DTOs/Account/InviteUserDto.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; namespace API.DTOs.Account; -public class InviteUserDto +public sealed record InviteUserDto { [Required] public string Email { get; set; } = default!; diff --git a/API/DTOs/Account/InviteUserResponse.cs b/API/DTOs/Account/InviteUserResponse.cs index a7e0d86ea..ed16bd05e 100644 --- a/API/DTOs/Account/InviteUserResponse.cs +++ b/API/DTOs/Account/InviteUserResponse.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Account; -public class InviteUserResponse +public sealed record InviteUserResponse { /// /// Email link used to setup the user account diff --git a/API/DTOs/Account/LoginDto.cs b/API/DTOs/Account/LoginDto.cs index fe8fce088..97338640b 100644 --- a/API/DTOs/Account/LoginDto.cs +++ b/API/DTOs/Account/LoginDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.Account; #nullable enable -public class LoginDto +public sealed record LoginDto { public string Username { get; init; } = default!; public string Password { get; set; } = default!; diff --git a/API/DTOs/Account/MigrateUserEmailDto.cs b/API/DTOs/Account/MigrateUserEmailDto.cs index 60d042165..4630c510f 100644 --- a/API/DTOs/Account/MigrateUserEmailDto.cs +++ b/API/DTOs/Account/MigrateUserEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Account; -public class MigrateUserEmailDto +public sealed record MigrateUserEmailDto { public string Email { get; set; } = default!; public string Username { get; set; } = default!; diff --git a/API/DTOs/Account/ResetPasswordDto.cs b/API/DTOs/Account/ResetPasswordDto.cs index 51a195131..545ca5ba6 100644 --- a/API/DTOs/Account/ResetPasswordDto.cs +++ b/API/DTOs/Account/ResetPasswordDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Account; -public class ResetPasswordDto +public sealed record ResetPasswordDto { /// /// The Username of the User diff --git a/API/DTOs/Account/TokenRequestDto.cs b/API/DTOs/Account/TokenRequestDto.cs index 85ab9f87a..5c798721c 100644 --- a/API/DTOs/Account/TokenRequestDto.cs +++ b/API/DTOs/Account/TokenRequestDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Account; -public class TokenRequestDto +public sealed record TokenRequestDto { public string Token { get; init; } = default!; public string RefreshToken { get; init; } = default!; diff --git a/API/DTOs/Account/UpdateAgeRestrictionDto.cs b/API/DTOs/Account/UpdateAgeRestrictionDto.cs index ef6be1bba..2fa9c89d2 100644 --- a/API/DTOs/Account/UpdateAgeRestrictionDto.cs +++ b/API/DTOs/Account/UpdateAgeRestrictionDto.cs @@ -3,7 +3,7 @@ using API.Entities.Enums; namespace API.DTOs.Account; -public class UpdateAgeRestrictionDto +public sealed record UpdateAgeRestrictionDto { [Required] public AgeRating AgeRating { get; set; } diff --git a/API/DTOs/Account/UpdateEmailDto.cs b/API/DTOs/Account/UpdateEmailDto.cs index eac06be53..873862ba1 100644 --- a/API/DTOs/Account/UpdateEmailDto.cs +++ b/API/DTOs/Account/UpdateEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Account; -public class UpdateEmailDto +public sealed record UpdateEmailDto { public string Email { get; set; } = default!; public string Password { get; set; } = default!; diff --git a/API/DTOs/Account/UpdateUserDto.cs b/API/DTOs/Account/UpdateUserDto.cs index c40124b7b..0cb0eaf66 100644 --- a/API/DTOs/Account/UpdateUserDto.cs +++ b/API/DTOs/Account/UpdateUserDto.cs @@ -4,12 +4,16 @@ using System.ComponentModel.DataAnnotations; namespace API.DTOs.Account; #nullable enable -public record UpdateUserDto +public sealed record UpdateUserDto { + /// public int UserId { get; set; } + /// public string Username { get; set; } = default!; + /// /// List of Roles to assign to user. If admin not present, Pleb will be applied. /// If admin present, all libraries will be granted access and will ignore those from DTO. + /// public IList Roles { get; init; } = default!; /// /// A list of libraries to grant access to @@ -19,8 +23,6 @@ public record UpdateUserDto /// An Age Rating which will limit the account to seeing everything equal to or below said rating. /// public AgeRestrictionDto AgeRestriction { get; init; } = default!; - /// - /// Email of the user - /// + /// public string? Email { get; set; } = default!; } diff --git a/API/DTOs/BulkActionDto.cs b/API/DTOs/BulkActionDto.cs index d3ce75293..c26a73e9c 100644 --- a/API/DTOs/BulkActionDto.cs +++ b/API/DTOs/BulkActionDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs; -public class BulkActionDto +public sealed record BulkActionDto { public List Ids { get; set; } /** diff --git a/API/DTOs/ChapterDetailPlusDto.cs b/API/DTOs/ChapterDetailPlusDto.cs new file mode 100644 index 000000000..d99482e55 --- /dev/null +++ b/API/DTOs/ChapterDetailPlusDto.cs @@ -0,0 +1,14 @@ +#nullable enable +using System.Collections.Generic; +using API.DTOs.SeriesDetail; + +namespace API.DTOs; + +public sealed record ChapterDetailPlusDto +{ + public float Rating { get; set; } + public bool HasBeenRated { get; set; } + + public IList Reviews { get; set; } = []; + public IList Ratings { get; set; } = []; +} diff --git a/API/DTOs/ChapterDto.cs b/API/DTOs/ChapterDto.cs index 70c77e92d..70fb12e85 100644 --- a/API/DTOs/ChapterDto.cs +++ b/API/DTOs/ChapterDto.cs @@ -13,37 +13,24 @@ namespace API.DTOs; /// public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage { + /// public int Id { get; init; } - /// - /// Range of chapters. Chapter 2-4 -> "2-4". Chapter 2 -> "2". If special, will be special name. - /// - /// This can be something like 19.HU or Alpha as some comics are like this + /// public string Range { get; init; } = default!; - /// - /// Smallest number of the Range. - /// + /// [Obsolete("Use MinNumber and MaxNumber instead")] public string Number { get; init; } = default!; - /// - /// This may be 0 under the circumstance that the Issue is "Alpha" or other non-standard numbers. - /// + /// public float MinNumber { get; init; } + /// public float MaxNumber { get; init; } - /// - /// The sorting order of the Chapter. Inherits from MinNumber, but can be overridden. - /// + /// public float SortOrder { get; set; } - /// - /// Total number of pages in all MangaFiles - /// + /// public int Pages { get; init; } - /// - /// If this Chapter contains files that could only be identified as Series or has Special Identifier from filename - /// + /// public bool IsSpecial { get; init; } - /// - /// Used for books/specials to display custom title. For non-specials/books, will be set to - /// + /// public string Title { get; set; } = default!; /// /// The files that represent this Chapter @@ -61,46 +48,25 @@ public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage /// The last time a chapter was read by current authenticated user /// public DateTime LastReadingProgress { get; set; } - /// - /// If the Cover Image is locked for this entity - /// + /// public bool CoverImageLocked { get; set; } - /// - /// Volume Id this Chapter belongs to - /// + /// public int VolumeId { get; init; } - /// - /// When chapter was created - /// + /// public DateTime CreatedUtc { get; set; } + /// public DateTime LastModifiedUtc { get; set; } - /// - /// When chapter was created in local server time - /// - /// This is required for Tachiyomi Extension + /// public DateTime Created { get; set; } - /// - /// When the chapter was released. - /// - /// Metadata field + /// public DateTime ReleaseDate { get; init; } - /// - /// Title of the Chapter/Issue - /// - /// Metadata field + /// public string TitleName { get; set; } = default!; - /// - /// Summary of the Chapter - /// - /// This is not set normally, only for Series Detail + /// public string Summary { get; init; } = default!; - /// - /// Age Rating for the issue/chapter - /// + /// public AgeRating AgeRating { get; init; } - /// - /// Total words in a Chapter (books only) - /// + /// public long WordCount { get; set; } = 0L; /// /// Formatted Volume title ie) Volume 2. @@ -113,14 +79,9 @@ public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage public int MaxHoursToRead { get; set; } /// public float AvgHoursToRead { get; set; } - /// - /// Comma-separated link of urls to external services that have some relation to the Chapter - /// + /// public string WebLinks { get; set; } - /// - /// ISBN-13 (usually) of the Chapter - /// - /// This is guaranteed to be Valid + /// public string ISBN { get; set; } #region Metadata @@ -146,51 +107,60 @@ public class ChapterDto : IHasReadTimeEstimate, IHasCoverImage /// public ICollection Tags { get; set; } = new List(); public PublicationStatus PublicationStatus { get; set; } - /// - /// Language for the Chapter/Issue - /// + /// public string? Language { get; set; } - /// - /// Number in the TotalCount of issues - /// + /// public int Count { get; set; } - /// - /// Total number of issues for the series - /// + /// public int TotalCount { get; set; } + /// public bool LanguageLocked { get; set; } + /// public bool SummaryLocked { get; set; } - /// - /// Locked by user so metadata updates from scan loop will not override AgeRating - /// + /// public bool AgeRatingLocked { get; set; } - /// - /// Locked by user so metadata updates from scan loop will not override PublicationStatus - /// public bool PublicationStatusLocked { get; set; } + /// public bool GenresLocked { get; set; } + /// public bool TagsLocked { get; set; } + /// public bool WriterLocked { get; set; } + /// public bool CharacterLocked { get; set; } + /// public bool ColoristLocked { get; set; } + /// public bool EditorLocked { get; set; } + /// public bool InkerLocked { get; set; } + /// public bool ImprintLocked { get; set; } + /// public bool LettererLocked { get; set; } + /// public bool PencillerLocked { get; set; } + /// public bool PublisherLocked { get; set; } + /// public bool TranslatorLocked { get; set; } + /// public bool TeamLocked { get; set; } + /// public bool LocationLocked { get; set; } + /// public bool CoverArtistLocked { get; set; } public bool ReleaseYearLocked { get; set; } #endregion - public string CoverImage { get; set; } - public string PrimaryColor { get; set; } = string.Empty; - public string SecondaryColor { get; set; } = string.Empty; + /// + public string? CoverImage { get; set; } + /// + public string? PrimaryColor { get; set; } = string.Empty; + /// + public string? SecondaryColor { get; set; } = string.Empty; public void ResetColorScape() { diff --git a/API/DTOs/Collection/AppUserCollectionDto.cs b/API/DTOs/Collection/AppUserCollectionDto.cs index ecfb5c062..0634b5d83 100644 --- a/API/DTOs/Collection/AppUserCollectionDto.cs +++ b/API/DTOs/Collection/AppUserCollectionDto.cs @@ -6,52 +6,52 @@ using API.Services.Plus; namespace API.DTOs.Collection; #nullable enable -public class AppUserCollectionDto : IHasCoverImage +public sealed record AppUserCollectionDto : IHasCoverImage { public int Id { get; init; } - public string Title { get; set; } = default!; - public string? Summary { get; set; } = default!; - public bool Promoted { get; set; } - public AgeRating AgeRating { get; set; } + public string Title { get; init; } = default!; + public string? Summary { get; init; } = default!; + public bool Promoted { get; init; } + public AgeRating AgeRating { get; init; } /// /// This is used to tell the UI if it should request a Cover Image or not. If null or empty, it has not been set. /// public string? CoverImage { get; set; } = string.Empty; - public string PrimaryColor { get; set; } = string.Empty; - public string SecondaryColor { get; set; } = string.Empty; - public bool CoverImageLocked { get; set; } + public string? PrimaryColor { get; set; } = string.Empty; + public string? SecondaryColor { get; set; } = string.Empty; + public bool CoverImageLocked { get; init; } /// /// Number of Series in the Collection /// - public int ItemCount { get; set; } + public int ItemCount { get; init; } /// /// Owner of the Collection /// - public string? Owner { get; set; } + public string? Owner { get; init; } /// /// Last time Kavita Synced the Collection with an upstream source (for non Kavita sourced collections) /// - public DateTime LastSyncUtc { get; set; } + public DateTime LastSyncUtc { get; init; } /// /// Who created/manages the list. Non-Kavita lists are not editable by the user, except to promote /// - public ScrobbleProvider Source { get; set; } = ScrobbleProvider.Kavita; + public ScrobbleProvider Source { get; init; } = ScrobbleProvider.Kavita; /// /// For Non-Kavita sourced collections, the url to sync from /// - public string? SourceUrl { get; set; } + public string? SourceUrl { get; init; } /// /// Total number of items as of the last sync. Not applicable for Kavita managed collections. /// - public int TotalSourceCount { get; set; } + public int TotalSourceCount { get; init; } /// /// A
separated string of all missing series ///
- public string? MissingSeriesFromSource { get; set; } + public string? MissingSeriesFromSource { get; init; } public void ResetColorScape() { diff --git a/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs b/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs index 1d078959d..0a2270fbf 100644 --- a/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs +++ b/API/DTOs/CollectionTags/CollectionTagBulkAddDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.CollectionTags; -public class CollectionTagBulkAddDto +public sealed record CollectionTagBulkAddDto { /// /// Collection Tag Id diff --git a/API/DTOs/CollectionTags/CollectionTagDto.cs b/API/DTOs/CollectionTags/CollectionTagDto.cs index ec9939ebd..911622051 100644 --- a/API/DTOs/CollectionTags/CollectionTagDto.cs +++ b/API/DTOs/CollectionTags/CollectionTagDto.cs @@ -3,15 +3,21 @@ namespace API.DTOs.CollectionTags; [Obsolete("Use AppUserCollectionDto")] -public class CollectionTagDto +public sealed record CollectionTagDto { + /// public int Id { get; set; } + /// public string Title { get; set; } = default!; + /// public string Summary { get; set; } = default!; + /// public bool Promoted { get; set; } /// /// The cover image string. This is used on Frontend to show or hide the Cover Image /// + /// public string CoverImage { get; set; } = default!; + /// public bool CoverImageLocked { get; set; } } diff --git a/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs b/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs index 19e9a11e2..139834a60 100644 --- a/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs +++ b/API/DTOs/CollectionTags/UpdateSeriesForTagDto.cs @@ -4,7 +4,7 @@ using API.DTOs.Collection; namespace API.DTOs.CollectionTags; -public class UpdateSeriesForTagDto +public sealed record UpdateSeriesForTagDto { public AppUserCollectionDto Tag { get; init; } = default!; public IEnumerable SeriesIdsToRemove { get; init; } = default!; diff --git a/API/DTOs/ColorScape.cs b/API/DTOs/ColorScape.cs index d95346af7..5351f2351 100644 --- a/API/DTOs/ColorScape.cs +++ b/API/DTOs/ColorScape.cs @@ -4,7 +4,7 @@ /// /// A primary and secondary color /// -public class ColorScape +public sealed record ColorScape { public required string? Primary { get; set; } public required string? Secondary { get; set; } diff --git a/API/DTOs/CopySettingsFromLibraryDto.cs b/API/DTOs/CopySettingsFromLibraryDto.cs index ee75f7422..5ca5ead51 100644 --- a/API/DTOs/CopySettingsFromLibraryDto.cs +++ b/API/DTOs/CopySettingsFromLibraryDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs; -public class CopySettingsFromLibraryDto +public sealed record CopySettingsFromLibraryDto { public int SourceLibraryId { get; set; } public List TargetLibraryIds { get; set; } diff --git a/API/DTOs/CoverDb/CoverDbAuthor.cs b/API/DTOs/CoverDb/CoverDbAuthor.cs index 2f023398a..ca924801f 100644 --- a/API/DTOs/CoverDb/CoverDbAuthor.cs +++ b/API/DTOs/CoverDb/CoverDbAuthor.cs @@ -3,7 +3,7 @@ using YamlDotNet.Serialization; namespace API.DTOs.CoverDb; -public class CoverDbAuthor +public sealed record CoverDbAuthor { [YamlMember(Alias = "name", ApplyNamingConventions = false)] public string Name { get; set; } diff --git a/API/DTOs/CoverDb/CoverDbPeople.cs b/API/DTOs/CoverDb/CoverDbPeople.cs index c0f5e327e..2e825eac7 100644 --- a/API/DTOs/CoverDb/CoverDbPeople.cs +++ b/API/DTOs/CoverDb/CoverDbPeople.cs @@ -3,7 +3,7 @@ using YamlDotNet.Serialization; namespace API.DTOs.CoverDb; -public class CoverDbPeople +public sealed record CoverDbPeople { [YamlMember(Alias = "people", ApplyNamingConventions = false)] public List People { get; set; } = new List(); diff --git a/API/DTOs/CoverDb/CoverDbPersonIds.cs b/API/DTOs/CoverDb/CoverDbPersonIds.cs index 9c59415e6..5816bb479 100644 --- a/API/DTOs/CoverDb/CoverDbPersonIds.cs +++ b/API/DTOs/CoverDb/CoverDbPersonIds.cs @@ -3,7 +3,7 @@ namespace API.DTOs.CoverDb; #nullable enable -public class CoverDbPersonIds +public sealed record CoverDbPersonIds { [YamlMember(Alias = "hardcover_id", ApplyNamingConventions = false)] public string? HardcoverId { get; set; } = null; diff --git a/API/DTOs/Dashboard/DashboardStreamDto.cs b/API/DTOs/Dashboard/DashboardStreamDto.cs index 59e5f4f7d..297a706b1 100644 --- a/API/DTOs/Dashboard/DashboardStreamDto.cs +++ b/API/DTOs/Dashboard/DashboardStreamDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs.Dashboard; -public class DashboardStreamDto +public sealed record DashboardStreamDto { public int Id { get; set; } public required string Name { get; set; } diff --git a/API/DTOs/Dashboard/GroupedSeriesDto.cs b/API/DTOs/Dashboard/GroupedSeriesDto.cs index 3b283de34..940e42c40 100644 --- a/API/DTOs/Dashboard/GroupedSeriesDto.cs +++ b/API/DTOs/Dashboard/GroupedSeriesDto.cs @@ -5,7 +5,7 @@ namespace API.DTOs.Dashboard; /// /// This is a representation of a Series with some amount of underlying files within it. This is used for Recently Updated Series section /// -public class GroupedSeriesDto +public sealed record GroupedSeriesDto { public string SeriesName { get; set; } = default!; public int SeriesId { get; set; } diff --git a/API/DTOs/Dashboard/RecentlyAddedItemDto.cs b/API/DTOs/Dashboard/RecentlyAddedItemDto.cs index 2e5658e2e..bb0360b30 100644 --- a/API/DTOs/Dashboard/RecentlyAddedItemDto.cs +++ b/API/DTOs/Dashboard/RecentlyAddedItemDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Dashboard; /// /// A mesh of data for Recently added volume/chapters /// -public class RecentlyAddedItemDto +public sealed record RecentlyAddedItemDto { public string SeriesName { get; set; } = default!; public int SeriesId { get; set; } diff --git a/API/DTOs/Dashboard/SmartFilterDto.cs b/API/DTOs/Dashboard/SmartFilterDto.cs index b23a74c69..c1bc4d7e1 100644 --- a/API/DTOs/Dashboard/SmartFilterDto.cs +++ b/API/DTOs/Dashboard/SmartFilterDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Dashboard; -public class SmartFilterDto +public sealed record SmartFilterDto { public int Id { get; set; } public required string Name { get; set; } diff --git a/API/DTOs/Dashboard/UpdateDashboardStreamPositionDto.cs b/API/DTOs/Dashboard/UpdateDashboardStreamPositionDto.cs index c2320f1a9..476a0732e 100644 --- a/API/DTOs/Dashboard/UpdateDashboardStreamPositionDto.cs +++ b/API/DTOs/Dashboard/UpdateDashboardStreamPositionDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Dashboard; -public class UpdateDashboardStreamPositionDto +public sealed record UpdateDashboardStreamPositionDto { public int FromPosition { get; set; } public int ToPosition { get; set; } diff --git a/API/DTOs/Dashboard/UpdateStreamPositionDto.cs b/API/DTOs/Dashboard/UpdateStreamPositionDto.cs index f9005a585..8de0ffa6f 100644 --- a/API/DTOs/Dashboard/UpdateStreamPositionDto.cs +++ b/API/DTOs/Dashboard/UpdateStreamPositionDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Dashboard; -public class UpdateStreamPositionDto +public sealed record UpdateStreamPositionDto { public int FromPosition { get; set; } public int ToPosition { get; set; } diff --git a/API/DTOs/DeleteChaptersDto.cs b/API/DTOs/DeleteChaptersDto.cs index cbd21df36..9fad2f1fb 100644 --- a/API/DTOs/DeleteChaptersDto.cs +++ b/API/DTOs/DeleteChaptersDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs; -public class DeleteChaptersDto +public sealed record DeleteChaptersDto { public IList ChapterIds { get; set; } = default!; } diff --git a/API/DTOs/DeleteSeriesDto.cs b/API/DTOs/DeleteSeriesDto.cs index 12687fc25..ec9ba0c68 100644 --- a/API/DTOs/DeleteSeriesDto.cs +++ b/API/DTOs/DeleteSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs; -public class DeleteSeriesDto +public sealed record DeleteSeriesDto { public IList SeriesIds { get; set; } = default!; } diff --git a/API/DTOs/Device/CreateDeviceDto.cs b/API/DTOs/Device/CreateDeviceDto.cs index 7e59483fa..a8fdb6bc9 100644 --- a/API/DTOs/Device/CreateDeviceDto.cs +++ b/API/DTOs/Device/CreateDeviceDto.cs @@ -3,7 +3,7 @@ using API.Entities.Enums.Device; namespace API.DTOs.Device; -public class CreateDeviceDto +public sealed record CreateDeviceDto { [Required] public string Name { get; set; } = default!; diff --git a/API/DTOs/Device/DeviceDto.cs b/API/DTOs/Device/DeviceDto.cs index b2e83e6fc..42140dcc1 100644 --- a/API/DTOs/Device/DeviceDto.cs +++ b/API/DTOs/Device/DeviceDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Device; /// /// A Device is an entity that can receive data from Kavita (kindle) /// -public class DeviceDto +public sealed record DeviceDto { /// /// The device Id diff --git a/API/DTOs/Device/SendSeriesToDeviceDto.cs b/API/DTOs/Device/SendSeriesToDeviceDto.cs index a0a907464..58ce2293b 100644 --- a/API/DTOs/Device/SendSeriesToDeviceDto.cs +++ b/API/DTOs/Device/SendSeriesToDeviceDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Device; -public class SendSeriesToDeviceDto +public sealed record SendSeriesToDeviceDto { public int DeviceId { get; set; } public int SeriesId { get; set; } diff --git a/API/DTOs/Device/SendToDeviceDto.cs b/API/DTOs/Device/SendToDeviceDto.cs index fd88eaf59..a7a4dc0ff 100644 --- a/API/DTOs/Device/SendToDeviceDto.cs +++ b/API/DTOs/Device/SendToDeviceDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Device; -public class SendToDeviceDto +public sealed record SendToDeviceDto { public int DeviceId { get; set; } public IReadOnlyList ChapterIds { get; set; } = default!; diff --git a/API/DTOs/Device/UpdateDeviceDto.cs b/API/DTOs/Device/UpdateDeviceDto.cs index d28d372c3..2c3e72ea1 100644 --- a/API/DTOs/Device/UpdateDeviceDto.cs +++ b/API/DTOs/Device/UpdateDeviceDto.cs @@ -3,7 +3,7 @@ using API.Entities.Enums.Device; namespace API.DTOs.Device; -public class UpdateDeviceDto +public sealed record UpdateDeviceDto { [Required] public int Id { get; set; } diff --git a/API/DTOs/Downloads/DownloadBookmarkDto.cs b/API/DTOs/Downloads/DownloadBookmarkDto.cs index 5b7240b68..00f763dac 100644 --- a/API/DTOs/Downloads/DownloadBookmarkDto.cs +++ b/API/DTOs/Downloads/DownloadBookmarkDto.cs @@ -4,7 +4,7 @@ using API.DTOs.Reader; namespace API.DTOs.Downloads; -public class DownloadBookmarkDto +public sealed record DownloadBookmarkDto { [Required] public IEnumerable Bookmarks { get; set; } = default!; diff --git a/API/DTOs/Email/ConfirmationEmailDto.cs b/API/DTOs/Email/ConfirmationEmailDto.cs index 1a48c9974..197395794 100644 --- a/API/DTOs/Email/ConfirmationEmailDto.cs +++ b/API/DTOs/Email/ConfirmationEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Email; -public class ConfirmationEmailDto +public sealed record ConfirmationEmailDto { public string InvitingUser { get; init; } = default!; public string EmailAddress { get; init; } = default!; diff --git a/API/DTOs/Email/EmailHistoryDto.cs b/API/DTOs/Email/EmailHistoryDto.cs index ca3549550..c2968d091 100644 --- a/API/DTOs/Email/EmailHistoryDto.cs +++ b/API/DTOs/Email/EmailHistoryDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Email; -public class EmailHistoryDto +public sealed record EmailHistoryDto { public long Id { get; set; } public bool Sent { get; set; } diff --git a/API/DTOs/Email/EmailMigrationDto.cs b/API/DTOs/Email/EmailMigrationDto.cs index f051e7337..5354afdaa 100644 --- a/API/DTOs/Email/EmailMigrationDto.cs +++ b/API/DTOs/Email/EmailMigrationDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Email; -public class EmailMigrationDto +public sealed record EmailMigrationDto { public string EmailAddress { get; init; } = default!; public string Username { get; init; } = default!; diff --git a/API/DTOs/Email/EmailTestResultDto.cs b/API/DTOs/Email/EmailTestResultDto.cs index 263e725c4..9be868eab 100644 --- a/API/DTOs/Email/EmailTestResultDto.cs +++ b/API/DTOs/Email/EmailTestResultDto.cs @@ -3,7 +3,7 @@ /// /// Represents if Test Email Service URL was successful or not and if any error occured /// -public class EmailTestResultDto +public sealed record EmailTestResultDto { public bool Successful { get; set; } public string ErrorMessage { get; set; } = default!; diff --git a/API/DTOs/Email/PasswordResetEmailDto.cs b/API/DTOs/Email/PasswordResetEmailDto.cs index 06abba171..9fda066a9 100644 --- a/API/DTOs/Email/PasswordResetEmailDto.cs +++ b/API/DTOs/Email/PasswordResetEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Email; -public class PasswordResetEmailDto +public sealed record PasswordResetEmailDto { public string EmailAddress { get; init; } = default!; public string ServerConfirmationLink { get; init; } = default!; diff --git a/API/DTOs/Email/SendToDto.cs b/API/DTOs/Email/SendToDto.cs index 1261d110c..eacd29449 100644 --- a/API/DTOs/Email/SendToDto.cs +++ b/API/DTOs/Email/SendToDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Email; -public class SendToDto +public sealed record SendToDto { public string DestinationEmail { get; set; } = default!; public IEnumerable FilePaths { get; set; } = default!; diff --git a/API/DTOs/Email/TestEmailDto.cs b/API/DTOs/Email/TestEmailDto.cs index 37c12ed30..44c11bd6c 100644 --- a/API/DTOs/Email/TestEmailDto.cs +++ b/API/DTOs/Email/TestEmailDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Email; -public class TestEmailDto +public sealed record TestEmailDto { public string Url { get; set; } = default!; } diff --git a/API/DTOs/Filtering/FilterDto.cs b/API/DTOs/Filtering/FilterDto.cs index 9205a7bba..cb3374838 100644 --- a/API/DTOs/Filtering/FilterDto.cs +++ b/API/DTOs/Filtering/FilterDto.cs @@ -5,7 +5,7 @@ using API.Entities.Enums; namespace API.DTOs.Filtering; #nullable enable -public class FilterDto +public sealed record FilterDto { /// /// The type of Formats you want to be returned. An empty list will return all formats back diff --git a/API/DTOs/Filtering/LanguageDto.cs b/API/DTOs/Filtering/LanguageDto.cs index bc7ebb5cc..dde85f07e 100644 --- a/API/DTOs/Filtering/LanguageDto.cs +++ b/API/DTOs/Filtering/LanguageDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Filtering; -public class LanguageDto +public sealed record LanguageDto { public required string IsoCode { get; set; } public required string Title { get; set; } diff --git a/API/DTOs/Filtering/Range.cs b/API/DTOs/Filtering/Range.cs index a75164fa3..e697f26e1 100644 --- a/API/DTOs/Filtering/Range.cs +++ b/API/DTOs/Filtering/Range.cs @@ -4,7 +4,7 @@ /// /// Represents a range between two int/float/double /// -public class Range +public sealed record Range { public T? Min { get; init; } public T? Max { get; init; } diff --git a/API/DTOs/Filtering/ReadStatus.cs b/API/DTOs/Filtering/ReadStatus.cs index eeb786714..81498ecb5 100644 --- a/API/DTOs/Filtering/ReadStatus.cs +++ b/API/DTOs/Filtering/ReadStatus.cs @@ -3,7 +3,7 @@ /// /// Represents the Reading Status. This is a flag and allows multiple statues /// -public class ReadStatus +public sealed record ReadStatus { public bool NotRead { get; set; } = true; public bool InProgress { get; set; } = true; diff --git a/API/DTOs/Filtering/SortOptions.cs b/API/DTOs/Filtering/SortOptions.cs index 00bf91675..a08e2968e 100644 --- a/API/DTOs/Filtering/SortOptions.cs +++ b/API/DTOs/Filtering/SortOptions.cs @@ -3,7 +3,7 @@ /// /// Sorting Options for a query /// -public class SortOptions +public sealed record SortOptions { public SortField SortField { get; set; } public bool IsAscending { get; set; } = true; diff --git a/API/DTOs/Filtering/v2/DecodeFilterDto.cs b/API/DTOs/Filtering/v2/DecodeFilterDto.cs index 18dc166e7..db4c7ecce 100644 --- a/API/DTOs/Filtering/v2/DecodeFilterDto.cs +++ b/API/DTOs/Filtering/v2/DecodeFilterDto.cs @@ -3,7 +3,7 @@ /// /// For requesting an encoded filter to be decoded /// -public class DecodeFilterDto +public sealed record DecodeFilterDto { public string EncodedFilter { get; set; } } diff --git a/API/DTOs/Filtering/v2/FilterStatementDto.cs b/API/DTOs/Filtering/v2/FilterStatementDto.cs index a6192093e..ebe6d16af 100644 --- a/API/DTOs/Filtering/v2/FilterStatementDto.cs +++ b/API/DTOs/Filtering/v2/FilterStatementDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Filtering.v2; -public class FilterStatementDto +public sealed record FilterStatementDto { public FilterComparison Comparison { get; set; } public FilterField Field { get; set; } diff --git a/API/DTOs/Filtering/v2/FilterV2Dto.cs b/API/DTOs/Filtering/v2/FilterV2Dto.cs index 5bc50ff2f..11dc42a6b 100644 --- a/API/DTOs/Filtering/v2/FilterV2Dto.cs +++ b/API/DTOs/Filtering/v2/FilterV2Dto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Filtering.v2; /// /// Metadata filtering for v2 API only /// -public class FilterV2Dto +public sealed record FilterV2Dto { /// /// Not used in the UI. diff --git a/API/DTOs/Jobs/JobDto.cs b/API/DTOs/Jobs/JobDto.cs index 648765a34..55419811f 100644 --- a/API/DTOs/Jobs/JobDto.cs +++ b/API/DTOs/Jobs/JobDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Jobs; -public class JobDto +public sealed record JobDto { /// /// Job Id diff --git a/API/DTOs/JumpBar/JumpKeyDto.cs b/API/DTOs/JumpBar/JumpKeyDto.cs index 5a98a85ca..8dc5b4a8e 100644 --- a/API/DTOs/JumpBar/JumpKeyDto.cs +++ b/API/DTOs/JumpBar/JumpKeyDto.cs @@ -3,7 +3,7 @@ /// /// Represents an individual button in a Jump Bar /// -public class JumpKeyDto +public sealed record JumpKeyDto { /// /// Number of items in this Key diff --git a/API/DTOs/KavitaLocale.cs b/API/DTOs/KavitaLocale.cs index decfb7395..51868605f 100644 --- a/API/DTOs/KavitaLocale.cs +++ b/API/DTOs/KavitaLocale.cs @@ -1,6 +1,6 @@ namespace API.DTOs; -public class KavitaLocale +public sealed record KavitaLocale { public string FileName { get; set; } // Key public string RenderName { get; set; } diff --git a/API/DTOs/KavitaPlus/Account/AniListUpdateDto.cs b/API/DTOs/KavitaPlus/Account/AniListUpdateDto.cs index c6d2e07cc..c053bd34e 100644 --- a/API/DTOs/KavitaPlus/Account/AniListUpdateDto.cs +++ b/API/DTOs/KavitaPlus/Account/AniListUpdateDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.KavitaPlus.Account; -public class AniListUpdateDto +public sealed record AniListUpdateDto { public string Token { get; set; } } diff --git a/API/DTOs/KavitaPlus/Account/UserTokenInfo.cs b/API/DTOs/KavitaPlus/Account/UserTokenInfo.cs index 220bd9e7e..340ad0f4c 100644 --- a/API/DTOs/KavitaPlus/Account/UserTokenInfo.cs +++ b/API/DTOs/KavitaPlus/Account/UserTokenInfo.cs @@ -5,7 +5,7 @@ namespace API.DTOs.KavitaPlus.Account; /// /// Represents information around a user's tokens and their status /// -public class UserTokenInfo +public sealed record UserTokenInfo { public int UserId { get; set; } public string Username { get; set; } diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs index 547bb63a8..2b7dea8e6 100644 --- a/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs +++ b/API/DTOs/KavitaPlus/ExternalMetadata/ExternalMetadataIdsDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.KavitaPlus.ExternalMetadata; /// /// Used for matching and fetching metadata on a series /// -internal class ExternalMetadataIdsDto +internal sealed record ExternalMetadataIdsDto { public long? MalId { get; set; } public int? AniListId { get; set; } diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs index f63fe5a9e..6cd911700 100644 --- a/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs +++ b/API/DTOs/KavitaPlus/ExternalMetadata/MatchSeriesRequestDto.cs @@ -4,7 +4,7 @@ using API.DTOs.Scrobbling; namespace API.DTOs.KavitaPlus.ExternalMetadata; #nullable enable -internal class MatchSeriesRequestDto +internal sealed record MatchSeriesRequestDto { public string SeriesName { get; set; } public ICollection AlternativeNames { get; set; } diff --git a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs index 26411bce7..d0cbb7bd3 100644 --- a/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs +++ b/API/DTOs/KavitaPlus/ExternalMetadata/SeriesDetailPlusApiDto.cs @@ -1,11 +1,12 @@ using System.Collections.Generic; +using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Recommendation; using API.DTOs.Scrobbling; using API.DTOs.SeriesDetail; namespace API.DTOs.KavitaPlus.ExternalMetadata; -internal class SeriesDetailPlusApiDto +internal sealed record SeriesDetailPlusApiDto { public IEnumerable Recommendations { get; set; } public IEnumerable Reviews { get; set; } diff --git a/API/DTOs/KavitaPlus/License/EncryptLicenseDto.cs b/API/DTOs/KavitaPlus/License/EncryptLicenseDto.cs index eedbed2ef..dd85dd063 100644 --- a/API/DTOs/KavitaPlus/License/EncryptLicenseDto.cs +++ b/API/DTOs/KavitaPlus/License/EncryptLicenseDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.KavitaPlus.License; #nullable enable -public class EncryptLicenseDto +public sealed record EncryptLicenseDto { public required string License { get; set; } public required string InstallId { get; set; } diff --git a/API/DTOs/KavitaPlus/License/LicenseInfoDto.cs b/API/DTOs/KavitaPlus/License/LicenseInfoDto.cs index 398556aac..2cd9b5896 100644 --- a/API/DTOs/KavitaPlus/License/LicenseInfoDto.cs +++ b/API/DTOs/KavitaPlus/License/LicenseInfoDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.KavitaPlus.License; -public class LicenseInfoDto +public sealed record LicenseInfoDto { /// /// If cancelled, will represent cancellation date. If not, will represent repayment date diff --git a/API/DTOs/KavitaPlus/License/LicenseValidDto.cs b/API/DTOs/KavitaPlus/License/LicenseValidDto.cs index 56ee6cf73..a7bd476ce 100644 --- a/API/DTOs/KavitaPlus/License/LicenseValidDto.cs +++ b/API/DTOs/KavitaPlus/License/LicenseValidDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.KavitaPlus.License; -public class LicenseValidDto +public sealed record LicenseValidDto { public required string License { get; set; } public required string InstallId { get; set; } diff --git a/API/DTOs/KavitaPlus/License/ResetLicenseDto.cs b/API/DTOs/KavitaPlus/License/ResetLicenseDto.cs index 60496ee0e..d0fd9b666 100644 --- a/API/DTOs/KavitaPlus/License/ResetLicenseDto.cs +++ b/API/DTOs/KavitaPlus/License/ResetLicenseDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.KavitaPlus.License; -public class ResetLicenseDto +public sealed record ResetLicenseDto { public required string License { get; set; } public required string InstallId { get; set; } diff --git a/API/DTOs/KavitaPlus/License/UpdateLicenseDto.cs b/API/DTOs/KavitaPlus/License/UpdateLicenseDto.cs index 4621810f0..28b47efbe 100644 --- a/API/DTOs/KavitaPlus/License/UpdateLicenseDto.cs +++ b/API/DTOs/KavitaPlus/License/UpdateLicenseDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.KavitaPlus.License; #nullable enable -public class UpdateLicenseDto +public sealed record UpdateLicenseDto { /// /// License Key received from Kavita+ diff --git a/API/DTOs/KavitaPlus/Manage/ManageMatchFilterDto.cs b/API/DTOs/KavitaPlus/Manage/ManageMatchFilterDto.cs index 60bed32b0..8eb38c98a 100644 --- a/API/DTOs/KavitaPlus/Manage/ManageMatchFilterDto.cs +++ b/API/DTOs/KavitaPlus/Manage/ManageMatchFilterDto.cs @@ -12,7 +12,7 @@ public enum MatchStateOption DontMatch = 4 } -public class ManageMatchFilterDto +public sealed record ManageMatchFilterDto { public MatchStateOption MatchStateOption { get; set; } = MatchStateOption.All; public string SearchTerm { get; set; } = string.Empty; diff --git a/API/DTOs/KavitaPlus/Manage/ManageMatchSeriesDto.cs b/API/DTOs/KavitaPlus/Manage/ManageMatchSeriesDto.cs index 14617e7f0..a51e63ee9 100644 --- a/API/DTOs/KavitaPlus/Manage/ManageMatchSeriesDto.cs +++ b/API/DTOs/KavitaPlus/Manage/ManageMatchSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.KavitaPlus.Manage; -public class ManageMatchSeriesDto +public sealed record ManageMatchSeriesDto { public SeriesDto Series { get; set; } public bool IsMatched { get; set; } diff --git a/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs b/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs index 6b711513c..1dcd8494c 100644 --- a/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs +++ b/API/DTOs/KavitaPlus/Metadata/ExternalChapterDto.cs @@ -7,7 +7,7 @@ namespace API.DTOs.KavitaPlus.Metadata; /// /// Information about an individual issue/chapter/book from Kavita+ /// -public class ExternalChapterDto +public sealed record ExternalChapterDto { public string Title { get; set; } diff --git a/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs b/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs index 2ea746214..a3cd378b2 100644 --- a/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs +++ b/API/DTOs/KavitaPlus/Metadata/ExternalSeriesDetailDto.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; -using API.DTOs.KavitaPlus.Metadata; +using API.DTOs.Recommendation; using API.DTOs.Scrobbling; using API.Services.Plus; -namespace API.DTOs.Recommendation; +namespace API.DTOs.KavitaPlus.Metadata; #nullable enable /// /// This is AniListSeries /// -public class ExternalSeriesDetailDto +public sealed record ExternalSeriesDetailDto { public string Name { get; set; } public int? AniListId { get; set; } diff --git a/API/DTOs/KavitaPlus/Metadata/MetadataFieldMappingDto.cs b/API/DTOs/KavitaPlus/Metadata/MetadataFieldMappingDto.cs index 796cfeb1a..a9debabd1 100644 --- a/API/DTOs/KavitaPlus/Metadata/MetadataFieldMappingDto.cs +++ b/API/DTOs/KavitaPlus/Metadata/MetadataFieldMappingDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.KavitaPlus.Metadata; -public class MetadataFieldMappingDto +public sealed record MetadataFieldMappingDto { public int Id { get; set; } public MetadataFieldType SourceType { get; set; } diff --git a/API/DTOs/KavitaPlus/Metadata/MetadataSettingsDto.cs b/API/DTOs/KavitaPlus/Metadata/MetadataSettingsDto.cs index 1dd26a7bc..e9f6614bc 100644 --- a/API/DTOs/KavitaPlus/Metadata/MetadataSettingsDto.cs +++ b/API/DTOs/KavitaPlus/Metadata/MetadataSettingsDto.cs @@ -7,7 +7,7 @@ using NotImplementedException = System.NotImplementedException; namespace API.DTOs.KavitaPlus.Metadata; -public class MetadataSettingsDto +public sealed record MetadataSettingsDto { /// /// If writing any sort of metadata from upstream (AniList, Hardcover) source is allowed diff --git a/API/DTOs/KavitaPlus/Metadata/SeriesCharacter.cs b/API/DTOs/KavitaPlus/Metadata/SeriesCharacter.cs index bb5a3f20a..2b57548cd 100644 --- a/API/DTOs/KavitaPlus/Metadata/SeriesCharacter.cs +++ b/API/DTOs/KavitaPlus/Metadata/SeriesCharacter.cs @@ -9,7 +9,7 @@ public enum CharacterRole } -public class SeriesCharacter +public sealed record SeriesCharacter { public string Name { get; set; } public required string Description { get; set; } diff --git a/API/DTOs/KavitaPlus/Metadata/SeriesRelationship.cs b/API/DTOs/KavitaPlus/Metadata/SeriesRelationship.cs index bd42e73a1..0b1f619a2 100644 --- a/API/DTOs/KavitaPlus/Metadata/SeriesRelationship.cs +++ b/API/DTOs/KavitaPlus/Metadata/SeriesRelationship.cs @@ -5,7 +5,7 @@ using API.Services.Plus; namespace API.DTOs.KavitaPlus.Metadata; -public class ALMediaTitle +public sealed record ALMediaTitle { public string? EnglishTitle { get; set; } public string RomajiTitle { get; set; } @@ -13,7 +13,7 @@ public class ALMediaTitle public string PreferredTitle { get; set; } } -public class SeriesRelationship +public sealed record SeriesRelationship { public int AniListId { get; set; } public int? MalId { get; set; } diff --git a/API/DTOs/LibraryDto.cs b/API/DTOs/LibraryDto.cs index 18dea9434..8ba687346 100644 --- a/API/DTOs/LibraryDto.cs +++ b/API/DTOs/LibraryDto.cs @@ -1,12 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; using API.Entities.Enums; namespace API.DTOs; #nullable enable -public class LibraryDto +public sealed record LibraryDto { public int Id { get; init; } public string? Name { get; init; } diff --git a/API/DTOs/MangaFileDto.cs b/API/DTOs/MangaFileDto.cs index 9f2f19a42..23bb37467 100644 --- a/API/DTOs/MangaFileDto.cs +++ b/API/DTOs/MangaFileDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs; #nullable enable -public class MangaFileDto +public sealed record MangaFileDto { public int Id { get; init; } /// diff --git a/API/DTOs/MediaErrors/MediaErrorDto.cs b/API/DTOs/MediaErrors/MediaErrorDto.cs index bfaf57124..b77ee88be 100644 --- a/API/DTOs/MediaErrors/MediaErrorDto.cs +++ b/API/DTOs/MediaErrors/MediaErrorDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.MediaErrors; -public class MediaErrorDto +public sealed record MediaErrorDto { /// /// Format Type (RAR, ZIP, 7Zip, Epub, PDF) diff --git a/API/DTOs/MemberDto.cs b/API/DTOs/MemberDto.cs index 7b750b32f..f5f24b284 100644 --- a/API/DTOs/MemberDto.cs +++ b/API/DTOs/MemberDto.cs @@ -8,7 +8,7 @@ namespace API.DTOs; /// /// Represents a member of a Kavita server. /// -public class MemberDto +public sealed record MemberDto { public int Id { get; init; } public string? Username { get; init; } diff --git a/API/DTOs/Metadata/AgeRatingDto.cs b/API/DTOs/Metadata/AgeRatingDto.cs index 07523c3fe..bfa835ef5 100644 --- a/API/DTOs/Metadata/AgeRatingDto.cs +++ b/API/DTOs/Metadata/AgeRatingDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Metadata; -public class AgeRatingDto +public sealed record AgeRatingDto { public AgeRating Value { get; set; } public required string Title { get; set; } diff --git a/API/DTOs/Metadata/ChapterMetadataDto.cs b/API/DTOs/Metadata/ChapterMetadataDto.cs index bbd93d618..1adc52cd1 100644 --- a/API/DTOs/Metadata/ChapterMetadataDto.cs +++ b/API/DTOs/Metadata/ChapterMetadataDto.cs @@ -9,7 +9,7 @@ namespace API.DTOs.Metadata; /// Exclusively metadata about a given chapter /// [Obsolete("Will not be maintained as of v0.8.1")] -public class ChapterMetadataDto +public sealed record ChapterMetadataDto { public int Id { get; set; } public int ChapterId { get; set; } diff --git a/API/DTOs/Metadata/GenreTagDto.cs b/API/DTOs/Metadata/GenreTagDto.cs index cf05ebbff..4846048d2 100644 --- a/API/DTOs/Metadata/GenreTagDto.cs +++ b/API/DTOs/Metadata/GenreTagDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Metadata; -public class GenreTagDto +public sealed record GenreTagDto { public int Id { get; set; } public required string Title { get; set; } diff --git a/API/DTOs/Metadata/Matching/ExternalSeriesMatchDto.cs b/API/DTOs/Metadata/Matching/ExternalSeriesMatchDto.cs index aefd697ba..774581b37 100644 --- a/API/DTOs/Metadata/Matching/ExternalSeriesMatchDto.cs +++ b/API/DTOs/Metadata/Matching/ExternalSeriesMatchDto.cs @@ -1,8 +1,9 @@ +using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Recommendation; namespace API.DTOs.Metadata.Matching; -public class ExternalSeriesMatchDto +public sealed record ExternalSeriesMatchDto { public ExternalSeriesDetailDto Series { get; set; } public float MatchRating { get; set; } diff --git a/API/DTOs/Metadata/Matching/MatchSeriesDto.cs b/API/DTOs/Metadata/Matching/MatchSeriesDto.cs index 1f401e787..bb497b9ab 100644 --- a/API/DTOs/Metadata/Matching/MatchSeriesDto.cs +++ b/API/DTOs/Metadata/Matching/MatchSeriesDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.Metadata.Matching; /// /// Used for matching a series with Kavita+ for metadata and scrobbling /// -public class MatchSeriesDto +public sealed record MatchSeriesDto { /// /// When set, Kavita will stop attempting to match this series and will not perform any scrobbling diff --git a/API/DTOs/Metadata/PublicationStatusDto.cs b/API/DTOs/Metadata/PublicationStatusDto.cs index b8166a6e5..b4f12500a 100644 --- a/API/DTOs/Metadata/PublicationStatusDto.cs +++ b/API/DTOs/Metadata/PublicationStatusDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Metadata; -public class PublicationStatusDto +public sealed record PublicationStatusDto { public PublicationStatus Value { get; set; } public required string Title { get; set; } diff --git a/API/DTOs/Metadata/TagDto.cs b/API/DTOs/Metadata/TagDto.cs index 59e03a279..f8deb6913 100644 --- a/API/DTOs/Metadata/TagDto.cs +++ b/API/DTOs/Metadata/TagDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Metadata; -public class TagDto +public sealed record TagDto { public int Id { get; set; } public required string Title { get; set; } diff --git a/API/DTOs/OPDS/Feed.cs b/API/DTOs/OPDS/Feed.cs index 76a740b89..5f4c4b115 100644 --- a/API/DTOs/OPDS/Feed.cs +++ b/API/DTOs/OPDS/Feed.cs @@ -4,11 +4,13 @@ using System.Xml.Serialization; namespace API.DTOs.OPDS; +// TODO: OPDS Dtos are internal state, shouldn't be in DTO directory + /// /// /// [XmlRoot("feed", Namespace = "http://www.w3.org/2005/Atom")] -public class Feed +public sealed record Feed { [XmlElement("updated")] public string Updated { get; init; } = DateTime.UtcNow.ToString("s"); diff --git a/API/DTOs/OPDS/FeedAuthor.cs b/API/DTOs/OPDS/FeedAuthor.cs index 1fd3e6cd2..4196997dd 100644 --- a/API/DTOs/OPDS/FeedAuthor.cs +++ b/API/DTOs/OPDS/FeedAuthor.cs @@ -2,7 +2,7 @@ namespace API.DTOs.OPDS; -public class FeedAuthor +public sealed record FeedAuthor { [XmlElement("name")] public string Name { get; set; } diff --git a/API/DTOs/OPDS/FeedCategory.cs b/API/DTOs/OPDS/FeedCategory.cs index 3129fab60..2352b4af2 100644 --- a/API/DTOs/OPDS/FeedCategory.cs +++ b/API/DTOs/OPDS/FeedCategory.cs @@ -2,7 +2,7 @@ namespace API.DTOs.OPDS; -public class FeedCategory +public sealed record FeedCategory { [XmlAttribute("scheme")] public string Scheme { get; } = "http://www.bisg.org/standards/bisac_subject/index.html"; diff --git a/API/DTOs/OPDS/FeedEntry.cs b/API/DTOs/OPDS/FeedEntry.cs index da8b53b74..838ebd124 100644 --- a/API/DTOs/OPDS/FeedEntry.cs +++ b/API/DTOs/OPDS/FeedEntry.cs @@ -5,7 +5,7 @@ using System.Xml.Serialization; namespace API.DTOs.OPDS; #nullable enable -public class FeedEntry +public sealed record FeedEntry { [XmlElement("updated")] public string Updated { get; init; } = DateTime.UtcNow.ToString("s"); diff --git a/API/DTOs/OPDS/FeedEntryContent.cs b/API/DTOs/OPDS/FeedEntryContent.cs index 3e95ce643..4de9b73bd 100644 --- a/API/DTOs/OPDS/FeedEntryContent.cs +++ b/API/DTOs/OPDS/FeedEntryContent.cs @@ -2,7 +2,7 @@ namespace API.DTOs.OPDS; -public class FeedEntryContent +public sealed record FeedEntryContent { [XmlAttribute("type")] public string Type = "text"; diff --git a/API/DTOs/OPDS/FeedLink.cs b/API/DTOs/OPDS/FeedLink.cs index cff3b6736..28c55bbe8 100644 --- a/API/DTOs/OPDS/FeedLink.cs +++ b/API/DTOs/OPDS/FeedLink.cs @@ -3,7 +3,7 @@ using System.Xml.Serialization; namespace API.DTOs.OPDS; -public class FeedLink +public sealed record FeedLink { [XmlIgnore] public bool IsPageStream { get; set; } diff --git a/API/DTOs/OPDS/OpenSearchDescription.cs b/API/DTOs/OPDS/OpenSearchDescription.cs index cc8392a88..eba26572f 100644 --- a/API/DTOs/OPDS/OpenSearchDescription.cs +++ b/API/DTOs/OPDS/OpenSearchDescription.cs @@ -3,7 +3,7 @@ namespace API.DTOs.OPDS; [XmlRoot("OpenSearchDescription", Namespace = "http://a9.com/-/spec/opensearch/1.1/")] -public class OpenSearchDescription +public sealed record OpenSearchDescription { /// /// Contains a brief human-readable title that identifies this search engine. diff --git a/API/DTOs/OPDS/SearchLink.cs b/API/DTOs/OPDS/SearchLink.cs index dba67f3bd..b4698c221 100644 --- a/API/DTOs/OPDS/SearchLink.cs +++ b/API/DTOs/OPDS/SearchLink.cs @@ -2,7 +2,7 @@ namespace API.DTOs.OPDS; -public class SearchLink +public sealed record SearchLink { [XmlAttribute("type")] public string Type { get; set; } = default!; diff --git a/API/DTOs/Person/UpdatePersonDto.cs b/API/DTOs/Person/UpdatePersonDto.cs index d21fb7350..29190151f 100644 --- a/API/DTOs/Person/UpdatePersonDto.cs +++ b/API/DTOs/Person/UpdatePersonDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs; #nullable enable -public class UpdatePersonDto +public sealed record UpdatePersonDto { [Required] public int Id { get; init; } diff --git a/API/DTOs/Progress/FullProgressDto.cs b/API/DTOs/Progress/FullProgressDto.cs index 7d0b47f60..4f97ab44a 100644 --- a/API/DTOs/Progress/FullProgressDto.cs +++ b/API/DTOs/Progress/FullProgressDto.cs @@ -5,7 +5,7 @@ namespace API.DTOs.Progress; /// /// A full progress Record from the DB (not all data, only what's needed for API) /// -public class FullProgressDto +public sealed record FullProgressDto { public int Id { get; set; } public int ChapterId { get; set; } diff --git a/API/DTOs/Progress/ProgressDto.cs b/API/DTOs/Progress/ProgressDto.cs index 9fc9010aa..0add848c5 100644 --- a/API/DTOs/Progress/ProgressDto.cs +++ b/API/DTOs/Progress/ProgressDto.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations; namespace API.DTOs.Progress; #nullable enable -public class ProgressDto +public sealed record ProgressDto { [Required] public int VolumeId { get; set; } diff --git a/API/DTOs/RatingDto.cs b/API/DTOs/RatingDto.cs index e2cd9d342..101aa7ac5 100644 --- a/API/DTOs/RatingDto.cs +++ b/API/DTOs/RatingDto.cs @@ -1,12 +1,18 @@ -using API.Services.Plus; +using API.Entities; +using API.Entities.Enums; +using API.Entities.Metadata; +using API.Services.Plus; namespace API.DTOs; #nullable enable -public class RatingDto +public sealed record RatingDto { + public int AverageScore { get; set; } public int FavoriteCount { get; set; } public ScrobbleProvider Provider { get; set; } + /// + public RatingAuthority Authority { get; set; } = RatingAuthority.User; public string? ProviderUrl { get; set; } } diff --git a/API/DTOs/Reader/BookChapterItem.cs b/API/DTOs/Reader/BookChapterItem.cs index dcfb7b904..892e82e27 100644 --- a/API/DTOs/Reader/BookChapterItem.cs +++ b/API/DTOs/Reader/BookChapterItem.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Reader; -public class BookChapterItem +public sealed record BookChapterItem { /// /// Name of the Chapter diff --git a/API/DTOs/Reader/BookInfoDto.cs b/API/DTOs/Reader/BookInfoDto.cs index 3e5cc30dd..5c4e530c6 100644 --- a/API/DTOs/Reader/BookInfoDto.cs +++ b/API/DTOs/Reader/BookInfoDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Reader; -public class BookInfoDto : IChapterInfoDto +public sealed record BookInfoDto : IChapterInfoDto { public string BookTitle { get; set; } = default! ; public int SeriesId { get; set; } diff --git a/API/DTOs/Reader/BookmarkDto.cs b/API/DTOs/Reader/BookmarkDto.cs index ef4cf3d6d..da18fc28e 100644 --- a/API/DTOs/Reader/BookmarkDto.cs +++ b/API/DTOs/Reader/BookmarkDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.Reader; #nullable enable -public class BookmarkDto +public sealed record BookmarkDto { public int Id { get; set; } [Required] diff --git a/API/DTOs/Reader/BulkRemoveBookmarkForSeriesDto.cs b/API/DTOs/Reader/BulkRemoveBookmarkForSeriesDto.cs index 7490f837c..51ccf5cc3 100644 --- a/API/DTOs/Reader/BulkRemoveBookmarkForSeriesDto.cs +++ b/API/DTOs/Reader/BulkRemoveBookmarkForSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Reader; -public class BulkRemoveBookmarkForSeriesDto +public sealed record BulkRemoveBookmarkForSeriesDto { public ICollection SeriesIds { get; init; } = default!; } diff --git a/API/DTOs/Reader/ChapterInfoDto.cs b/API/DTOs/Reader/ChapterInfoDto.cs index 4584a5830..4da08a31d 100644 --- a/API/DTOs/Reader/ChapterInfoDto.cs +++ b/API/DTOs/Reader/ChapterInfoDto.cs @@ -7,7 +7,7 @@ namespace API.DTOs.Reader; /// /// Information about the Chapter for the Reader to render /// -public class ChapterInfoDto : IChapterInfoDto +public sealed record ChapterInfoDto : IChapterInfoDto { /// /// The Chapter Number diff --git a/API/DTOs/Reader/CreatePersonalToCDto.cs b/API/DTOs/Reader/CreatePersonalToCDto.cs index 3b80ece4a..95272ca58 100644 --- a/API/DTOs/Reader/CreatePersonalToCDto.cs +++ b/API/DTOs/Reader/CreatePersonalToCDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.Reader; #nullable enable -public class CreatePersonalToCDto +public sealed record CreatePersonalToCDto { public required int ChapterId { get; set; } public required int VolumeId { get; set; } diff --git a/API/DTOs/Reader/FileDimensionDto.cs b/API/DTOs/Reader/FileDimensionDto.cs index baee20dd0..7a7d2978f 100644 --- a/API/DTOs/Reader/FileDimensionDto.cs +++ b/API/DTOs/Reader/FileDimensionDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Reader; -public class FileDimensionDto +public sealed record FileDimensionDto { public int Width { get; set; } public int Height { get; set; } diff --git a/API/DTOs/Reader/HourEstimateRangeDto.cs b/API/DTOs/Reader/HourEstimateRangeDto.cs index 8c8bd11a9..3facf8e56 100644 --- a/API/DTOs/Reader/HourEstimateRangeDto.cs +++ b/API/DTOs/Reader/HourEstimateRangeDto.cs @@ -3,7 +3,7 @@ /// /// A range of time to read a selection (series, chapter, etc) /// -public record HourEstimateRangeDto +public sealed record HourEstimateRangeDto { /// /// Min hours to read the selection diff --git a/API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs b/API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs index 50187ec81..4c39f7d76 100644 --- a/API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs +++ b/API/DTOs/Reader/MarkMultipleSeriesAsReadDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Reader; -public class MarkMultipleSeriesAsReadDto +public sealed record MarkMultipleSeriesAsReadDto { public IReadOnlyList SeriesIds { get; init; } = default!; } diff --git a/API/DTOs/Reader/MarkReadDto.cs b/API/DTOs/Reader/MarkReadDto.cs index 9bf46a6d5..c6f7367c0 100644 --- a/API/DTOs/Reader/MarkReadDto.cs +++ b/API/DTOs/Reader/MarkReadDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Reader; -public class MarkReadDto +public sealed record MarkReadDto { public int SeriesId { get; init; } } diff --git a/API/DTOs/Reader/MarkVolumeReadDto.cs b/API/DTOs/Reader/MarkVolumeReadDto.cs index 47ffd2649..be95d2e98 100644 --- a/API/DTOs/Reader/MarkVolumeReadDto.cs +++ b/API/DTOs/Reader/MarkVolumeReadDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Reader; -public class MarkVolumeReadDto +public sealed record MarkVolumeReadDto { public int SeriesId { get; init; } public int VolumeId { get; init; } diff --git a/API/DTOs/Reader/MarkVolumesReadDto.cs b/API/DTOs/Reader/MarkVolumesReadDto.cs index ebe1cd76c..b07bfbc67 100644 --- a/API/DTOs/Reader/MarkVolumesReadDto.cs +++ b/API/DTOs/Reader/MarkVolumesReadDto.cs @@ -5,7 +5,7 @@ namespace API.DTOs.Reader; /// /// This is used for bulk updating a set of volume and or chapters in one go /// -public class MarkVolumesReadDto +public sealed record MarkVolumesReadDto { public int SeriesId { get; set; } /// diff --git a/API/DTOs/Reader/PersonalToCDto.cs b/API/DTOs/Reader/PersonalToCDto.cs index 144ed561f..c979d9d78 100644 --- a/API/DTOs/Reader/PersonalToCDto.cs +++ b/API/DTOs/Reader/PersonalToCDto.cs @@ -2,7 +2,7 @@ #nullable enable -public class PersonalToCDto +public sealed record PersonalToCDto { public required int ChapterId { get; set; } public required int PageNumber { get; set; } diff --git a/API/DTOs/Reader/RemoveBookmarkForSeriesDto.cs b/API/DTOs/Reader/RemoveBookmarkForSeriesDto.cs index ed6368a4f..ecbb744c8 100644 --- a/API/DTOs/Reader/RemoveBookmarkForSeriesDto.cs +++ b/API/DTOs/Reader/RemoveBookmarkForSeriesDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Reader; -public class RemoveBookmarkForSeriesDto +public sealed record RemoveBookmarkForSeriesDto { public int SeriesId { get; init; } } diff --git a/API/DTOs/ReadingLists/CBL/CblBook.cs b/API/DTOs/ReadingLists/CBL/CblBook.cs index 08930e208..d51795b8d 100644 --- a/API/DTOs/ReadingLists/CBL/CblBook.cs +++ b/API/DTOs/ReadingLists/CBL/CblBook.cs @@ -5,7 +5,7 @@ namespace API.DTOs.ReadingLists.CBL; [XmlRoot(ElementName="Book")] -public class CblBook +public sealed record CblBook { [XmlAttribute("Series")] public string Series { get; set; } diff --git a/API/DTOs/ReadingLists/CBL/CblConflictsDto.cs b/API/DTOs/ReadingLists/CBL/CblConflictsDto.cs index 70a002884..35234923f 100644 --- a/API/DTOs/ReadingLists/CBL/CblConflictsDto.cs +++ b/API/DTOs/ReadingLists/CBL/CblConflictsDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.ReadingLists.CBL; -public class CblConflictQuestion +public sealed record CblConflictQuestion { public string SeriesName { get; set; } public IList LibrariesIds { get; set; } diff --git a/API/DTOs/ReadingLists/CBL/CblImportSummary.cs b/API/DTOs/ReadingLists/CBL/CblImportSummary.cs index 136a31aa8..b9716421e 100644 --- a/API/DTOs/ReadingLists/CBL/CblImportSummary.cs +++ b/API/DTOs/ReadingLists/CBL/CblImportSummary.cs @@ -75,7 +75,7 @@ public enum CblImportReason InvalidFile = 9, } -public class CblBookResult +public sealed record CblBookResult { /// /// Order in the CBL @@ -114,7 +114,7 @@ public class CblBookResult /// /// Represents the summary from the Import of a given CBL /// -public class CblImportSummaryDto +public sealed record CblImportSummaryDto { public string CblName { get; set; } /// diff --git a/API/DTOs/ReadingLists/CBL/CblReadingList.cs b/API/DTOs/ReadingLists/CBL/CblReadingList.cs index 001e6434b..15b349f42 100644 --- a/API/DTOs/ReadingLists/CBL/CblReadingList.cs +++ b/API/DTOs/ReadingLists/CBL/CblReadingList.cs @@ -5,7 +5,7 @@ namespace API.DTOs.ReadingLists.CBL; [XmlRoot(ElementName="Books")] -public class CblBooks +public sealed record CblBooks { [XmlElement(ElementName="Book")] public List Book { get; set; } @@ -13,7 +13,7 @@ public class CblBooks [XmlRoot(ElementName="ReadingList")] -public class CblReadingList +public sealed record CblReadingList { /// /// Name of the Reading List diff --git a/API/DTOs/ReadingLists/CreateReadingListDto.cs b/API/DTOs/ReadingLists/CreateReadingListDto.cs index 783253007..543215722 100644 --- a/API/DTOs/ReadingLists/CreateReadingListDto.cs +++ b/API/DTOs/ReadingLists/CreateReadingListDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.ReadingLists; -public class CreateReadingListDto +public sealed record CreateReadingListDto { public string Title { get; init; } = default!; } diff --git a/API/DTOs/ReadingLists/DeleteReadingListsDto.cs b/API/DTOs/ReadingLists/DeleteReadingListsDto.cs index 8417f8132..8ce92f939 100644 --- a/API/DTOs/ReadingLists/DeleteReadingListsDto.cs +++ b/API/DTOs/ReadingLists/DeleteReadingListsDto.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; namespace API.DTOs.ReadingLists; -public class DeleteReadingListsDto +public sealed record DeleteReadingListsDto { [Required] public IList ReadingListIds { get; set; } diff --git a/API/DTOs/ReadingLists/PromoteReadingListsDto.cs b/API/DTOs/ReadingLists/PromoteReadingListsDto.cs index f64bbb5ca..8915274de 100644 --- a/API/DTOs/ReadingLists/PromoteReadingListsDto.cs +++ b/API/DTOs/ReadingLists/PromoteReadingListsDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.ReadingLists; -public class PromoteReadingListsDto +public sealed record PromoteReadingListsDto { public IList ReadingListIds { get; init; } public bool Promoted { get; init; } diff --git a/API/DTOs/ReadingLists/ReadingListCast.cs b/API/DTOs/ReadingLists/ReadingListCast.cs index 4532df7d5..8f2587426 100644 --- a/API/DTOs/ReadingLists/ReadingListCast.cs +++ b/API/DTOs/ReadingLists/ReadingListCast.cs @@ -2,7 +2,7 @@ namespace API.DTOs.ReadingLists; -public class ReadingListCast +public sealed record ReadingListCast { public ICollection Writers { get; set; } = []; public ICollection CoverArtists { get; set; } = []; diff --git a/API/DTOs/ReadingLists/ReadingListDto.cs b/API/DTOs/ReadingLists/ReadingListDto.cs index 6508e7bd4..cbc16275d 100644 --- a/API/DTOs/ReadingLists/ReadingListDto.cs +++ b/API/DTOs/ReadingLists/ReadingListDto.cs @@ -5,7 +5,7 @@ using API.Entities.Interfaces; namespace API.DTOs.ReadingLists; #nullable enable -public class ReadingListDto : IHasCoverImage +public sealed record ReadingListDto : IHasCoverImage { public int Id { get; init; } public string Title { get; set; } = default!; @@ -20,8 +20,8 @@ public class ReadingListDto : IHasCoverImage /// public string? CoverImage { get; set; } = string.Empty; - public string PrimaryColor { get; set; } = string.Empty; - public string SecondaryColor { get; set; } = string.Empty; + public string? PrimaryColor { get; set; } = string.Empty; + public string? SecondaryColor { get; set; } = string.Empty; /// /// Number of Items in the Reading List diff --git a/API/DTOs/ReadingLists/ReadingListInfoDto.cs b/API/DTOs/ReadingLists/ReadingListInfoDto.cs index bd95b9226..64a305f43 100644 --- a/API/DTOs/ReadingLists/ReadingListInfoDto.cs +++ b/API/DTOs/ReadingLists/ReadingListInfoDto.cs @@ -3,7 +3,7 @@ using API.Entities.Interfaces; namespace API.DTOs.ReadingLists; -public class ReadingListInfoDto : IHasReadTimeEstimate +public sealed record ReadingListInfoDto : IHasReadTimeEstimate { /// /// Total Pages across all Reading List Items diff --git a/API/DTOs/ReadingLists/ReadingListItemDto.cs b/API/DTOs/ReadingLists/ReadingListItemDto.cs index 4fca5360c..8edec14f1 100644 --- a/API/DTOs/ReadingLists/ReadingListItemDto.cs +++ b/API/DTOs/ReadingLists/ReadingListItemDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs.ReadingLists; #nullable enable -public class ReadingListItemDto +public sealed record ReadingListItemDto { public int Id { get; init; } public int Order { get; init; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListByChapterDto.cs b/API/DTOs/ReadingLists/UpdateReadingListByChapterDto.cs index 985f86ac0..6624c8a5c 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListByChapterDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListByChapterDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListByChapterDto +public sealed record UpdateReadingListByChapterDto { public int ChapterId { get; init; } public int SeriesId { get; init; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs b/API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs index 408963529..ba7625088 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListByMultipleDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListByMultipleDto +public sealed record UpdateReadingListByMultipleDto { public int SeriesId { get; init; } public int ReadingListId { get; init; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListByMultipleSeriesDto.cs b/API/DTOs/ReadingLists/UpdateReadingListByMultipleSeriesDto.cs index f910e9c06..910a5744d 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListByMultipleSeriesDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListByMultipleSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListByMultipleSeriesDto +public sealed record UpdateReadingListByMultipleSeriesDto { public int ReadingListId { get; init; } public IReadOnlyList SeriesIds { get; init; } = default!; diff --git a/API/DTOs/ReadingLists/UpdateReadingListBySeriesDto.cs b/API/DTOs/ReadingLists/UpdateReadingListBySeriesDto.cs index 0590882bd..4bb4aa7bb 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListBySeriesDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListBySeriesDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListBySeriesDto +public sealed record UpdateReadingListBySeriesDto { public int SeriesId { get; init; } public int ReadingListId { get; init; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListByVolumeDto.cs b/API/DTOs/ReadingLists/UpdateReadingListByVolumeDto.cs index f77c7d63a..422d1cc34 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListByVolumeDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListByVolumeDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListByVolumeDto +public sealed record UpdateReadingListByVolumeDto { public int VolumeId { get; init; } public int SeriesId { get; init; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListDto.cs b/API/DTOs/ReadingLists/UpdateReadingListDto.cs index 6b590707a..de273d825 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListDto.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.ReadingLists; -public class UpdateReadingListDto +public sealed record UpdateReadingListDto { [Required] public int ReadingListId { get; set; } diff --git a/API/DTOs/ReadingLists/UpdateReadingListPosition.cs b/API/DTOs/ReadingLists/UpdateReadingListPosition.cs index 3d0487144..04f2501a8 100644 --- a/API/DTOs/ReadingLists/UpdateReadingListPosition.cs +++ b/API/DTOs/ReadingLists/UpdateReadingListPosition.cs @@ -5,7 +5,7 @@ namespace API.DTOs.ReadingLists; /// /// DTO for moving a reading list item to another position within the same list /// -public class UpdateReadingListPosition +public sealed record UpdateReadingListPosition { [Required] public int ReadingListId { get; set; } [Required] public int ReadingListItemId { get; set; } diff --git a/API/DTOs/Recommendation/ExternalSeriesDto.cs b/API/DTOs/Recommendation/ExternalSeriesDto.cs index d393443af..752001a39 100644 --- a/API/DTOs/Recommendation/ExternalSeriesDto.cs +++ b/API/DTOs/Recommendation/ExternalSeriesDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.Recommendation; #nullable enable -public class ExternalSeriesDto +public sealed record ExternalSeriesDto { public required string Name { get; set; } public required string CoverUrl { get; set; } diff --git a/API/DTOs/Recommendation/MetadataTagDto.cs b/API/DTOs/Recommendation/MetadataTagDto.cs index b219dedc1..a7eb76284 100644 --- a/API/DTOs/Recommendation/MetadataTagDto.cs +++ b/API/DTOs/Recommendation/MetadataTagDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Recommendation; -public class MetadataTagDto +public sealed record MetadataTagDto { public string Name { get; set; } public string Description { get; private set; } diff --git a/API/DTOs/Recommendation/RecommendationDto.cs b/API/DTOs/Recommendation/RecommendationDto.cs index 679245a87..387661324 100644 --- a/API/DTOs/Recommendation/RecommendationDto.cs +++ b/API/DTOs/Recommendation/RecommendationDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Recommendation; -public class RecommendationDto +public sealed record RecommendationDto { public IList OwnedSeries { get; set; } = new List(); public IList ExternalSeries { get; set; } = new List(); diff --git a/API/DTOs/Recommendation/SeriesStaffDto.cs b/API/DTOs/Recommendation/SeriesStaffDto.cs index e4c6f6423..e074e8625 100644 --- a/API/DTOs/Recommendation/SeriesStaffDto.cs +++ b/API/DTOs/Recommendation/SeriesStaffDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.Recommendation; #nullable enable -public class SeriesStaffDto +public sealed record SeriesStaffDto { public required string Name { get; set; } public string? FirstName { get; set; } diff --git a/API/DTOs/RefreshSeriesDto.cs b/API/DTOs/RefreshSeriesDto.cs index 0e94fc44b..ad26afba2 100644 --- a/API/DTOs/RefreshSeriesDto.cs +++ b/API/DTOs/RefreshSeriesDto.cs @@ -3,7 +3,7 @@ /// /// Used for running some task against a Series. /// -public class RefreshSeriesDto +public sealed record RefreshSeriesDto { /// /// Library Id series belongs to diff --git a/API/DTOs/RegisterDto.cs b/API/DTOs/RegisterDto.cs index 2d4d3b77f..e117af872 100644 --- a/API/DTOs/RegisterDto.cs +++ b/API/DTOs/RegisterDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs; #nullable enable -public class RegisterDto +public sealed record RegisterDto { [Required] public string Username { get; init; } = default!; diff --git a/API/DTOs/ScanFolderDto.cs b/API/DTOs/ScanFolderDto.cs index 684de909e..141f7f0b5 100644 --- a/API/DTOs/ScanFolderDto.cs +++ b/API/DTOs/ScanFolderDto.cs @@ -3,7 +3,7 @@ /// /// DTO for requesting a folder to be scanned /// -public class ScanFolderDto +public sealed record ScanFolderDto { /// /// Api key for a user with Admin permissions diff --git a/API/DTOs/Scrobbling/MalUserInfoDto.cs b/API/DTOs/Scrobbling/MalUserInfoDto.cs index 407639e2a..b6fefc053 100644 --- a/API/DTOs/Scrobbling/MalUserInfoDto.cs +++ b/API/DTOs/Scrobbling/MalUserInfoDto.cs @@ -3,7 +3,7 @@ /// /// Information about a User's MAL connection /// -public class MalUserInfoDto +public sealed record MalUserInfoDto { public required string Username { get; set; } /// diff --git a/API/DTOs/Scrobbling/MediaRecommendationDto.cs b/API/DTOs/Scrobbling/MediaRecommendationDto.cs index 3f565296b..476d77279 100644 --- a/API/DTOs/Scrobbling/MediaRecommendationDto.cs +++ b/API/DTOs/Scrobbling/MediaRecommendationDto.cs @@ -4,7 +4,7 @@ using API.Services.Plus; namespace API.DTOs.Scrobbling; #nullable enable -public record MediaRecommendationDto +public sealed record MediaRecommendationDto { public int Rating { get; set; } public IEnumerable RecommendationNames { get; set; } = null!; diff --git a/API/DTOs/Scrobbling/PlusSeriesDto.cs b/API/DTOs/Scrobbling/PlusSeriesDto.cs index dca9aca92..4d0ef4ea1 100644 --- a/API/DTOs/Scrobbling/PlusSeriesDto.cs +++ b/API/DTOs/Scrobbling/PlusSeriesDto.cs @@ -4,7 +4,7 @@ /// /// Represents information about a potential Series for Kavita+ /// -public record PlusSeriesRequestDto +public sealed record PlusSeriesRequestDto { public int? AniListId { get; set; } public long? MalId { get; set; } diff --git a/API/DTOs/Scrobbling/ScrobbleDto.cs b/API/DTOs/Scrobbling/ScrobbleDto.cs index e8420e785..b90441059 100644 --- a/API/DTOs/Scrobbling/ScrobbleDto.cs +++ b/API/DTOs/Scrobbling/ScrobbleDto.cs @@ -36,7 +36,7 @@ public enum PlusMediaFormat } -public class ScrobbleDto +public sealed record ScrobbleDto { /// /// User's access token to allow us to talk on their behalf diff --git a/API/DTOs/Scrobbling/ScrobbleErrorDto.cs b/API/DTOs/Scrobbling/ScrobbleErrorDto.cs index da85f28f1..7caaad1ca 100644 --- a/API/DTOs/Scrobbling/ScrobbleErrorDto.cs +++ b/API/DTOs/Scrobbling/ScrobbleErrorDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Scrobbling; -public class ScrobbleErrorDto +public sealed record ScrobbleErrorDto { /// /// Developer defined string diff --git a/API/DTOs/Scrobbling/ScrobbleEventDto.cs b/API/DTOs/Scrobbling/ScrobbleEventDto.cs index b62c87866..7b1ccd75a 100644 --- a/API/DTOs/Scrobbling/ScrobbleEventDto.cs +++ b/API/DTOs/Scrobbling/ScrobbleEventDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.Scrobbling; #nullable enable -public class ScrobbleEventDto +public sealed record ScrobbleEventDto { public string SeriesName { get; set; } public int SeriesId { get; set; } diff --git a/API/DTOs/Scrobbling/ScrobbleHoldDto.cs b/API/DTOs/Scrobbling/ScrobbleHoldDto.cs index dcfe7726f..3e09e4799 100644 --- a/API/DTOs/Scrobbling/ScrobbleHoldDto.cs +++ b/API/DTOs/Scrobbling/ScrobbleHoldDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Scrobbling; -public class ScrobbleHoldDto +public sealed record ScrobbleHoldDto { public string SeriesName { get; set; } public int SeriesId { get; set; } diff --git a/API/DTOs/Scrobbling/ScrobbleResponseDto.cs b/API/DTOs/Scrobbling/ScrobbleResponseDto.cs index a63e955d7..53d3a0cc9 100644 --- a/API/DTOs/Scrobbling/ScrobbleResponseDto.cs +++ b/API/DTOs/Scrobbling/ScrobbleResponseDto.cs @@ -4,7 +4,7 @@ /// /// Response from Kavita+ Scrobble API /// -public class ScrobbleResponseDto +public sealed record ScrobbleResponseDto { public bool Successful { get; set; } public string? ErrorMessage { get; set; } diff --git a/API/DTOs/Search/BookmarkSearchResultDto.cs b/API/DTOs/Search/BookmarkSearchResultDto.cs index 5d53add1f..c11d2a2b8 100644 --- a/API/DTOs/Search/BookmarkSearchResultDto.cs +++ b/API/DTOs/Search/BookmarkSearchResultDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Search; -public class BookmarkSearchResultDto +public sealed record BookmarkSearchResultDto { public int LibraryId { get; set; } public int VolumeId { get; set; } diff --git a/API/DTOs/Search/SearchResultDto.cs b/API/DTOs/Search/SearchResultDto.cs index 6fcae3b5d..c497b55dd 100644 --- a/API/DTOs/Search/SearchResultDto.cs +++ b/API/DTOs/Search/SearchResultDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Search; -public class SearchResultDto +public sealed record SearchResultDto { public int SeriesId { get; init; } public string Name { get; init; } = default!; diff --git a/API/DTOs/Search/SearchResultGroupDto.cs b/API/DTOs/Search/SearchResultGroupDto.cs index f7a622664..20a53f853 100644 --- a/API/DTOs/Search/SearchResultGroupDto.cs +++ b/API/DTOs/Search/SearchResultGroupDto.cs @@ -10,7 +10,7 @@ namespace API.DTOs.Search; /// /// Represents all Search results for a query /// -public class SearchResultGroupDto +public sealed record SearchResultGroupDto { public IEnumerable Libraries { get; set; } = default!; public IEnumerable Series { get; set; } = default!; diff --git a/API/DTOs/SeriesByIdsDto.cs b/API/DTOs/SeriesByIdsDto.cs index 12e13d96f..cb4c52b1e 100644 --- a/API/DTOs/SeriesByIdsDto.cs +++ b/API/DTOs/SeriesByIdsDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs; -public class SeriesByIdsDto +public sealed record SeriesByIdsDto { public int[] SeriesIds { get; init; } = default!; } diff --git a/API/DTOs/SeriesDetail/NextExpectedChapterDto.cs b/API/DTOs/SeriesDetail/NextExpectedChapterDto.cs index 0f1a8eb4b..1bea81c84 100644 --- a/API/DTOs/SeriesDetail/NextExpectedChapterDto.cs +++ b/API/DTOs/SeriesDetail/NextExpectedChapterDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.SeriesDetail; -public class NextExpectedChapterDto +public sealed record NextExpectedChapterDto { public float ChapterNumber { get; set; } public float VolumeNumber { get; set; } diff --git a/API/DTOs/SeriesDetail/RelatedSeriesDto.cs b/API/DTOs/SeriesDetail/RelatedSeriesDto.cs index 29b9eb263..a186dc295 100644 --- a/API/DTOs/SeriesDetail/RelatedSeriesDto.cs +++ b/API/DTOs/SeriesDetail/RelatedSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.SeriesDetail; -public class RelatedSeriesDto +public sealed record RelatedSeriesDto { /// /// The parent relationship Series diff --git a/API/DTOs/SeriesDetail/SeriesDetailDto.cs b/API/DTOs/SeriesDetail/SeriesDetailDto.cs index 65d657c67..c4f15552d 100644 --- a/API/DTOs/SeriesDetail/SeriesDetailDto.cs +++ b/API/DTOs/SeriesDetail/SeriesDetailDto.cs @@ -7,7 +7,7 @@ namespace API.DTOs.SeriesDetail; /// This is a special DTO for a UI page in Kavita. This performs sorting and grouping and returns exactly what UI requires for layout. /// This is subject to change, do not rely on this Data model. /// -public class SeriesDetailDto +public sealed record SeriesDetailDto { /// /// Specials for the Series. These will have their title and range cleaned to remove the special marker and prepare diff --git a/API/DTOs/SeriesDetail/SeriesDetailPlusDto.cs b/API/DTOs/SeriesDetail/SeriesDetailPlusDto.cs index 76e77ae2c..95f5f39bd 100644 --- a/API/DTOs/SeriesDetail/SeriesDetailPlusDto.cs +++ b/API/DTOs/SeriesDetail/SeriesDetailPlusDto.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Recommendation; namespace API.DTOs.SeriesDetail; @@ -8,7 +9,7 @@ namespace API.DTOs.SeriesDetail; /// All the data from Kavita+ for Series Detail /// /// This is what the UI sees, not what the API sends back -public class SeriesDetailPlusDto +public sealed record SeriesDetailPlusDto { public RecommendationDto? Recommendations { get; set; } public IEnumerable Reviews { get; set; } diff --git a/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs b/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs index f19ad9ca8..a1bb2057e 100644 --- a/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs +++ b/API/DTOs/SeriesDetail/UpdateRelatedSeriesDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.SeriesDetail; -public class UpdateRelatedSeriesDto +public sealed record UpdateRelatedSeriesDto { public int SeriesId { get; set; } public IList Adaptations { get; set; } = default!; diff --git a/API/DTOs/SeriesDetail/UpdateUserReviewDto.cs b/API/DTOs/SeriesDetail/UpdateUserReviewDto.cs index b25b01672..7af9441c1 100644 --- a/API/DTOs/SeriesDetail/UpdateUserReviewDto.cs +++ b/API/DTOs/SeriesDetail/UpdateUserReviewDto.cs @@ -1,10 +1,10 @@ -using System.ComponentModel.DataAnnotations; - + namespace API.DTOs.SeriesDetail; #nullable enable -public class UpdateUserReviewDto +public sealed record UpdateUserReviewDto { public int SeriesId { get; set; } + public int? ChapterId { get; set; } public string Body { get; set; } } diff --git a/API/DTOs/SeriesDetail/UserReviewDto.cs b/API/DTOs/SeriesDetail/UserReviewDto.cs index 0e080d43f..9e05bbd65 100644 --- a/API/DTOs/SeriesDetail/UserReviewDto.cs +++ b/API/DTOs/SeriesDetail/UserReviewDto.cs @@ -1,4 +1,6 @@ -using API.Services.Plus; +using API.Entities; +using API.Entities.Enums; +using API.Services.Plus; namespace API.DTOs.SeriesDetail; #nullable enable @@ -7,7 +9,7 @@ namespace API.DTOs.SeriesDetail; /// Represents a User Review for a given Series /// /// The user does not need to be a Kavita user -public class UserReviewDto +public sealed record UserReviewDto { /// /// A tagline for the review @@ -26,6 +28,7 @@ public class UserReviewDto /// The series this is for /// public int SeriesId { get; set; } + public int? ChapterId { get; set; } /// /// The library this series belongs in /// @@ -54,4 +57,8 @@ public class UserReviewDto /// If this review is External, which Provider did it come from /// public ScrobbleProvider Provider { get; set; } = ScrobbleProvider.Kavita; + /// + /// Source of the Rating + /// + public RatingAuthority Authority { get; set; } = RatingAuthority.User; } diff --git a/API/DTOs/SeriesDto.cs b/API/DTOs/SeriesDto.cs index 6aa1ecefd..8a49d4c05 100644 --- a/API/DTOs/SeriesDto.cs +++ b/API/DTOs/SeriesDto.cs @@ -5,14 +5,21 @@ using API.Entities.Interfaces; namespace API.DTOs; #nullable enable -public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage +public sealed record SeriesDto : IHasReadTimeEstimate, IHasCoverImage { + /// public int Id { get; init; } + /// public string? Name { get; init; } + /// public string? OriginalName { get; init; } + /// public string? LocalizedName { get; init; } + /// public string? SortName { get; init; } + /// public int Pages { get; init; } + /// public bool CoverImageLocked { get; set; } /// /// Sum of pages read from linked Volumes. Calculated at API-time. @@ -22,9 +29,7 @@ public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage /// DateTime representing last time the series was Read. Calculated at API-time. /// public DateTime LatestReadDate { get; set; } - /// - /// DateTime representing last time a chapter was added to the Series - /// + /// public DateTime LastChapterAdded { get; set; } /// /// Rating from logged in user. Calculated at API-time. @@ -35,17 +40,19 @@ public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage /// public bool HasUserRated { get; set; } + /// public MangaFormat Format { get; set; } + /// public DateTime Created { get; set; } - public bool NameLocked { get; set; } + /// public bool SortNameLocked { get; set; } + /// public bool LocalizedNameLocked { get; set; } - /// - /// Total number of words for the series. Only applies to epubs. - /// + /// public long WordCount { get; set; } + /// public int LibraryId { get; set; } public string LibraryName { get; set; } = default!; /// @@ -54,33 +61,25 @@ public class SeriesDto : IHasReadTimeEstimate, IHasCoverImage public int MaxHoursToRead { get; set; } /// public float AvgHoursToRead { get; set; } - /// - /// The highest level folder for this Series - /// + /// public string FolderPath { get; set; } = default!; - /// - /// Lowest path (that is under library root) that contains all files for the series. - /// - /// must be used before setting + /// public string? LowestFolderPath { get; set; } - /// - /// The last time the folder for this series was scanned - /// + /// public DateTime LastFolderScanned { get; set; } #region KavitaPlus - /// - /// Do not match the series with any external Metadata service. This will automatically opt it out of scrobbling. - /// + /// public bool DontMatch { get; set; } - /// - /// If the series was unable to match, it will be blacklisted until a manual metadata match overrides it - /// + /// public bool IsBlacklisted { get; set; } #endregion + /// public string? CoverImage { get; set; } - public string PrimaryColor { get; set; } = string.Empty; - public string SecondaryColor { get; set; } = string.Empty; + /// + public string? PrimaryColor { get; set; } = string.Empty; + /// + public string? SecondaryColor { get; set; } = string.Empty; public void ResetColorScape() { diff --git a/API/DTOs/SeriesMetadataDto.cs b/API/DTOs/SeriesMetadataDto.cs index 3f344dff5..701034d80 100644 --- a/API/DTOs/SeriesMetadataDto.cs +++ b/API/DTOs/SeriesMetadataDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs; -public class SeriesMetadataDto +public sealed record SeriesMetadataDto { public int Id { get; set; } public string Summary { get; set; } = string.Empty; diff --git a/API/DTOs/Settings/SMTPConfigDto.cs b/API/DTOs/Settings/SMTPConfigDto.cs index 07cc58cb8..c14140062 100644 --- a/API/DTOs/Settings/SMTPConfigDto.cs +++ b/API/DTOs/Settings/SMTPConfigDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Settings; -public class SmtpConfigDto +public sealed record SmtpConfigDto { public string SenderAddress { get; set; } = string.Empty; public string SenderDisplayName { get; set; } = string.Empty; diff --git a/API/DTOs/Settings/ServerSettingDTO.cs b/API/DTOs/Settings/ServerSettingDTO.cs index 78db88d7d..372042250 100644 --- a/API/DTOs/Settings/ServerSettingDTO.cs +++ b/API/DTOs/Settings/ServerSettingDTO.cs @@ -6,7 +6,7 @@ using API.Services; namespace API.DTOs.Settings; #nullable enable -public class ServerSettingDto +public sealed record ServerSettingDto { public string CacheDirectory { get; set; } = default!; diff --git a/API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs b/API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs index 1b081913d..ae1d927a9 100644 --- a/API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs +++ b/API/DTOs/SideNav/BulkUpdateSideNavStreamVisibilityDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.SideNav; -public class BulkUpdateSideNavStreamVisibilityDto +public sealed record BulkUpdateSideNavStreamVisibilityDto { public required IList Ids { get; set; } public required bool Visibility { get; set; } diff --git a/API/DTOs/SideNav/ExternalSourceDto.cs b/API/DTOs/SideNav/ExternalSourceDto.cs index e9ae03066..382124e8a 100644 --- a/API/DTOs/SideNav/ExternalSourceDto.cs +++ b/API/DTOs/SideNav/ExternalSourceDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.SideNav; -public class ExternalSourceDto +public sealed record ExternalSourceDto { public required int Id { get; set; } = 0; public required string Name { get; set; } diff --git a/API/DTOs/SideNav/SideNavStreamDto.cs b/API/DTOs/SideNav/SideNavStreamDto.cs index fdef82a08..f4c196244 100644 --- a/API/DTOs/SideNav/SideNavStreamDto.cs +++ b/API/DTOs/SideNav/SideNavStreamDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs.SideNav; #nullable enable -public class SideNavStreamDto +public sealed record SideNavStreamDto { public int Id { get; set; } public required string Name { get; set; } diff --git a/API/DTOs/Statistics/Count.cs b/API/DTOs/Statistics/Count.cs index 411b44897..1577e682c 100644 --- a/API/DTOs/Statistics/Count.cs +++ b/API/DTOs/Statistics/Count.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Statistics; -public class StatCount : ICount +public sealed record StatCount : ICount { public T Value { get; set; } = default!; public long Count { get; set; } diff --git a/API/DTOs/Statistics/FileExtensionBreakdownDto.cs b/API/DTOs/Statistics/FileExtensionBreakdownDto.cs index 1f122d992..7a248caef 100644 --- a/API/DTOs/Statistics/FileExtensionBreakdownDto.cs +++ b/API/DTOs/Statistics/FileExtensionBreakdownDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs.Statistics; #nullable enable -public class FileExtensionDto +public sealed record FileExtensionDto { public string? Extension { get; set; } public MangaFormat Format { get; set; } @@ -12,7 +12,7 @@ public class FileExtensionDto public long TotalFiles { get; set; } } -public class FileExtensionBreakdownDto +public sealed record FileExtensionBreakdownDto { /// /// Total bytes for all files diff --git a/API/DTOs/Statistics/PagesReadOnADayCount.cs b/API/DTOs/Statistics/PagesReadOnADayCount.cs index b1a6bb1ea..fc56d9cc0 100644 --- a/API/DTOs/Statistics/PagesReadOnADayCount.cs +++ b/API/DTOs/Statistics/PagesReadOnADayCount.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Statistics; -public class PagesReadOnADayCount : ICount +public sealed record PagesReadOnADayCount : ICount { /// /// The day of the readings diff --git a/API/DTOs/Statistics/ReadHistoryEvent.cs b/API/DTOs/Statistics/ReadHistoryEvent.cs index 496148789..5d8262aef 100644 --- a/API/DTOs/Statistics/ReadHistoryEvent.cs +++ b/API/DTOs/Statistics/ReadHistoryEvent.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Statistics; /// /// Represents a single User's reading event /// -public class ReadHistoryEvent +public sealed record ReadHistoryEvent { public int UserId { get; set; } public required string? UserName { get; set; } = default!; diff --git a/API/DTOs/Statistics/ServerStatisticsDto.cs b/API/DTOs/Statistics/ServerStatisticsDto.cs index 57fd5abce..3d22d9a56 100644 --- a/API/DTOs/Statistics/ServerStatisticsDto.cs +++ b/API/DTOs/Statistics/ServerStatisticsDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs.Statistics; #nullable enable -public class ServerStatisticsDto +public sealed record ServerStatisticsDto { public long ChapterCount { get; set; } public long VolumeCount { get; set; } diff --git a/API/DTOs/Statistics/TopReadsDto.cs b/API/DTOs/Statistics/TopReadsDto.cs index 806360533..d11594dca 100644 --- a/API/DTOs/Statistics/TopReadsDto.cs +++ b/API/DTOs/Statistics/TopReadsDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs.Statistics; #nullable enable -public class TopReadDto +public sealed record TopReadDto { public int UserId { get; set; } public string? Username { get; set; } = default!; diff --git a/API/DTOs/Statistics/UserReadStatistics.cs b/API/DTOs/Statistics/UserReadStatistics.cs index 5da4b491e..5c6935c6e 100644 --- a/API/DTOs/Statistics/UserReadStatistics.cs +++ b/API/DTOs/Statistics/UserReadStatistics.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace API.DTOs.Statistics; #nullable enable -public class UserReadStatistics +public sealed record UserReadStatistics { /// /// Total number of pages read diff --git a/API/DTOs/Stats/FileExtensionExportDto.cs b/API/DTOs/Stats/FileExtensionExportDto.cs index 6ed554d75..e881960a5 100644 --- a/API/DTOs/Stats/FileExtensionExportDto.cs +++ b/API/DTOs/Stats/FileExtensionExportDto.cs @@ -5,7 +5,7 @@ namespace API.DTOs.Stats; /// /// Excel export for File Extension Report /// -public class FileExtensionExportDto +public sealed record FileExtensionExportDto { [Name("Path")] public string FilePath { get; set; } diff --git a/API/DTOs/Stats/ServerInfoSlimDto.cs b/API/DTOs/Stats/ServerInfoSlimDto.cs index 0b47fa2f3..f1abb2e1d 100644 --- a/API/DTOs/Stats/ServerInfoSlimDto.cs +++ b/API/DTOs/Stats/ServerInfoSlimDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Stats; /// /// This is just for the Server tab on UI /// -public class ServerInfoSlimDto +public sealed record ServerInfoSlimDto { /// /// Unique Id that represents a unique install diff --git a/API/DTOs/Stats/V3/LibraryStatV3.cs b/API/DTOs/Stats/V3/LibraryStatV3.cs index 51af34b58..33ac86d2b 100644 --- a/API/DTOs/Stats/V3/LibraryStatV3.cs +++ b/API/DTOs/Stats/V3/LibraryStatV3.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs.Stats.V3; -public class LibraryStatV3 +public sealed record LibraryStatV3 { public bool IncludeInDashboard { get; set; } public bool IncludeInSearch { get; set; } diff --git a/API/DTOs/Stats/V3/RelationshipStatV3.cs b/API/DTOs/Stats/V3/RelationshipStatV3.cs index e8e1e7440..37b63cb9a 100644 --- a/API/DTOs/Stats/V3/RelationshipStatV3.cs +++ b/API/DTOs/Stats/V3/RelationshipStatV3.cs @@ -5,7 +5,7 @@ namespace API.DTOs.Stats.V3; /// /// KavitaStats - Information about Series Relationships /// -public class RelationshipStatV3 +public sealed record RelationshipStatV3 { public int Count { get; set; } public RelationKind Relationship { get; set; } diff --git a/API/DTOs/Stats/V3/ServerInfoV3Dto.cs b/API/DTOs/Stats/V3/ServerInfoV3Dto.cs index 0bf95403f..8ed3079f5 100644 --- a/API/DTOs/Stats/V3/ServerInfoV3Dto.cs +++ b/API/DTOs/Stats/V3/ServerInfoV3Dto.cs @@ -7,7 +7,7 @@ namespace API.DTOs.Stats.V3; /// /// Represents information about a Kavita Installation for Kavita Stats v3 API /// -public class ServerInfoV3Dto +public sealed record ServerInfoV3Dto { /// /// Unique Id that represents a unique install diff --git a/API/DTOs/Stats/V3/UserStatV3.cs b/API/DTOs/Stats/V3/UserStatV3.cs index 7f4e080ba..450a2e409 100644 --- a/API/DTOs/Stats/V3/UserStatV3.cs +++ b/API/DTOs/Stats/V3/UserStatV3.cs @@ -5,7 +5,7 @@ using API.Entities.Enums.Device; namespace API.DTOs.Stats.V3; -public class UserStatV3 +public sealed record UserStatV3 { public AgeRestriction AgeRestriction { get; set; } /// diff --git a/API/DTOs/System/DirectoryDto.cs b/API/DTOs/System/DirectoryDto.cs index e6e94f4e4..3b1408f7f 100644 --- a/API/DTOs/System/DirectoryDto.cs +++ b/API/DTOs/System/DirectoryDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.System; -public class DirectoryDto +public sealed record DirectoryDto { /// /// Name of the directory diff --git a/API/DTOs/Theme/ColorScapeDto.cs b/API/DTOs/Theme/ColorScapeDto.cs index 066e87d84..2ebd96e2b 100644 --- a/API/DTOs/Theme/ColorScapeDto.cs +++ b/API/DTOs/Theme/ColorScapeDto.cs @@ -4,7 +4,7 @@ /// /// A set of colors for the color scape system in the UI /// -public class ColorScapeDto +public sealed record ColorScapeDto { public string? Primary { get; set; } public string? Secondary { get; set; } diff --git a/API/DTOs/Theme/DownloadableSiteThemeDto.cs b/API/DTOs/Theme/DownloadableSiteThemeDto.cs index dbcedfe61..b27263d92 100644 --- a/API/DTOs/Theme/DownloadableSiteThemeDto.cs +++ b/API/DTOs/Theme/DownloadableSiteThemeDto.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace API.DTOs.Theme; -public class DownloadableSiteThemeDto +public sealed record DownloadableSiteThemeDto { /// /// Theme Name diff --git a/API/DTOs/Theme/SiteThemeDto.cs b/API/DTOs/Theme/SiteThemeDto.cs index eb2a14904..7ae8369e9 100644 --- a/API/DTOs/Theme/SiteThemeDto.cs +++ b/API/DTOs/Theme/SiteThemeDto.cs @@ -7,7 +7,7 @@ namespace API.DTOs.Theme; /// /// Represents a set of css overrides the user can upload to Kavita and will load into webui /// -public class SiteThemeDto +public sealed record SiteThemeDto { public int Id { get; set; } /// diff --git a/API/DTOs/Theme/UpdateDefaultThemeDto.cs b/API/DTOs/Theme/UpdateDefaultThemeDto.cs index 0f2b129f3..aac0858c3 100644 --- a/API/DTOs/Theme/UpdateDefaultThemeDto.cs +++ b/API/DTOs/Theme/UpdateDefaultThemeDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Theme; -public class UpdateDefaultThemeDto +public sealed record UpdateDefaultThemeDto { public int ThemeId { get; set; } } diff --git a/API/DTOs/Update/UpdateNotificationDto.cs b/API/DTOs/Update/UpdateNotificationDto.cs index 2f9550746..b535684f0 100644 --- a/API/DTOs/Update/UpdateNotificationDto.cs +++ b/API/DTOs/Update/UpdateNotificationDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.Update; /// /// Update Notification denoting a new release available for user to update to /// -public class UpdateNotificationDto +public sealed record UpdateNotificationDto { /// /// Current installed Version diff --git a/API/DTOs/UpdateChapterDto.cs b/API/DTOs/UpdateChapterDto.cs index 2ca0a12a9..ec2f1cf62 100644 --- a/API/DTOs/UpdateChapterDto.cs +++ b/API/DTOs/UpdateChapterDto.cs @@ -5,7 +5,7 @@ using API.Entities.Enums; namespace API.DTOs; -public class UpdateChapterDto +public sealed record UpdateChapterDto { public int Id { get; init; } public string Summary { get; set; } = string.Empty; diff --git a/API/DTOs/UpdateLibraryDto.cs b/API/DTOs/UpdateLibraryDto.cs index de02f304d..9bd47fd39 100644 --- a/API/DTOs/UpdateLibraryDto.cs +++ b/API/DTOs/UpdateLibraryDto.cs @@ -4,7 +4,7 @@ using API.Entities.Enums; namespace API.DTOs; -public class UpdateLibraryDto +public sealed record UpdateLibraryDto { [Required] public int Id { get; init; } diff --git a/API/DTOs/UpdateLibraryForUserDto.cs b/API/DTOs/UpdateLibraryForUserDto.cs index c90b697e2..4ce8d0df8 100644 --- a/API/DTOs/UpdateLibraryForUserDto.cs +++ b/API/DTOs/UpdateLibraryForUserDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs; -public class UpdateLibraryForUserDto +public sealed record UpdateLibraryForUserDto { public required string Username { get; init; } public required IEnumerable SelectedLibraries { get; init; } = new List(); diff --git a/API/DTOs/UpdateRBSDto.cs b/API/DTOs/UpdateRBSDto.cs index a7e0c3fc9..fa8bb78f9 100644 --- a/API/DTOs/UpdateRBSDto.cs +++ b/API/DTOs/UpdateRBSDto.cs @@ -3,7 +3,7 @@ namespace API.DTOs; #nullable enable -public class UpdateRbsDto +public sealed record UpdateRbsDto { public required string Username { get; init; } public IList? Roles { get; init; } diff --git a/API/DTOs/UpdateSeriesRatingDto.cs b/API/DTOs/UpdateRatingDto.cs similarity index 58% rename from API/DTOs/UpdateSeriesRatingDto.cs rename to API/DTOs/UpdateRatingDto.cs index 5dafa35af..472a94fe9 100644 --- a/API/DTOs/UpdateSeriesRatingDto.cs +++ b/API/DTOs/UpdateRatingDto.cs @@ -1,7 +1,8 @@ namespace API.DTOs; -public class UpdateSeriesRatingDto +public sealed record UpdateRatingDto { public int SeriesId { get; init; } + public int? ChapterId { get; init; } public float UserRating { get; init; } } diff --git a/API/DTOs/UpdateSeriesDto.cs b/API/DTOs/UpdateSeriesDto.cs index ab4ffcb22..a4a9baf8c 100644 --- a/API/DTOs/UpdateSeriesDto.cs +++ b/API/DTOs/UpdateSeriesDto.cs @@ -1,7 +1,7 @@ namespace API.DTOs; #nullable enable -public class UpdateSeriesDto +public sealed record UpdateSeriesDto { public int Id { get; init; } public string? LocalizedName { get; init; } diff --git a/API/DTOs/UpdateSeriesMetadataDto.cs b/API/DTOs/UpdateSeriesMetadataDto.cs index 75150b3fa..5225f5486 100644 --- a/API/DTOs/UpdateSeriesMetadataDto.cs +++ b/API/DTOs/UpdateSeriesMetadataDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs; -public class UpdateSeriesMetadataDto +public sealed record UpdateSeriesMetadataDto { public SeriesMetadataDto SeriesMetadata { get; set; } = null!; } diff --git a/API/DTOs/Uploads/UploadFileDto.cs b/API/DTOs/Uploads/UploadFileDto.cs index 72fe7da9b..8d5cdf4cb 100644 --- a/API/DTOs/Uploads/UploadFileDto.cs +++ b/API/DTOs/Uploads/UploadFileDto.cs @@ -1,6 +1,6 @@ namespace API.DTOs.Uploads; -public class UploadFileDto +public sealed record UploadFileDto { /// /// Id of the Entity diff --git a/API/DTOs/Uploads/UploadUrlDto.cs b/API/DTOs/Uploads/UploadUrlDto.cs index f2699befd..3f4e625c3 100644 --- a/API/DTOs/Uploads/UploadUrlDto.cs +++ b/API/DTOs/Uploads/UploadUrlDto.cs @@ -2,7 +2,7 @@ namespace API.DTOs.Uploads; -public class UploadUrlDto +public sealed record UploadUrlDto { /// /// External url diff --git a/API/DTOs/UserDto.cs b/API/DTOs/UserDto.cs index e89e17df9..88dc97a5d 100644 --- a/API/DTOs/UserDto.cs +++ b/API/DTOs/UserDto.cs @@ -5,7 +5,7 @@ using API.DTOs.Account; namespace API.DTOs; #nullable enable -public class UserDto +public sealed record UserDto { public string Username { get; init; } = null!; public string Email { get; init; } = null!; diff --git a/API/DTOs/UserPreferencesDto.cs b/API/DTOs/UserPreferencesDto.cs index 14987ae77..6645a8f39 100644 --- a/API/DTOs/UserPreferencesDto.cs +++ b/API/DTOs/UserPreferencesDto.cs @@ -7,104 +7,61 @@ using API.Entities.Enums.UserPreferences; namespace API.DTOs; #nullable enable -public class UserPreferencesDto +public sealed record UserPreferencesDto { - /// - /// Manga Reader Option: What direction should the next/prev page buttons go - /// + /// [Required] public ReadingDirection ReadingDirection { get; set; } - /// - /// Manga Reader Option: How should the image be scaled to screen - /// + /// [Required] public ScalingOption ScalingOption { get; set; } - /// - /// Manga Reader Option: Which side of a split image should we show first - /// + /// [Required] public PageSplitOption PageSplitOption { get; set; } - /// - /// Manga Reader Option: How the manga reader should perform paging or reading of the file - /// - /// Webtoon uses scrolling to page, LeftRight uses paging by clicking left/right side of reader, UpDown uses paging - /// by clicking top/bottom sides of reader. - /// - /// + /// [Required] public ReaderMode ReaderMode { get; set; } - /// - /// Manga Reader Option: How many pages to display in the reader at once - /// + /// [Required] public LayoutMode LayoutMode { get; set; } - /// - /// Manga Reader Option: Emulate a book by applying a shadow effect on the pages - /// + /// [Required] public bool EmulateBook { get; set; } - /// - /// Manga Reader Option: Background color of the reader - /// + /// [Required] public string BackgroundColor { get; set; } = "#000000"; - /// - /// Manga Reader Option: Should swiping trigger pagination - /// + /// [Required] public bool SwipeToPaginate { get; set; } - /// - /// Manga Reader Option: Allow the menu to close after 6 seconds without interaction - /// + /// [Required] public bool AutoCloseMenu { get; set; } - /// - /// Manga Reader Option: Show screen hints to the user on some actions, ie) pagination direction change - /// + /// [Required] public bool ShowScreenHints { get; set; } = true; - /// - /// Manga Reader Option: Allow Automatic Webtoon detection - /// + /// [Required] public bool AllowAutomaticWebtoonReaderDetection { get; set; } - - /// - /// Book Reader Option: Override extra Margin - /// + /// [Required] public int BookReaderMargin { get; set; } - /// - /// Book Reader Option: Override line-height - /// + /// [Required] public int BookReaderLineSpacing { get; set; } - /// - /// Book Reader Option: Override font size - /// + /// [Required] public int BookReaderFontSize { get; set; } - /// - /// Book Reader Option: Maps to the default Kavita font-family (inherit) or an override - /// + /// [Required] public string BookReaderFontFamily { get; set; } = null!; - - /// - /// Book Reader Option: Allows tapping on side of screens to paginate - /// + /// [Required] public bool BookReaderTapToPaginate { get; set; } - /// - /// Book Reader Option: What direction should the next/prev page buttons go - /// + /// [Required] public ReadingDirection BookReaderReadingDirection { get; set; } - - /// - /// Book Reader Option: What writing style should be used, horizontal or vertical. - /// + /// [Required] public WritingStyle BookReaderWritingStyle { get; set; } @@ -116,79 +73,46 @@ public class UserPreferencesDto public SiteThemeDto? Theme { get; set; } [Required] public string BookReaderThemeName { get; set; } = null!; + /// [Required] public BookPageLayoutMode BookReaderLayoutMode { get; set; } - /// - /// Book Reader Option: A flag that hides the menu-ing system behind a click on the screen. This should be used with tap to paginate, but the app doesn't enforce this. - /// - /// Defaults to false + /// [Required] public bool BookReaderImmersiveMode { get; set; } = false; - /// - /// Global Site Option: If the UI should layout items as Cards or List items - /// - /// Defaults to Cards + /// [Required] public PageLayoutMode GlobalPageLayoutMode { get; set; } = PageLayoutMode.Cards; - /// - /// UI Site Global Setting: If unread summaries should be blurred until expanded or unless user has read it already - /// - /// Defaults to false + /// [Required] public bool BlurUnreadSummaries { get; set; } = false; - /// - /// UI Site Global Setting: Should Kavita prompt user to confirm downloads that are greater than 100 MB. - /// + /// [Required] public bool PromptForDownloadSize { get; set; } = true; - /// - /// UI Site Global Setting: Should Kavita disable CSS transitions - /// + /// [Required] public bool NoTransitions { get; set; } = false; - /// - /// When showing series, only parent series or series with no relationships will be returned - /// + /// [Required] public bool CollapseSeriesRelationships { get; set; } = false; - /// - /// UI Site Global Setting: Should series reviews be shared with all users in the server - /// + /// [Required] public bool ShareReviews { get; set; } = false; - /// - /// UI Site Global Setting: The language locale that should be used for the user - /// + /// [Required] public string Locale { get; set; } - /// - /// PDF Reader: Theme of the Reader - /// + /// [Required] public PdfTheme PdfTheme { get; set; } = PdfTheme.Dark; - /// - /// PDF Reader: Scroll mode of the reader - /// + /// [Required] public PdfScrollMode PdfScrollMode { get; set; } = PdfScrollMode.Vertical; - /// - /// PDF Reader: Layout Mode of the reader - /// - [Required] - public PdfLayoutMode PdfLayoutMode { get; set; } = PdfLayoutMode.Multiple; - /// - /// PDF Reader: Spread Mode of the reader - /// + /// [Required] public PdfSpreadMode PdfSpreadMode { get; set; } = PdfSpreadMode.None; - /// - /// Kavita+: Should this account have Scrobbling enabled for AniList - /// + /// public bool AniListScrobblingEnabled { get; set; } - /// - /// Kavita+: Should this account have Want to Read Sync enabled - /// + /// public bool WantToReadSync { get; set; } } diff --git a/API/DTOs/VolumeDto.cs b/API/DTOs/VolumeDto.cs index 8ef22a93b..fffccea59 100644 --- a/API/DTOs/VolumeDto.cs +++ b/API/DTOs/VolumeDto.cs @@ -1,5 +1,4 @@ - -using System; +using System; using System.Collections.Generic; using API.Entities; using API.Entities.Interfaces; @@ -8,14 +7,15 @@ using API.Services.Tasks.Scanner.Parser; namespace API.DTOs; -public class VolumeDto : IHasReadTimeEstimate, IHasCoverImage +public sealed record VolumeDto : IHasReadTimeEstimate, IHasCoverImage { + /// public int Id { get; set; } - /// + /// public float MinNumber { get; set; } - /// + /// public float MaxNumber { get; set; } - /// + /// public string Name { get; set; } = default!; /// /// This will map to MinNumber. Number was removed in v0.7.13.8/v0.7.14 @@ -24,17 +24,21 @@ public class VolumeDto : IHasReadTimeEstimate, IHasCoverImage public int Number { get; set; } public int Pages { get; set; } public int PagesRead { get; set; } + /// public DateTime LastModifiedUtc { get; set; } + /// public DateTime CreatedUtc { get; set; } /// /// When chapter was created in local server time /// /// This is required for Tachiyomi Extension + /// public DateTime Created { get; set; } /// /// When chapter was last modified in local server time /// /// This is required for Tachiyomi Extension + /// public DateTime LastModified { get; set; } public int SeriesId { get; set; } public ICollection Chapters { get; set; } = new List(); @@ -64,10 +68,14 @@ public class VolumeDto : IHasReadTimeEstimate, IHasCoverImage return MinNumber.Is(Parser.SpecialVolumeNumber); } + /// public string CoverImage { get; set; } + /// private bool CoverImageLocked { get; set; } - public string PrimaryColor { get; set; } = string.Empty; - public string SecondaryColor { get; set; } = string.Empty; + /// + public string? PrimaryColor { get; set; } = string.Empty; + /// + public string? SecondaryColor { get; set; } = string.Empty; public void ResetColorScape() { diff --git a/API/DTOs/WantToRead/UpdateWantToReadDto.cs b/API/DTOs/WantToRead/UpdateWantToReadDto.cs index f1b38cea2..a5be26857 100644 --- a/API/DTOs/WantToRead/UpdateWantToReadDto.cs +++ b/API/DTOs/WantToRead/UpdateWantToReadDto.cs @@ -6,7 +6,7 @@ namespace API.DTOs.WantToRead; /// /// A list of Series to pass when working with Want To Read APIs /// -public class UpdateWantToReadDto +public sealed record UpdateWantToReadDto { /// /// List of Series Ids that will be Added/Removed diff --git a/API/Data/DataContext.cs b/API/Data/DataContext.cs index 4533a5dbf..714e29fdf 100644 --- a/API/Data/DataContext.cs +++ b/API/Data/DataContext.cs @@ -71,6 +71,7 @@ public sealed class DataContext : IdentityDbContext ExternalSeriesMetadata { get; set; } = null!; public DbSet ExternalRecommendation { get; set; } = null!; public DbSet ManualMigrationHistory { get; set; } = null!; + [Obsolete] public DbSet SeriesBlacklist { get; set; } = null!; public DbSet AppUserCollection { get; set; } = null!; public DbSet ChapterPeople { get; set; } = null!; @@ -78,6 +79,7 @@ public sealed class DataContext : IdentityDbContext EmailHistory { get; set; } = null!; public DbSet MetadataSettings { get; set; } = null!; public DbSet MetadataFieldMapping { get; set; } = null!; + public DbSet AppUserChapterRating { get; set; } = null!; protected override void OnModelCreating(ModelBuilder builder) { diff --git a/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.Designer.cs b/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.Designer.cs new file mode 100644 index 000000000..52e2c4a86 --- /dev/null +++ b/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.Designer.cs @@ -0,0 +1,3536 @@ +// +using System; +using API.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace API.Data.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20250429150140_ChapterRatingAndReviews")] + partial class ChapterRatingAndReviews + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.4"); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("AgeRestriction") + .HasColumnType("INTEGER"); + + b.Property("AgeRestrictionIncludeUnknowns") + .HasColumnType("INTEGER"); + + b.Property("AniListAccessToken") + .HasColumnType("TEXT"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("ConfirmationToken") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("HasRunScrobbleEventGeneration") + .HasColumnType("INTEGER"); + + b.Property("LastActive") + .HasColumnType("TEXT"); + + b.Property("LastActiveUtc") + .HasColumnType("TEXT"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("MalAccessToken") + .HasColumnType("TEXT"); + + b.Property("MalUserName") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventGenerationRan") + .HasColumnType("TEXT"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Page") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserBookmark"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserChapterRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastSyncUtc") + .HasColumnType("TEXT"); + + b.Property("MissingSeriesFromSource") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Source") + .HasColumnType("INTEGER"); + + b.Property("SourceUrl") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TotalSourceCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserCollection"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(4); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserDashboardStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiKey") + .HasColumnType("TEXT"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Host") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserExternalSource"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserOnDeckRemoval"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowAutomaticWebtoonReaderDetection") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AniListScrobblingEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("AutoCloseMenu") + .HasColumnType("INTEGER"); + + b.Property("BackgroundColor") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("#000000"); + + b.Property("BlurUnreadSummaries") + .HasColumnType("INTEGER"); + + b.Property("BookReaderFontFamily") + .HasColumnType("TEXT"); + + b.Property("BookReaderFontSize") + .HasColumnType("INTEGER"); + + b.Property("BookReaderImmersiveMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLayoutMode") + .HasColumnType("INTEGER"); + + b.Property("BookReaderLineSpacing") + .HasColumnType("INTEGER"); + + b.Property("BookReaderMargin") + .HasColumnType("INTEGER"); + + b.Property("BookReaderReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("BookReaderTapToPaginate") + .HasColumnType("INTEGER"); + + b.Property("BookReaderWritingStyle") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("BookThemeName") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("Dark"); + + b.Property("CollapseSeriesRelationships") + .HasColumnType("INTEGER"); + + b.Property("EmulateBook") + .HasColumnType("INTEGER"); + + b.Property("GlobalPageLayoutMode") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.Property("LayoutMode") + .HasColumnType("INTEGER"); + + b.Property("Locale") + .IsRequired() + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue("en"); + + b.Property("NoTransitions") + .HasColumnType("INTEGER"); + + b.Property("PageSplitOption") + .HasColumnType("INTEGER"); + + b.Property("PdfScrollMode") + .HasColumnType("INTEGER"); + + b.Property("PdfSpreadMode") + .HasColumnType("INTEGER"); + + b.Property("PdfTheme") + .HasColumnType("INTEGER"); + + b.Property("PromptForDownloadSize") + .HasColumnType("INTEGER"); + + b.Property("ReaderMode") + .HasColumnType("INTEGER"); + + b.Property("ReadingDirection") + .HasColumnType("INTEGER"); + + b.Property("ScalingOption") + .HasColumnType("INTEGER"); + + b.Property("ShareReviews") + .HasColumnType("INTEGER"); + + b.Property("ShowScreenHints") + .HasColumnType("INTEGER"); + + b.Property("SwipeToPaginate") + .HasColumnType("INTEGER"); + + b.Property("ThemeId") + .HasColumnType("INTEGER"); + + b.Property("WantToReadSync") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.HasKey("Id"); + + b.HasIndex("AppUserId") + .IsUnique(); + + b.HasIndex("ThemeId"); + + b.ToTable("AppUserPreferences"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PagesRead") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserProgresses"); + }); + + modelBuilder.Entity("API.Entities.AppUserRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserRating"); + }); + + modelBuilder.Entity("API.Entities.AppUserRole", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("API.Entities.AppUserSideNavStream", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSourceId") + .HasColumnType("INTEGER"); + + b.Property("IsProvided") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("SmartFilterId") + .HasColumnType("INTEGER"); + + b.Property("StreamType") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(5); + + b.Property("Visible") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SmartFilterId"); + + b.HasIndex("Visible"); + + b.ToTable("AppUserSideNavStream"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Filter") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("AppUserSmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("BookScrollId") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("PageNumber") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserTableOfContent"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserWantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("AlternateCount") + .HasColumnType("INTEGER"); + + b.Property("AlternateNumber") + .HasColumnType("TEXT"); + + b.Property("AlternateSeries") + .HasColumnType("TEXT"); + + b.Property("AverageExternalRating") + .HasColumnType("REAL"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ISBN") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("ISBNLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("IsSpecial") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Number") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("Range") + .HasColumnType("TEXT"); + + b.Property("ReleaseDate") + .HasColumnType("TEXT"); + + b.Property("ReleaseDateLocked") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesGroup") + .HasColumnType("TEXT"); + + b.Property("SortOrder") + .HasColumnType("REAL"); + + b.Property("SortOrderLocked") + .HasColumnType("INTEGER"); + + b.Property("StoryArc") + .HasColumnType("TEXT"); + + b.Property("StoryArcNumber") + .HasColumnType("TEXT"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.Property("TitleName") + .HasColumnType("TEXT"); + + b.Property("TitleNameLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("VolumeId"); + + b.ToTable("Chapter"); + }); + + modelBuilder.Entity("API.Entities.CollectionTag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Id", "Promoted") + .IsUnique(); + + b.ToTable("CollectionTag"); + }); + + modelBuilder.Entity("API.Entities.Device", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EmailAddress") + .HasColumnType("TEXT"); + + b.Property("IpAddress") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastUsed") + .HasColumnType("TEXT"); + + b.Property("LastUsedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Platform") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("Device"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DeliveryStatus") + .HasColumnType("TEXT"); + + b.Property("EmailTemplate") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SendDate") + .HasColumnType("TEXT"); + + b.Property("Sent") + .HasColumnType("INTEGER"); + + b.Property("Subject") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("Sent", "AppUserId", "EmailTemplate", "SendDate"); + + b.ToTable("EmailHistory"); + }); + + modelBuilder.Entity("API.Entities.FolderPath", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Path") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("FolderPath"); + }); + + modelBuilder.Entity("API.Entities.Genre", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Genre"); + }); + + modelBuilder.Entity("API.Entities.History.ManualMigrationHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("ProductVersion") + .HasColumnType("TEXT"); + + b.Property("RanAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("ManualMigrationHistory"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AllowMetadataMatching") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("AllowScrobbling") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("FolderWatching") + .HasColumnType("INTEGER"); + + b.Property("IncludeInDashboard") + .HasColumnType("INTEGER"); + + b.Property("IncludeInRecommended") + .HasColumnType("INTEGER"); + + b.Property("IncludeInSearch") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LastScanned") + .HasColumnType("TEXT"); + + b.Property("ManageCollections") + .HasColumnType("INTEGER"); + + b.Property("ManageReadingLists") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryExcludePattern", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("Pattern") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryExcludePattern"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("FileTypeGroup") + .HasColumnType("INTEGER"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("LibraryFileTypeGroup"); + }); + + modelBuilder.Entity("API.Entities.MangaFile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Bytes") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("LastFileAnalysis") + .HasColumnType("TEXT"); + + b.Property("LastFileAnalysisUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("MangaFile"); + }); + + modelBuilder.Entity("API.Entities.MediaError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("Extension") + .HasColumnType("TEXT"); + + b.Property("FilePath") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MediaError"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("AverageScore") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("FavoriteCount") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ProviderUrl") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalRating"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalRecommendation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("CoverUrl") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Url") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("ExternalRecommendation"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Authority") + .HasColumnType("INTEGER"); + + b.Property("Body") + .HasColumnType("TEXT"); + + b.Property("BodyJustText") + .HasColumnType("TEXT"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("INTEGER"); + + b.Property("RawBody") + .HasColumnType("TEXT"); + + b.Property("Score") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("SiteUrl") + .HasColumnType("TEXT"); + + b.Property("Tagline") + .HasColumnType("TEXT"); + + b.Property("TotalVotes") + .HasColumnType("INTEGER"); + + b.Property("Username") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.ToTable("ExternalReview"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AverageExternalRating") + .HasColumnType("INTEGER"); + + b.Property("CbrId") + .HasColumnType("INTEGER"); + + b.Property("GoogleBooksId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("ValidUntilUtc") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.ToTable("ExternalSeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("LastChecked") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("SeriesBlacklist"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AgeRatingLocked") + .HasColumnType("INTEGER"); + + b.Property("CharacterLocked") + .HasColumnType("INTEGER"); + + b.Property("ColoristLocked") + .HasColumnType("INTEGER"); + + b.Property("CoverArtistLocked") + .HasColumnType("INTEGER"); + + b.Property("EditorLocked") + .HasColumnType("INTEGER"); + + b.Property("GenresLocked") + .HasColumnType("INTEGER"); + + b.Property("ImprintLocked") + .HasColumnType("INTEGER"); + + b.Property("InkerLocked") + .HasColumnType("INTEGER"); + + b.Property("Language") + .HasColumnType("TEXT"); + + b.Property("LanguageLocked") + .HasColumnType("INTEGER"); + + b.Property("LettererLocked") + .HasColumnType("INTEGER"); + + b.Property("LocationLocked") + .HasColumnType("INTEGER"); + + b.Property("MaxCount") + .HasColumnType("INTEGER"); + + b.Property("PencillerLocked") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("PublicationStatusLocked") + .HasColumnType("INTEGER"); + + b.Property("PublisherLocked") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYear") + .HasColumnType("INTEGER"); + + b.Property("ReleaseYearLocked") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("SummaryLocked") + .HasColumnType("INTEGER"); + + b.Property("TagsLocked") + .HasColumnType("INTEGER"); + + b.Property("TeamLocked") + .HasColumnType("INTEGER"); + + b.Property("TotalCount") + .HasColumnType("INTEGER"); + + b.Property("TranslatorLocked") + .HasColumnType("INTEGER"); + + b.Property("WebLinks") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValue(""); + + b.Property("WriterLocked") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId") + .IsUnique(); + + b.HasIndex("Id", "SeriesId") + .IsUnique(); + + b.ToTable("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RelationKind") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("TargetSeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.HasIndex("TargetSeriesId"); + + b.ToTable("SeriesRelation"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("DestinationType") + .HasColumnType("INTEGER"); + + b.Property("DestinationValue") + .HasColumnType("TEXT"); + + b.Property("ExcludeFromSource") + .HasColumnType("INTEGER"); + + b.Property("MetadataSettingsId") + .HasColumnType("INTEGER"); + + b.Property("SourceType") + .HasColumnType("INTEGER"); + + b.Property("SourceValue") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MetadataSettingsId"); + + b.ToTable("MetadataFieldMapping"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRatingMappings") + .HasColumnType("TEXT"); + + b.Property("Blacklist") + .HasColumnType("TEXT"); + + b.Property("EnableChapterCoverImage") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterPublisher") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterReleaseDate") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableChapterTitle") + .HasColumnType("INTEGER"); + + b.Property("EnableCoverImage") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("EnableGenres") + .HasColumnType("INTEGER"); + + b.Property("EnableLocalizedName") + .HasColumnType("INTEGER"); + + b.Property("EnablePeople") + .HasColumnType("INTEGER"); + + b.Property("EnablePublicationStatus") + .HasColumnType("INTEGER"); + + b.Property("EnableRelationships") + .HasColumnType("INTEGER"); + + b.Property("EnableStartDate") + .HasColumnType("INTEGER"); + + b.Property("EnableSummary") + .HasColumnType("INTEGER"); + + b.Property("EnableTags") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("FirstLastPeopleNaming") + .HasColumnType("INTEGER"); + + b.Property("Overrides") + .HasColumnType("TEXT"); + + b.PrimitiveCollection("PersonRoles") + .HasColumnType("TEXT"); + + b.Property("Whitelist") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .HasColumnType("INTEGER"); + + b.HasKey("ChapterId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("ChapterPeople"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("Asin") + .HasColumnType("TEXT"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("HardcoverId") + .HasColumnType("TEXT"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.Property("SeriesMetadataId") + .HasColumnType("INTEGER"); + + b.Property("PersonId") + .HasColumnType("INTEGER"); + + b.Property("Role") + .HasColumnType("INTEGER"); + + b.Property("KavitaPlusConnection") + .HasColumnType("INTEGER"); + + b.Property("OrderWeight") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(0); + + b.HasKey("SeriesMetadataId", "PersonId", "Role"); + + b.HasIndex("PersonId"); + + b.ToTable("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AgeRating") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("EndingMonth") + .HasColumnType("INTEGER"); + + b.Property("EndingYear") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("NormalizedTitle") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("Promoted") + .HasColumnType("INTEGER"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("StartingMonth") + .HasColumnType("INTEGER"); + + b.Property("StartingYear") + .HasColumnType("INTEGER"); + + b.Property("Summary") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.ToTable("ReadingList"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ReadingListId") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ChapterId"); + + b.HasIndex("ReadingListId"); + + b.HasIndex("SeriesId"); + + b.HasIndex("VolumeId"); + + b.ToTable("ReadingListItem"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Comment") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Details") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId") + .HasColumnType("INTEGER"); + + b.Property("ScrobbleEventId1") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ScrobbleEventId1"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleError"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AniListId") + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterNumber") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("ErrorDetails") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsErrored") + .HasColumnType("INTEGER"); + + b.Property("IsProcessed") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("MalId") + .HasColumnType("INTEGER"); + + b.Property("ProcessDateUtc") + .HasColumnType("TEXT"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("ReviewBody") + .HasColumnType("TEXT"); + + b.Property("ReviewTitle") + .HasColumnType("TEXT"); + + b.Property("ScrobbleEventType") + .HasColumnType("INTEGER"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("VolumeNumber") + .HasColumnType("REAL"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("LibraryId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleEvent"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("SeriesId"); + + b.ToTable("ScrobbleHold"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("DontMatch") + .HasColumnType("INTEGER"); + + b.Property("FolderPath") + .HasColumnType("TEXT"); + + b.Property("Format") + .HasColumnType("INTEGER"); + + b.Property("IsBlacklisted") + .HasColumnType("INTEGER"); + + b.Property("LastChapterAdded") + .HasColumnType("TEXT"); + + b.Property("LastChapterAddedUtc") + .HasColumnType("TEXT"); + + b.Property("LastFolderScanned") + .HasColumnType("TEXT"); + + b.Property("LastFolderScannedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LibraryId") + .HasColumnType("INTEGER"); + + b.Property("LocalizedName") + .HasColumnType("TEXT"); + + b.Property("LocalizedNameLocked") + .HasColumnType("INTEGER"); + + b.Property("LowestFolderPath") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedLocalizedName") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("OriginalName") + .HasColumnType("TEXT"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SortName") + .HasColumnType("TEXT"); + + b.Property("SortNameLocked") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("LibraryId"); + + b.ToTable("Series"); + }); + + modelBuilder.Entity("API.Entities.ServerSetting", b => + { + b.Property("Key") + .HasColumnType("INTEGER"); + + b.Property("RowVersion") + .IsConcurrencyToken() + .HasColumnType("INTEGER"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("ServerSetting"); + }); + + modelBuilder.Entity("API.Entities.ServerStatistics", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChapterCount") + .HasColumnType("INTEGER"); + + b.Property("FileCount") + .HasColumnType("INTEGER"); + + b.Property("GenreCount") + .HasColumnType("INTEGER"); + + b.Property("PersonCount") + .HasColumnType("INTEGER"); + + b.Property("SeriesCount") + .HasColumnType("INTEGER"); + + b.Property("TagCount") + .HasColumnType("INTEGER"); + + b.Property("UserCount") + .HasColumnType("INTEGER"); + + b.Property("VolumeCount") + .HasColumnType("INTEGER"); + + b.Property("Year") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.ToTable("ServerStatistics"); + }); + + modelBuilder.Entity("API.Entities.SiteTheme", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .HasColumnType("TEXT"); + + b.Property("CompatibleVersion") + .HasColumnType("TEXT"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("Description") + .HasColumnType("TEXT"); + + b.Property("FileName") + .HasColumnType("TEXT"); + + b.Property("GitHubPath") + .HasColumnType("TEXT"); + + b.Property("IsDefault") + .HasColumnType("INTEGER"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasColumnType("TEXT"); + + b.Property("PreviewUrls") + .HasColumnType("TEXT"); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ShaHash") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("SiteTheme"); + }); + + modelBuilder.Entity("API.Entities.Tag", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("NormalizedTitle") + .HasColumnType("TEXT"); + + b.Property("Title") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedTitle") + .IsUnique(); + + b.ToTable("Tag"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AvgHoursToRead") + .HasColumnType("REAL"); + + b.Property("CoverImage") + .HasColumnType("TEXT"); + + b.Property("CoverImageLocked") + .HasColumnType("INTEGER"); + + b.Property("Created") + .HasColumnType("TEXT"); + + b.Property("CreatedUtc") + .HasColumnType("TEXT"); + + b.Property("LastModified") + .HasColumnType("TEXT"); + + b.Property("LastModifiedUtc") + .HasColumnType("TEXT"); + + b.Property("LookupName") + .HasColumnType("TEXT"); + + b.Property("MaxHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MaxNumber") + .HasColumnType("REAL"); + + b.Property("MinHoursToRead") + .HasColumnType("INTEGER"); + + b.Property("MinNumber") + .HasColumnType("REAL"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("Pages") + .HasColumnType("INTEGER"); + + b.Property("PrimaryColor") + .HasColumnType("TEXT"); + + b.Property("SecondaryColor") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.Property("WordCount") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("SeriesId"); + + b.ToTable("Volume"); + }); + + modelBuilder.Entity("AppUserCollectionSeries", b => + { + b.Property("CollectionsId") + .HasColumnType("INTEGER"); + + b.Property("ItemsId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionsId", "ItemsId"); + + b.HasIndex("ItemsId"); + + b.ToTable("AppUserCollectionSeries"); + }); + + modelBuilder.Entity("AppUserLibrary", b => + { + b.Property("AppUsersId") + .HasColumnType("INTEGER"); + + b.Property("LibrariesId") + .HasColumnType("INTEGER"); + + b.HasKey("AppUsersId", "LibrariesId"); + + b.HasIndex("LibrariesId"); + + b.ToTable("AppUserLibrary"); + }); + + modelBuilder.Entity("ChapterGenre", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "GenresId"); + + b.HasIndex("GenresId"); + + b.ToTable("ChapterGenre"); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.Property("ChaptersId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("ChaptersId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("ChapterTag"); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.Property("CollectionTagsId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("CollectionTagsId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("CollectionTagSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.Property("ExternalRatingsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRatingsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRatingExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.Property("ExternalRecommendationsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalRecommendationsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalRecommendationExternalSeriesMetadata"); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.Property("ExternalReviewsId") + .HasColumnType("INTEGER"); + + b.Property("ExternalSeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("ExternalReviewsId", "ExternalSeriesMetadatasId"); + + b.HasIndex("ExternalSeriesMetadatasId"); + + b.ToTable("ExternalReviewExternalSeriesMetadata"); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.Property("GenresId") + .HasColumnType("INTEGER"); + + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.HasKey("GenresId", "SeriesMetadatasId"); + + b.HasIndex("SeriesMetadatasId"); + + b.ToTable("GenreSeriesMetadata"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.Property("SeriesMetadatasId") + .HasColumnType("INTEGER"); + + b.Property("TagsId") + .HasColumnType("INTEGER"); + + b.HasKey("SeriesMetadatasId", "TagsId"); + + b.HasIndex("TagsId"); + + b.ToTable("SeriesMetadataTag"); + }); + + modelBuilder.Entity("API.Entities.AppUserBookmark", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Bookmarks") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ChapterRatings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Ratings") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserCollection", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Collections") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserDashboardStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("DashboardStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserExternalSource", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ExternalSources") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserOnDeckRemoval", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserPreferences", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithOne("UserPreferences") + .HasForeignKey("API.Entities.AppUserPreferences", "AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.SiteTheme", "Theme") + .WithMany() + .HasForeignKey("ThemeId"); + + b.Navigation("AppUser"); + + b.Navigation("Theme"); + }); + + modelBuilder.Entity("API.Entities.AppUserProgress", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Progresses") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", null) + .WithMany("UserProgress") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany("Progress") + .HasForeignKey("SeriesId") + .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.HasOne("API.Entities.Series", "Series") + .WithMany("Ratings") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + 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.AppUserSideNavStream", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SideNavStreams") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.AppUserSmartFilter", "SmartFilter") + .WithMany() + .HasForeignKey("SmartFilterId"); + + b.Navigation("AppUser"); + + b.Navigation("SmartFilter"); + }); + + modelBuilder.Entity("API.Entities.AppUserSmartFilter", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("SmartFilters") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.AppUserTableOfContent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("TableOfContents") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.AppUserWantToRead", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("WantToRead") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + 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.Device", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("Devices") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.EmailHistory", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + 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.LibraryExcludePattern", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryExcludePatterns") + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Library"); + }); + + modelBuilder.Entity("API.Entities.LibraryFileTypeGroup", b => + { + b.HasOne("API.Entities.Library", "Library") + .WithMany("LibraryFileTypes") + .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.Metadata.ExternalRating", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalRatings") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("ExternalSeriesMetadata") + .HasForeignKey("API.Entities.Metadata.ExternalSeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesBlacklist", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithOne("Metadata") + .HasForeignKey("API.Entities.Metadata.SeriesMetadata", "SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesRelation", b => + { + b.HasOne("API.Entities.Series", "Series") + .WithMany("Relations") + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "TargetSeries") + .WithMany("RelationOf") + .HasForeignKey("TargetSeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Series"); + + b.Navigation("TargetSeries"); + }); + + modelBuilder.Entity("API.Entities.MetadataFieldMapping", b => + { + b.HasOne("API.Entities.MetadataMatching.MetadataSettings", "MetadataSettings") + .WithMany("FieldMappings") + .HasForeignKey("MetadataSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MetadataSettings"); + }); + + modelBuilder.Entity("API.Entities.Person.ChapterPeople", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("People") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("ChapterPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("Person"); + }); + + modelBuilder.Entity("API.Entities.Person.SeriesMetadataPeople", b => + { + b.HasOne("API.Entities.Person.Person", "Person") + .WithMany("SeriesMetadataPeople") + .HasForeignKey("PersonId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", "SeriesMetadata") + .WithMany("People") + .HasForeignKey("SeriesMetadataId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Person"); + + b.Navigation("SeriesMetadata"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ReadingLists") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + }); + + modelBuilder.Entity("API.Entities.ReadingListItem", b => + { + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany() + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.ReadingList", "ReadingList") + .WithMany("Items") + .HasForeignKey("ReadingListId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Volume", "Volume") + .WithMany() + .HasForeignKey("VolumeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Chapter"); + + b.Navigation("ReadingList"); + + b.Navigation("Series"); + + b.Navigation("Volume"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleError", b => + { + b.HasOne("API.Entities.Scrobble.ScrobbleEvent", "ScrobbleEvent") + .WithMany() + .HasForeignKey("ScrobbleEventId1"); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("ScrobbleEvent"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleEvent", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany() + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Library", "Library") + .WithMany() + .HasForeignKey("LibraryId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Library"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Scrobble.ScrobbleHold", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ScrobbleHolds") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Series"); + }); + + 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("AppUserCollectionSeries", b => + { + b.HasOne("API.Entities.AppUserCollection", null) + .WithMany() + .HasForeignKey("CollectionsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", null) + .WithMany() + .HasForeignKey("ItemsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + 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("ChapterGenre", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ChapterTag", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany() + .HasForeignKey("ChaptersId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("CollectionTagSeriesMetadata", b => + { + b.HasOne("API.Entities.CollectionTag", null) + .WithMany() + .HasForeignKey("CollectionTagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRatingExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRating", null) + .WithMany() + .HasForeignKey("ExternalRatingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalRecommendationExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalRecommendation", null) + .WithMany() + .HasForeignKey("ExternalRecommendationsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("ExternalReviewExternalSeriesMetadata", b => + { + b.HasOne("API.Entities.Metadata.ExternalReview", null) + .WithMany() + .HasForeignKey("ExternalReviewsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.ExternalSeriesMetadata", null) + .WithMany() + .HasForeignKey("ExternalSeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("GenreSeriesMetadata", b => + { + b.HasOne("API.Entities.Genre", null) + .WithMany() + .HasForeignKey("GenresId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("API.Entities.AppRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("API.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("SeriesMetadataTag", b => + { + b.HasOne("API.Entities.Metadata.SeriesMetadata", null) + .WithMany() + .HasForeignKey("SeriesMetadatasId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Tag", null) + .WithMany() + .HasForeignKey("TagsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("API.Entities.AppRole", b => + { + b.Navigation("UserRoles"); + }); + + modelBuilder.Entity("API.Entities.AppUser", b => + { + b.Navigation("Bookmarks"); + + b.Navigation("ChapterRatings"); + + b.Navigation("Collections"); + + b.Navigation("DashboardStreams"); + + b.Navigation("Devices"); + + b.Navigation("ExternalSources"); + + b.Navigation("Progresses"); + + b.Navigation("Ratings"); + + b.Navigation("ReadingLists"); + + b.Navigation("ScrobbleHolds"); + + b.Navigation("SideNavStreams"); + + b.Navigation("SmartFilters"); + + b.Navigation("TableOfContents"); + + b.Navigation("UserPreferences"); + + b.Navigation("UserRoles"); + + b.Navigation("WantToRead"); + }); + + modelBuilder.Entity("API.Entities.Chapter", b => + { + b.Navigation("ExternalRatings"); + + b.Navigation("ExternalReviews"); + + b.Navigation("Files"); + + b.Navigation("People"); + + b.Navigation("Ratings"); + + b.Navigation("UserProgress"); + }); + + modelBuilder.Entity("API.Entities.Library", b => + { + b.Navigation("Folders"); + + b.Navigation("LibraryExcludePatterns"); + + b.Navigation("LibraryFileTypes"); + + b.Navigation("Series"); + }); + + modelBuilder.Entity("API.Entities.Metadata.SeriesMetadata", b => + { + b.Navigation("People"); + }); + + modelBuilder.Entity("API.Entities.MetadataMatching.MetadataSettings", b => + { + b.Navigation("FieldMappings"); + }); + + modelBuilder.Entity("API.Entities.Person.Person", b => + { + b.Navigation("ChapterPeople"); + + b.Navigation("SeriesMetadataPeople"); + }); + + modelBuilder.Entity("API.Entities.ReadingList", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("API.Entities.Series", b => + { + b.Navigation("ExternalSeriesMetadata"); + + b.Navigation("Metadata"); + + b.Navigation("Progress"); + + b.Navigation("Ratings"); + + b.Navigation("RelationOf"); + + b.Navigation("Relations"); + + b.Navigation("Volumes"); + }); + + modelBuilder.Entity("API.Entities.Volume", b => + { + b.Navigation("Chapters"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.cs b/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.cs new file mode 100644 index 000000000..5ab51aaba --- /dev/null +++ b/API/Data/Migrations/20250429150140_ChapterRatingAndReviews.cs @@ -0,0 +1,165 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace API.Data.Migrations +{ + /// + public partial class ChapterRatingAndReviews : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Authority", + table: "ExternalReview", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ChapterId", + table: "ExternalReview", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "Authority", + table: "ExternalRating", + type: "INTEGER", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "ChapterId", + table: "ExternalRating", + type: "INTEGER", + nullable: true); + + migrationBuilder.AddColumn( + name: "AverageExternalRating", + table: "Chapter", + type: "REAL", + nullable: false, + defaultValue: 0f); + + migrationBuilder.CreateTable( + name: "AppUserChapterRating", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rating = table.Column(type: "REAL", nullable: false), + HasBeenRated = table.Column(type: "INTEGER", nullable: false), + Review = table.Column(type: "TEXT", nullable: true), + SeriesId = table.Column(type: "INTEGER", nullable: false), + ChapterId = table.Column(type: "INTEGER", nullable: false), + AppUserId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AppUserChapterRating", x => x.Id); + table.ForeignKey( + name: "FK_AppUserChapterRating_AspNetUsers_AppUserId", + column: x => x.AppUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AppUserChapterRating_Chapter_ChapterId", + column: x => x.ChapterId, + principalTable: "Chapter", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AppUserChapterRating_Series_SeriesId", + column: x => x.SeriesId, + principalTable: "Series", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ExternalReview_ChapterId", + table: "ExternalReview", + column: "ChapterId"); + + migrationBuilder.CreateIndex( + name: "IX_ExternalRating_ChapterId", + table: "ExternalRating", + column: "ChapterId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_AppUserId", + table: "AppUserChapterRating", + column: "AppUserId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_ChapterId", + table: "AppUserChapterRating", + column: "ChapterId"); + + migrationBuilder.CreateIndex( + name: "IX_AppUserChapterRating_SeriesId", + table: "AppUserChapterRating", + column: "SeriesId"); + + migrationBuilder.AddForeignKey( + name: "FK_ExternalRating_Chapter_ChapterId", + table: "ExternalRating", + column: "ChapterId", + principalTable: "Chapter", + principalColumn: "Id"); + + migrationBuilder.AddForeignKey( + name: "FK_ExternalReview_Chapter_ChapterId", + table: "ExternalReview", + column: "ChapterId", + principalTable: "Chapter", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_ExternalRating_Chapter_ChapterId", + table: "ExternalRating"); + + migrationBuilder.DropForeignKey( + name: "FK_ExternalReview_Chapter_ChapterId", + table: "ExternalReview"); + + migrationBuilder.DropTable( + name: "AppUserChapterRating"); + + migrationBuilder.DropIndex( + name: "IX_ExternalReview_ChapterId", + table: "ExternalReview"); + + migrationBuilder.DropIndex( + name: "IX_ExternalRating_ChapterId", + table: "ExternalRating"); + + migrationBuilder.DropColumn( + name: "Authority", + table: "ExternalReview"); + + migrationBuilder.DropColumn( + name: "ChapterId", + table: "ExternalReview"); + + migrationBuilder.DropColumn( + name: "Authority", + table: "ExternalRating"); + + migrationBuilder.DropColumn( + name: "ChapterId", + table: "ExternalRating"); + + migrationBuilder.DropColumn( + name: "AverageExternalRating", + table: "Chapter"); + } + } +} diff --git a/API/Data/Migrations/DataContextModelSnapshot.cs b/API/Data/Migrations/DataContextModelSnapshot.cs index ab2115091..a66568dcc 100644 --- a/API/Data/Migrations/DataContextModelSnapshot.cs +++ b/API/Data/Migrations/DataContextModelSnapshot.cs @@ -195,6 +195,41 @@ namespace API.Data.Migrations b.ToTable("AppUserBookmark"); }); + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AppUserId") + .HasColumnType("INTEGER"); + + b.Property("ChapterId") + .HasColumnType("INTEGER"); + + b.Property("HasBeenRated") + .HasColumnType("INTEGER"); + + b.Property("Rating") + .HasColumnType("REAL"); + + b.Property("Review") + .HasColumnType("TEXT"); + + b.Property("SeriesId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("AppUserId"); + + b.HasIndex("ChapterId"); + + b.HasIndex("SeriesId"); + + b.ToTable("AppUserChapterRating"); + }); + modelBuilder.Entity("API.Entities.AppUserCollection", b => { b.Property("Id") @@ -752,6 +787,9 @@ namespace API.Data.Migrations b.Property("AlternateSeries") .HasColumnType("TEXT"); + b.Property("AverageExternalRating") + .HasColumnType("REAL"); + b.Property("AvgHoursToRead") .HasColumnType("REAL"); @@ -1316,9 +1354,15 @@ namespace API.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Authority") + .HasColumnType("INTEGER"); + b.Property("AverageScore") .HasColumnType("INTEGER"); + b.Property("ChapterId") + .HasColumnType("INTEGER"); + b.Property("FavoriteCount") .HasColumnType("INTEGER"); @@ -1333,6 +1377,8 @@ namespace API.Data.Migrations b.HasKey("Id"); + b.HasIndex("ChapterId"); + b.ToTable("ExternalRating"); }); @@ -1379,12 +1425,18 @@ namespace API.Data.Migrations .ValueGeneratedOnAdd() .HasColumnType("INTEGER"); + b.Property("Authority") + .HasColumnType("INTEGER"); + b.Property("Body") .HasColumnType("TEXT"); b.Property("BodyJustText") .HasColumnType("TEXT"); + b.Property("ChapterId") + .HasColumnType("INTEGER"); + b.Property("Provider") .HasColumnType("INTEGER"); @@ -1414,6 +1466,8 @@ namespace API.Data.Migrations b.HasKey("Id"); + b.HasIndex("ChapterId"); + b.ToTable("ExternalReview"); }); @@ -2618,6 +2672,33 @@ namespace API.Data.Migrations b.Navigation("AppUser"); }); + modelBuilder.Entity("API.Entities.AppUserChapterRating", b => + { + b.HasOne("API.Entities.AppUser", "AppUser") + .WithMany("ChapterRatings") + .HasForeignKey("AppUserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Chapter", "Chapter") + .WithMany("Ratings") + .HasForeignKey("ChapterId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("API.Entities.Series", "Series") + .WithMany() + .HasForeignKey("SeriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AppUser"); + + b.Navigation("Chapter"); + + b.Navigation("Series"); + }); + modelBuilder.Entity("API.Entities.AppUserCollection", b => { b.HasOne("API.Entities.AppUser", "AppUser") @@ -2905,6 +2986,20 @@ namespace API.Data.Migrations b.Navigation("Chapter"); }); + modelBuilder.Entity("API.Entities.Metadata.ExternalRating", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalRatings") + .HasForeignKey("ChapterId"); + }); + + modelBuilder.Entity("API.Entities.Metadata.ExternalReview", b => + { + b.HasOne("API.Entities.Chapter", null) + .WithMany("ExternalReviews") + .HasForeignKey("ChapterId"); + }); + modelBuilder.Entity("API.Entities.Metadata.ExternalSeriesMetadata", b => { b.HasOne("API.Entities.Series", "Series") @@ -3332,6 +3427,8 @@ namespace API.Data.Migrations { b.Navigation("Bookmarks"); + b.Navigation("ChapterRatings"); + b.Navigation("Collections"); b.Navigation("DashboardStreams"); @@ -3363,10 +3460,16 @@ namespace API.Data.Migrations modelBuilder.Entity("API.Entities.Chapter", b => { + b.Navigation("ExternalRatings"); + + b.Navigation("ExternalReviews"); + b.Navigation("Files"); b.Navigation("People"); + b.Navigation("Ratings"); + b.Navigation("UserProgress"); }); diff --git a/API/Data/Repositories/ChapterRepository.cs b/API/Data/Repositories/ChapterRepository.cs index f34032d79..ce7c44baa 100644 --- a/API/Data/Repositories/ChapterRepository.cs +++ b/API/Data/Repositories/ChapterRepository.cs @@ -5,8 +5,10 @@ using System.Threading.Tasks; using API.DTOs; using API.DTOs.Metadata; using API.DTOs.Reader; +using API.DTOs.SeriesDetail; using API.Entities; using API.Entities.Enums; +using API.Entities.Metadata; using API.Extensions; using API.Extensions.QueryExtensions; using AutoMapper; @@ -24,7 +26,9 @@ public enum ChapterIncludes Files = 4, People = 8, Genres = 16, - Tags = 32 + Tags = 32, + ExternalReviews = 1 << 6, + ExternalRatings = 1 << 7 } public interface IChapterRepository @@ -48,6 +52,11 @@ public interface IChapterRepository Task AddChapterModifiers(int userId, ChapterDto chapter); IEnumerable GetChaptersForSeries(int seriesId); Task> GetAllChaptersForSeries(int seriesId); + Task GetAverageUserRating(int chapterId, int userId); + Task> GetExternalChapterReviewDtos(int chapterId); + Task> GetExternalChapterReview(int chapterId); + Task> GetExternalChapterRatingDtos(int chapterId); + Task> GetExternalChapterRatings(int chapterId); } public class ChapterRepository : IChapterRepository { @@ -310,4 +319,55 @@ public class ChapterRepository : IChapterRepository .ThenInclude(cp => cp.Person) .ToListAsync(); } + + public async Task GetAverageUserRating(int chapterId, int userId) + { + // If there is 0 or 1 rating and that rating is you, return 0 back + var countOfRatingsThatAreUser = await _context.AppUserChapterRating + .Where(r => r.ChapterId == chapterId && r.HasBeenRated) + .CountAsync(u => u.AppUserId == userId); + if (countOfRatingsThatAreUser == 1) + { + return 0; + } + var avg = (await _context.AppUserChapterRating + .Where(r => r.ChapterId == chapterId && r.HasBeenRated) + .AverageAsync(r => (int?) r.Rating)); + return avg.HasValue ? (int) (avg.Value * 20) : 0; + } + + public async Task> GetExternalChapterReviewDtos(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .SelectMany(c => c.ExternalReviews) + // Don't use ProjectTo, it fails to map int to float (??) + .Select(r => _mapper.Map(r)) + .ToListAsync(); + } + + public async Task> GetExternalChapterReview(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .SelectMany(c => c.ExternalReviews) + .ToListAsync(); + } + + public async Task> GetExternalChapterRatingDtos(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .SelectMany(c => c.ExternalRatings) + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); + } + + public async Task> GetExternalChapterRatings(int chapterId) + { + return await _context.Chapter + .Where(c => c.Id == chapterId) + .SelectMany(c => c.ExternalRatings) + .ToListAsync(); + } } diff --git a/API/Data/Repositories/SeriesRepository.cs b/API/Data/Repositories/SeriesRepository.cs index 31ddc22f1..d9c78c770 100644 --- a/API/Data/Repositories/SeriesRepository.cs +++ b/API/Data/Repositories/SeriesRepository.cs @@ -13,6 +13,7 @@ using API.DTOs.CollectionTags; using API.DTOs.Dashboard; using API.DTOs.Filtering; using API.DTOs.Filtering.v2; +using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Metadata; using API.DTOs.ReadingLists; using API.DTOs.Recommendation; @@ -57,6 +58,8 @@ public enum SeriesIncludes ExternalRatings = 128, ExternalRecommendations = 256, ExternalMetadata = 512, + + ExternalData = ExternalMetadata | ExternalReviews | ExternalRatings | ExternalRecommendations, } /// @@ -563,7 +566,13 @@ public class SeriesRepository : ISeriesRepository if (!fullSeries) return await query.ToListAsync(); - return await query.Include(s => s.Volumes) + return await query + .Include(s => s.Volumes) + .ThenInclude(v => v.Chapters) + .ThenInclude(c => c.ExternalRatings) + .Include(s => s.Volumes) + .ThenInclude(v => v.Chapters) + .ThenInclude(c => c.ExternalReviews) .Include(s => s.Relations) .Include(s => s.Metadata) diff --git a/API/Data/Repositories/UserRepository.cs b/API/Data/Repositories/UserRepository.cs index ef790f29e..e55338c8b 100644 --- a/API/Data/Repositories/UserRepository.cs +++ b/API/Data/Repositories/UserRepository.cs @@ -42,7 +42,8 @@ public enum AppUserIncludes DashboardStreams = 2048, SideNavStreams = 4096, ExternalSources = 8192, - Collections = 16384 // 2^14 + Collections = 16384, // 2^14 + ChapterRatings = 1 << 15, } public interface IUserRepository @@ -65,7 +66,9 @@ public interface IUserRepository Task IsUserAdminAsync(AppUser? user); Task> GetRoles(int userId); Task GetUserRatingAsync(int seriesId, int userId); + Task GetUserChapterRatingAsync(int userId, int chapterId); Task> GetUserRatingDtosForSeriesAsync(int seriesId, int userId); + Task> GetUserRatingDtosForChapterAsync(int chapterId, int userId); Task GetPreferencesAsync(string username); Task> GetBookmarkDtosForSeries(int userId, int seriesId); Task> GetBookmarkDtosForVolume(int userId, int volumeId); @@ -587,7 +590,14 @@ public class UserRepository : IUserRepository { return await _context.AppUserRating .Where(r => r.SeriesId == seriesId && r.AppUserId == userId) - .SingleOrDefaultAsync(); + .FirstOrDefaultAsync(); + } + + public async Task GetUserChapterRatingAsync(int userId, int chapterId) + { + return await _context.AppUserChapterRating + .Where(r => r.AppUserId == userId && r.ChapterId == chapterId) + .FirstOrDefaultAsync(); } public async Task> GetUserRatingDtosForSeriesAsync(int seriesId, int userId) @@ -603,6 +613,19 @@ public class UserRepository : IUserRepository .ToListAsync(); } + public async Task> GetUserRatingDtosForChapterAsync(int chapterId, int userId) + { + return await _context.AppUserChapterRating + .Include(r => r.AppUser) + .Where(r => r.ChapterId == chapterId) + .Where(r => r.AppUser.UserPreferences.ShareReviews || r.AppUserId == userId) + .OrderBy(r => r.AppUserId == userId) + .ThenBy(r => r.Rating) + .AsSplitQuery() + .ProjectTo(_mapper.ConfigurationProvider) + .ToListAsync(); + } + public async Task GetPreferencesAsync(string username) { return await _context.AppUserPreferences diff --git a/API/Entities/AppUser.cs b/API/Entities/AppUser.cs index b95cfd260..50f795041 100644 --- a/API/Entities/AppUser.cs +++ b/API/Entities/AppUser.cs @@ -19,6 +19,7 @@ public class AppUser : IdentityUser, IHasConcurrencyToken public ICollection UserRoles { get; set; } = null!; public ICollection Progresses { get; set; } = null!; public ICollection Ratings { get; set; } = null!; + public ICollection ChapterRatings { get; set; } = null!; public AppUserPreferences UserPreferences { get; set; } = null!; /// /// Bookmarks associated with this User diff --git a/API/Entities/AppUserChapterRating.cs b/API/Entities/AppUserChapterRating.cs new file mode 100644 index 000000000..a78096bda --- /dev/null +++ b/API/Entities/AppUserChapterRating.cs @@ -0,0 +1,30 @@ +namespace API.Entities; + +public class AppUserChapterRating +{ + public int Id { get; set; } + /// + /// A number between 0-5.0 that represents how good a series is. + /// + public float Rating { get; set; } + /// + /// If the rating has been explicitly set. Otherwise, the 0.0 rating should be ignored as it's not rated + /// + public bool HasBeenRated { get; set; } + /// + /// A short summary the user can write when giving their review. + /// + public string? Review { get; set; } + /// + /// An optional tagline for the review + /// + public int SeriesId { get; set; } + public Series Series { get; set; } = null!; + + public int ChapterId { get; set; } + public Chapter Chapter { get; set; } = null!; + + // Relationships + public int AppUserId { get; set; } + public AppUser AppUser { get; set; } = null!; +} diff --git a/API/Entities/AppUserRating.cs b/API/Entities/AppUserRating.cs index 5d66a06e4..e76838926 100644 --- a/API/Entities/AppUserRating.cs +++ b/API/Entities/AppUserRating.cs @@ -26,7 +26,6 @@ public class AppUserRating public int SeriesId { get; set; } public Series Series { get; set; } = null!; - // Relationships public int AppUserId { get; set; } public AppUser AppUser { get; set; } = null!; diff --git a/API/Entities/Chapter.cs b/API/Entities/Chapter.cs index 83a547fd7..61a70c8a2 100644 --- a/API/Entities/Chapter.cs +++ b/API/Entities/Chapter.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using API.Entities.Enums; using API.Entities.Interfaces; +using API.Entities.Metadata; using API.Entities.Person; using API.Extensions; using API.Services.Tasks.Scanner.Parser; @@ -125,6 +126,11 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage public string WebLinks { get; set; } = string.Empty; public string ISBN { get; set; } = string.Empty; + /// + /// (Kavita+) Average rating from Kavita+ metadata + /// + public float AverageExternalRating { get; set; } = 0f; + #region Locks public bool AgeRatingLocked { get; set; } @@ -160,6 +166,7 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage /// public ICollection Genres { get; set; } = new List(); public ICollection Tags { get; set; } = new List(); + public ICollection Ratings { get; set; } = []; public ICollection UserProgress { get; set; } @@ -168,6 +175,9 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage public Volume Volume { get; set; } = null!; public int VolumeId { get; set; } + public ICollection ExternalReviews { get; set; } = []; + public ICollection ExternalRatings { get; set; } = null!; + public void UpdateFrom(ParserInfo info) { Files ??= new List(); @@ -192,8 +202,6 @@ public class Chapter : IEntityDate, IHasReadTimeEstimate, IHasCoverImage /// public string GetNumberTitle() { - // BUG: TODO: On non-english locales, for floats, the range will be 20,5 but the NumberTitle will return 20.5 - // Have I fixed this with TryParse CultureInvariant try { if (MinNumber.Is(MaxNumber)) diff --git a/API/Entities/Enums/RatingAuthority.cs b/API/Entities/Enums/RatingAuthority.cs new file mode 100644 index 000000000..0f358a9a7 --- /dev/null +++ b/API/Entities/Enums/RatingAuthority.cs @@ -0,0 +1,17 @@ +using System.ComponentModel; + +namespace API.Entities.Enums; + +public enum RatingAuthority +{ + /// + /// Rating was from a User (internet or local) + /// + [Description("User")] + User = 0, + /// + /// Rating was from Professional Critics + /// + [Description("Critic")] + Critic = 1, +} diff --git a/API/Entities/Metadata/ExternalRating.cs b/API/Entities/Metadata/ExternalRating.cs index b325353e4..7fc2b9353 100644 --- a/API/Entities/Metadata/ExternalRating.cs +++ b/API/Entities/Metadata/ExternalRating.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.Entities.Enums; using API.Services.Plus; namespace API.Entities.Metadata; @@ -10,8 +11,16 @@ public class ExternalRating public int AverageScore { get; set; } public int FavoriteCount { get; set; } public ScrobbleProvider Provider { get; set; } + /// + /// Where this rating comes from: Critic or User + /// + public RatingAuthority Authority { get; set; } = RatingAuthority.User; public string? ProviderUrl { get; set; } public int SeriesId { get; set; } + /// + /// This can be null when for a series-rating + /// + public int? ChapterId { get; set; } public ICollection ExternalSeriesMetadatas { get; set; } = null!; } diff --git a/API/Entities/Metadata/ExternalReview.cs b/API/Entities/Metadata/ExternalReview.cs index 6304d98ad..73c71e5ee 100644 --- a/API/Entities/Metadata/ExternalReview.cs +++ b/API/Entities/Metadata/ExternalReview.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using API.Entities.Enums; using API.Services.Plus; namespace API.Entities.Metadata; @@ -20,6 +21,7 @@ public class ExternalReview /// public string RawBody { get; set; } public required ScrobbleProvider Provider { get; set; } + public RatingAuthority Authority { get; set; } = RatingAuthority.User; public string SiteUrl { get; set; } /// /// Reviewer's username @@ -37,6 +39,7 @@ public class ExternalReview public int SeriesId { get; set; } + public int? ChapterId { get; set; } // Relationships public ICollection ExternalSeriesMetadatas { get; set; } = null!; diff --git a/API/Extensions/ApplicationServiceExtensions.cs b/API/Extensions/ApplicationServiceExtensions.cs index 774413e8e..e004fcc25 100644 --- a/API/Extensions/ApplicationServiceExtensions.cs +++ b/API/Extensions/ApplicationServiceExtensions.cs @@ -52,6 +52,7 @@ public static class ApplicationServiceExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); @@ -84,6 +85,7 @@ public static class ApplicationServiceExtensions services.AddEasyCaching(options => { options.UseInMemory(EasyCacheProfiles.Favicon); + options.UseInMemory(EasyCacheProfiles.Publisher); options.UseInMemory(EasyCacheProfiles.Library); options.UseInMemory(EasyCacheProfiles.RevokedJwt); options.UseInMemory(EasyCacheProfiles.LocaleOptions); diff --git a/API/Extensions/ImageExtensions.cs b/API/Extensions/ImageExtensions.cs index 720f572a9..5779b18ec 100644 --- a/API/Extensions/ImageExtensions.cs +++ b/API/Extensions/ImageExtensions.cs @@ -1,18 +1,62 @@ using System; +using System.Collections.Generic; using System.IO; -using NetVips; +using System.Linq; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; -using Image = NetVips.Image; +using Image = SixLabors.ImageSharp.Image; namespace API.Extensions; public static class ImageExtensions { - public static int GetResolution(this Image image) + + /// + /// Structure to hold various image quality metrics + /// + private sealed class ImageQualityMetrics { - return image.Width * image.Height; + public int Width { get; set; } + public int Height { get; set; } + public bool IsColor { get; set; } + public double Colorfulness { get; set; } + public double Contrast { get; set; } + public double Sharpness { get; set; } + public double NoiseLevel { get; set; } + } + + + /// + /// Calculate a similarity score (0-1f) based on resolution difference and MSE. + /// + /// Path to first image + /// Path to the second image + /// Similarity score between 0-1, where 1 is identical + public static float CalculateSimilarity(this string imagePath1, string imagePath2) + { + if (!File.Exists(imagePath1) || !File.Exists(imagePath2)) + { + throw new FileNotFoundException("One or both image files do not exist"); + } + + // Load both images as Rgba32 (consistent with the rest of the code) + using var img1 = Image.Load(imagePath1); + using var img2 = Image.Load(imagePath2); + + // Calculate resolution difference factor + var res1 = img1.Width * img1.Height; + var res2 = img2.Width * img2.Height; + var resolutionDiff = Math.Abs(res1 - res2) / (float) Math.Max(res1, res2); + + // Calculate mean squared error for pixel differences + var mse = img1.GetMeanSquaredError(img2); + + // Normalize MSE (65025 = 255², which is the max possible squared difference per channel) + var normalizedMse = 1f - Math.Min(1f, mse / 65025f); + + // Final similarity score (weighted average of resolution difference and color difference) + return Math.Max(0f, 1f - (resolutionDiff * 0.5f) - (1f - normalizedMse) * 0.5f); } /// @@ -43,80 +87,351 @@ public static class ImageExtensions } } - return (float)(totalDiff / (img1.Width * img1.Height)); - } - - public static float GetSimilarity(this string imagePath1, string imagePath2) - { - if (!File.Exists(imagePath1) || !File.Exists(imagePath2)) - { - throw new FileNotFoundException("One or both image files do not exist"); - } - - // Calculate similarity score - return CalculateSimilarity(imagePath1, imagePath2); + return (float) (totalDiff / (img1.Width * img1.Height)); } /// - /// Determines which image is "better" based on similarity and resolution. + /// Determines which image is "better" based on multiple quality factors + /// using only the cross-platform ImageSharp library /// /// Path to first image - /// Path to second image - /// Minimum similarity to consider images similar + /// Path to the second image + /// Whether to prefer color images over grayscale (default: true) /// The path of the better image - public static string GetBetterImage(this string imagePath1, string imagePath2, float similarityThreshold = 0.7f) + public static string GetBetterImage(this string imagePath1, string imagePath2, bool preferColor = true) { if (!File.Exists(imagePath1) || !File.Exists(imagePath2)) { throw new FileNotFoundException("One or both image files do not exist"); } - // Calculate similarity score - var similarity = CalculateSimilarity(imagePath1, imagePath2); + // Quick metadata check to get width/height without loading full pixel data + var info1 = Image.Identify(imagePath1); + var info2 = Image.Identify(imagePath2); - using var img1 = Image.NewFromFile(imagePath1, access: Enums.Access.Sequential); - using var img2 = Image.NewFromFile(imagePath2, access: Enums.Access.Sequential); + // Calculate resolution factor + double resolutionFactor1 = info1.Width * info1.Height; + double resolutionFactor2 = info2.Width * info2.Height; - var resolution1 = img1.Width * img1.Height; - var resolution2 = img2.Width * img2.Height; + // If one image is significantly higher resolution (3x or more), just pick it + // This avoids fully loading both images when the choice is obvious + if (resolutionFactor1 > resolutionFactor2 * 3) + return imagePath1; + if (resolutionFactor2 > resolutionFactor1 * 3) + return imagePath2; - // If images are similar, choose the one with higher resolution - if (similarity >= similarityThreshold) + // Otherwise, we need to analyze the actual image data for both + + // NOTE: We HAVE to use these scope blocks and load image here otherwise memory-mapped section exception will occur + ImageQualityMetrics metrics1; + using (var img1 = Image.Load(imagePath1)) { - return resolution1 >= resolution2 ? imagePath1 : imagePath2; + metrics1 = GetImageQualityMetrics(img1); } - // If images are not similar, allow the new image - return imagePath2; + ImageQualityMetrics metrics2; + using (var img2 = Image.Load(imagePath2)) + { + metrics2 = GetImageQualityMetrics(img2); + } + + + // If one is color, and one is grayscale, then we prefer color + if (preferColor && metrics1.IsColor != metrics2.IsColor) + { + return metrics1.IsColor ? imagePath1 : imagePath2; + } + + // Calculate overall quality scores + var score1 = CalculateOverallScore(metrics1); + var score2 = CalculateOverallScore(metrics2); + + return score1 >= score2 ? imagePath1 : imagePath2; + } + + + /// + /// Calculate a weighted overall score based on metrics + /// + private static double CalculateOverallScore(ImageQualityMetrics metrics) + { + // Resolution factor (normalized to HD resolution) + var resolutionFactor = Math.Min(1.0, (metrics.Width * metrics.Height) / (double) (1920 * 1080)); + + // Color factor + var colorFactor = metrics.IsColor ? (0.5 + 0.5 * metrics.Colorfulness) : 0.3; + + // Quality factors + var contrastFactor = Math.Min(1.0, metrics.Contrast); + var sharpnessFactor = Math.Min(1.0, metrics.Sharpness); + + // Noise penalty (less noise is better) + var noisePenalty = Math.Max(0, 1.0 - metrics.NoiseLevel); + + // Weighted combination + return (resolutionFactor * 0.35) + + (colorFactor * 0.3) + + (contrastFactor * 0.15) + + (sharpnessFactor * 0.15) + + (noisePenalty * 0.05); } /// - /// Calculate a similarity score (0-1f) based on resolution difference and MSE. + /// Gets quality metrics for an image /// - /// - /// - /// - private static float CalculateSimilarity(string imagePath1, string imagePath2) + private static ImageQualityMetrics GetImageQualityMetrics(Image image) { - if (!File.Exists(imagePath1) || !File.Exists(imagePath2)) + // Create a smaller version if the image is large to speed up analysis + Image workingImage; + if (image.Width > 512 || image.Height > 512) { - return -1; + workingImage = image.Clone(ctx => ctx.Resize( + new ResizeOptions { + Size = new Size(512), + Mode = ResizeMode.Max + })); + } + else + { + workingImage = image.Clone(); } - using var img1 = Image.NewFromFile(imagePath1, access: Enums.Access.Sequential); - using var img2 = Image.NewFromFile(imagePath2, access: Enums.Access.Sequential); + var metrics = new ImageQualityMetrics + { + Width = image.Width, + Height = image.Height + }; - var res1 = img1.Width * img1.Height; - var res2 = img2.Width * img2.Height; - var resolutionDiff = Math.Abs(res1 - res2) / (float)Math.Max(res1, res2); + // Color analysis (is the image color or grayscale?) + var colorInfo = AnalyzeColorfulness(workingImage); + metrics.IsColor = colorInfo.IsColor; + metrics.Colorfulness = colorInfo.Colorfulness; - using var imgSharp1 = SixLabors.ImageSharp.Image.Load(imagePath1); - using var imgSharp2 = SixLabors.ImageSharp.Image.Load(imagePath2); + // Contrast analysis + metrics.Contrast = CalculateContrast(workingImage); - var mse = imgSharp1.GetMeanSquaredError(imgSharp2); - var normalizedMse = 1f - Math.Min(1f, mse / 65025f); // Normalize based on max color diff + // Sharpness estimation + metrics.Sharpness = EstimateSharpness(workingImage); - // Final similarity score (weighted) - return Math.Max(0f, 1f - (resolutionDiff * 0.5f) - (1f - normalizedMse) * 0.5f); + // Noise estimation + metrics.NoiseLevel = EstimateNoiseLevel(workingImage); + + // Clean up + workingImage.Dispose(); + + return metrics; + } + + /// + /// Analyzes colorfulness of an image + /// + private static (bool IsColor, double Colorfulness) AnalyzeColorfulness(Image image) + { + // For performance, sample a subset of pixels + var sampleSize = Math.Min(1000, image.Width * image.Height); + var stepSize = Math.Max(1, (image.Width * image.Height) / sampleSize); + + var colorCount = 0; + List<(int R, int G, int B)> samples = []; + + // Sample pixels + for (var i = 0; i < image.Width * image.Height; i += stepSize) + { + var x = i % image.Width; + var y = i / image.Width; + + var pixel = image[x, y]; + + // Check if RGB channels differ by a threshold + // High difference indicates color, low difference indicates grayscale + var rMinusG = Math.Abs(pixel.R - pixel.G); + var rMinusB = Math.Abs(pixel.R - pixel.B); + var gMinusB = Math.Abs(pixel.G - pixel.B); + + if (rMinusG > 15 || rMinusB > 15 || gMinusB > 15) + { + colorCount++; + } + + samples.Add((pixel.R, pixel.G, pixel.B)); + } + + // Calculate colorfulness metric based on Hasler and Süsstrunk's approach + // This measures the spread and intensity of colors + if (samples.Count <= 0) return (false, 0); + + // Calculate rg and yb opponent channels + var rg = samples.Select(p => p.R - p.G).ToList(); + var yb = samples.Select(p => 0.5 * (p.R + p.G) - p.B).ToList(); + + // Calculate standard deviation and mean of opponent channels + var rgStdDev = CalculateStdDev(rg); + var ybStdDev = CalculateStdDev(yb); + var rgMean = rg.Average(); + var ybMean = yb.Average(); + + // Combine into colorfulness metric + var stdRoot = Math.Sqrt(rgStdDev * rgStdDev + ybStdDev * ybStdDev); + var meanRoot = Math.Sqrt(rgMean * rgMean + ybMean * ybMean); + + var colorfulness = stdRoot + 0.3 * meanRoot; + + // Normalize to 0-1 range (typical colorfulness is 0-100) + colorfulness = Math.Min(1.0, colorfulness / 100.0); + + var isColor = (double)colorCount / samples.Count > 0.05; + + return (isColor, colorfulness); + + } + + /// + /// Calculate standard deviation of a list of values + /// + private static double CalculateStdDev(List values) + { + var mean = values.Average(); + var sumOfSquaresOfDifferences = values.Select(val => (val - mean) * (val - mean)).Sum(); + return Math.Sqrt(sumOfSquaresOfDifferences / values.Count); + } + + /// + /// Calculate standard deviation of a list of values + /// + private static double CalculateStdDev(List values) + { + var mean = values.Average(); + var sumOfSquaresOfDifferences = values.Select(val => (val - mean) * (val - mean)).Sum(); + return Math.Sqrt(sumOfSquaresOfDifferences / values.Count); + } + + /// + /// Calculates contrast of an image + /// + private static double CalculateContrast(Image image) + { + // For performance, sample a subset of pixels + var sampleSize = Math.Min(1000, image.Width * image.Height); + var stepSize = Math.Max(1, (image.Width * image.Height) / sampleSize); + + List luminanceValues = new(); + + // Sample pixels and calculate luminance + for (var i = 0; i < image.Width * image.Height; i += stepSize) + { + var x = i % image.Width; + var y = i / image.Width; + + var pixel = image[x, y]; + + // Calculate luminance + var luminance = (int)(0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B); + luminanceValues.Add(luminance); + } + + if (luminanceValues.Count < 2) + return 0; + + // Use RMS contrast (root-mean-square of pixel intensity) + var mean = luminanceValues.Average(); + var sumOfSquaresOfDifferences = luminanceValues.Sum(l => Math.Pow(l - mean, 2)); + var rmsContrast = Math.Sqrt(sumOfSquaresOfDifferences / luminanceValues.Count) / mean; + + // Normalize to 0-1 range + return Math.Min(1.0, rmsContrast); + } + + /// + /// Estimates sharpness using simple Laplacian-based method + /// + private static double EstimateSharpness(Image image) + { + // For simplicity, convert to grayscale + var grayImage = new int[image.Width, image.Height]; + + // Convert to grayscale + for (var y = 0; y < image.Height; y++) + { + for (var x = 0; x < image.Width; x++) + { + var pixel = image[x, y]; + grayImage[x, y] = (int)(0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B); + } + } + + // Apply Laplacian filter (3x3) + // The Laplacian measures local variations - higher values indicate edges/details + double laplacianSum = 0; + var validPixels = 0; + + // Laplacian kernel: [0, 1, 0, 1, -4, 1, 0, 1, 0] + for (var y = 1; y < image.Height - 1; y++) + { + for (var x = 1; x < image.Width - 1; x++) + { + var laplacian = + grayImage[x, y - 1] + + grayImage[x - 1, y] - 4 * grayImage[x, y] + grayImage[x + 1, y] + + grayImage[x, y + 1]; + + laplacianSum += Math.Abs(laplacian); + validPixels++; + } + } + + if (validPixels == 0) + return 0; + + // Calculate variance of Laplacian + var laplacianVariance = laplacianSum / validPixels; + + // Normalize to 0-1 range (typical values range from 0-1000) + return Math.Min(1.0, laplacianVariance / 1000.0); + } + + /// + /// Estimates noise level using simple block-based variance method + /// + private static double EstimateNoiseLevel(Image image) + { + // Block size for noise estimation + const int blockSize = 8; + List blockVariances = new(); + + // Calculate variance in small blocks throughout the image + for (var y = 0; y < image.Height - blockSize; y += blockSize) + { + for (var x = 0; x < image.Width - blockSize; x += blockSize) + { + List blockValues = new(); + + // Sample block + for (var by = 0; by < blockSize; by++) + { + for (var bx = 0; bx < blockSize; bx++) + { + var pixel = image[x + bx, y + by]; + var value = (int)(0.299 * pixel.R + 0.587 * pixel.G + 0.114 * pixel.B); + blockValues.Add(value); + } + } + + // Calculate variance of this block + var blockMean = blockValues.Average(); + var blockVariance = blockValues.Sum(v => Math.Pow(v - blockMean, 2)) / blockValues.Count; + blockVariances.Add(blockVariance); + } + } + + if (blockVariances.Count == 0) + return 0; + + // Sort block variances and take lowest 10% (likely uniform areas where noise is most visible) + blockVariances.Sort(); + var smoothBlocksCount = Math.Max(1, blockVariances.Count / 10); + var averageNoiseVariance = blockVariances.Take(smoothBlocksCount).Average(); + + // Normalize to 0-1 range (typical noise variances are 0-100) + return Math.Min(1.0, averageNoiseVariance / 100.0); } } diff --git a/API/Extensions/QueryExtensions/IncludesExtensions.cs b/API/Extensions/QueryExtensions/IncludesExtensions.cs index 983f6798e..864c4e5a1 100644 --- a/API/Extensions/QueryExtensions/IncludesExtensions.cs +++ b/API/Extensions/QueryExtensions/IncludesExtensions.cs @@ -1,6 +1,7 @@ using System.Linq; using API.Data.Repositories; using API.Entities; +using API.Entities.Metadata; using Microsoft.EntityFrameworkCore; namespace API.Extensions.QueryExtensions; @@ -72,6 +73,18 @@ public static class IncludesExtensions .Include(c => c.Tags); } + if (includes.HasFlag(ChapterIncludes.ExternalReviews)) + { + queryable = queryable + .Include(c => c.ExternalReviews); + } + + if (includes.HasFlag(ChapterIncludes.ExternalRatings)) + { + queryable = queryable + .Include(c => c.ExternalRatings); + } + return queryable.AsSplitQuery(); } @@ -253,6 +266,11 @@ public static class IncludesExtensions .ThenInclude(c => c.Items); } + if (includeFlags.HasFlag(AppUserIncludes.ChapterRatings)) + { + query = query.Include(u => u.ChapterRatings); + } + return query.AsSplitQuery(); } diff --git a/API/Helpers/AutoMapperProfiles.cs b/API/Helpers/AutoMapperProfiles.cs index 69ed884fd..334403ab3 100644 --- a/API/Helpers/AutoMapperProfiles.cs +++ b/API/Helpers/AutoMapperProfiles.cs @@ -97,6 +97,16 @@ public class AutoMapperProfiles : Profile .ForMember(dest => dest.Username, opt => opt.MapFrom(src => src.AppUser.UserName)); + CreateMap() + .ForMember(dest => dest.LibraryId, + opt => + opt.MapFrom(src => src.Series.LibraryId)) + .ForMember(dest => dest.Body, + opt => + opt.MapFrom(src => src.Review)) + .ForMember(dest => dest.Username, + opt => + opt.MapFrom(src => src.AppUser.UserName)); CreateMap() .ForMember(dest => dest.PageNum, diff --git a/API/Helpers/Builders/AppUserChapterRatingBuilder.cs b/API/Helpers/Builders/AppUserChapterRatingBuilder.cs new file mode 100644 index 000000000..b5deb9228 --- /dev/null +++ b/API/Helpers/Builders/AppUserChapterRatingBuilder.cs @@ -0,0 +1,40 @@ +#nullable enable +using System; +using API.Entities; + +namespace API.Helpers.Builders; + +public class ChapterRatingBuilder : IEntityBuilder +{ + private readonly AppUserChapterRating _rating; + public AppUserChapterRating Build() => _rating; + + public ChapterRatingBuilder(AppUserChapterRating? rating = null) + { + _rating = rating ?? new AppUserChapterRating(); + } + + public ChapterRatingBuilder WithSeriesId(int seriesId) + { + _rating.SeriesId = seriesId; + return this; + } + + public ChapterRatingBuilder WithChapterId(int chapterId) + { + _rating.ChapterId = chapterId; + return this; + } + + public ChapterRatingBuilder WithRating(int rating) + { + _rating.Rating = Math.Clamp(rating, 0, 5); + return this; + } + + public ChapterRatingBuilder WithBody(string body) + { + _rating.Review = body; + return this; + } +} diff --git a/API/Program.cs b/API/Program.cs index 77fac9e49..852844f2f 100644 --- a/API/Program.cs +++ b/API/Program.cs @@ -20,9 +20,11 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using NetVips; using Serilog; using Serilog.Events; using Serilog.Sinks.AspNetCore.SignalR.Extensions; +using Log = Serilog.Log; namespace API; #nullable enable @@ -143,6 +145,8 @@ public class Program var settings = await unitOfWork.SettingsRepository.GetSettingsDtoAsync(); LogLevelOptions.SwitchLogLevel(settings.LoggingLevel); + InitNetVips(); + await host.RunAsync(); } catch (Exception ex) { @@ -225,4 +229,14 @@ public class Program webBuilder.UseStartup(); }); + + /// + /// Ensure NetVips does not cache + /// + /// https://github.com/kleisauke/net-vips/issues/6#issuecomment-394379299 + private static void InitNetVips() + { + Cache.MaxFiles = 0; + + } } diff --git a/API/Services/DirectoryService.cs b/API/Services/DirectoryService.cs index ae9383c7b..7e308d92e 100644 --- a/API/Services/DirectoryService.cs +++ b/API/Services/DirectoryService.cs @@ -69,6 +69,7 @@ public interface IDirectoryService IEnumerable GetFiles(string path, string fileNameRegex = "", SearchOption searchOption = SearchOption.TopDirectoryOnly); bool ExistOrCreate(string directoryPath); void DeleteFiles(IEnumerable files); + void CopyFile(string sourcePath, string destinationPath, bool overwrite = true); void RemoveNonImages(string directoryName); void Flatten(string directoryName); Task CheckWriteAccess(string directoryName); @@ -937,6 +938,27 @@ public class DirectoryService : IDirectoryService } } + public void CopyFile(string sourcePath, string destinationPath, bool overwrite = true) + { + if (!File.Exists(sourcePath)) + { + throw new FileNotFoundException("Source file not found", sourcePath); + } + + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (string.IsNullOrEmpty(destinationDirectory)) + { + throw new ArgumentException("Destination path does not contain a directory", nameof(destinationPath)); + } + + if (!Directory.Exists(destinationDirectory)) + { + FileSystem.Directory.CreateDirectory(destinationDirectory); + } + + FileSystem.File.Copy(sourcePath, destinationPath, overwrite); + } + /// /// Returns the human-readable file size for an arbitrary, 64-bit file size /// The default format is "0.## XB", e.g. "4.2 KB" or "1.43 GB" @@ -1090,4 +1112,23 @@ public class DirectoryService : IDirectoryService FlattenDirectory(root, subDirectory, ref directoryIndex); } } + + /// + /// If the file is locked or not existing + /// + /// + /// + public static bool IsFileLocked(string filePath) + { + try + { + if (!File.Exists(filePath)) return false; + using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.None); + return false; // If this works, the file is not locked + } + catch (IOException) + { + return true; // File is locked by another process + } + } } diff --git a/API/Services/Plus/ExternalMetadataService.cs b/API/Services/Plus/ExternalMetadataService.cs index aef97bdda..f9af923a2 100644 --- a/API/Services/Plus/ExternalMetadataService.cs +++ b/API/Services/Plus/ExternalMetadataService.cs @@ -1085,15 +1085,104 @@ public class ExternalMetadataService : IExternalMetadataService madeModification = await UpdateChapterPeople(chapter, settings, PersonRole.Writer, potentialMatch.Writers) || madeModification; madeModification = await UpdateChapterCoverImage(chapter, settings, potentialMatch.CoverImageUrl) || madeModification; + madeModification = await UpdateExternalChapterMetadata(chapter, settings, potentialMatch) || madeModification; _unitOfWork.ChapterRepository.Update(chapter); await _unitOfWork.CommitAsync(); } + return madeModification; + } + + private async Task UpdateExternalChapterMetadata(Chapter chapter, MetadataSettingsDto settings, ExternalChapterDto metadata) + { + if (!settings.Enabled) return false; + + if (metadata.UserReviews.Count == 0 && metadata.CriticReviews.Count == 0) + { + return false; + } + + var madeModification = false; + + #region Review + + // Remove existing Reviews + var existingReviews = await _unitOfWork.ChapterRepository.GetExternalChapterReview(chapter.Id); + _unitOfWork.ExternalSeriesMetadataRepository.Remove(existingReviews); + + + List externalReviews = []; + externalReviews.AddRange(metadata.CriticReviews + .Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body)) + .Select(r => + { + var review = _mapper.Map(r); + review.ChapterId = chapter.Id; + review.Authority = RatingAuthority.Critic; + CleanCbrReview(ref review); + return review; + })); + externalReviews.AddRange(metadata.UserReviews + .Where(r => !string.IsNullOrWhiteSpace(r.Username) && !string.IsNullOrWhiteSpace(r.Body)) + .Select(r => + { + var review = _mapper.Map(r); + review.ChapterId = chapter.Id; + review.Authority = RatingAuthority.User; + CleanCbrReview(ref review); + return review; + })); + + chapter.ExternalReviews = externalReviews; + madeModification = externalReviews.Count > 0; + _logger.LogDebug("Added {Count} reviews for chapter {ChapterId}", externalReviews.Count, chapter.Id); + #endregion + + #region Rating + + var averageCriticRating = metadata.CriticReviews.Average(r => r.Rating); + var averageUserRating = metadata.UserReviews.Average(r => r.Rating); + + var existingRatings = await _unitOfWork.ChapterRepository.GetExternalChapterRatings(chapter.Id); + _unitOfWork.ExternalSeriesMetadataRepository.Remove(existingRatings); + + chapter.ExternalRatings = + [ + new ExternalRating + { + AverageScore = (int) averageUserRating, + Provider = ScrobbleProvider.Cbr, + Authority = RatingAuthority.User, + ProviderUrl = metadata.IssueUrl, + }, + new ExternalRating + { + AverageScore = (int) averageCriticRating, + Provider = ScrobbleProvider.Cbr, + Authority = RatingAuthority.Critic, + ProviderUrl = metadata.IssueUrl, + + }, + ]; + + chapter.AverageExternalRating = averageUserRating; + + madeModification = averageUserRating > 0f || averageCriticRating > 0f || madeModification; + + #endregion return madeModification; } + private static void CleanCbrReview(ref ExternalReview review) + { + // CBR has Read Full Review which links to site, but we already have that + review.Body = review.Body.Replace("Read Full Review", string.Empty).TrimEnd(); + review.RawBody = review.RawBody.Replace("Read Full Review", string.Empty).TrimEnd(); + review.BodyJustText = review.BodyJustText.Replace("Read Full Review", string.Empty).TrimEnd(); + } + private static bool UpdateChapterSummary(Chapter chapter, MetadataSettingsDto settings, string? summary) { diff --git a/API/Services/Plus/WantToReadSyncService.cs b/API/Services/Plus/WantToReadSyncService.cs index 07861710c..a6d536911 100644 --- a/API/Services/Plus/WantToReadSyncService.cs +++ b/API/Services/Plus/WantToReadSyncService.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using API.Data; using API.Data.Repositories; +using API.DTOs.KavitaPlus.Metadata; using API.DTOs.Recommendation; using API.DTOs.SeriesDetail; using API.Entities; diff --git a/API/Services/RatingService.cs b/API/Services/RatingService.cs new file mode 100644 index 000000000..ccaebba69 --- /dev/null +++ b/API/Services/RatingService.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using API.Data; +using API.Data.Repositories; +using API.DTOs; +using API.Entities; +using API.Services.Plus; +using Hangfire; +using Microsoft.Extensions.Logging; + +namespace API.Services; + +public interface IRatingService +{ + /// + /// Updates the users' rating for a given series + /// + /// Should include ratings + /// + /// + Task UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto); + + /// + /// Updates the users' rating for a given chapter + /// + /// Should include ratings + /// chapterId must be set + /// + Task UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto); +} + +public class RatingService: IRatingService +{ + + private readonly IUnitOfWork _unitOfWork; + private readonly IScrobblingService _scrobblingService; + private readonly ILogger _logger; + + public RatingService(IUnitOfWork unitOfWork, IScrobblingService scrobblingService, ILogger logger) + { + _unitOfWork = unitOfWork; + _scrobblingService = scrobblingService; + _logger = logger; + } + + public async Task UpdateSeriesRating(AppUser user, UpdateRatingDto updateRatingDto) + { + var userRating = + await _unitOfWork.UserRepository.GetUserRatingAsync(updateRatingDto.SeriesId, user.Id) ?? + new AppUserRating(); + + try + { + userRating.Rating = Math.Clamp(updateRatingDto.UserRating, 0f, 5f); + userRating.HasBeenRated = true; + userRating.SeriesId = updateRatingDto.SeriesId; + + if (userRating.Id == 0) + { + user.Ratings ??= new List(); + user.Ratings.Add(userRating); + } + + _unitOfWork.UserRepository.Update(user); + + if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync()) + { + BackgroundJob.Enqueue(() => + _scrobblingService.ScrobbleRatingUpdate(user.Id, updateRatingDto.SeriesId, + userRating.Rating)); + return true; + } + } + catch (Exception ex) + { + _logger.LogError(ex, "There was an exception saving rating"); + } + + await _unitOfWork.RollbackAsync(); + user.Ratings?.Remove(userRating); + + return false; + } + + public async Task UpdateChapterRating(AppUser user, UpdateRatingDto updateRatingDto) + { + if (updateRatingDto.ChapterId == null) + { + return false; + } + + var userRating = + await _unitOfWork.UserRepository.GetUserChapterRatingAsync(user.Id, updateRatingDto.ChapterId.Value) ?? + new AppUserChapterRating(); + + try + { + userRating.Rating = Math.Clamp(updateRatingDto.UserRating, 0f, 5f); + userRating.HasBeenRated = true; + userRating.SeriesId = updateRatingDto.SeriesId; + userRating.ChapterId = updateRatingDto.ChapterId.Value; + + if (userRating.Id == 0) + { + user.ChapterRatings ??= new List(); + user.ChapterRatings.Add(userRating); + } + + _unitOfWork.UserRepository.Update(user); + + await _unitOfWork.CommitAsync(); + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "There was an exception saving rating"); + } + + await _unitOfWork.RollbackAsync(); + user.ChapterRatings?.Remove(userRating); + + return false; + } + +} diff --git a/API/Services/SeriesService.cs b/API/Services/SeriesService.cs index 7c2d4f6eb..6ac5e0ecb 100644 --- a/API/Services/SeriesService.cs +++ b/API/Services/SeriesService.cs @@ -29,13 +29,11 @@ public interface ISeriesService { Task GetSeriesDetail(int seriesId, int userId); Task UpdateSeriesMetadata(UpdateSeriesMetadataDto updateSeriesMetadataDto); - Task UpdateRating(AppUser user, UpdateSeriesRatingDto updateSeriesRatingDto); Task DeleteMultipleSeries(IList seriesIds); Task UpdateRelatedSeries(UpdateRelatedSeriesDto dto); Task GetRelatedSeries(int userId, int seriesId); Task FormatChapterTitle(int userId, ChapterDto chapter, LibraryType libraryType, bool withHash = true); Task FormatChapterTitle(int userId, Chapter chapter, LibraryType libraryType, bool withHash = true); - Task FormatChapterTitle(int userId, bool isSpecial, LibraryType libraryType, string chapterRange, string? chapterTitle, bool withHash); Task FormatChapterName(int userId, LibraryType libraryType, bool withHash = false); @@ -447,63 +445,12 @@ public class SeriesService : ISeriesService } - - /// - /// - /// - /// User with Ratings includes - /// - /// - public async Task UpdateRating(AppUser? user, UpdateSeriesRatingDto updateSeriesRatingDto) - { - if (user == null) - { - _logger.LogError("Cannot update rating of null user"); - return false; - } - - var userRating = - await _unitOfWork.UserRepository.GetUserRatingAsync(updateSeriesRatingDto.SeriesId, user.Id) ?? - new AppUserRating(); - try - { - userRating.Rating = Math.Clamp(updateSeriesRatingDto.UserRating, 0f, 5f); - userRating.HasBeenRated = true; - userRating.SeriesId = updateSeriesRatingDto.SeriesId; - - if (userRating.Id == 0) - { - user.Ratings ??= new List(); - user.Ratings.Add(userRating); - } - - _unitOfWork.UserRepository.Update(user); - - if (!_unitOfWork.HasChanges() || await _unitOfWork.CommitAsync()) - { - BackgroundJob.Enqueue(() => - _scrobblingService.ScrobbleRatingUpdate(user.Id, updateSeriesRatingDto.SeriesId, - userRating.Rating)); - return true; - } - } - catch (Exception ex) - { - _logger.LogError(ex, "There was an exception saving rating"); - } - - await _unitOfWork.RollbackAsync(); - user.Ratings?.Remove(userRating); - - return false; - } - public async Task DeleteMultipleSeries(IList seriesIds) { try { var chapterMappings = - await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync(seriesIds.ToArray()); + await _unitOfWork.SeriesRepository.GetChapterIdWithSeriesIdForSeriesAsync([.. seriesIds]); var allChapterIds = new List(); foreach (var mapping in chapterMappings) @@ -511,9 +458,8 @@ public class SeriesService : ISeriesService allChapterIds.AddRange(mapping.Value); } - // NOTE: This isn't getting all the people and whatnot currently + // NOTE: This isn't getting all the people and whatnot currently due to the lack of includes var series = await _unitOfWork.SeriesRepository.GetSeriesByIdsAsync(seriesIds); - _unitOfWork.SeriesRepository.Remove(series); var libraryIds = series.Select(s => s.LibraryId); @@ -534,7 +480,8 @@ public class SeriesService : ISeriesService await _unitOfWork.AppUserProgressRepository.CleanupAbandonedChapters(); await _unitOfWork.CollectionTagRepository.RemoveCollectionsWithoutSeries(); - _taskScheduler.CleanupChapters(allChapterIds.ToArray()); + _taskScheduler.CleanupChapters([.. allChapterIds]); + return true; } catch (Exception ex) diff --git a/API/Services/Tasks/Metadata/CoverDbService.cs b/API/Services/Tasks/Metadata/CoverDbService.cs index c76bb99d1..cebf08b97 100644 --- a/API/Services/Tasks/Metadata/CoverDbService.cs +++ b/API/Services/Tasks/Metadata/CoverDbService.cs @@ -45,6 +45,7 @@ public class CoverDbService : ICoverDbService private readonly IImageService _imageService; private readonly IUnitOfWork _unitOfWork; private readonly IEventHub _eventHub; + private TimeSpan _cacheTime = TimeSpan.FromDays(10); private const string NewHost = "https://www.kavitareader.com/CoversDB/"; @@ -80,6 +81,22 @@ public class CoverDbService : ICoverDbService _eventHub = eventHub; } + /// + /// Downloads the favicon image from a given website URL, optionally falling back to a custom method if standard methods fail. + /// + /// The full URL of the website to extract the favicon from. + /// The desired image encoding format for saving the favicon (e.g., WebP, PNG). + /// + /// A string representing the filename of the downloaded favicon image, saved to the configured favicon directory. + /// + /// + /// Thrown when favicon retrieval fails or if a previously failed domain is detected in cache. + /// + /// + /// This method first checks for a cached failure to avoid re-requesting bad links. + /// It then attempts to parse HTML for `link` tags pointing to `.png` favicons and + /// falls back to an internal fallback method if needed. Valid results are saved to disk. + /// public async Task DownloadFaviconAsync(string url, EncodeFormat encodeFormat) { // Parse the URL to get the domain (including subdomain) @@ -97,7 +114,7 @@ public class CoverDbService : ICoverDbService throw new KavitaException($"Kavita has already tried to fetch from {sanitizedBaseUrl} and failed. Skipping duplicate check"); } - await provider.SetAsync(baseUrl, string.Empty, TimeSpan.FromDays(10)); + await provider.SetAsync(baseUrl, string.Empty, _cacheTime); if (FaviconUrlMapper.TryGetValue(baseUrl, out var value)) { url = value; @@ -156,23 +173,10 @@ public class CoverDbService : ICoverDbService // Create the destination file path using var image = Image.PngloadStream(faviconStream); var filename = ImageService.GetWebLinkFormat(baseUrl, encodeFormat); - switch (encodeFormat) - { - case EncodeFormat.PNG: - image.Pngsave(Path.Combine(_directoryService.FaviconDirectory, filename)); - break; - case EncodeFormat.WEBP: - image.Webpsave(Path.Combine(_directoryService.FaviconDirectory, filename)); - break; - case EncodeFormat.AVIF: - image.Heifsave(Path.Combine(_directoryService.FaviconDirectory, filename)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(encodeFormat), encodeFormat, null); - } - + image.WriteToFile(Path.Combine(_directoryService.FaviconDirectory, filename)); _logger.LogDebug("Favicon for {Domain} downloaded and saved successfully", domain); + return filename; } catch (Exception ex) { @@ -185,6 +189,17 @@ public class CoverDbService : ICoverDbService { try { + // Sanitize user input + publisherName = publisherName.Replace(Environment.NewLine, string.Empty).Replace("\r", string.Empty).Replace("\n", string.Empty); + var provider = _cacheFactory.GetCachingProvider(EasyCacheProfiles.Publisher); + var res = await provider.GetAsync(publisherName); + if (res.HasValue) + { + _logger.LogInformation("Kavita has already tried to fetch Publisher: {PublisherName} and failed. Skipping duplicate check", publisherName); + throw new KavitaException($"Kavita has already tried to fetch Publisher: {publisherName} and failed. Skipping duplicate check"); + } + + await provider.SetAsync(publisherName, string.Empty, _cacheTime); var publisherLink = await FallbackToKavitaReaderPublisher(publisherName); if (string.IsNullOrEmpty(publisherLink)) { @@ -200,23 +215,10 @@ public class CoverDbService : ICoverDbService // Create the destination file path using var image = Image.NewFromStream(publisherStream); var filename = ImageService.GetPublisherFormat(publisherName, encodeFormat); - switch (encodeFormat) - { - case EncodeFormat.PNG: - image.Pngsave(Path.Combine(_directoryService.PublisherDirectory, filename)); - break; - case EncodeFormat.WEBP: - image.Webpsave(Path.Combine(_directoryService.PublisherDirectory, filename)); - break; - case EncodeFormat.AVIF: - image.Heifsave(Path.Combine(_directoryService.PublisherDirectory, filename)); - break; - default: - throw new ArgumentOutOfRangeException(nameof(encodeFormat), encodeFormat, null); - } - + image.WriteToFile(Path.Combine(_directoryService.PublisherDirectory, filename)); _logger.LogDebug("Publisher image for {PublisherName} downloaded and saved successfully", publisherName.Sanitize()); + return filename; } catch (Exception ex) { @@ -282,40 +284,30 @@ public class CoverDbService : ICoverDbService return null; } - private async Task DownloadImageFromUrl(string filenameWithoutExtension, EncodeFormat encodeFormat, string url) + private async Task DownloadImageFromUrl(string filenameWithoutExtension, EncodeFormat encodeFormat, string url, string? targetDirectory = null) { + // TODO: I need to unit test this to ensure it works when overwriting, etc + + // Target Directory defaults to CoverImageDirectory, but can be temp for when comparison between images is used + targetDirectory ??= _directoryService.CoverImageDirectory; + // Create the destination file path var filename = filenameWithoutExtension + encodeFormat.GetExtension(); - var targetFile = Path.Combine(_directoryService.CoverImageDirectory, filename); - - // Ensure if file exists, we delete to overwrite + var targetFile = Path.Combine(targetDirectory, filename); _logger.LogTrace("Fetching person image from {Url}", url.Sanitize()); // Download the file using Flurl - var personStream = await url + var imageStream = await url .AllowHttpStatus("2xx,304") .GetStreamAsync(); - using var image = Image.NewFromStream(personStream); - switch (encodeFormat) - { - case EncodeFormat.PNG: - image.Pngsave(targetFile); - break; - case EncodeFormat.WEBP: - image.Webpsave(targetFile); - break; - case EncodeFormat.AVIF: - image.Heifsave(targetFile); - break; - default: - throw new ArgumentOutOfRangeException(nameof(encodeFormat), encodeFormat, null); - } + using var image = Image.NewFromStream(imageStream); + image.WriteToFile(targetFile); return filename; } - private async Task GetCoverPersonImagePath(Person person) + private async Task GetCoverPersonImagePath(Person person) { var tempFile = Path.Join(_directoryService.LongTermCacheDirectory, "people.yml"); @@ -372,25 +364,22 @@ public class CoverDbService : ICoverDbService await CacheDataAsync(urlsFileName, allOverrides); - if (!string.IsNullOrEmpty(allOverrides)) + if (string.IsNullOrEmpty(allOverrides)) return correctSizeLink; + + var cleanedBaseUrl = baseUrl.Replace("https://", string.Empty); + var externalFile = allOverrides + .Split("\n") + .FirstOrDefault(url => + cleanedBaseUrl.Equals(url.Replace(".png", string.Empty)) || + cleanedBaseUrl.Replace("www.", string.Empty).Equals(url.Replace(".png", string.Empty) + )); + + if (string.IsNullOrEmpty(externalFile)) { - var cleanedBaseUrl = baseUrl.Replace("https://", string.Empty); - var externalFile = allOverrides - .Split("\n") - .FirstOrDefault(url => - cleanedBaseUrl.Equals(url.Replace(".png", string.Empty)) || - cleanedBaseUrl.Replace("www.", string.Empty).Equals(url.Replace(".png", string.Empty) - )); - - if (string.IsNullOrEmpty(externalFile)) - { - throw new KavitaException($"Could not grab favicon from {baseUrl.Sanitize()}"); - } - - correctSizeLink = $"{NewHost}favicons/" + externalFile; + throw new KavitaException($"Could not grab favicon from {baseUrl.Sanitize()}"); } - return correctSizeLink; + return $"{NewHost}favicons/{externalFile}"; } private async Task FallbackToKavitaReaderPublisher(string publisherName) @@ -403,34 +392,30 @@ public class CoverDbService : ICoverDbService // Cache immediately await CacheDataAsync(publisherFileName, allOverrides); + if (string.IsNullOrEmpty(allOverrides)) return externalLink; - if (!string.IsNullOrEmpty(allOverrides)) - { - var externalFile = allOverrides - .Split("\n") - .Select(publisherLine => - { - var tokens = publisherLine.Split("|"); - if (tokens.Length != 2) return null; - var aliases = tokens[0]; - // Multiple publisher aliases are separated by # - if (aliases.Split("#").Any(name => name.ToLowerInvariant().Trim().Equals(publisherName.ToLowerInvariant().Trim()))) - { - return tokens[1]; - } - return null; - }) - .FirstOrDefault(url => !string.IsNullOrEmpty(url)); - - if (string.IsNullOrEmpty(externalFile)) + var externalFile = allOverrides + .Split("\n") + .Select(publisherLine => { - throw new KavitaException($"Could not grab publisher image for {publisherName}"); - } + var tokens = publisherLine.Split("|"); + if (tokens.Length != 2) return null; + var aliases = tokens[0]; + // Multiple publisher aliases are separated by # + if (aliases.Split("#").Any(name => name.ToLowerInvariant().Trim().Equals(publisherName.ToLowerInvariant().Trim()))) + { + return tokens[1]; + } + return null; + }) + .FirstOrDefault(url => !string.IsNullOrEmpty(url)); - externalLink = $"{NewHost}publishers/" + externalFile; + if (string.IsNullOrEmpty(externalFile)) + { + throw new KavitaException($"Could not grab publisher image for {publisherName}"); } - return externalLink; + return $"{NewHost}publishers/{externalLink}"; } private async Task CacheDataAsync(string fileName, string? content) @@ -473,33 +458,67 @@ public class CoverDbService : ICoverDbService /// Will check against all known null image placeholders to avoid writing it public async Task SetPersonCoverByUrl(Person person, string url, bool fromBase64 = true, bool checkNoImagePlaceholder = false) { - // TODO: Refactor checkNoImagePlaceholder bool to an action that evaluates how to process Image if (!string.IsNullOrEmpty(url)) { - var filePath = await CreateThumbnail(url, $"{ImageService.GetPersonFormat(person.Id)}", fromBase64); + var tempDir = _directoryService.TempDirectory; + var format = ImageService.GetPersonFormat(person.Id); + var finalFileName = format + ".webp"; + var tempFileName = format + "_new"; + var tempFilePath = await CreateThumbnail(url, tempFileName, fromBase64, tempDir); - // Additional check to see if downloaded image is similar and we have a higher resolution - if (checkNoImagePlaceholder) + if (!string.IsNullOrEmpty(tempFilePath)) { - var matchRating = Path.Join(_directoryService.AssetsDirectory, "anilist-no-image-placeholder.jpg").GetSimilarity(Path.Join(_directoryService.CoverImageDirectory, filePath))!; + var tempFullPath = Path.Combine(tempDir, tempFilePath); + var finalFullPath = Path.Combine(_directoryService.CoverImageDirectory, finalFileName); - if (matchRating >= 0.9f) + // Skip setting image if it's similar to a known placeholder + if (checkNoImagePlaceholder) { - if (string.IsNullOrEmpty(person.CoverImage)) + var placeholderPath = Path.Combine(_directoryService.AssetsDirectory, "anilist-no-image-placeholder.jpg"); + var similarity = placeholderPath.CalculateSimilarity(tempFullPath); + if (similarity >= 0.9f) { - filePath = null; + _logger.LogInformation("Skipped setting placeholder image for person {PersonId} due to high similarity ({Similarity})", person.Id, similarity); + _directoryService.DeleteFiles([tempFullPath]); + return; + } + } + + try + { + if (!string.IsNullOrEmpty(person.CoverImage)) + { + var existingPath = Path.Combine(_directoryService.CoverImageDirectory, person.CoverImage); + var betterImage = existingPath.GetBetterImage(tempFullPath)!; + + var choseNewImage = string.Equals(betterImage, tempFullPath, StringComparison.OrdinalIgnoreCase); + if (choseNewImage) + { + _directoryService.DeleteFiles([existingPath]); + _directoryService.CopyFile(tempFullPath, finalFullPath); + person.CoverImage = finalFileName; + } + else + { + _directoryService.DeleteFiles([tempFullPath]); + person.CoverImage = Path.GetFileName(existingPath); + } } else { - filePath = Path.GetFileName(Path.Join(_directoryService.CoverImageDirectory, person.CoverImage)); + _directoryService.CopyFile(tempFullPath, finalFullPath); + person.CoverImage = finalFileName; } - } - } + catch (Exception ex) + { + _logger.LogError(ex, "Error choosing better image for Person: {PersonId}", person.Id); + _directoryService.CopyFile(tempFullPath, finalFullPath); + person.CoverImage = finalFileName; + } + + _directoryService.DeleteFiles([tempFullPath]); - if (!string.IsNullOrEmpty(filePath)) - { - person.CoverImage = filePath; person.CoverImageLocked = true; _imageService.UpdateColorScape(person); _unitOfWork.PersonRepository.Update(person); @@ -532,31 +551,52 @@ public class CoverDbService : ICoverDbService { if (!string.IsNullOrEmpty(url)) { - var filePath = await CreateThumbnail(url, $"{ImageService.GetSeriesFormat(series.Id)}", fromBase64); + var tempDir = _directoryService.TempDirectory; + var format = ImageService.GetSeriesFormat(series.Id); + var finalFileName = format + ".webp"; + var tempFileName = format + "_new"; + var tempFilePath = await CreateThumbnail(url, tempFileName, fromBase64, tempDir); - if (!string.IsNullOrEmpty(filePath)) + if (!string.IsNullOrEmpty(tempFilePath)) { - // Additional check to see if downloaded image is similar and we have a higher resolution + var tempFullPath = Path.Combine(tempDir, tempFilePath); + var finalFullPath = Path.Combine(_directoryService.CoverImageDirectory, finalFileName); + if (chooseBetterImage && !string.IsNullOrEmpty(series.CoverImage)) { try { - var betterImage = Path.Join(_directoryService.CoverImageDirectory, series.CoverImage) - .GetBetterImage(Path.Join(_directoryService.CoverImageDirectory, filePath))!; - filePath = Path.GetFileName(betterImage); + var existingPath = Path.Combine(_directoryService.CoverImageDirectory, series.CoverImage); + var betterImage = existingPath.GetBetterImage(tempFullPath)!; + + var choseNewImage = string.Equals(betterImage, tempFullPath, StringComparison.OrdinalIgnoreCase); + if (choseNewImage) + { + _directoryService.DeleteFiles([existingPath]); + _directoryService.CopyFile(tempFullPath, finalFullPath); + series.CoverImage = finalFileName; + } + else + { + _directoryService.DeleteFiles([tempFullPath]); + series.CoverImage = Path.GetFileName(existingPath); + } } catch (Exception ex) { - _logger.LogError(ex, "There was an issue trying to choose a better cover image for Series: {SeriesName} ({SeriesId})", series.Name, series.Id); + _logger.LogError(ex, "Error choosing better image for Series: {SeriesId}", series.Id); + _directoryService.CopyFile(tempFullPath, finalFullPath); + series.CoverImage = finalFileName; } } - - series.CoverImage = filePath; - series.CoverImageLocked = true; - if (series.CoverImage == null) + else { - _logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null"); + _directoryService.CopyFile(tempFullPath, finalFullPath); + series.CoverImage = finalFileName; } + + _directoryService.DeleteFiles([tempFullPath]); + series.CoverImageLocked = true; _imageService.UpdateColorScape(series); _unitOfWork.SeriesRepository.Update(series); } @@ -565,10 +605,7 @@ public class CoverDbService : ICoverDbService { series.CoverImage = null; series.CoverImageLocked = false; - if (series.CoverImage == null) - { - _logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null"); - } + _logger.LogDebug("[SeriesCoverImageBug] Setting Series Cover Image to null"); _imageService.UpdateColorScape(series); _unitOfWork.SeriesRepository.Update(series); } @@ -585,26 +622,52 @@ public class CoverDbService : ICoverDbService { if (!string.IsNullOrEmpty(url)) { - var filePath = await CreateThumbnail(url, $"{ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId)}", fromBase64); + var tempDirectory = _directoryService.TempDirectory; + var finalFileName = ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId) + ".webp"; + var tempFileName = ImageService.GetChapterFormat(chapter.Id, chapter.VolumeId) + "_new"; - if (!string.IsNullOrEmpty(filePath)) + var tempFilePath = await CreateThumbnail(url, tempFileName, fromBase64, tempDirectory); + + if (!string.IsNullOrEmpty(tempFilePath)) { - // Additional check to see if downloaded image is similar and we have a higher resolution + var tempFullPath = Path.Combine(tempDirectory, tempFilePath); + var finalFullPath = Path.Combine(_directoryService.CoverImageDirectory, finalFileName); + if (chooseBetterImage && !string.IsNullOrEmpty(chapter.CoverImage)) { try { - var betterImage = Path.Join(_directoryService.CoverImageDirectory, chapter.CoverImage) - .GetBetterImage(Path.Join(_directoryService.CoverImageDirectory, filePath))!; - filePath = Path.GetFileName(betterImage); + var existingPath = Path.Combine(_directoryService.CoverImageDirectory, chapter.CoverImage); + var betterImage = existingPath.GetBetterImage(tempFullPath)!; + var choseNewImage = string.Equals(betterImage, tempFullPath, StringComparison.OrdinalIgnoreCase); + + if (choseNewImage) + { + // This will fail if Cover gen is done just before this as there is a bug with files getting locked. + _directoryService.DeleteFiles([existingPath]); + _directoryService.CopyFile(tempFullPath, finalFullPath); + _directoryService.DeleteFiles([tempFullPath]); + } + else + { + _directoryService.DeleteFiles([tempFullPath]); + } + + chapter.CoverImage = finalFileName; } catch (Exception ex) { _logger.LogError(ex, "There was an issue trying to choose a better cover image for Chapter: {FileName} ({ChapterId})", chapter.Range, chapter.Id); } } + else + { + // No comparison needed, just copy and rename to final + _directoryService.CopyFile(tempFullPath, finalFullPath); + _directoryService.DeleteFiles([tempFullPath]); + chapter.CoverImage = finalFileName; + } - chapter.CoverImage = filePath; chapter.CoverImageLocked = true; _imageService.UpdateColorScape(chapter); _unitOfWork.ChapterRepository.Update(chapter); @@ -621,13 +684,26 @@ public class CoverDbService : ICoverDbService if (_unitOfWork.HasChanges()) { await _unitOfWork.CommitAsync(); - await _eventHub.SendMessageAsync(MessageFactory.CoverUpdate, - MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter), false); + await _eventHub.SendMessageAsync( + MessageFactory.CoverUpdate, + MessageFactory.CoverUpdateEvent(chapter.Id, MessageFactoryEntityTypes.Chapter), + false + ); } } - private async Task CreateThumbnail(string url, string filename, bool fromBase64 = true) + /// + /// + /// + /// + /// Filename without extension + /// + /// Not useable with fromBase64. Allows a different directory to be written to + /// + private async Task CreateThumbnail(string url, string filenameWithoutExtension, bool fromBase64 = true, string? targetDirectory = null) { + targetDirectory ??= _directoryService.CoverImageDirectory; + var settings = await _unitOfWork.SettingsRepository.GetSettingsDtoAsync(); var encodeFormat = settings.EncodeMediaAs; var coverImageSize = settings.CoverImageSize; @@ -635,9 +711,9 @@ public class CoverDbService : ICoverDbService if (fromBase64) { return _imageService.CreateThumbnailFromBase64(url, - filename, encodeFormat, coverImageSize.GetDimensions().Width); + filenameWithoutExtension, encodeFormat, coverImageSize.GetDimensions().Width); } - return await DownloadImageFromUrl(filename, encodeFormat, url); + return await DownloadImageFromUrl(filenameWithoutExtension, encodeFormat, url, targetDirectory); } } diff --git a/API/Services/Tasks/Scanner/ProcessSeries.cs b/API/Services/Tasks/Scanner/ProcessSeries.cs index 59721fe61..454c72733 100644 --- a/API/Services/Tasks/Scanner/ProcessSeries.cs +++ b/API/Services/Tasks/Scanner/ProcessSeries.cs @@ -194,8 +194,8 @@ public class ProcessSeries : IProcessSeries if (seriesAdded) { // See if any recommendations can link up to the series and pre-fetch external metadata for the series - BackgroundJob.Enqueue(() => - _externalMetadataService.FetchSeriesMetadata(series.Id, series.Library.Type)); + // BackgroundJob.Enqueue(() => + // _externalMetadataService.FetchSeriesMetadata(series.Id, series.Library.Type)); await _eventHub.SendMessageAsync(MessageFactory.SeriesAdded, MessageFactory.SeriesAddedEvent(series.Id, series.Name, series.LibraryId), false); @@ -214,6 +214,10 @@ public class ProcessSeries : IProcessSeries return; } + if (seriesAdded) + { + await _externalMetadataService.FetchSeriesMetadata(series.Id, series.Library.Type); + } await _metadataService.GenerateCoversForSeries(series.LibraryId, series.Id, false, false); await _wordCountAnalyzerService.ScanSeries(series.LibraryId, series.Id, forceUpdate); } diff --git a/API/Startup.cs b/API/Startup.cs index 188c2b2dd..34af22154 100644 --- a/API/Startup.cs +++ b/API/Startup.cs @@ -138,8 +138,8 @@ public class Startup { c.SwaggerDoc("v1", new OpenApiInfo { - Version = "3.1.0", - Title = $"Kavita (v{BuildInfo.Version})", + Version = BuildInfo.Version.ToString(), + Title = $"Kavita", Description = $"Kavita provides a set of APIs that are authenticated by JWT. JWT token can be copied from local storage. Assume all fields of a payload are required. Built against v{BuildInfo.Version}", License = new OpenApiLicense { diff --git a/Kavita.Common/Kavita.Common.csproj b/Kavita.Common/Kavita.Common.csproj index 265de4a23..2c9ab6dc0 100644 --- a/Kavita.Common/Kavita.Common.csproj +++ b/Kavita.Common/Kavita.Common.csproj @@ -3,7 +3,7 @@ net9.0 kavitareader.com Kavita - 0.8.6.5 + 0.8.6.8 en true diff --git a/UI/Web/src/app/_models/chapter-detail-plus.ts b/UI/Web/src/app/_models/chapter-detail-plus.ts new file mode 100644 index 000000000..2a17089e1 --- /dev/null +++ b/UI/Web/src/app/_models/chapter-detail-plus.ts @@ -0,0 +1,9 @@ +import {UserReview} from "../_single-module/review-card/user-review"; +import {Rating} from "./rating"; + +export type ChapterDetailPlus = { + rating: number; + hasBeenRated: boolean; + reviews: UserReview[]; + ratings: Rating[]; +}; diff --git a/UI/Web/src/app/_models/rating.ts b/UI/Web/src/app/_models/rating.ts index a4c4b79ed..7132706f9 100644 --- a/UI/Web/src/app/_models/rating.ts +++ b/UI/Web/src/app/_models/rating.ts @@ -1,9 +1,15 @@ import {ScrobbleProvider} from "../_services/scrobbling.service"; +export enum RatingAuthority { + User = 0, + Critic = 1, +} + export interface Rating { averageScore: number; meanScore: number; favoriteCount: number; provider: ScrobbleProvider; providerUrl: string | undefined; + authority: RatingAuthority; } diff --git a/UI/Web/src/app/_services/action.service.ts b/UI/Web/src/app/_services/action.service.ts index 1cf4e448e..fd24bd9ff 100644 --- a/UI/Web/src/app/_services/action.service.ts +++ b/UI/Web/src/app/_services/action.service.ts @@ -536,7 +536,7 @@ export class ActionService { addMultipleSeriesToWantToReadList(seriesIds: Array, callback?: VoidActionCallback) { this.memberService.addSeriesToWantToRead(seriesIds).subscribe(() => { - this.toastr.success('Series added to Want to Read list'); + this.toastr.success(translate('toasts.series-added-want-to-read')); if (callback) { callback(); } diff --git a/UI/Web/src/app/_services/chapter.service.ts b/UI/Web/src/app/_services/chapter.service.ts index c722031bd..6a6f7a600 100644 --- a/UI/Web/src/app/_services/chapter.service.ts +++ b/UI/Web/src/app/_services/chapter.service.ts @@ -1,8 +1,9 @@ -import { Injectable } from '@angular/core'; +import {Injectable} from '@angular/core'; import {environment} from "../../environments/environment"; -import { HttpClient } from "@angular/common/http"; +import {HttpClient} from "@angular/common/http"; import {Chapter} from "../_models/chapter"; import {TextResonse} from "../_types/text-response"; +import {ChapterDetailPlus} from "../_models/chapter-detail-plus"; @Injectable({ providedIn: 'root' @@ -29,4 +30,8 @@ export class ChapterService { return this.httpClient.post(this.baseUrl + 'chapter/update', chapter, TextResonse); } + chapterDetailPlus(seriesId: number, chapterId: number) { + return this.httpClient.get(this.baseUrl + `chapter/chapter-detail-plus?chapterId=${chapterId}&seriesId=${seriesId}`); + } + } diff --git a/UI/Web/src/app/_services/review.service.ts b/UI/Web/src/app/_services/review.service.ts new file mode 100644 index 000000000..b8635bcf8 --- /dev/null +++ b/UI/Web/src/app/_services/review.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@angular/core'; +import {UserReview} from "../_single-module/review-card/user-review"; +import {environment} from "../../environments/environment"; +import {HttpClient} from "@angular/common/http"; +import {Rating} from "../_models/rating"; + +@Injectable({ + providedIn: 'root' +}) +export class ReviewService { + + private baseUrl = environment.apiUrl; + + constructor(private httpClient: HttpClient) { } + + deleteReview(seriesId: number, chapterId?: number) { + if (chapterId) { + return this.httpClient.delete(this.baseUrl + `review/chapter?chapterId=${chapterId}`); + } + + return this.httpClient.delete(this.baseUrl + `review/series?seriesId=${seriesId}`); + } + + updateReview(seriesId: number, body: string, chapterId?: number) { + if (chapterId) { + return this.httpClient.post(this.baseUrl + `review/chapter`, { + seriesId, chapterId, body + }); + } + + return this.httpClient.post(this.baseUrl + 'review/series', { + seriesId, body + }); + } + + updateRating(seriesId: number, userRating: number, chapterId?: number) { + if (chapterId) { + return this.httpClient.post(this.baseUrl + 'rating/chapter', { + seriesId, chapterId, userRating + }) + } + + return this.httpClient.post(this.baseUrl + 'rating/series', { + seriesId, userRating + }) + } + + overallRating(seriesId: number, chapterId?: number) { + if (chapterId) { + return this.httpClient.get(this.baseUrl + `rating/overall-chapter?chapterId=${chapterId}`); + } + + return this.httpClient.get(this.baseUrl + `rating/overall-series?seriesId=${seriesId}`); + } + +} diff --git a/UI/Web/src/app/_services/series.service.ts b/UI/Web/src/app/_services/series.service.ts index f221b2f1a..b440b1eb7 100644 --- a/UI/Web/src/app/_services/series.service.ts +++ b/UI/Web/src/app/_services/series.service.ts @@ -203,27 +203,9 @@ export class SeriesService { return this.httpClient.get(this.baseUrl + 'series/series-detail?seriesId=' + seriesId); } - - - deleteReview(seriesId: number) { - return this.httpClient.delete(this.baseUrl + 'review?seriesId=' + seriesId); - } - updateReview(seriesId: number, body: string) { - return this.httpClient.post(this.baseUrl + 'review', { - seriesId, body - }); - } - - getReviews(seriesId: number) { - return this.httpClient.get>(this.baseUrl + 'review?seriesId=' + seriesId); - } - getRatings(seriesId: number) { return this.httpClient.get>(this.baseUrl + 'rating?seriesId=' + seriesId); } - getOverallRating(seriesId: number) { - return this.httpClient.get(this.baseUrl + 'rating/overall?seriesId=' + seriesId); - } removeFromOnDeck(seriesId: number) { return this.httpClient.post(this.baseUrl + 'series/remove-from-on-deck?seriesId=' + seriesId, {}); diff --git a/UI/Web/src/app/_services/volume.service.ts b/UI/Web/src/app/_services/volume.service.ts index f53a20543..8c9f9e17e 100644 --- a/UI/Web/src/app/_services/volume.service.ts +++ b/UI/Web/src/app/_services/volume.service.ts @@ -28,4 +28,5 @@ export class VolumeService { updateVolume(volume: any) { return this.httpClient.post(this.baseUrl + 'volume/update', volume, TextResonse); } + } diff --git a/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.html b/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.html index c34a3d888..5a9804d54 100644 --- a/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.html +++ b/UI/Web/src/app/_single-module/match-series-modal/match-series-modal.component.html @@ -53,7 +53,7 @@ } @empty { @if (!isLoading) { - {{t('no-results')}} +

{{t('no-results')}}

} } } diff --git a/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.html b/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.html index 15d0c5239..2d94dd848 100644 --- a/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.html +++ b/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.html @@ -34,7 +34,11 @@
@if ((item.series.volumes || 0) > 0 || (item.series.chapters || 0) > 0) { {{t('volume-count', {num: item.series.volumes})}} - {{t('chapter-count', {num: item.series.chapters})}} + @if (item.series.plusMediaFormat === PlusMediaFormat.Comic) { + {{t('issue-count', {num: item.series.chapters})}} + } @else { + {{t('chapter-count', {num: item.series.chapters})}} + } } @else { {{t('releasing')}} } diff --git a/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.ts b/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.ts index 7dd953ad8..9e3044884 100644 --- a/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.ts +++ b/UI/Web/src/app/_single-module/match-series-result-item/match-series-result-item.component.ts @@ -14,6 +14,7 @@ import {ReadMoreComponent} from "../../shared/read-more/read-more.component"; import {TranslocoDirective} from "@jsverse/transloco"; import {PlusMediaFormatPipe} from "../../_pipes/plus-media-format.pipe"; import {LoadingComponent} from "../../shared/loading/loading.component"; +import {PlusMediaFormat} from "../../_models/series-detail/external-series-detail"; @Component({ selector: 'app-match-series-result-item', @@ -47,4 +48,5 @@ export class MatchSeriesResultItemComponent { this.selected.emit(this.item); } + protected readonly PlusMediaFormat = PlusMediaFormat; } diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.html b/UI/Web/src/app/_single-module/review-card/review-card.component.html index 99a788471..c5bade722 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.html +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.html @@ -27,6 +27,9 @@ {{review.username}} } {{(isMyReview ? '' : review.username | defaultValue:'')}} + @if (review.authority === RatingAuthority.Critic) { + ({{t('critic')}}) + }
@if (review.isExternal){ {{t('rating-percentage', {r: review.score})}} diff --git a/UI/Web/src/app/_single-module/review-card/review-card.component.ts b/UI/Web/src/app/_single-module/review-card/review-card.component.ts index 55216b169..f86bca966 100644 --- a/UI/Web/src/app/_single-module/review-card/review-card.component.ts +++ b/UI/Web/src/app/_single-module/review-card/review-card.component.ts @@ -13,15 +13,13 @@ import {UserReview} from "./user-review"; import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; import {ReviewCardModalComponent} from "../review-card-modal/review-card-modal.component"; import {AccountService} from "../../_services/account.service"; -import { - ReviewSeriesModalCloseEvent, - ReviewSeriesModalComponent -} from "../review-series-modal/review-series-modal.component"; +import {ReviewModalCloseEvent, ReviewModalComponent} from "../review-modal/review-modal.component"; import {ReadMoreComponent} from "../../shared/read-more/read-more.component"; import {DefaultValuePipe} from "../../_pipes/default-value.pipe"; import {ProviderImagePipe} from "../../_pipes/provider-image.pipe"; import {TranslocoDirective} from "@jsverse/transloco"; import {ScrobbleProvider} from "../../_services/scrobbling.service"; +import {RatingAuthority} from "../../_models/rating"; @Component({ selector: 'app-review-card', @@ -35,7 +33,7 @@ export class ReviewCardComponent implements OnInit { protected readonly ScrobbleProvider = ScrobbleProvider; @Input({required: true}) review!: UserReview; - @Output() refresh = new EventEmitter(); + @Output() refresh = new EventEmitter(); isMyReview: boolean = false; @@ -44,7 +42,7 @@ export class ReviewCardComponent implements OnInit { ngOnInit() { this.accountService.currentUser$.subscribe(u => { if (u) { - this.isMyReview = this.review.username === u.username; + this.isMyReview = this.review.username === u.username && !this.review.isExternal; this.cdRef.markForCheck(); } }); @@ -53,16 +51,19 @@ export class ReviewCardComponent implements OnInit { showModal() { let component; if (this.isMyReview) { - component = ReviewSeriesModalComponent; + component = ReviewModalComponent; } else { component = ReviewCardModalComponent; } const ref = this.modalService.open(component, {size: 'lg', fullscreen: 'md'}); + ref.componentInstance.review = this.review; - ref.closed.subscribe((res: ReviewSeriesModalCloseEvent | undefined) => { + ref.closed.subscribe((res: ReviewModalCloseEvent | undefined) => { if (res) { this.refresh.emit(res); } }) } + + protected readonly RatingAuthority = RatingAuthority; } diff --git a/UI/Web/src/app/_single-module/review-card/user-review.ts b/UI/Web/src/app/_single-module/review-card/user-review.ts index 1b5771463..58af94dea 100644 --- a/UI/Web/src/app/_single-module/review-card/user-review.ts +++ b/UI/Web/src/app/_single-module/review-card/user-review.ts @@ -1,8 +1,11 @@ import {ScrobbleProvider} from "../../_services/scrobbling.service"; +import {RatingAuthority} from "../../_models/rating"; + export interface UserReview { seriesId: number; libraryId: number; + chapterId?: number; score: number; username: string; body: string; @@ -11,4 +14,5 @@ export interface UserReview { bodyJustText?: string; siteUrl?: string; provider: ScrobbleProvider; + authority: RatingAuthority; } diff --git a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html b/UI/Web/src/app/_single-module/review-modal/review-modal.component.html similarity index 83% rename from UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html rename to UI/Web/src/app/_single-module/review-modal/review-modal.component.html index 6539a4e41..582a538c3 100644 --- a/UI/Web/src/app/_single-module/review-series-modal/review-series-modal.component.html +++ b/UI/Web/src/app/_single-module/review-modal/review-modal.component.html @@ -1,4 +1,4 @@ - +