DeutschLernen/GermanApp/Application/UseCases/Commands/CreateQuizCommand.cs
Lasse Rune Hansen 242d5ea60b feat(backend): Complete Quiz System implementation
- Add Quiz entity with full domain logic (Create, Update, Activate, Deactivate)
- Add QuizQuestion entity updated to use QuizId instead of LessonId
- Add QuizRepository with all CRUD and query methods
- Add QuizQuestionRepository with QuizId-based and LessonId-based (legacy) methods
- Add QuizDto, CreateQuizDto, UpdateQuizDto, QuizWithQuestionsDto, QuizListItemDto
- Add QuizQuestionDto updated with QuizId, LessonId, QuizTitle, Points fields
- Add CreateQuizCommand, UpdateQuizCommand, DeleteQuizCommand, GetQuizWithQuestionsCommand
- Add QuizService with full quiz management (CRUD, activate, deactivate, counts, queries)
- Add QuizQuestionService updated with QuizId-based methods
- Add QuizzesController with comprehensive endpoints (GET, POST, PUT, DELETE)
- Add QuizzesController endpoints for quiz questions (by-quiz, random, active)
- Update QuizQuestionsController with new QuizId-based endpoints (backward compatible)
- Register QuizRepository and QuizService in DI container
- Update IRepository interfaces with QuizId-based methods
- Add SubmitQuizDto for quiz answer submission

Clean Architecture layers maintained:
- Domain: Quiz, QuizQuestion entities with business logic
- Application: DTOs, Commands, Services
- Infrastructure: Repositories, DbContext
- Presentation: Controllers

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-13 07:41:46 +02:00

162 lines
5.2 KiB
C#

using GermanApp.Application.DTOs;
using GermanApp.Domain.Entities;
using GermanApp.Domain.Exceptions;
using GermanApp.Domain.Interfaces;
namespace GermanApp.Application.UseCases.Commands;
/// <summary>
/// Command for creating a new quiz.
/// This follows the CQRS pattern - commands represent write operations.
/// </summary>
public record CreateQuizCommand(CreateQuizDto QuizData) : ICommand<QuizDto>;
/// <summary>
/// Handler for the CreateQuizCommand.
/// </summary>
public class CreateQuizCommandHandler : ICommandHandler<CreateQuizCommand, QuizDto>
{
private readonly IQuizRepository _quizRepository;
private readonly ILessonRepository _lessonRepository;
public CreateQuizCommandHandler(
IQuizRepository quizRepository,
ILessonRepository lessonRepository)
{
_quizRepository = quizRepository;
_lessonRepository = lessonRepository;
}
public async Task<QuizDto> Handle(CreateQuizCommand command, CancellationToken cancellationToken)
{
// Validate that the lesson exists
var lesson = await _lessonRepository.GetByIdAsync(command.QuizData.LessonId, cancellationToken);
if (lesson == null)
throw new NotFoundException("Lesson does not exist");
// Validate business rules
if (command.QuizData.PassingScore < 0 || command.QuizData.PassingScore > 100)
{
throw new ValidationException("Passing score must be between 0 and 100");
}
if (command.QuizData.TimeLimitMinutes < 0)
{
throw new ValidationException("Time limit cannot be negative");
}
// Convert DTO to domain entity
var quiz = command.QuizData.ToEntity();
// Add to repository
var createdQuiz = await _quizRepository.AddAsync(quiz, cancellationToken);
// Return DTO representation
return createdQuiz.ToDto();
}
}
/// <summary>
/// Command for updating an existing quiz.
/// </summary>
public record UpdateQuizCommand(int Id, UpdateQuizDto QuizData) : ICommand<QuizDto?>;
/// <summary>
/// Handler for the UpdateQuizCommand.
/// </summary>
public class UpdateQuizCommandHandler : ICommandHandler<UpdateQuizCommand, QuizDto?>
{
private readonly IQuizRepository _quizRepository;
private readonly ILessonRepository _lessonRepository;
public UpdateQuizCommandHandler(
IQuizRepository quizRepository,
ILessonRepository lessonRepository)
{
_quizRepository = quizRepository;
_lessonRepository = lessonRepository;
}
public async Task<QuizDto?> Handle(UpdateQuizCommand command, CancellationToken cancellationToken)
{
var quiz = await _quizRepository.GetByIdAsync(command.Id, cancellationToken);
if (quiz == null)
return null;
// Validate that the lesson exists
var lesson = await _lessonRepository.GetByIdAsync(command.QuizData.LessonId, cancellationToken);
if (lesson == null)
throw new NotFoundException("Lesson does not exist");
// Validate business rules
if (command.QuizData.PassingScore < 0 || command.QuizData.PassingScore > 100)
{
throw new ValidationException("Passing score must be between 0 and 100");
}
if (command.QuizData.TimeLimitMinutes < 0)
{
throw new ValidationException("Time limit cannot be negative");
}
// Update the quiz
quiz.UpdateFromDto(command.QuizData);
await _quizRepository.UpdateAsync(quiz, cancellationToken);
// Reload and return
var updatedQuiz = await _quizRepository.GetByIdAsync(command.Id, cancellationToken);
return updatedQuiz?.ToDto();
}
}
/// <summary>
/// Command for deleting a quiz.
/// </summary>
public record DeleteQuizCommand(int Id) : ICommand<bool>;
/// <summary>
/// Handler for the DeleteQuizCommand.
/// </summary>
public class DeleteQuizCommandHandler : ICommandHandler<DeleteQuizCommand, bool>
{
private readonly IQuizRepository _quizRepository;
public DeleteQuizCommandHandler(IQuizRepository quizRepository)
{
_quizRepository = quizRepository;
}
public async Task<bool> Handle(DeleteQuizCommand command, CancellationToken cancellationToken)
{
var quiz = await _quizRepository.GetByIdAsync(command.Id, cancellationToken);
if (quiz == null)
return false;
await _quizRepository.DeleteAsync(quiz, cancellationToken);
return true;
}
}
/// <summary>
/// Command for getting a quiz with its questions.
/// </summary>
public record GetQuizWithQuestionsCommand(int Id) : ICommand<QuizWithQuestionsDto?>;
/// <summary>
/// Handler for the GetQuizWithQuestionsCommand.
/// </summary>
public class GetQuizWithQuestionsCommandHandler : ICommandHandler<GetQuizWithQuestionsCommand, QuizWithQuestionsDto?>
{
private readonly IQuizRepository _quizRepository;
public GetQuizWithQuestionsCommandHandler(IQuizRepository quizRepository)
{
_quizRepository = quizRepository;
}
public async Task<QuizWithQuestionsDto?> Handle(GetQuizWithQuestionsCommand command, CancellationToken cancellationToken)
{
var quiz = await _quizRepository.GetWithQuestionsAsync(command.Id, cancellationToken);
return quiz?.ToQuizWithQuestionsDto();
}
}