using Microsoft.EntityFrameworkCore; namespace MangaReader.Core.Data; public class MangaContext(DbContextOptions options) : DbContext(options) { public DbSet Mangas { get; set; } public DbSet MangaCovers { get; set; } public DbSet MangaTitles { get; set; } public DbSet MangaDescriptions { get; set; } public DbSet Sources { get; set; } public DbSet MangaSources { get; set; } public DbSet Contributors { get; set; } public DbSet MangaContributors { get; set; } public DbSet Genres { get; set; } public DbSet MangaGenres { get; set; } //public DbSet MangaChapters { get; set; } //public DbSet ChapterSources { get; set; } //public DbSet ChapterPages { get; set; } public DbSet SourceTitles { get; set; } public DbSet SourceDescriptions { get; set; } public DbSet SourceCovers { get; set; } public DbSet SourceChapters { get; set; } public DbSet SourcePages { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); ConfigureManga(modelBuilder); ConfigureMangaCover(modelBuilder); ConfigureMangaTitle(modelBuilder); ConfigureMangaDescription(modelBuilder); ConfigureSource(modelBuilder); ConfigureMangaSource(modelBuilder); ConfigureContributor(modelBuilder); ConfigureMangaContributor(modelBuilder); ConfigureGenre(modelBuilder); ConfigureMangaGenre(modelBuilder); //ConfigureMangaChapter(modelBuilder); //ConfigureChapterSource(modelBuilder); //ConfigureChapterPage(modelBuilder); ConfigureSourceTitle(modelBuilder); ConfigureSourceDescription(modelBuilder); ConfigureMangaSourceCover(modelBuilder); ConfigureMangaSourceChapter(modelBuilder); ConfigureSourcePage(modelBuilder); } private static void ConfigureManga(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.MangaId); //modelBuilder.Entity() // .HasIndex(x => x.Title) // .IsUnique(); modelBuilder.Entity() .HasIndex(x => x.Slug) .IsUnique(); } private static void ConfigureMangaCover(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.MangaCoverId); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Covers) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .HasIndex(x => x.Guid) .IsUnique(); //modelBuilder // .Entity() // .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() .HasKey(mangaTitle => mangaTitle.MangaTitleId); modelBuilder.Entity() .Property(mt => mt.Name) .IsRequired(); modelBuilder.Entity() .Property(mt => mt.Language) .IsRequired(); modelBuilder.Entity() .HasIndex(mangaTitle => new { mangaTitle.MangaId, mangaTitle.Name, mangaTitle.Language }) .IsUnique(); modelBuilder .Entity() .HasIndex(mangaTitle => mangaTitle.Name); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Titles) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureMangaDescription(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(mangaDescription => mangaDescription.MangaDescriptionId); modelBuilder.Entity() .Property(mangaDescription => mangaDescription.Text) .IsRequired(); modelBuilder.Entity() .Property(mangaDescription => mangaDescription.Language) .IsRequired(); modelBuilder.Entity() .HasIndex(mangaDescription => new { mangaDescription.MangaId, mangaDescription.Text, mangaDescription.Language }) .IsUnique(); modelBuilder .Entity() .HasIndex(mangaDescription => mangaDescription.Text); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Descriptions) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureSource(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.SourceId); modelBuilder .Entity() .HasIndex(x => x.Name) .IsUnique(true); } private static void ConfigureMangaSource(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(mangaSource => mangaSource.MangaSourceId); //modelBuilder // .Entity() // .HasKey(mangaSource => new { mangaSource.MangaId, mangaSource.SourceId }); modelBuilder.Entity() .HasIndex(x => x.Url) .IsUnique(); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Sources) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureContributor(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.ContributorId); modelBuilder .Entity() .HasIndex(x => x.Name) .IsUnique(true); } private static void ConfigureMangaContributor(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(mc => new { mc.MangaId, mc.ContributorId, mc.Role }); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Contributors) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureGenre(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.GenreId); modelBuilder .Entity() .HasIndex(x => x.Name) .IsUnique(true); } private static void ConfigureMangaGenre(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(mangaGenre => new { mangaGenre.MangaId, mangaGenre.GenreId }); modelBuilder .Entity() .HasOne(x => x.Manga) .WithMany(x => x.Genres) .HasForeignKey(x => x.MangaId) .OnDelete(DeleteBehavior.Cascade); } //private static void ConfigureMangaChapter(ModelBuilder modelBuilder) //{ // modelBuilder // .Entity() // .HasKey(x => x.MangaChapterId); // modelBuilder // .Entity() // .HasOne(x => x.Manga) // .WithMany(x => x.Chapters) // .HasForeignKey(x => x.MangaId) // .OnDelete(DeleteBehavior.Cascade); //} //private static void ConfigureChapterSource(ModelBuilder modelBuilder) //{ // modelBuilder // .Entity() // .HasKey(chapterSource => new { chapterSource.MangaChapterId, chapterSource.SourceId }); // modelBuilder // .Entity() // .HasOne(x => x.Chapter) // .WithMany(x => x.Sources) // .HasForeignKey(x => x.MangaChapterId) // .OnDelete(DeleteBehavior.Cascade); //} //private static void ConfigureChapterPage(ModelBuilder modelBuilder) //{ // modelBuilder // .Entity() // .HasKey(chapterPage => chapterPage.ChapterPageId); // modelBuilder // .Entity() // .HasIndex(chapterPage => new { chapterPage.MangaChapterId, chapterPage.PageNumber }) // .IsUnique(true); // modelBuilder // .Entity() // .HasOne(x => x.MangaChapter) // .WithMany(x => x.Pages) // .HasForeignKey(x => x.MangaChapterId) // .OnDelete(DeleteBehavior.Cascade); //} private static void ConfigureSourceTitle(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.SourceTitleId); modelBuilder .Entity() .Property(x => x.Name) .IsRequired() .HasMaxLength(512); // Avoid duplicate rows coming from the same source record modelBuilder .Entity() .HasIndex(x => new { x.MangaSourceId, x.Language, x.Name }) .IsUnique(); modelBuilder .Entity() .HasOne(x => x.MangaSource) .WithMany(x => x.Titles) .HasForeignKey(x => x.MangaSourceId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureSourceDescription(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(x => x.SourceDescriptionId); modelBuilder .Entity() .Property(x => x.Text) .IsRequired(); // If sources can emit multiple descriptions per language, keep it non-unique. // If not, uncomment: //modelBuilder // .Entity() // .HasIndex(x => new { x.MangaSourceId, x.Language }) // .IsUnique(); modelBuilder .Entity() .HasOne(x => x.MangaSource) .WithMany(x => x.Descriptions) .HasForeignKey(x => x.MangaSourceId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureMangaSourceCover(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(sourceCover => sourceCover.SourceCoverId); modelBuilder .Entity() .Property(x => x.Url) .IsRequired() .HasMaxLength(2048); modelBuilder .Entity() .HasIndex(sourceCover => new { sourceCover.MangaSourceId, sourceCover.Url }) .IsUnique(true); modelBuilder .Entity() .HasOne(x => x.MangaSource) .WithMany(x => x.Covers) .HasForeignKey(x => x.MangaSourceId) .OnDelete(DeleteBehavior.Cascade); modelBuilder .Entity() .HasOne(x => x.MangaCover) .WithMany() // or .WithMany(c => c.SourceCovers) if you add a collection nav on MangaCover .HasForeignKey(x => x.MangaCoverId) .OnDelete(DeleteBehavior.SetNull); // Helpful for lookups when you dedupe/file-link after download modelBuilder .Entity() .HasIndex(x => x.MangaCoverId); } private static void ConfigureMangaSourceChapter(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(sourceChapter => sourceChapter.SourceChapterId); modelBuilder .Entity() .HasIndex(sourceChapter => new { sourceChapter.MangaSourceId, sourceChapter.ChapterNumber, sourceChapter.Url }) .IsUnique(true); modelBuilder .Entity() .HasOne(x => x.MangaSource) .WithMany(x => x.Chapters) .HasForeignKey(x => x.MangaSourceId) .OnDelete(DeleteBehavior.Cascade); } private static void ConfigureSourcePage(ModelBuilder modelBuilder) { modelBuilder .Entity() .HasKey(sourcePage => sourcePage.SourcePageId); modelBuilder .Entity() .HasIndex(sourcePage => new { sourcePage.SourceChapterId, sourcePage.PageNumber }) .IsUnique(true); modelBuilder .Entity() .HasOne(x => x.Chapter) .WithMany(x => x.Pages) .HasForeignKey(x => x.SourceChapterId) .OnDelete(DeleteBehavior.Cascade); } }