Added manga data and pipeline.
This commit is contained in:
11
MangaReader.Core/Data/ChapterPage.cs
Normal file
11
MangaReader.Core/Data/ChapterPage.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class ChapterPage
|
||||||
|
{
|
||||||
|
public int ChapterPageId { get; set; }
|
||||||
|
|
||||||
|
public int MangaChapterId { get; set; }
|
||||||
|
public required MangaChapter MangaChapter { get; set; }
|
||||||
|
|
||||||
|
public int PageNumber { get; set; }
|
||||||
|
}
|
||||||
12
MangaReader.Core/Data/ChapterSource.cs
Normal file
12
MangaReader.Core/Data/ChapterSource.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class ChapterSource
|
||||||
|
{
|
||||||
|
public int MangaChapterId { get; set; }
|
||||||
|
public required MangaChapter Chapter { get; set; }
|
||||||
|
|
||||||
|
public int SourceId { get; set; }
|
||||||
|
public required Source Source { get; set; }
|
||||||
|
|
||||||
|
public required string Url { get; set; }
|
||||||
|
}
|
||||||
7
MangaReader.Core/Data/Genre.cs
Normal file
7
MangaReader.Core/Data/Genre.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class Genre
|
||||||
|
{
|
||||||
|
public int GenreId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
namespace MangaReader.Core.Data;
|
|
||||||
|
|
||||||
public class Manga
|
public class Manga
|
||||||
{
|
{
|
||||||
@@ -24,297 +22,3 @@ public class Manga
|
|||||||
Chapters = new HashSet<MangaChapter>();
|
Chapters = new HashSet<MangaChapter>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MangaCover
|
|
||||||
{
|
|
||||||
public int MangaCoverId { get; set; }
|
|
||||||
|
|
||||||
public int MangaId { get; set; }
|
|
||||||
public required Manga Manga { get; set; }
|
|
||||||
|
|
||||||
public required Guid Guid { get; set; }
|
|
||||||
public required string FileExtension { get; set; }
|
|
||||||
public string? Description { get; set; }
|
|
||||||
public bool IsPrimary { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MangaTitle
|
|
||||||
{
|
|
||||||
public int MangaTitleId { get; set; }
|
|
||||||
|
|
||||||
public int MangaId { get; set; }
|
|
||||||
public required Manga Manga { get; set; }
|
|
||||||
|
|
||||||
public required string TitleEntry { get; set; }
|
|
||||||
public TitleType TitleType { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum TitleType
|
|
||||||
{
|
|
||||||
Primary,
|
|
||||||
OfficialTranslation,
|
|
||||||
FanTranslation,
|
|
||||||
Synonym,
|
|
||||||
Abbreviation,
|
|
||||||
Romaji,
|
|
||||||
Japanese
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Source
|
|
||||||
{
|
|
||||||
public int SourceId { get; set; }
|
|
||||||
public required string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MangaSource
|
|
||||||
{
|
|
||||||
public int MangaId { get; set; }
|
|
||||||
public required Manga Manga { get; set; }
|
|
||||||
|
|
||||||
public int SourceId { get; set; }
|
|
||||||
public required Source Source { get; set; }
|
|
||||||
|
|
||||||
public required string Url { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Genre
|
|
||||||
{
|
|
||||||
public int GenreId { get; set; }
|
|
||||||
public required string Name { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MangaGenre
|
|
||||||
{
|
|
||||||
public int MangaId { get; set; }
|
|
||||||
public required Manga Manga { get; set; }
|
|
||||||
|
|
||||||
public int GenreId { get; set; }
|
|
||||||
public required Genre Genre { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class MangaChapter
|
|
||||||
{
|
|
||||||
public int MangaChapterId { get; set; }
|
|
||||||
|
|
||||||
public int MangaId { get; set; }
|
|
||||||
public required Manga Manga { get; set; }
|
|
||||||
|
|
||||||
public int? VolumeNumber { get; set; }
|
|
||||||
public int ChapterNumber { get; set; }
|
|
||||||
public string? Title { get; set; }
|
|
||||||
|
|
||||||
public virtual ICollection<ChapterSource> Sources { get; set; } = [];
|
|
||||||
public virtual ICollection<ChapterPage> Pages { get; set; } = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ChapterSource
|
|
||||||
{
|
|
||||||
public int MangaChapterId { get; set; }
|
|
||||||
public required MangaChapter Chapter { get; set; }
|
|
||||||
|
|
||||||
public int SourceId { get; set; }
|
|
||||||
public required Source Source { get; set; }
|
|
||||||
|
|
||||||
public required string Url { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ChapterPage
|
|
||||||
{
|
|
||||||
public int ChapterPageId { get; set; }
|
|
||||||
|
|
||||||
public int MangaChapterId { get; set; }
|
|
||||||
public required MangaChapter MangaChapter { get; set; }
|
|
||||||
|
|
||||||
public int PageNumber { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public class MangaContext(DbContextOptions options) : DbContext(options)
|
|
||||||
{
|
|
||||||
public DbSet<Manga> Mangas { get; set; }
|
|
||||||
public DbSet<MangaCover> MangaCovers { get; set; }
|
|
||||||
public DbSet<MangaTitle> MangaTitles { get; set; }
|
|
||||||
public DbSet<Source> Sources { get; set; }
|
|
||||||
public DbSet<MangaSource> MangaSources { get; set; }
|
|
||||||
public DbSet<Genre> Genres { get; set; }
|
|
||||||
public DbSet<MangaGenre> MangaGenres { get; set; }
|
|
||||||
public DbSet<MangaChapter> MangaChapters { get; set; }
|
|
||||||
public DbSet<ChapterSource> ChapterSources { get; set; }
|
|
||||||
public DbSet<ChapterPage> ChapterPages { get; set; }
|
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
base.OnModelCreating(modelBuilder);
|
|
||||||
|
|
||||||
ConfigureManga(modelBuilder);
|
|
||||||
ConfigureMangaCover(modelBuilder);
|
|
||||||
ConfigureMangaTitle(modelBuilder);
|
|
||||||
ConfigureSource(modelBuilder);
|
|
||||||
ConfigureMangaSource(modelBuilder);
|
|
||||||
ConfigureGenre(modelBuilder);
|
|
||||||
ConfigureMangaGenre(modelBuilder);
|
|
||||||
ConfigureMangaChapter(modelBuilder);
|
|
||||||
ConfigureChapterSource(modelBuilder);
|
|
||||||
ConfigureChapterPage(modelBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureManga(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<Manga>()
|
|
||||||
.HasKey(x => x.MangaId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<Manga>()
|
|
||||||
.HasIndex(x => x.Slug)
|
|
||||||
.IsUnique();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureMangaCover(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaCover>()
|
|
||||||
.HasKey(x => x.MangaCoverId);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaCover>()
|
|
||||||
.HasOne(x => x.Manga)
|
|
||||||
.WithMany(x => x.Covers)
|
|
||||||
.HasForeignKey(x => x.MangaId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
|
|
||||||
modelBuilder.Entity<MangaCover>()
|
|
||||||
.HasIndex(x => x.Guid)
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
//modelBuilder
|
|
||||||
// .Entity<MangaCover>()
|
|
||||||
// .HasIndex(x => new { x.MangaId, x.IsPrimary })
|
|
||||||
// .IsUnique()
|
|
||||||
// .HasFilter("[IsPrimary] = 1"); // Enforce only one primary cover per manga
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureMangaTitle(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaTitle>()
|
|
||||||
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
|
|
||||||
|
|
||||||
modelBuilder.Entity<MangaTitle>()
|
|
||||||
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.TitleEntry })
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaTitle>()
|
|
||||||
.HasIndex(mangaTitle => mangaTitle.TitleEntry);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaTitle>()
|
|
||||||
.HasOne(x => x.Manga)
|
|
||||||
.WithMany(x => x.Titles)
|
|
||||||
.HasForeignKey(x => x.MangaId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureSource(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<Source>()
|
|
||||||
.HasKey(x => x.SourceId);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<Source>()
|
|
||||||
.HasIndex(x => x.Name)
|
|
||||||
.IsUnique(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureMangaSource(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaSource>()
|
|
||||||
.HasKey(mangaSource => new { mangaSource.MangaId, mangaSource.SourceId });
|
|
||||||
|
|
||||||
modelBuilder.Entity<MangaSource>()
|
|
||||||
.HasIndex(x => x.Url)
|
|
||||||
.IsUnique();
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaSource>()
|
|
||||||
.HasOne(x => x.Manga)
|
|
||||||
.WithMany(x => x.Sources)
|
|
||||||
.HasForeignKey(x => x.MangaId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureGenre(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<Genre>()
|
|
||||||
.HasKey(x => x.GenreId);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<Genre>()
|
|
||||||
.HasIndex(x => x.Name)
|
|
||||||
.IsUnique(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureMangaGenre(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaGenre>()
|
|
||||||
.HasKey(mangaGenre => new { mangaGenre.MangaId, mangaGenre.GenreId });
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaGenre>()
|
|
||||||
.HasOne(x => x.Manga)
|
|
||||||
.WithMany(x => x.Genres)
|
|
||||||
.HasForeignKey(x => x.MangaId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureMangaChapter(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaChapter>()
|
|
||||||
.HasKey(x => x.MangaChapterId);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<MangaChapter>()
|
|
||||||
.HasOne(x => x.Manga)
|
|
||||||
.WithMany(x => x.Chapters)
|
|
||||||
.HasForeignKey(x => x.MangaId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureChapterSource(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ChapterSource>()
|
|
||||||
.HasKey(chapterSource => new { chapterSource.MangaChapterId, chapterSource.SourceId });
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ChapterSource>()
|
|
||||||
.HasOne(x => x.Chapter)
|
|
||||||
.WithMany(x => x.Sources)
|
|
||||||
.HasForeignKey(x => x.MangaChapterId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ConfigureChapterPage(ModelBuilder modelBuilder)
|
|
||||||
{
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ChapterPage>()
|
|
||||||
.HasKey(chapterPage => chapterPage.ChapterPageId);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ChapterPage>()
|
|
||||||
.HasIndex(chapterPage => new { chapterPage.MangaChapterId, chapterPage.PageNumber })
|
|
||||||
.IsUnique(true);
|
|
||||||
|
|
||||||
modelBuilder
|
|
||||||
.Entity<ChapterPage>()
|
|
||||||
.HasOne(x => x.MangaChapter)
|
|
||||||
.WithMany(x => x.Pages)
|
|
||||||
.HasForeignKey(x => x.MangaChapterId)
|
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
16
MangaReader.Core/Data/MangaChapter.cs
Normal file
16
MangaReader.Core/Data/MangaChapter.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaChapter
|
||||||
|
{
|
||||||
|
public int MangaChapterId { get; set; }
|
||||||
|
|
||||||
|
public int MangaId { get; set; }
|
||||||
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
|
public int? VolumeNumber { get; set; }
|
||||||
|
public required float ChapterNumber { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
|
||||||
|
public virtual ICollection<ChapterSource> Sources { get; set; } = [];
|
||||||
|
public virtual ICollection<ChapterPage> Pages { get; set; } = [];
|
||||||
|
}
|
||||||
193
MangaReader.Core/Data/MangaContext.cs
Normal file
193
MangaReader.Core/Data/MangaContext.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaContext(DbContextOptions options) : DbContext(options)
|
||||||
|
{
|
||||||
|
public DbSet<Manga> Mangas { get; set; }
|
||||||
|
public DbSet<MangaCover> MangaCovers { get; set; }
|
||||||
|
public DbSet<MangaTitle> MangaTitles { get; set; }
|
||||||
|
public DbSet<Source> Sources { get; set; }
|
||||||
|
public DbSet<MangaSource> MangaSources { get; set; }
|
||||||
|
public DbSet<Genre> Genres { get; set; }
|
||||||
|
public DbSet<MangaGenre> MangaGenres { get; set; }
|
||||||
|
public DbSet<MangaChapter> MangaChapters { get; set; }
|
||||||
|
public DbSet<ChapterSource> ChapterSources { get; set; }
|
||||||
|
public DbSet<ChapterPage> ChapterPages { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|
||||||
|
ConfigureManga(modelBuilder);
|
||||||
|
ConfigureMangaCover(modelBuilder);
|
||||||
|
ConfigureMangaTitle(modelBuilder);
|
||||||
|
ConfigureSource(modelBuilder);
|
||||||
|
ConfigureMangaSource(modelBuilder);
|
||||||
|
ConfigureGenre(modelBuilder);
|
||||||
|
ConfigureMangaGenre(modelBuilder);
|
||||||
|
ConfigureMangaChapter(modelBuilder);
|
||||||
|
ConfigureChapterSource(modelBuilder);
|
||||||
|
ConfigureChapterPage(modelBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureManga(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<Manga>()
|
||||||
|
.HasKey(x => x.MangaId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<Manga>()
|
||||||
|
.HasIndex(x => x.Slug)
|
||||||
|
.IsUnique();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaCover(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaCover>()
|
||||||
|
.HasKey(x => x.MangaCoverId);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaCover>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Covers)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaCover>()
|
||||||
|
.HasIndex(x => x.Guid)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
//modelBuilder
|
||||||
|
// .Entity<MangaCover>()
|
||||||
|
// .HasIndex(x => new { x.MangaId, x.IsPrimary })
|
||||||
|
// .IsUnique()
|
||||||
|
// .HasFilter("[IsPrimary] = 1"); // Enforce only one primary cover per manga
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaTitle(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaTitle>()
|
||||||
|
.HasKey(mangaTitle => mangaTitle.MangaTitleId);
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaTitle>()
|
||||||
|
.HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.TitleEntry })
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaTitle>()
|
||||||
|
.HasIndex(mangaTitle => mangaTitle.TitleEntry);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaTitle>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Titles)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureSource(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<Source>()
|
||||||
|
.HasKey(x => x.SourceId);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<Source>()
|
||||||
|
.HasIndex(x => x.Name)
|
||||||
|
.IsUnique(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaSource(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaSource>()
|
||||||
|
.HasKey(mangaSource => new { mangaSource.MangaId, mangaSource.SourceId });
|
||||||
|
|
||||||
|
modelBuilder.Entity<MangaSource>()
|
||||||
|
.HasIndex(x => x.Url)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaSource>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Sources)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureGenre(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<Genre>()
|
||||||
|
.HasKey(x => x.GenreId);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<Genre>()
|
||||||
|
.HasIndex(x => x.Name)
|
||||||
|
.IsUnique(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaGenre(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaGenre>()
|
||||||
|
.HasKey(mangaGenre => new { mangaGenre.MangaId, mangaGenre.GenreId });
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaGenre>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Genres)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMangaChapter(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaChapter>()
|
||||||
|
.HasKey(x => x.MangaChapterId);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<MangaChapter>()
|
||||||
|
.HasOne(x => x.Manga)
|
||||||
|
.WithMany(x => x.Chapters)
|
||||||
|
.HasForeignKey(x => x.MangaId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureChapterSource(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<ChapterSource>()
|
||||||
|
.HasKey(chapterSource => new { chapterSource.MangaChapterId, chapterSource.SourceId });
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<ChapterSource>()
|
||||||
|
.HasOne(x => x.Chapter)
|
||||||
|
.WithMany(x => x.Sources)
|
||||||
|
.HasForeignKey(x => x.MangaChapterId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureChapterPage(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder
|
||||||
|
.Entity<ChapterPage>()
|
||||||
|
.HasKey(chapterPage => chapterPage.ChapterPageId);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<ChapterPage>()
|
||||||
|
.HasIndex(chapterPage => new { chapterPage.MangaChapterId, chapterPage.PageNumber })
|
||||||
|
.IsUnique(true);
|
||||||
|
|
||||||
|
modelBuilder
|
||||||
|
.Entity<ChapterPage>()
|
||||||
|
.HasOne(x => x.MangaChapter)
|
||||||
|
.WithMany(x => x.Pages)
|
||||||
|
.HasForeignKey(x => x.MangaChapterId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
MangaReader.Core/Data/MangaCover.cs
Normal file
14
MangaReader.Core/Data/MangaCover.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaCover
|
||||||
|
{
|
||||||
|
public int MangaCoverId { get; set; }
|
||||||
|
|
||||||
|
public int MangaId { get; set; }
|
||||||
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
|
public required Guid Guid { get; set; }
|
||||||
|
public required string FileExtension { get; set; }
|
||||||
|
public string? Description { get; set; }
|
||||||
|
public bool IsPrimary { get; set; }
|
||||||
|
}
|
||||||
10
MangaReader.Core/Data/MangaGenre.cs
Normal file
10
MangaReader.Core/Data/MangaGenre.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaGenre
|
||||||
|
{
|
||||||
|
public int MangaId { get; set; }
|
||||||
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
|
public int GenreId { get; set; }
|
||||||
|
public required Genre Genre { get; set; }
|
||||||
|
}
|
||||||
12
MangaReader.Core/Data/MangaSource.cs
Normal file
12
MangaReader.Core/Data/MangaSource.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaSource
|
||||||
|
{
|
||||||
|
public int MangaId { get; set; }
|
||||||
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
|
public int SourceId { get; set; }
|
||||||
|
public required Source Source { get; set; }
|
||||||
|
|
||||||
|
public required string Url { get; set; }
|
||||||
|
}
|
||||||
12
MangaReader.Core/Data/MangaTitle.cs
Normal file
12
MangaReader.Core/Data/MangaTitle.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class MangaTitle
|
||||||
|
{
|
||||||
|
public int MangaTitleId { get; set; }
|
||||||
|
|
||||||
|
public int MangaId { get; set; }
|
||||||
|
public required Manga Manga { get; set; }
|
||||||
|
|
||||||
|
public required string TitleEntry { get; set; }
|
||||||
|
public TitleType TitleType { get; set; }
|
||||||
|
}
|
||||||
7
MangaReader.Core/Data/Source.cs
Normal file
7
MangaReader.Core/Data/Source.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public class Source
|
||||||
|
{
|
||||||
|
public int SourceId { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
}
|
||||||
12
MangaReader.Core/Data/TitleType.cs
Normal file
12
MangaReader.Core/Data/TitleType.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace MangaReader.Core.Data;
|
||||||
|
|
||||||
|
public enum TitleType
|
||||||
|
{
|
||||||
|
Primary,
|
||||||
|
OfficialTranslation,
|
||||||
|
FanTranslation,
|
||||||
|
Synonym,
|
||||||
|
Abbreviation,
|
||||||
|
Romaji,
|
||||||
|
Japanese
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Folder Include="WebSearch\MangaDex\" />
|
<Folder Include="Search\MangaDex\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
8
MangaReader.Core/Pipeline/IMangaPipeline.cs
Normal file
8
MangaReader.Core/Pipeline/IMangaPipeline.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using MangaReader.Core.WebCrawlers;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Pipeline;
|
||||||
|
|
||||||
|
public interface IMangaPipeline
|
||||||
|
{
|
||||||
|
Task RunAsync(SourceManga mangaDto);
|
||||||
|
}
|
||||||
144
MangaReader.Core/Pipeline/MangaPipeline.cs
Normal file
144
MangaReader.Core/Pipeline/MangaPipeline.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using MangaReader.Core.Data;
|
||||||
|
using MangaReader.Core.WebCrawlers;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace MangaReader.Core.Pipeline;
|
||||||
|
|
||||||
|
public partial class MangaPipeline(MangaContext context) : IMangaPipeline
|
||||||
|
{
|
||||||
|
public async Task RunAsync(SourceManga sourceManga)
|
||||||
|
{
|
||||||
|
Manga manga = await GetOrAddMangaAsync(sourceManga);
|
||||||
|
|
||||||
|
foreach (string alternateTitle in sourceManga.AlternateTitles)
|
||||||
|
{
|
||||||
|
await AddTitleAsync(manga, alternateTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string genre in sourceManga.Genres)
|
||||||
|
{
|
||||||
|
await LinkGenreAsync(manga, genre);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (SourceMangaChapter chapter in sourceManga.Chapters)
|
||||||
|
{
|
||||||
|
await AddChapterAsync(manga, chapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.SaveChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Manga> GetOrAddMangaAsync(SourceManga sourceManga)
|
||||||
|
{
|
||||||
|
Manga? manga = await context.Mangas.FirstOrDefaultAsync(manga => manga.Title == sourceManga.Title);
|
||||||
|
|
||||||
|
if (manga != null)
|
||||||
|
return manga;
|
||||||
|
|
||||||
|
manga = new()
|
||||||
|
{
|
||||||
|
Title = sourceManga.Title,
|
||||||
|
Slug = GenerateSlug(sourceManga.Title),
|
||||||
|
};
|
||||||
|
|
||||||
|
context.Add(manga);
|
||||||
|
|
||||||
|
return manga;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateSlug(string title)
|
||||||
|
{
|
||||||
|
title = title.ToLowerInvariant();
|
||||||
|
title = RemoveInvalidCharsRegex().Replace(title, ""); // remove invalid chars
|
||||||
|
title = RemoveSpacesWithDashRegex().Replace(title, "-"); // replace spaces with dash
|
||||||
|
|
||||||
|
return title.Trim('-');
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex(@"[^a-z0-9\s-]")]
|
||||||
|
private static partial Regex RemoveInvalidCharsRegex();
|
||||||
|
|
||||||
|
[GeneratedRegex(@"\s+")]
|
||||||
|
private static partial Regex RemoveSpacesWithDashRegex();
|
||||||
|
|
||||||
|
private async Task AddTitleAsync(Manga manga, string title)
|
||||||
|
{
|
||||||
|
MangaTitle? mangaTitle = await context.MangaTitles.FirstOrDefaultAsync(mt => mt.Manga == manga && mt.TitleEntry == title);
|
||||||
|
|
||||||
|
if (mangaTitle != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mangaTitle = new()
|
||||||
|
{
|
||||||
|
Manga = manga,
|
||||||
|
TitleEntry = title,
|
||||||
|
};
|
||||||
|
|
||||||
|
context.MangaTitles.Add(mangaTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LinkGenreAsync(Manga manga, string genreName)
|
||||||
|
{
|
||||||
|
Genre genre = await GetOrAddGenreAsync(genreName);
|
||||||
|
|
||||||
|
MangaGenre? mangaGenre = await context.MangaGenres.FirstOrDefaultAsync(x => x.Manga == manga && x.Genre == genre);
|
||||||
|
|
||||||
|
if (mangaGenre != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
mangaGenre = new()
|
||||||
|
{
|
||||||
|
Manga = manga,
|
||||||
|
Genre = genre
|
||||||
|
};
|
||||||
|
|
||||||
|
context.MangaGenres.Add(mangaGenre);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<Genre> GetOrAddGenreAsync(string genreName)
|
||||||
|
{
|
||||||
|
Genre? genre = await context.Genres.FirstOrDefaultAsync(x => x.Name == genreName);
|
||||||
|
|
||||||
|
if (genre != null)
|
||||||
|
return genre;
|
||||||
|
|
||||||
|
genre = new()
|
||||||
|
{
|
||||||
|
Name = genreName,
|
||||||
|
};
|
||||||
|
|
||||||
|
await context.Genres.AddAsync(genre);
|
||||||
|
|
||||||
|
return genre;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddChapterAsync(Manga manga, SourceMangaChapter sourceeMangaChapter)
|
||||||
|
{
|
||||||
|
MangaChapter mangaChapter = await context.MangaChapters.FirstOrDefaultAsync(x => x.ChapterNumber == sourceeMangaChapter.Number)
|
||||||
|
?? AddMangaChapter(manga, sourceeMangaChapter);
|
||||||
|
|
||||||
|
if (mangaChapter.VolumeNumber is null && sourceeMangaChapter.Volume is not null)
|
||||||
|
{
|
||||||
|
mangaChapter.VolumeNumber = sourceeMangaChapter.Volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mangaChapter.Title is null && sourceeMangaChapter.Name is not null)
|
||||||
|
{
|
||||||
|
mangaChapter.Title = sourceeMangaChapter.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MangaChapter AddMangaChapter(Manga manga, SourceMangaChapter sourceeMangaChapter)
|
||||||
|
{
|
||||||
|
MangaChapter mangaChapter = new()
|
||||||
|
{
|
||||||
|
Manga = manga,
|
||||||
|
ChapterNumber = sourceeMangaChapter.Number
|
||||||
|
};
|
||||||
|
|
||||||
|
context.MangaChapters.Add(mangaChapter);
|
||||||
|
|
||||||
|
return mangaChapter;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace MangaReader.Core.WebSearch;
|
namespace MangaReader.Core.Search;
|
||||||
|
|
||||||
public interface IMangaWebSearch<T>
|
public interface IMangaSearchProvider<T>
|
||||||
{
|
{
|
||||||
Task<MangaSearchResult[]> SearchAsync(string keyword);
|
Task<MangaSearchResult[]> SearchAsync(string keyword);
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.HttpService;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace MangaReader.Core.WebSearch;
|
namespace MangaReader.Core.Search;
|
||||||
|
|
||||||
public abstract class MangaWebSearchBase<T>(IHttpService httpService) : IMangaWebSearch<T>
|
public abstract class MangaSearchProviderBase<T>(IHttpService httpService) : IMangaSearchProvider<T>
|
||||||
{
|
{
|
||||||
private static JsonSerializerOptions _jsonSerializerOptions = new()
|
private static JsonSerializerOptions _jsonSerializerOptions = new()
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
namespace MangaReader.Core.WebSearch;
|
namespace MangaReader.Core.Search;
|
||||||
|
|
||||||
public record MangaSearchResult
|
public record MangaSearchResult
|
||||||
{
|
{
|
||||||
|
public required string Source { get; init; }
|
||||||
public required string Url { get; init; }
|
public required string Url { get; init; }
|
||||||
public required string Title { get; init; }
|
public required string Title { get; init; }
|
||||||
public string? Author { get; init; }
|
public string? Author { get; init; }
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.HttpService;
|
||||||
|
|
||||||
namespace MangaReader.Core.WebSearch.NatoManga;
|
namespace MangaReader.Core.Search.NatoManga;
|
||||||
|
|
||||||
public class NatoMangaWebSearch(IHttpService httpService) : MangaWebSearchBase<NatoMangaSearchResult[]>(httpService)
|
public class NatoMangaSearchProvider(IHttpService httpService) : MangaSearchProviderBase<NatoMangaSearchResult[]>(httpService)
|
||||||
{
|
{
|
||||||
// https://www.natomanga.com/home/search/json?searchword=gal_can_t_be_kind
|
// https://www.natomanga.com/home/search/json?searchword=gal_can_t_be_kind
|
||||||
|
|
||||||
@@ -16,6 +16,7 @@ public class NatoMangaWebSearch(IHttpService httpService) : MangaWebSearchBase<N
|
|||||||
IEnumerable<MangaSearchResult> mangaSearchResults = searchResult.Select(searchResult =>
|
IEnumerable<MangaSearchResult> mangaSearchResults = searchResult.Select(searchResult =>
|
||||||
new MangaSearchResult()
|
new MangaSearchResult()
|
||||||
{
|
{
|
||||||
|
Source = "NatoManga",
|
||||||
Title = searchResult.Name,
|
Title = searchResult.Name,
|
||||||
Url = searchResult.Url
|
Url = searchResult.Url
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace MangaReader.Core.WebSearch.NatoManga;
|
namespace MangaReader.Core.Search.NatoManga;
|
||||||
|
|
||||||
public record NatoMangaSearchResult
|
public record NatoMangaSearchResult
|
||||||
{
|
{
|
||||||
@@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
public interface IMangaWebCrawler
|
public interface IMangaWebCrawler
|
||||||
{
|
{
|
||||||
MangaDTO GetManga(string url);
|
SourceManga GetManga(string url);
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,12 @@ namespace MangaReader.Core.WebCrawlers.MangaNato;
|
|||||||
|
|
||||||
public class MangaNatoWebCrawler : MangaWebCrawler
|
public class MangaNatoWebCrawler : MangaWebCrawler
|
||||||
{
|
{
|
||||||
public override MangaDTO GetManga(string url)
|
public override SourceManga GetManga(string url)
|
||||||
{
|
{
|
||||||
HtmlDocument document = GetHtmlDocument(url);
|
HtmlDocument document = GetHtmlDocument(url);
|
||||||
MangaNatoMangaDocument node = new(document);
|
MangaNatoMangaDocument node = new(document);
|
||||||
|
|
||||||
MangaDTO manga = new()
|
SourceManga manga = new()
|
||||||
{
|
{
|
||||||
Title = node.TitleNode.InnerText,
|
Title = node.TitleNode.InnerText,
|
||||||
AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
||||||
@@ -101,9 +101,9 @@ public class MangaNatoWebCrawler : MangaWebCrawler
|
|||||||
return (int)Math.Round(average / best * 100);
|
return (int)Math.Round(average / best * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<MangaChapterDTO> GetChapters(HtmlNodeCollection chapterNodes)
|
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection chapterNodes)
|
||||||
{
|
{
|
||||||
List<MangaChapterDTO> chapters = [];
|
List<SourceMangaChapter> chapters = [];
|
||||||
|
|
||||||
foreach (var node in chapterNodes)
|
foreach (var node in chapterNodes)
|
||||||
{
|
{
|
||||||
@@ -111,7 +111,7 @@ public class MangaNatoWebCrawler : MangaWebCrawler
|
|||||||
HtmlNode chapterViewNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-view')]");
|
HtmlNode chapterViewNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-view')]");
|
||||||
HtmlNode chapterTimeNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-time')]");
|
HtmlNode chapterTimeNode = node.SelectSingleNode(".//span[contains(@class, 'chapter-time')]");
|
||||||
|
|
||||||
MangaChapterDTO chapter = new()
|
SourceMangaChapter chapter = new()
|
||||||
{
|
{
|
||||||
Number = GetChapterNumber(chapterNameNode),
|
Number = GetChapterNumber(chapterNameNode),
|
||||||
Name = chapterNameNode.InnerText,
|
Name = chapterNameNode.InnerText,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace MangaReader.Core.WebCrawlers;
|
|||||||
|
|
||||||
public abstract class MangaWebCrawler : IMangaWebCrawler
|
public abstract class MangaWebCrawler : IMangaWebCrawler
|
||||||
{
|
{
|
||||||
public abstract MangaDTO GetManga(string url);
|
public abstract SourceManga GetManga(string url);
|
||||||
|
|
||||||
protected virtual HtmlDocument GetHtmlDocument(string url)
|
protected virtual HtmlDocument GetHtmlDocument(string url)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ namespace MangaReader.Core.WebCrawlers.NatoManga;
|
|||||||
|
|
||||||
public class NatoMangaWebCrawler : MangaWebCrawler
|
public class NatoMangaWebCrawler : MangaWebCrawler
|
||||||
{
|
{
|
||||||
public override MangaDTO GetManga(string url)
|
public override SourceManga GetManga(string url)
|
||||||
{
|
{
|
||||||
HtmlDocument document = GetHtmlDocument(url);
|
HtmlDocument document = GetHtmlDocument(url);
|
||||||
NatoMangaHtmlDocument node = new(document);
|
NatoMangaHtmlDocument node = new(document);
|
||||||
|
|
||||||
MangaDTO manga = new()
|
SourceManga manga = new()
|
||||||
{
|
{
|
||||||
Title = node.TitleNode?.InnerText ?? string.Empty,
|
Title = node.TitleNode?.InnerText ?? string.Empty,
|
||||||
//AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
//AlternateTitles = GetAlternateTitles(node.AlternateTitlesNode),
|
||||||
@@ -116,9 +116,9 @@ public class NatoMangaWebCrawler : MangaWebCrawler
|
|||||||
return (int)Math.Round(average / best * 100);
|
return (int)Math.Round(average / best * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<MangaChapterDTO> GetChapters(HtmlNodeCollection? chapterNodes)
|
private static List<SourceMangaChapter> GetChapters(HtmlNodeCollection? chapterNodes)
|
||||||
{
|
{
|
||||||
List<MangaChapterDTO> chapters = [];
|
List<SourceMangaChapter> chapters = [];
|
||||||
|
|
||||||
if (chapterNodes == null)
|
if (chapterNodes == null)
|
||||||
return chapters;
|
return chapters;
|
||||||
@@ -137,7 +137,7 @@ public class NatoMangaWebCrawler : MangaWebCrawler
|
|||||||
if (chapterNameNode == null)
|
if (chapterNameNode == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
MangaChapterDTO chapter = new()
|
SourceMangaChapter chapter = new()
|
||||||
{
|
{
|
||||||
Number = GetChapterNumber(chapterNameNode),
|
Number = GetChapterNumber(chapterNameNode),
|
||||||
Name = chapterNameNode.InnerText,
|
Name = chapterNameNode.InnerText,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace MangaReader.Core.WebCrawlers;
|
namespace MangaReader.Core.WebCrawlers;
|
||||||
|
|
||||||
public class MangaDTO
|
public class SourceManga
|
||||||
{
|
{
|
||||||
public required string Title { get; set; }
|
public required string Title { get; set; }
|
||||||
public string? Description { get; set; }
|
public string? Description { get; set; }
|
||||||
@@ -12,5 +12,5 @@ public class MangaDTO
|
|||||||
public long? Views { get; set; }
|
public long? Views { get; set; }
|
||||||
public float? RatingPercent { get; set; }
|
public float? RatingPercent { get; set; }
|
||||||
public int? Votes { get; set; }
|
public int? Votes { get; set; }
|
||||||
public List<MangaChapterDTO> Chapters { get; set; } = [];
|
public List<SourceMangaChapter> Chapters { get; set; } = [];
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace MangaReader.Core.WebCrawlers;
|
namespace MangaReader.Core.WebCrawlers;
|
||||||
|
|
||||||
public class MangaChapterDTO
|
public class SourceMangaChapter
|
||||||
{
|
{
|
||||||
public int? Volume { get; set; }
|
public int? Volume { get; set; }
|
||||||
public float? Number { get; set; }
|
public required float Number { get; set; }
|
||||||
public string? Name { get; set; }
|
public string? Name { get; set; }
|
||||||
public required string Url { get; set; }
|
public required string Url { get; set; }
|
||||||
public long? Views { get; set; }
|
public long? Views { get; set; }
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using MangaReader.Core.HttpService;
|
using MangaReader.Core.HttpService;
|
||||||
using MangaReader.Core.WebSearch;
|
using MangaReader.Core.Search;
|
||||||
using MangaReader.Core.WebSearch.NatoManga;
|
using MangaReader.Core.Search.NatoManga;
|
||||||
using MangaReader.Tests.Utilities;
|
using MangaReader.Tests.Utilities;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Shouldly;
|
using Shouldly;
|
||||||
@@ -20,8 +20,8 @@ public class NatoMangaWebSearchTests
|
|||||||
httpService.GetStringAsync(Arg.Any<string>())
|
httpService.GetStringAsync(Arg.Any<string>())
|
||||||
.Returns(Task.FromResult(searchResultJson));
|
.Returns(Task.FromResult(searchResultJson));
|
||||||
|
|
||||||
NatoMangaWebSearch webSearch = new(httpService);
|
NatoMangaSearchProvider searchProvider = new(httpService);
|
||||||
MangaSearchResult[] searchResult = await webSearch.SearchAsync("Gals Can't Be Kind");
|
MangaSearchResult[] searchResult = await searchProvider.SearchAsync("Gals Can't Be Kind");
|
||||||
|
|
||||||
searchResult.Length.ShouldBe(2);
|
searchResult.Length.ShouldBe(2);
|
||||||
searchResult[0].Title.ShouldBe("Gal Can't Be Kind to Otaku!");
|
searchResult[0].Title.ShouldBe("Gal Can't Be Kind to Otaku!");
|
||||||
|
|||||||
Reference in New Issue
Block a user