- 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>
162 lines
5.2 KiB
C#
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();
|
|
}
|
|
}
|