- 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>
161 lines
5.6 KiB
C#
161 lines
5.6 KiB
C#
using GermanApp.Domain.Entities;
|
|
using GermanApp.Domain.Interfaces;
|
|
using GermanApp.Infrastructure.Data.DbContext;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace GermanApp.Infrastructure.Data.Repositories;
|
|
|
|
/// <summary>
|
|
/// Entity Framework Core implementation of ILessonRepository.
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class LessonRepository : ILessonRepository
|
|
{
|
|
private readonly AppDbContext _context;
|
|
|
|
public LessonRepository(AppDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<Lesson?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.FirstOrDefaultAsync(l => l.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<Lesson>> GetAllAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<Lesson> AddAsync(Lesson entity, CancellationToken cancellationToken = default)
|
|
{
|
|
await _context.Lessons.AddAsync(entity, cancellationToken);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
return entity;
|
|
}
|
|
|
|
public async Task UpdateAsync(Lesson entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.Lessons.Update(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task DeleteAsync(Lesson entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.Lessons.Remove(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> ExistsAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.AnyAsync(l => l.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<Lesson>> GetByLevelAsync(int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.Where(l => l.LevelId == levelId)
|
|
.OrderBy(l => l.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<Lesson>> GetAllOrderedAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.OrderBy(l => l.Level.Order)
|
|
.ThenBy(l => l.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<Lesson?> GetFirstLessonInLevelAsync(int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.Where(l => l.LevelId == levelId)
|
|
.OrderBy(l => l.Order)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<Lesson?> GetNextLessonAsync(int currentLessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
var currentLesson = await _context.Lessons
|
|
.Where(l => l.Id == currentLessonId)
|
|
.Select(l => new { l.LevelId, l.Order })
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
|
|
if (currentLesson == null)
|
|
return null;
|
|
|
|
return await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.Where(l => l.LevelId == currentLesson.LevelId && l.Order > currentLesson.Order)
|
|
.OrderBy(l => l.Order)
|
|
.FirstOrDefaultAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<Lesson>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default)
|
|
{
|
|
// Get all lessons ordered by level and lesson order
|
|
var allLessons = await _context.Lessons
|
|
.Include(l => l.Level)
|
|
.OrderBy(l => l.Level.Order)
|
|
.ThenBy(l => l.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
|
|
if (allLessons.Count == 0)
|
|
return new List<Lesson>();
|
|
|
|
// Get completed lessons for this user
|
|
var completedLessonIds = await _context.UserProgress
|
|
.Where(up => up.UserId == userId && up.IsCompleted)
|
|
.Select(up => up.LessonId)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
// Find the first lesson in the first level
|
|
var firstLesson = allLessons.First();
|
|
|
|
// If user hasn't completed any lessons, they can only access the first one
|
|
if (completedLessonIds.Count == 0)
|
|
return new List<Lesson> { firstLesson };
|
|
|
|
// Get the highest ordered lesson that the user has completed
|
|
var completedLessons = allLessons
|
|
.Where(l => completedLessonIds.Contains(l.Id))
|
|
.OrderBy(l => l.Level.Order)
|
|
.ThenBy(l => l.Order)
|
|
.ToList();
|
|
|
|
if (completedLessons.Count == 0)
|
|
return new List<Lesson> { firstLesson };
|
|
|
|
var lastCompleted = completedLessons.Last();
|
|
|
|
// User can access all lessons up to and including the next one after the last completed
|
|
var accessibleLessons = allLessons
|
|
.TakeWhile(l => l.Id != lastCompleted.Id)
|
|
.ToList();
|
|
|
|
// Add the last completed and the next one
|
|
accessibleLessons.Add(lastCompleted);
|
|
|
|
var nextLessonIndex = allLessons.FindIndex(l => l.Id == lastCompleted.Id) + 1;
|
|
if (nextLessonIndex < allLessons.Count)
|
|
{
|
|
accessibleLessons.Add(allLessons[nextLessonIndex]);
|
|
}
|
|
|
|
return accessibleLessons;
|
|
}
|
|
}
|