DeutschLernen/GermanApp/Domain/Entities/QuizQuestion.cs
Lasse Rune Hansen 04ad7ef008 feat(backend/domain): add quiz question feature with Docker migration support
- Add QuizQuestion and QuizOption domain entities with QuestionType enum
- Add IQuizQuestionRepository and IQuizOptionRepository interfaces
- Add QuizQuestionRepository and QuizOptionRepository EF Core implementations
- Add QuizQuestionService with CRUD, quiz submission, and statistics
- Add QuizQuestionsController with comprehensive REST API endpoints
- Add QuizQuestionDto and related DTOs for API communication
- Add EF Core migration (20260612050652_AddQuizQuestionTables) for QuizQuestion and QuizOption
- Add seed data with sample quiz questions for Greetings, Numbers, Grammar lessons
- Update AppDbContext with DbSets and entity configurations
- Update Program.cs with migration retry logic for Docker
- Update docker-compose.yml with SeedDatabase configuration
- Add unit tests for QuizQuestionService

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-12 16:49:27 +02:00

266 lines
7.2 KiB
C#

namespace GermanApp.Domain.Entities;
/// <summary>
/// Represents a quiz question for a lesson.
/// Each question has a type (multiple choice, true/false, fill-in-the-blank, etc.)
/// and is associated with a specific lesson.
/// </summary>
public class QuizQuestion
{
public int Id { get; private set; }
public int LessonId { 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 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 Lesson? Lesson { 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="lessonId">ID of the parent lesson</param>
/// <param name="questionText">The question text</param>
/// <param name="type">The question type</param>
/// <param name="correctAnswer">The correct answer</param>
/// <param name="difficulty">Difficulty level (1-5)</param>
/// <param name="order">Sort order within the lesson</param>
public static QuizQuestion Create(
int lessonId,
string questionText,
QuestionType type,
string correctAnswer,
int difficulty = 3,
int order = 1)
{
if (difficulty < 1 || difficulty > 5)
throw new ArgumentOutOfRangeException(nameof(difficulty), "Difficulty must be between 1 and 5");
if (string.IsNullOrWhiteSpace(questionText))
throw new ArgumentException("Question text cannot be empty", nameof(questionText));
return new QuizQuestion
{
LessonId = lessonId,
QuestionText = questionText,
Type = type,
CorrectAnswer = correctAnswer,
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 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 lesson.
/// </summary>
public void UpdateLesson(int newLessonId)
{
LessonId = newLessonId;
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
}