- 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>
127 lines
4.7 KiB
C#
127 lines
4.7 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 IUserProgressRepository.
|
|
/// This is part of the Infrastructure layer.
|
|
/// </summary>
|
|
public class UserProgressRepository : IUserProgressRepository
|
|
{
|
|
private readonly AppDbContext _context;
|
|
|
|
public UserProgressRepository(AppDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
public async Task<UserProgress?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.Include(up => up.User)
|
|
.Include(up => up.Lesson)
|
|
.FirstOrDefaultAsync(up => up.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<UserProgress>> GetAllAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.Include(up => up.User)
|
|
.Include(up => up.Lesson)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<UserProgress> AddAsync(UserProgress entity, CancellationToken cancellationToken = default)
|
|
{
|
|
await _context.UserProgress.AddAsync(entity, cancellationToken);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
return entity;
|
|
}
|
|
|
|
public async Task UpdateAsync(UserProgress entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.UserProgress.Update(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task DeleteAsync(UserProgress entity, CancellationToken cancellationToken = default)
|
|
{
|
|
_context.UserProgress.Remove(entity);
|
|
await _context.SaveChangesAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> ExistsAsync(int id, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.AnyAsync(up => up.Id == id, cancellationToken);
|
|
}
|
|
|
|
public async Task<UserProgress?> GetByUserAndLessonAsync(int userId, int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.Include(up => up.User)
|
|
.Include(up => up.Lesson)
|
|
.FirstOrDefaultAsync(up => up.UserId == userId && up.LessonId == lessonId, cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<UserProgress>> GetByUserAsync(int userId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.Include(up => up.Lesson)
|
|
.ThenInclude(l => l.Level)
|
|
.Where(up => up.UserId == userId)
|
|
.OrderBy(up => up.Lesson.Level!.Order)
|
|
.ThenBy(up => up.Lesson.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<IReadOnlyList<UserProgress>> GetByUserAndLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.Include(up => up.Lesson)
|
|
.Where(up => up.UserId == userId && up.Lesson.LevelId == levelId)
|
|
.OrderBy(up => up.Lesson.Order)
|
|
.AsNoTracking()
|
|
.ToListAsync(cancellationToken);
|
|
}
|
|
|
|
public async Task<bool> HasUserCompletedLessonAsync(int userId, int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _context.UserProgress
|
|
.AnyAsync(up => up.UserId == userId && up.LessonId == lessonId && up.IsCompleted, cancellationToken);
|
|
}
|
|
|
|
public async Task<double> GetAverageScoreForLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
var progresses = await _context.UserProgress
|
|
.Where(up => up.UserId == userId && up.Lesson.LevelId == levelId && up.QuizScore > 0)
|
|
.Select(up => up.QuizScore)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
if (progresses.Count == 0)
|
|
return 0;
|
|
|
|
return progresses.Average();
|
|
}
|
|
|
|
public async Task<double> GetLevelCompletionPercentageAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
// Get total lessons in the level
|
|
var totalLessons = await _context.Lessons
|
|
.CountAsync(l => l.LevelId == levelId, cancellationToken);
|
|
|
|
if (totalLessons == 0)
|
|
return 0;
|
|
|
|
// Get completed lessons count for user in this level
|
|
var completedCount = await _context.UserProgress
|
|
.CountAsync(up => up.UserId == userId && up.Lesson.LevelId == levelId && up.IsCompleted, cancellationToken);
|
|
|
|
return (double)completedCount / totalLessons * 100;
|
|
}
|
|
}
|