using Microsoft.EntityFrameworkCore; using YesChef.Api.Entities; namespace YesChef.Api.Data; public class YesChefDb(DbContextOptions options) : DbContext(options) { public DbSet Families => Set(); public DbSet FamilyMemberships => Set(); public DbSet Users => Set(); public DbSet Stores => Set(); public DbSet StoreSections => Set(); public DbSet ShoppingLists => Set(); public DbSet ShoppingListItems => Set(); public DbSet Recipes => Set(); public DbSet RecipeIngredients => Set(); public DbSet Invites => Set(); public DbSet PasswordResetTokens => Set(); public DbSet Products => Set(); public DbSet FamilyProducts => Set(); public DbSet FamilyProductOverrides => Set(); public DbSet ProductStoreSections => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(e => { e.HasIndex(f => f.InviteCode).IsUnique(); e.Property(f => f.Name).HasMaxLength(100); e.Property(f => f.InviteCode).HasMaxLength(100); }); modelBuilder.Entity(e => { e.HasKey(m => new { m.UserId, m.FamilyId }); e.HasOne(m => m.User).WithMany().HasForeignKey(m => m.UserId).OnDelete(DeleteBehavior.Cascade); e.HasOne(m => m.Family).WithMany().HasForeignKey(m => m.FamilyId).OnDelete(DeleteBehavior.Cascade); e.Property(m => m.Role).HasConversion(); }); modelBuilder.Entity(e => { e.HasIndex(u => u.Name).IsUnique(); e.Property(u => u.Name).HasMaxLength(100); e.Property(u => u.Email).HasMaxLength(254); e.HasIndex(u => u.Email).IsUnique(); }); modelBuilder.Entity(e => { e.HasOne(t => t.User).WithMany().HasForeignKey(t => t.UserId).OnDelete(DeleteBehavior.Cascade); e.Property(t => t.TokenHash).HasMaxLength(64); e.HasIndex(t => t.TokenHash).IsUnique(); e.HasIndex(t => new { t.UserId, t.ConsumedAt }); }); modelBuilder.Entity(e => { e.HasOne(i => i.Family).WithMany().HasForeignKey(i => i.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(i => i.IssuedByUser).WithMany().HasForeignKey(i => i.IssuedByUserId).OnDelete(DeleteBehavior.Restrict); e.HasOne(i => i.ConsumedByUser).WithMany().HasForeignKey(i => i.ConsumedByUserId).OnDelete(DeleteBehavior.SetNull); e.Property(i => i.Email).HasMaxLength(254); e.Property(i => i.TokenHash).HasMaxLength(64); e.HasIndex(i => i.TokenHash).IsUnique(); e.HasIndex(i => new { i.FamilyId, i.ConsumedAt }); }); modelBuilder.Entity(e => { e.HasOne(s => s.Family).WithMany().HasForeignKey(s => s.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasIndex(s => new { s.FamilyId, s.Name }).IsUnique(); e.Property(s => s.Name).HasMaxLength(100); }); modelBuilder.Entity(e => { e.HasOne(s => s.Family).WithMany().HasForeignKey(s => s.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(s => s.Store).WithMany().HasForeignKey(s => s.StoreId).OnDelete(DeleteBehavior.Cascade); e.HasIndex(s => new { s.StoreId, s.Name }).IsUnique(); e.Property(s => s.Name).HasMaxLength(100); }); modelBuilder.Entity(e => { e.Property(l => l.Name).HasMaxLength(200); e.HasOne(l => l.Family).WithMany().HasForeignKey(l => l.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(l => l.Store).WithMany().HasForeignKey(l => l.StoreId); e.HasOne(l => l.CreatedByUser).WithMany().HasForeignKey(l => l.CreatedByUserId); e.HasMany(l => l.Items).WithOne(i => i.ShoppingList).HasForeignKey(i => i.ShoppingListId).OnDelete(DeleteBehavior.Cascade); e.HasIndex(l => l.FamilyId); }); modelBuilder.Entity(e => { e.Property(i => i.Name).HasMaxLength(300); e.HasOne(i => i.Family).WithMany().HasForeignKey(i => i.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(i => i.CheckedByUser).WithMany().HasForeignKey(i => i.CheckedByUserId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.RemovedByUser).WithMany().HasForeignKey(i => i.RemovedByUserId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.Recipe).WithMany().HasForeignKey(i => i.RecipeId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.Section).WithMany().HasForeignKey(i => i.SectionId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.Product).WithMany().HasForeignKey(i => i.ProductId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.FamilyProduct).WithMany().HasForeignKey(i => i.FamilyProductId).OnDelete(DeleteBehavior.SetNull); e.HasIndex(i => i.FamilyId); }); modelBuilder.Entity(e => { e.Property(r => r.Title).HasMaxLength(300); e.HasOne(r => r.Family).WithMany().HasForeignKey(r => r.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(r => r.CreatedByUser).WithMany().HasForeignKey(r => r.CreatedByUserId); e.HasMany(r => r.Ingredients).WithOne(i => i.Recipe).HasForeignKey(i => i.RecipeId).OnDelete(DeleteBehavior.Cascade); e.HasIndex(r => r.FamilyId); }); modelBuilder.Entity(e => { e.Property(i => i.Name).HasMaxLength(200); e.Property(i => i.Quantity).HasMaxLength(50); e.HasOne(i => i.Family).WithMany().HasForeignKey(i => i.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(i => i.Product).WithMany().HasForeignKey(i => i.ProductId).OnDelete(DeleteBehavior.SetNull); e.HasOne(i => i.FamilyProduct).WithMany().HasForeignKey(i => i.FamilyProductId).OnDelete(DeleteBehavior.SetNull); }); modelBuilder.Entity(e => { e.Property(p => p.Name).HasMaxLength(200); e.Property(p => p.Brand).HasMaxLength(200); e.Property(p => p.Notes).HasMaxLength(1000); e.HasIndex(p => p.Name).IsUnique(); }); modelBuilder.Entity(e => { e.Property(p => p.Name).HasMaxLength(200); e.Property(p => p.Brand).HasMaxLength(200); e.Property(p => p.Notes).HasMaxLength(1000); e.HasOne(p => p.Family).WithMany().HasForeignKey(p => p.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasIndex(p => new { p.FamilyId, p.Name }).IsUnique(); }); modelBuilder.Entity(e => { e.HasKey(o => new { o.FamilyId, o.ProductId }); e.Property(o => o.Name).HasMaxLength(200); e.Property(o => o.Brand).HasMaxLength(200); e.Property(o => o.Notes).HasMaxLength(1000); e.HasOne(o => o.Family).WithMany().HasForeignKey(o => o.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(o => o.Product).WithMany().HasForeignKey(o => o.ProductId).OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity(e => { e.HasOne(p => p.Family).WithMany().HasForeignKey(p => p.FamilyId).OnDelete(DeleteBehavior.Cascade); e.HasOne(p => p.Store).WithMany().HasForeignKey(p => p.StoreId).OnDelete(DeleteBehavior.Cascade); e.HasOne(p => p.StoreSection).WithMany().HasForeignKey(p => p.StoreSectionId).OnDelete(DeleteBehavior.Cascade); e.HasOne(p => p.Product).WithMany().HasForeignKey(p => p.ProductId).OnDelete(DeleteBehavior.Cascade); e.HasOne(p => p.FamilyProduct).WithMany().HasForeignKey(p => p.FamilyProductId).OnDelete(DeleteBehavior.Cascade); // Filtered unique indexes — at most one row per (Family, Store, Product) // and one per (Family, Store, FamilyProduct). Postgres treats NULLs // as distinct, so the partial WHERE keeps the index from caring // about the inactive variant on each row. e.HasIndex(p => new { p.FamilyId, p.StoreId, p.ProductId }) .IsUnique() .HasFilter(@"""ProductId"" IS NOT NULL"); e.HasIndex(p => new { p.FamilyId, p.StoreId, p.FamilyProductId }) .IsUnique() .HasFilter(@"""FamilyProductId"" IS NOT NULL"); }); } }