using GermanApp.Domain.Entities; using Microsoft.EntityFrameworkCore; namespace GermanApp.Infrastructure.Data.DbContext; /// /// Entity Framework Core database context. /// This is part of the Infrastructure layer. /// public class AppDbContext : Microsoft.EntityFrameworkCore.DbContext { public AppDbContext(DbContextOptions options) : base(options) { } // DbSets for domain entities public DbSet Levels { get; set; } = null!; public DbSet Lessons { get; set; } = null!; public DbSet Users { get; set; } = null!; public DbSet UserProgress { get; set; } = null!; public DbSet RefreshTokens { get; set; } = null!; public DbSet Quizzes { get; set; } = null!; public DbSet QuizQuestions { get; set; } = null!; public DbSet QuizOptions { get; set; } = null!; // Note: Value objects are not stored directly as entities. // They are owned by entities and stored as part of the entity's data. protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // Configure Level entity modelBuilder.Entity(builder => { builder.HasKey(l => l.Id); builder.Property(l => l.Name).IsRequired().HasMaxLength(100); builder.Property(l => l.Code).IsRequired().HasMaxLength(10); builder.Property(l => l.Order).IsRequired(); // Ensure unique constraints builder.HasIndex(l => l.Code).IsUnique(); builder.HasIndex(l => l.Order).IsUnique(); }); // Configure Lesson entity modelBuilder.Entity(builder => { builder.HasKey(l => l.Id); builder.Property(l => l.Title).IsRequired().HasMaxLength(200); builder.Property(l => l.Description).HasMaxLength(2000); builder.Property(l => l.Topic).IsRequired().HasMaxLength(100); builder.Property(l => l.LevelId).IsRequired(); builder.Property(l => l.Order).IsRequired(); builder.Property(l => l.IsActive).HasDefaultValue(true); builder.Property(l => l.CreatedAt).IsRequired(); builder.Property(l => l.UpdatedAt).IsRequired(false); // Foreign key to Level with unique constraint per level builder.HasOne(l => l.Level) .WithMany() .HasForeignKey(l => l.LevelId) .OnDelete(DeleteBehavior.Cascade); // Unique constraint: one lesson per level per order builder.HasIndex(l => new { l.LevelId, l.Order }).IsUnique(); // Value object: GermanWord would be configured as an owned entity // builder.OwnsMany(l => l.Words, wordBuilder => // { // wordBuilder.Property(w => w.Value).HasColumnName("WordValue"); // wordBuilder.Property(w => w.Translation).HasColumnName("Translation"); // wordBuilder.Property(w => w.Article).HasColumnName("Article"); // wordBuilder.Property(w => w.PartOfSpeech).HasColumnName("PartOfSpeech"); // }); }); // Configure UserProgress entity modelBuilder.Entity(builder => { builder.HasKey(up => up.Id); builder.Property(up => up.UserId).IsRequired(); builder.Property(up => up.LessonId).IsRequired(); builder.Property(up => up.IsCompleted).HasDefaultValue(false); builder.Property(up => up.QuizScore).HasDefaultValue(0); builder.Property(up => up.LastAttemptDate).IsRequired(); // Foreign keys builder.HasOne(up => up.User) .WithMany() .HasForeignKey(up => up.UserId) .OnDelete(DeleteBehavior.Cascade); builder.HasOne(up => up.Lesson) .WithMany() .HasForeignKey(up => up.LessonId) .OnDelete(DeleteBehavior.Cascade); // Unique constraint: one progress record per user per lesson builder.HasIndex(up => new { up.UserId, up.LessonId }).IsUnique(); }); // Configure User entity modelBuilder.Entity(builder => { builder.HasKey(u => u.Id); builder.Property(u => u.Username).IsRequired().HasMaxLength(50); builder.Property(u => u.Email).IsRequired().HasMaxLength(100); builder.Property(u => u.PasswordHash).IsRequired().HasMaxLength(255); builder.Property(u => u.CurrentLevel).HasMaxLength(10).HasDefaultValue("A1"); builder.Property(u => u.Streak).HasDefaultValue(0); builder.Property(u => u.TotalPoints).HasDefaultValue(0); builder.Property(u => u.CreatedAt).IsRequired(); // Ensure unique constraints builder.HasIndex(u => u.Username).IsUnique(); builder.HasIndex(u => u.Email).IsUnique(); }); // Configure RefreshToken entity modelBuilder.Entity(builder => { builder.HasKey(r => r.Id); builder.Property(r => r.UserId).IsRequired(); builder.Property(r => r.Token).IsRequired().HasMaxLength(255); builder.Property(r => r.ExpiresAt).IsRequired(); builder.Property(r => r.IsActive).HasDefaultValue(true); builder.Property(r => r.CreatedAt).IsRequired(); builder.Property(r => r.RevokedAt).IsRequired(false); // Foreign key to User builder.HasOne() .WithMany() .HasForeignKey(r => r.UserId) .OnDelete(DeleteBehavior.Cascade); }); // Configure Quiz entity modelBuilder.Entity(builder => { builder.HasKey(q => q.Id); builder.Property(q => q.LessonId).IsRequired(); builder.Property(q => q.Title).IsRequired().HasMaxLength(200); builder.Property(q => q.Description).HasMaxLength(2000); builder.Property(q => q.PassingScore).IsRequired().HasDefaultValue(80); builder.Property(q => q.TimeLimitMinutes).HasDefaultValue(0); builder.Property(q => q.IsActive).HasDefaultValue(true); builder.Property(q => q.ShuffleQuestions).HasDefaultValue(true); builder.Property(q => q.CreatedAt).IsRequired(); builder.Property(q => q.UpdatedAt).IsRequired(false); // Foreign key to Lesson builder.HasOne(q => q.Lesson) .WithMany() .HasForeignKey(q => q.LessonId) .OnDelete(DeleteBehavior.Cascade); // Navigation to questions builder.HasMany(q => q.Questions) .WithOne(qq => qq.Quiz) .HasForeignKey(qq => qq.QuizId) .OnDelete(DeleteBehavior.Cascade); }); // Configure QuizQuestion entity modelBuilder.Entity(builder => { builder.HasKey(q => q.Id); builder.Property(q => q.QuizId).IsRequired(); builder.Property(q => q.QuestionText).IsRequired().HasMaxLength(2000); builder.Property(q => q.Type).IsRequired(); builder.Property(q => q.CorrectAnswer).HasMaxLength(1000); builder.Property(q => q.Points).IsRequired().HasDefaultValue(1); builder.Property(q => q.Difficulty).IsRequired().HasDefaultValue(3); builder.Property(q => q.Order).IsRequired().HasDefaultValue(1); builder.Property(q => q.IsActive).HasDefaultValue(true); builder.Property(q => q.CreatedAt).IsRequired(); builder.Property(q => q.UpdatedAt).IsRequired(false); // Foreign key to Quiz builder.HasOne(q => q.Quiz) .WithMany(qq => qq.Questions) .HasForeignKey(q => q.QuizId) .OnDelete(DeleteBehavior.Cascade); // Unique constraint: one question per quiz per order builder.HasIndex(q => new { q.QuizId, q.Order }).IsUnique(); // Navigation to options builder.HasMany(q => q.Options) .WithOne(o => o.QuizQuestion) .HasForeignKey(o => o.QuizQuestionId) .OnDelete(DeleteBehavior.Cascade); }); // Configure QuizOption entity modelBuilder.Entity(builder => { builder.HasKey(o => o.Id); builder.Property(o => o.QuizQuestionId).IsRequired(); builder.Property(o => o.Text).IsRequired().HasMaxLength(1000); builder.Property(o => o.IsCorrect).IsRequired().HasDefaultValue(false); builder.Property(o => o.Order).IsRequired().HasDefaultValue(1); // Foreign key to QuizQuestion builder.HasOne(o => o.QuizQuestion) .WithMany(q => q.Options) .HasForeignKey(o => o.QuizQuestionId) .OnDelete(DeleteBehavior.Cascade); // Unique constraint: one option per question per order builder.HasIndex(o => new { o.QuizQuestionId, o.Order }).IsUnique(); }); // Seed data (optional) - Note: For EF Core, we need to set properties directly // In a real application, use migrations or a separate seeding mechanism // modelBuilder.Entity().HasData( // new { Id = 1, Title = "Greetings", Description = "Basic German greetings", Level = 1, CreatedAt = DateTime.UtcNow }, // new { Id = 2, Title = "Numbers", Description = "German numbers 1-100", Level = 1, CreatedAt = DateTime.UtcNow }, // new { Id = 3, Title = "Grammar Basics", Description = "Basic German grammar", Level = 2, CreatedAt = DateTime.UtcNow } // ); } }