- Updated Lesson entity to use LevelId (int) instead of Level (int) - Added navigation property Level (Level entity) - Added Order, Topic, and IsActive properties to Lesson - Updated Lesson.Create() factory method signature to accept levelId, title, order, topic, description - Split Update method into individual property update methods - Updated LessonDto to use LevelId, LevelName, LevelCode instead of Level int - Added CreateLessonDto and UpdateLessonDto with all required fields - Added UpdateFromDto extension method for Lesson - Updated CreateLessonCommand to validate LevelId instead of Level - Updated SeedDataExtension to seed Level entities first, then use new Lesson.Create() signature - Made SeedDatabaseAsync async and updated Program.cs to await it - Updated LessonsEndpoints to use GetByLevelAsync for beginner/advanced queries - Fixed missing using directive for Domain.Entities Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
143 lines
6.2 KiB
C#
143 lines
6.2 KiB
C#
using GermanApp.Domain.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace GermanApp.Infrastructure.Data.DbContext;
|
|
|
|
/// <summary>
|
|
/// Entity Framework Core database context.
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class AppDbContext : Microsoft.EntityFrameworkCore.DbContext
|
|
{
|
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
// DbSets for domain entities
|
|
public DbSet<Level> Levels { get; set; } = null!;
|
|
public DbSet<Lesson> Lessons { get; set; } = null!;
|
|
public DbSet<User> Users { get; set; } = null!;
|
|
public DbSet<UserProgress> UserProgress { get; set; } = null!;
|
|
public DbSet<RefreshToken> RefreshTokens { 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<Level>(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<Lesson>(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<UserProgress>(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<User>(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<RefreshToken>(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<User>()
|
|
.WithMany()
|
|
.HasForeignKey(r => r.UserId)
|
|
.OnDelete(DeleteBehavior.Cascade);
|
|
});
|
|
|
|
// 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<Lesson>().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 }
|
|
// );
|
|
}
|
|
}
|