using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using NexusReader.Domain.Entities; using NexusReader.Domain.Enums; namespace NexusReader.Data.Persistence; public class AppDbContext : IdentityDbContext { public AppDbContext(DbContextOptions options) : base(options) { } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); // Suppress the pending model changes warning to avoid runtime exceptions in some environments optionsBuilder.ConfigureWarnings(w => w.Ignore(Microsoft.EntityFrameworkCore.Diagnostics.RelationalEventId.PendingModelChangesWarning)); } public DbSet SemanticKnowledgeCache => Set(); public DbSet KnowledgeUnits => Set(); public DbSet KnowledgeUnitLinks => Set(); public DbSet Ebooks => Set(); public DbSet QuizResults => Set(); public DbSet SubscriptionPlans => Set(); public DbSet Authors => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity(entity => { entity.Property(u => u.LastReadPageId).HasMaxLength(255); entity.Property(u => u.LastReadAt).IsRequired(false); entity.HasIndex(u => u.TenantId); entity.HasOne(u => u.SubscriptionPlan) .WithMany() .HasForeignKey(u => u.SubscriptionPlanId) .OnDelete(DeleteBehavior.Restrict); // Note: DefaultValue for int is 1 (which corresponds to 'Free' in our seed) entity.Property(u => u.SubscriptionPlanId) .HasDefaultValue(1); entity.Property(u => u.ThemePreference) .HasConversion() .HasDefaultValue(ThemeMode.System); }); modelBuilder.Entity(entity => { entity.HasIndex(p => p.PlanName).IsUnique(); }); modelBuilder.Entity(entity => { entity.HasKey(e => e.ContentHash); entity.HasIndex(e => e.ContentHash).IsUnique(); entity.HasIndex(e => e.TenantId); entity.Ignore(e => e.Embedding); }); modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasIndex(e => e.TenantId); entity.HasIndex(e => e.EbookId); entity.HasOne(e => e.Ebook) .WithMany() .HasForeignKey(e => e.EbookId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity.HasKey(e => e.Id); entity.HasOne(e => e.SourceUnit) .WithMany(u => u.OutgoingLinks) .HasForeignKey(e => e.SourceUnitId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(e => e.TargetUnit) .WithMany(u => u.IncomingLinks) .HasForeignKey(e => e.TargetUnitId) .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(entity => { entity.HasOne(e => e.User) .WithMany(u => u.Ebooks) .HasForeignKey(e => e.UserId) .OnDelete(DeleteBehavior.Cascade); entity.HasOne(e => e.Author) .WithMany(a => a.Ebooks) .HasForeignKey(e => e.AuthorId) .OnDelete(DeleteBehavior.Restrict); entity.HasIndex(e => e.TenantId); }); modelBuilder.Entity(entity => { entity.HasOne(e => e.User) .WithMany(u => u.QuizResults) .HasForeignKey(e => e.UserId) .OnDelete(DeleteBehavior.Cascade); entity.HasIndex(e => e.TenantId); }); // Seed Subscription Plans with deterministic IDs modelBuilder.Entity().HasData( new SubscriptionPlan { Id = 1, PlanName = SubscriptionPlan.FreeName, AITokenLimit = 5000, IsUnlimitedTokens = false, MonthlyPrice = 0m, StripeProductId = "prod_Free789" }, new SubscriptionPlan { Id = 2, PlanName = SubscriptionPlan.BasicName, AITokenLimit = 10000, IsUnlimitedTokens = false, MonthlyPrice = 9.99m, StripeProductId = "prod_basic_placeholder" }, new SubscriptionPlan { Id = 3, PlanName = SubscriptionPlan.ProName, AITokenLimit = 50000, IsUnlimitedTokens = false, MonthlyPrice = 19.99m, StripeProductId = "prod_pro_placeholder" }, new SubscriptionPlan { Id = 4, PlanName = SubscriptionPlan.EnterpriseName, AITokenLimit = 1000000000, IsUnlimitedTokens = true, MonthlyPrice = 99.99m, StripeProductId = "prod_enterprise_placeholder" } ); } }