- Created LevelDto.cs with LevelDto, CreateLevelDto, UpdateLevelDto - Created UserProgressDto.cs with UserProgressDto, UpdateUserProgressDto, LevelCompletionDto - Created LevelService.cs with CRUD operations for CEFR levels - Created LessonService.cs with CRUD operations and filtering by level - Created ProgressService.cs with user progress tracking, completion percentage, and lesson unlocking logic - Registered all new services in Program.cs - Fixed UserProgressDto ToEntity/UpdateFromDto to use domain entity methods Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
206 lines
8 KiB
C#
206 lines
8 KiB
C#
using GermanApp.Application.DTOs;
|
|
using GermanApp.Domain.Entities;
|
|
using GermanApp.Domain.Interfaces;
|
|
|
|
namespace GermanApp.Application.Services;
|
|
|
|
/// <summary>
|
|
/// Application service for managing user progress through lessons.
|
|
/// This is part of the Application layer.
|
|
/// </summary>
|
|
public class ProgressService
|
|
{
|
|
private readonly IUserProgressRepository _userProgressRepository;
|
|
private readonly ILessonRepository _lessonRepository;
|
|
private readonly ILevelRepository _levelRepository;
|
|
|
|
public ProgressService(
|
|
IUserProgressRepository userProgressRepository,
|
|
ILessonRepository lessonRepository,
|
|
ILevelRepository levelRepository)
|
|
{
|
|
_userProgressRepository = userProgressRepository;
|
|
_lessonRepository = lessonRepository;
|
|
_levelRepository = levelRepository;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets user progress for a specific lesson.
|
|
/// </summary>
|
|
public async Task<UserProgressDto?> GetUserProgressAsync(int userId, int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
var progress = await _userProgressRepository.GetByUserAndLessonAsync(userId, lessonId, cancellationToken);
|
|
return progress?.ToDto();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets all progress records for a user.
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<UserProgressDto>> GetUserProgressAsync(int userId, CancellationToken cancellationToken = default)
|
|
{
|
|
var progressRecords = await _userProgressRepository.GetByUserAsync(userId, cancellationToken);
|
|
return progressRecords.Select(p => p.ToDto()).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets progress for all lessons in a specific level for a user.
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<UserProgressDto>> GetUserProgressByLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
var progressRecords = await _userProgressRepository.GetByUserAndLevelAsync(userId, levelId, cancellationToken);
|
|
return progressRecords.Select(p => p.ToDto()).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a user has completed a lesson.
|
|
/// </summary>
|
|
public async Task<bool> HasUserCompletedLessonAsync(int userId, int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _userProgressRepository.HasUserCompletedLessonAsync(userId, lessonId, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Marks a lesson as completed for a user with a quiz score.
|
|
/// </summary>
|
|
public async Task<UserProgressDto> MarkLessonAsCompletedAsync(int userId, int lessonId, int quizScore, CancellationToken cancellationToken = default)
|
|
{
|
|
var progress = await _userProgressRepository.GetByUserAndLessonAsync(userId, lessonId, cancellationToken);
|
|
|
|
if (progress == null)
|
|
{
|
|
// Create new progress record
|
|
progress = UserProgress.Create(userId, lessonId);
|
|
progress.MarkAsCompleted(quizScore);
|
|
await _userProgressRepository.AddAsync(progress, cancellationToken);
|
|
}
|
|
else
|
|
{
|
|
// Update existing progress record
|
|
progress.MarkAsCompleted(quizScore);
|
|
await _userProgressRepository.UpdateAsync(progress, cancellationToken);
|
|
}
|
|
|
|
return progress.ToDto();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates user progress for a lesson without marking as completed.
|
|
/// </summary>
|
|
public async Task<UserProgressDto> UpdateUserProgressAsync(int userId, UpdateUserProgressDto dto, CancellationToken cancellationToken = default)
|
|
{
|
|
var progress = await _userProgressRepository.GetByUserAndLessonAsync(userId, dto.LessonId, cancellationToken);
|
|
|
|
if (progress == null)
|
|
{
|
|
// Create new progress record
|
|
progress = dto.ToEntity(userId);
|
|
await _userProgressRepository.AddAsync(progress, cancellationToken);
|
|
}
|
|
else
|
|
{
|
|
// Update existing progress record
|
|
progress.UpdateFromDto(dto);
|
|
await _userProgressRepository.UpdateAsync(progress, cancellationToken);
|
|
}
|
|
|
|
return progress.ToDto();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets user progress for a lesson.
|
|
/// </summary>
|
|
public async Task<bool> ResetLessonProgressAsync(int userId, int lessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
var progress = await _userProgressRepository.GetByUserAndLessonAsync(userId, lessonId, cancellationToken);
|
|
if (progress == null)
|
|
return false;
|
|
|
|
progress.Reset();
|
|
await _userProgressRepository.UpdateAsync(progress, cancellationToken);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the user's average score for a level.
|
|
/// </summary>
|
|
public async Task<double> GetAverageScoreForLevelAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _userProgressRepository.GetAverageScoreForLevelAsync(userId, levelId, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the percentage of lessons completed in a level.
|
|
/// </summary>
|
|
public async Task<double> GetLevelCompletionPercentageAsync(int userId, int levelId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await _userProgressRepository.GetLevelCompletionPercentageAsync(userId, levelId, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets level completion summaries for a user.
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<LevelCompletionDto>> GetLevelCompletionsAsync(int userId, CancellationToken cancellationToken = default)
|
|
{
|
|
var levels = await _levelRepository.GetAllOrderedAsync(cancellationToken);
|
|
var completions = new List<LevelCompletionDto>();
|
|
|
|
foreach (var level in levels)
|
|
{
|
|
var lessons = await _lessonRepository.GetByLevelAsync(level.Id, cancellationToken);
|
|
var completedCount = 0;
|
|
|
|
foreach (var lesson in lessons)
|
|
{
|
|
var hasCompleted = await _userProgressRepository.HasUserCompletedLessonAsync(userId, lesson.Id, cancellationToken);
|
|
if (hasCompleted)
|
|
completedCount++;
|
|
}
|
|
|
|
var percentage = lessons.Count > 0
|
|
? (double)completedCount / lessons.Count * 100
|
|
: 0;
|
|
|
|
completions.Add(new LevelCompletionDto(
|
|
level.Id,
|
|
level.Name,
|
|
level.Code,
|
|
lessons.Count,
|
|
completedCount,
|
|
percentage
|
|
));
|
|
}
|
|
|
|
return completions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets lessons that the user can access (completed previous lessons or first in level).
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<LessonDto>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default)
|
|
{
|
|
var lessons = await _lessonRepository.GetAccessibleLessonsAsync(userId, cancellationToken);
|
|
return lessons.Select(l => l.ToDto()).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the next lesson in a level is unlocked for the user.
|
|
/// </summary>
|
|
public async Task<bool> IsNextLessonUnlockedAsync(int userId, int currentLessonId, CancellationToken cancellationToken = default)
|
|
{
|
|
var currentLesson = await _lessonRepository.GetByIdAsync(currentLessonId, cancellationToken);
|
|
if (currentLesson == null)
|
|
return false;
|
|
|
|
// If this is the first lesson in the level, the next one is always unlocked
|
|
var firstLesson = await _lessonRepository.GetFirstLessonInLevelAsync(currentLesson.LevelId, cancellationToken);
|
|
if (firstLesson?.Id == currentLessonId)
|
|
{
|
|
var nextLesson = await _lessonRepository.GetNextLessonAsync(currentLessonId, cancellationToken);
|
|
return nextLesson != null;
|
|
}
|
|
|
|
// Check if the user has completed the current lesson
|
|
var hasCompleted = await _userProgressRepository.HasUserCompletedLessonAsync(userId, currentLessonId, cancellationToken);
|
|
return hasCompleted;
|
|
}
|
|
}
|