using API.Schema.Jobs; using API.Schema.MangaConnectors; using log4net; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; namespace API.Schema.Contexts; public class PgsqlContext(DbContextOptions options) : DbContext(options) { public DbSet Jobs { get; set; } public DbSet MangaConnectors { get; set; } public DbSet Mangas { get; set; } public DbSet LocalLibraries { get; set; } public DbSet Chapters { get; set; } public DbSet Authors { get; set; } public DbSet Tags { get; set; } private ILog Log => LogManager.GetLogger(GetType()); protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.EnableSensitiveDataLogging(); optionsBuilder.LogTo(s => { Log.Debug(s); }, [DbLoggerCategory.Query.Name], LogLevel.Trace, DbContextLoggerOptions.Level | DbContextLoggerOptions.Category); } protected override void OnModelCreating(ModelBuilder modelBuilder) { //Job Types modelBuilder.Entity() .HasDiscriminator(j => j.JobType) .HasValue(JobType.MoveFileOrFolderJob) .HasValue(JobType.MoveMangaLibraryJob) .HasValue(JobType.DownloadAvailableChaptersJob) .HasValue(JobType.DownloadSingleChapterJob) .HasValue(JobType.DownloadMangaCoverJob) .HasValue(JobType.RetrieveChaptersJob) .HasValue(JobType.UpdateChaptersDownloadedJob) .HasValue(JobType.UpdateSingleChapterDownloadedJob); //Job specification modelBuilder.Entity() .HasOne(j => j.Manga) .WithMany() .HasForeignKey(j => j.MangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Manga) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.Manga) .WithMany() .HasForeignKey(j => j.MangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Manga) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.Chapter) .WithMany() .HasForeignKey(j => j.ChapterId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Chapter) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.Manga) .WithMany() .HasForeignKey(j => j.MangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Manga) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.ToLibrary) .WithMany() .HasForeignKey(j => j.ToLibraryId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.ToLibrary) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.Manga) .WithMany() .HasForeignKey(j => j.MangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Manga) .EnableLazyLoading(); modelBuilder.Entity() .HasOne(j => j.Manga) .WithMany() .HasForeignKey(j => j.MangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(j => j.Manga) .EnableLazyLoading(); //Job has possible ParentJob modelBuilder.Entity() .HasOne(childJob => childJob.ParentJob) .WithMany() .HasForeignKey(childjob => childjob.ParentJobId) .OnDelete(DeleteBehavior.Cascade); //Job might be dependent on other Jobs modelBuilder.Entity() .HasMany(root => root.DependsOnJobs) .WithMany(); modelBuilder.Entity() .Navigation(j => j.DependsOnJobs) .AutoInclude(false); //MangaConnector Types modelBuilder.Entity() .HasDiscriminator(c => c.Name) .HasValue("Global") .HasValue("MangaDex") .HasValue("ComickIo"); //MangaConnector is responsible for many Manga modelBuilder.Entity() .HasMany() .WithOne(m => m.MangaConnector) .HasForeignKey(m => m.MangaConnectorName) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(m => m.MangaConnector) .AutoInclude(); //Manga has many Chapters modelBuilder.Entity() .HasMany(m => m.Chapters) .WithOne(c => c.ParentManga) .HasForeignKey(c => c.ParentMangaId) .IsRequired() .OnDelete(DeleteBehavior.Cascade); modelBuilder.Entity() .Navigation(c => c.ParentManga) .AutoInclude(); modelBuilder.Entity() .Navigation(m => m.Chapters) .AutoInclude(false); //Manga owns MangaAltTitles modelBuilder.Entity() .OwnsMany(m => m.AltTitles) .WithOwner(); modelBuilder.Entity() .Navigation(m => m.AltTitles) .AutoInclude(); //Manga owns Links modelBuilder.Entity() .OwnsMany(m => m.Links) .WithOwner(); modelBuilder.Entity() .Navigation(m => m.Links) .AutoInclude(); //Manga has many Tags associated with many Manga modelBuilder.Entity() .HasMany(m => m.MangaTags) .WithMany() .UsingEntity("MangaTagToManga", l=> l.HasOne(typeof(MangaTag)).WithMany().HasForeignKey("MangaTagIds").HasPrincipalKey(nameof(MangaTag.Tag)), r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)), j => j.HasKey("MangaTagIds", "MangaIds") ); modelBuilder.Entity() .Navigation(m => m.MangaTags) .AutoInclude(); //Manga has many Authors associated with many Manga modelBuilder.Entity() .HasMany(m => m.Authors) .WithMany() .UsingEntity("AuthorToManga", l=> l.HasOne(typeof(Author)).WithMany().HasForeignKey("AuthorIds").HasPrincipalKey(nameof(Author.AuthorId)), r => r.HasOne(typeof(Manga)).WithMany().HasForeignKey("MangaIds").HasPrincipalKey(nameof(Manga.MangaId)), j => j.HasKey("AuthorIds", "MangaIds") ); modelBuilder.Entity() .Navigation(m => m.Authors) .AutoInclude(); //LocalLibrary has many Mangas modelBuilder.Entity() .HasMany() .WithOne(m => m.Library) .HasForeignKey(m => m.LibraryId) .OnDelete(DeleteBehavior.SetNull); modelBuilder.Entity() .Navigation(m => m.Library) .AutoInclude(); } }