DeutschLernen/GermanApp/Domain/Entities/QuizQuestion.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

285 lines
7.8 KiB
C#

namespace GermanApp.Domain.Entities;
/// <summary>
/// Represents a quiz question for a quiz.
/// Each question has a type (multiple choice, true/false, fill-in-the-blank, etc.)
/// and is associated with a specific quiz.
/// </summary>
public class QuizQuestion
{
public int Id { get; private set; }
public int QuizId { get; private set; }
public string QuestionText { get; private set; } = string.Empty;
public QuestionType Type { get; private set; }
public string? CorrectAnswer { get; private set; }
public int Points { get; private set; } = 1; // Points for this question
public int Difficulty { get; private set; } // 1-5 scale
public int Order { get; private set; }
public bool IsActive { get; private set; } = true;
public DateTime CreatedAt { get; private set; }
public DateTime? UpdatedAt { get; private set; }
// Navigation property
public virtual Quiz? Quiz { get; private set; }
public virtual ICollection<QuizOption> Options { get; private set; } = new List<QuizOption>();
/// <summary>
/// Constructor for EF Core deserialization.
/// </summary>
private QuizQuestion() { }
/// <summary>
/// Factory method to create a new quiz question.
/// </summary>
/// <param name="quizId">ID of the parent quiz</param>
/// <param name="questionText">The question text</param>
/// <param name="type">The question type</param>
/// <param name="correctAnswer">The correct answer</param>
/// <param name="points">Points for this question (default 1)</param>
/// <param name="difficulty">Difficulty level (1-5, default 3)</param>
/// <param name="order">Sort order within the quiz</param>
public static QuizQuestion Create(
int quizId,
string questionText,
QuestionType type,
string correctAnswer,
int points = 1,
int difficulty = 3,
int order = 1)
{
if (difficulty < 1 || difficulty > 5)
throw new ArgumentOutOfRangeException(nameof(difficulty), "Difficulty must be between 1 and 5");
if (points < 0)
throw new ArgumentOutOfRangeException(nameof(points), "Points must be positive");
if (string.IsNullOrWhiteSpace(questionText))
throw new ArgumentException("Question text cannot be empty", nameof(questionText));
return new QuizQuestion
{
QuizId = quizId,
QuestionText = questionText,
Type = type,
CorrectAnswer = correctAnswer,
Points = points,
Difficulty = difficulty,
Order = order,
CreatedAt = DateTime.UtcNow
};
}
/// <summary>
/// Updates the question text.
/// </summary>
public void UpdateQuestionText(string newQuestionText)
{
if (string.IsNullOrWhiteSpace(newQuestionText))
throw new ArgumentException("Question text cannot be empty", nameof(newQuestionText));
QuestionText = newQuestionText;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the correct answer.
/// </summary>
public void UpdateCorrectAnswer(string newCorrectAnswer)
{
CorrectAnswer = newCorrectAnswer;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the points for this question.
/// </summary>
public void UpdatePoints(int newPoints)
{
if (newPoints < 0)
throw new ArgumentOutOfRangeException(nameof(newPoints), "Points must be positive");
Points = newPoints;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the difficulty level.
/// </summary>
public void UpdateDifficulty(int newDifficulty)
{
if (newDifficulty < 1 || newDifficulty > 5)
throw new ArgumentOutOfRangeException(nameof(newDifficulty), "Difficulty must be between 1 and 5");
Difficulty = newDifficulty;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the sort order.
/// </summary>
public void UpdateOrder(int newOrder)
{
Order = newOrder;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the question type.
/// </summary>
public void UpdateType(QuestionType newType)
{
Type = newType;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Updates the associated quiz.
/// </summary>
public void UpdateQuiz(int newQuizId)
{
QuizId = newQuizId;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Activates the quiz question.
/// </summary>
public void Activate()
{
IsActive = true;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Deactivates the quiz question.
/// </summary>
public void Deactivate()
{
IsActive = false;
UpdatedAt = DateTime.UtcNow;
}
/// <summary>
/// Adds an option to this question.
/// </summary>
public void AddOption(QuizOption option)
{
Options.Add(option);
}
/// <summary>
/// Removes an option from this question.
/// </summary>
public void RemoveOption(QuizOption option)
{
Options.Remove(option);
}
}
/// <summary>
/// Represents an option for a multiple-choice quiz question.
/// </summary>
public class QuizOption
{
public int Id { get; private set; }
public int QuizQuestionId { get; private set; }
public string Text { get; private set; } = string.Empty;
public bool IsCorrect { get; private set; }
public int Order { get; private set; }
// Navigation property
public virtual QuizQuestion? QuizQuestion { get; private set; }
/// <summary>
/// Constructor for EF Core deserialization.
/// </summary>
private QuizOption() { }
/// <summary>
/// Factory method to create a new quiz option.
/// </summary>
/// <param name="quizQuestionId">ID of the parent quiz question</param>
/// <param name="text">The option text</param>
/// <param name="isCorrect">Whether this is the correct answer</param>
/// <param name="order">Sort order within the question's options</param>
public static QuizOption Create(
int quizQuestionId,
string text,
bool isCorrect = false,
int order = 1)
{
if (string.IsNullOrWhiteSpace(text))
throw new ArgumentException("Option text cannot be empty", nameof(text));
return new QuizOption
{
QuizQuestionId = quizQuestionId,
Text = text,
IsCorrect = isCorrect,
Order = order
};
}
/// <summary>
/// Updates the option text.
/// </summary>
public void UpdateText(string newText)
{
if (string.IsNullOrWhiteSpace(newText))
throw new ArgumentException("Option text cannot be empty", nameof(newText));
Text = newText;
}
/// <summary>
/// Updates whether this option is correct.
/// </summary>
public void UpdateIsCorrect(bool newIsCorrect)
{
IsCorrect = newIsCorrect;
}
/// <summary>
/// Updates the sort order.
/// </summary>
public void UpdateOrder(int newOrder)
{
Order = newOrder;
}
}
/// <summary>
/// Enum representing the types of quiz questions.
/// </summary>
public enum QuestionType
{
/// <summary>
/// Multiple choice with single correct answer
/// </summary>
MultipleChoice = 0,
/// <summary>
/// Multiple choice with multiple correct answers
/// </summary>
MultipleSelect = 1,
/// <summary>
/// True or false question
/// </summary>
TrueFalse = 2,
/// <summary>
/// Fill-in-the-blank question
/// </summary>
FillInTheBlank = 3,
/// <summary>
/// Matching question
/// </summary>
Matching = 4,
/// <summary>
/// Short answer question
/// </summary>
ShortAnswer = 5
}