DeutschLernen/GermanApp/Application/Services/LessonService.cs
Lasse Rune Hansen a6021fb148
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
feat(backend/validation): add FluentValidation for DTOs and update controllers
- Add FluentValidation.AspNetCore package
- Create LevelValidators (CreateLevelValidator, UpdateLevelValidator)
- Create LessonValidators (CreateLessonValidator, UpdateLessonValidator)
- Register FluentValidation in Program.cs with auto-validation
- Update LevelsController and LessonsController to use IActionResult
- Make service methods virtual for Moq testing compatibility
- Add 26 integration tests for LevelsController (13 tests)
- Add 34 integration tests for LessonsController (17 tests)

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-09 17:35:14 +02:00

159 lines
6 KiB
C#

using GermanApp.Application.DTOs;
using GermanApp.Domain.Entities;
using GermanApp.Domain.Interfaces;
namespace GermanApp.Application.Services;
/// <summary>
/// Application service for managing lessons.
/// This is part of the Application layer.
/// </summary>
public class LessonService
{
private readonly ILessonRepository _lessonRepository;
private readonly ILevelRepository _levelRepository;
public LessonService(ILessonRepository lessonRepository, ILevelRepository levelRepository)
{
_lessonRepository = lessonRepository;
_levelRepository = levelRepository;
}
/// <summary>
/// Gets all lessons.
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetAllLessonsAsync(CancellationToken cancellationToken = default)
{
var lessons = await _lessonRepository.GetAllAsync(cancellationToken);
return lessons.Select(l => l.ToDto()).ToList();
}
/// <summary>
/// Gets all lessons ordered by level and lesson order.
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetAllOrderedLessonsAsync(CancellationToken cancellationToken = default)
{
var lessons = await _lessonRepository.GetAllOrderedAsync(cancellationToken);
return lessons.Select(l => l.ToDto()).ToList();
}
/// <summary>
/// Gets a lesson by its ID.
/// </summary>
public virtual async Task<LessonDto?> GetLessonByIdAsync(int id, CancellationToken cancellationToken = default)
{
var lesson = await _lessonRepository.GetByIdAsync(id, cancellationToken);
return lesson?.ToDto();
}
/// <summary>
/// Gets lessons by level ID.
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetLessonsByLevelAsync(int levelId, CancellationToken cancellationToken = default)
{
var lessons = await _lessonRepository.GetByLevelAsync(levelId, cancellationToken);
return lessons.Select(l => l.ToDto()).ToList();
}
/// <summary>
/// Gets beginner lessons (A1 and A2 - levels 1 and 2).
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetBeginnerLessonsAsync(CancellationToken cancellationToken = default)
{
var level1Lessons = await _lessonRepository.GetByLevelAsync(1, cancellationToken);
var level2Lessons = await _lessonRepository.GetByLevelAsync(2, cancellationToken);
var allLessons = level1Lessons.Concat(level2Lessons)
.OrderBy(l => l.LevelId)
.ThenBy(l => l.Order)
.ToList();
return allLessons.Select(l => l.ToDto()).ToList();
}
/// <summary>
/// Gets advanced lessons (B2 and C1 - levels 4 and 5).
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetAdvancedLessonsAsync(CancellationToken cancellationToken = default)
{
var level4Lessons = await _lessonRepository.GetByLevelAsync(4, cancellationToken);
var level5Lessons = await _lessonRepository.GetByLevelAsync(5, cancellationToken);
var allLessons = level4Lessons.Concat(level5Lessons)
.OrderBy(l => l.LevelId)
.ThenBy(l => l.Order)
.ToList();
return allLessons.Select(l => l.ToDto()).ToList();
}
/// <summary>
/// Creates a new lesson.
/// </summary>
public virtual async Task<LessonDto> CreateLessonAsync(CreateLessonDto dto, CancellationToken cancellationToken = default)
{
// Validate that the level exists
var level = await _levelRepository.GetByIdAsync(dto.LevelId, cancellationToken);
if (level == null)
throw new ArgumentException("Level does not exist");
var lesson = dto.ToEntity();
var createdLesson = await _lessonRepository.AddAsync(lesson, cancellationToken);
return createdLesson.ToDto();
}
/// <summary>
/// Updates an existing lesson.
/// </summary>
public virtual async Task<LessonDto?> UpdateLessonAsync(int id, UpdateLessonDto dto, CancellationToken cancellationToken = default)
{
var lesson = await _lessonRepository.GetByIdAsync(id, cancellationToken);
if (lesson == null)
return null;
// Validate that the level exists
var level = await _levelRepository.GetByIdAsync(dto.LevelId, cancellationToken);
if (level == null)
throw new ArgumentException("Level does not exist");
lesson.UpdateFromDto(dto);
await _lessonRepository.UpdateAsync(lesson, cancellationToken);
return lesson.ToDto();
}
/// <summary>
/// Deletes a lesson by its ID.
/// </summary>
public virtual async Task<bool> DeleteLessonAsync(int id, CancellationToken cancellationToken = default)
{
var lesson = await _lessonRepository.GetByIdAsync(id, cancellationToken);
if (lesson == null)
return false;
await _lessonRepository.DeleteAsync(lesson, cancellationToken);
return true;
}
/// <summary>
/// Gets the first lesson in a level.
/// </summary>
public virtual async Task<LessonDto?> GetFirstLessonInLevelAsync(int levelId, CancellationToken cancellationToken = default)
{
var lesson = await _lessonRepository.GetFirstLessonInLevelAsync(levelId, cancellationToken);
return lesson?.ToDto();
}
/// <summary>
/// Gets the next lesson after the specified one.
/// </summary>
public virtual async Task<LessonDto?> GetNextLessonAsync(int currentLessonId, CancellationToken cancellationToken = default)
{
var lesson = await _lessonRepository.GetNextLessonAsync(currentLessonId, cancellationToken);
return lesson?.ToDto();
}
/// <summary>
/// Gets lessons that the user can access (based on completion of previous lessons).
/// </summary>
public virtual async Task<IReadOnlyList<LessonDto>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default)
{
var lessons = await _lessonRepository.GetAccessibleLessonsAsync(userId, cancellationToken);
return lessons.Select(l => l.ToDto()).ToList();
}
}