DeutschLernen/GermanApp/Application/Services/ProgressService.cs
Lasse Rune Hansen be8c58671d feat(backend/application): add DTOs and services for Lesson Management
- 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>
2026-06-08 20:51:38 +02:00

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;
}
}