- 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>
182 lines
5.5 KiB
C#
182 lines
5.5 KiB
C#
namespace GermanApp.Domain.Entities;
|
|
|
|
/// <summary>
|
|
/// Represents a quiz that can be associated with a lesson.
|
|
/// A quiz contains multiple questions and has a passing score threshold.
|
|
/// </summary>
|
|
public class Quiz
|
|
{
|
|
public int Id { get; private set; }
|
|
public int LessonId { get; private set; }
|
|
public string Title { get; private set; } = string.Empty;
|
|
public string? Description { get; private set; }
|
|
public int PassingScore { get; private set; } = 80; // Default 80%
|
|
public int TimeLimitMinutes { get; private set; } = 0; // 0 = no limit
|
|
public bool IsActive { get; private set; } = true;
|
|
public bool ShuffleQuestions { get; private set; } = true;
|
|
public DateTime CreatedAt { get; private set; }
|
|
public DateTime? UpdatedAt { get; private set; }
|
|
|
|
// Navigation property
|
|
public virtual Lesson? Lesson { get; private set; }
|
|
public virtual ICollection<QuizQuestion> Questions { get; private set; } = new List<QuizQuestion>();
|
|
|
|
/// <summary>
|
|
/// Constructor for EF Core deserialization.
|
|
/// </summary>
|
|
private Quiz() { }
|
|
|
|
/// <summary>
|
|
/// Factory method to create a new quiz.
|
|
/// </summary>
|
|
/// <param name="lessonId">ID of the associated lesson</param>
|
|
/// <param name="title">Title of the quiz</param>
|
|
/// <param name="description">Optional description</param>
|
|
/// <param name="passingScore">Passing score percentage (default 80)</param>
|
|
/// <param name="timeLimitMinutes">Time limit in minutes (0 for no limit)</param>
|
|
public static Quiz Create(
|
|
int lessonId,
|
|
string title,
|
|
string? description = null,
|
|
int passingScore = 80,
|
|
int timeLimitMinutes = 0)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(title))
|
|
throw new ArgumentException("Quiz title cannot be empty", nameof(title));
|
|
|
|
if (passingScore < 0 || passingScore > 100)
|
|
throw new ArgumentOutOfRangeException(nameof(passingScore), "Passing score must be between 0 and 100");
|
|
|
|
if (timeLimitMinutes < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(timeLimitMinutes), "Time limit cannot be negative");
|
|
|
|
return new Quiz
|
|
{
|
|
LessonId = lessonId,
|
|
Title = title,
|
|
Description = description,
|
|
PassingScore = passingScore,
|
|
TimeLimitMinutes = timeLimitMinutes,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the quiz title.
|
|
/// </summary>
|
|
public void UpdateTitle(string newTitle)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(newTitle))
|
|
throw new ArgumentException("Quiz title cannot be empty", nameof(newTitle));
|
|
|
|
Title = newTitle;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the quiz description.
|
|
/// </summary>
|
|
public void UpdateDescription(string? newDescription)
|
|
{
|
|
Description = newDescription;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the passing score.
|
|
/// </summary>
|
|
public void UpdatePassingScore(int newPassingScore)
|
|
{
|
|
if (newPassingScore < 0 || newPassingScore > 100)
|
|
throw new ArgumentOutOfRangeException(nameof(newPassingScore), "Passing score must be between 0 and 100");
|
|
|
|
PassingScore = newPassingScore;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the time limit.
|
|
/// </summary>
|
|
public void UpdateTimeLimit(int newTimeLimitMinutes)
|
|
{
|
|
if (newTimeLimitMinutes < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(newTimeLimitMinutes), "Time limit cannot be negative");
|
|
|
|
TimeLimitMinutes = newTimeLimitMinutes;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the associated lesson.
|
|
/// </summary>
|
|
public void UpdateLesson(int newLessonId)
|
|
{
|
|
LessonId = newLessonId;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the shuffle questions setting.
|
|
/// </summary>
|
|
public void UpdateShuffleQuestions(bool newShuffleQuestions)
|
|
{
|
|
ShuffleQuestions = newShuffleQuestions;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Activates the quiz.
|
|
/// </summary>
|
|
public void Activate()
|
|
{
|
|
IsActive = true;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deactivates the quiz.
|
|
/// </summary>
|
|
public void Deactivate()
|
|
{
|
|
IsActive = false;
|
|
UpdatedAt = DateTime.UtcNow;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a question to this quiz.
|
|
/// </summary>
|
|
public void AddQuestion(QuizQuestion question)
|
|
{
|
|
if (question == null)
|
|
throw new ArgumentNullException(nameof(question));
|
|
|
|
Questions.Add(question);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a question from this quiz.
|
|
/// </summary>
|
|
public void RemoveQuestion(QuizQuestion question)
|
|
{
|
|
if (question == null)
|
|
throw new ArgumentNullException(nameof(question));
|
|
|
|
Questions.Remove(question);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the quiz is passed based on a score percentage.
|
|
/// </summary>
|
|
public bool IsPassed(double scorePercentage) => scorePercentage >= PassingScore;
|
|
|
|
/// <summary>
|
|
/// Calculates the score percentage based on correct answers.
|
|
/// </summary>
|
|
public double CalculateScore(int correctAnswers, int totalQuestions)
|
|
{
|
|
if (totalQuestions == 0)
|
|
return 0;
|
|
|
|
return (double)correctAnswers / totalQuestions * 100;
|
|
}
|
|
}
|