DeutschLernen/GermanApp/Application/Services/LessonUnlockService.cs
Lasse Rune Hansen 9215ed7a05 feat(backend/application): Complete Lesson Management feature implementation
- Integrated Quiz completion with ProgressService: when a quiz is passed (>=80%), the associated lesson is automatically marked as completed
- Created LessonUnlockService for centralized lesson unlocking business logic
- Created LevelCompletionCalculator for calculating level completion metrics
- Registered new services in Program.cs DI container
- Updated QuizQuestionService to include ProgressService dependency
- Updated documentation (ROADMAP.md, lesson-management.md) to reflect completion
- Fixed QuizQuestionServiceTests to work with updated dependencies

All tests pass (296 total: 148 unit + 148 integration).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-13 09:34:42 +02:00

110 lines
4.3 KiB
C#

using GermanApp.Domain.Interfaces;
namespace GermanApp.Application.Services;
/// <summary>
/// Domain service for managing lesson unlocking business logic.
/// This is part of the Application layer.
/// </summary>
public class LessonUnlockService
{
private readonly ILessonRepository _lessonRepository;
private readonly IUserProgressRepository _userProgressRepository;
public LessonUnlockService(
ILessonRepository lessonRepository,
IUserProgressRepository userProgressRepository)
{
_lessonRepository = lessonRepository;
_userProgressRepository = userProgressRepository;
}
/// <summary>
/// Checks if the next lesson in a level is unlocked for the user.
/// A lesson is unlocked if:
/// - It's the first lesson in the level, OR
/// - The previous lesson in the level has been completed
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="currentLessonId">The current lesson ID</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if the next lesson is unlocked, false otherwise</returns>
public virtual 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;
}
/// <summary>
/// Gets the next lesson ID that should be unlocked for a user after completing a lesson.
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="completedLessonId">The ID of the lesson that was just completed</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>The ID of the next lesson, or null if no more lessons in the level</returns>
public virtual async Task<int?> GetNextLessonIdAsync(
int userId,
int completedLessonId,
CancellationToken cancellationToken = default)
{
var currentLesson = await _lessonRepository.GetByIdAsync(completedLessonId, cancellationToken);
if (currentLesson == null)
return null;
var nextLesson = await _lessonRepository.GetNextLessonAsync(completedLessonId, cancellationToken);
return nextLesson?.Id;
}
/// <summary>
/// Gets all lessons that are accessible to a user (completed lessons + next unlocked lesson).
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>List of accessible lesson IDs</returns>
public virtual async Task<IReadOnlyList<int>> GetAccessibleLessonIdsAsync(
int userId,
CancellationToken cancellationToken = default)
{
var lessons = await _lessonRepository.GetAccessibleLessonsAsync(userId, cancellationToken);
return lessons.Select(l => l.Id).ToList();
}
/// <summary>
/// Checks if a specific lesson is accessible to a user.
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="lessonId">The lesson ID to check</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>True if the lesson is accessible, false otherwise</returns>
public virtual async Task<bool> IsLessonAccessibleAsync(
int userId,
int lessonId,
CancellationToken cancellationToken = default)
{
var accessibleLessons = await GetAccessibleLessonIdsAsync(userId, cancellationToken);
return accessibleLessons.Contains(lessonId);
}
}