DeutschLernen/GermanApp/Presentation/Controllers/QuizQuestionsController.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

287 lines
11 KiB
C#

using GermanApp.Application.DTOs;
using GermanApp.Application.Services;
using GermanApp.Domain.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GermanApp.Presentation.Controllers;
/// <summary>
/// API controller for managing quiz questions.
/// This is part of the Presentation layer.
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class QuizQuestionsController : ControllerBase
{
private readonly QuizQuestionService _quizQuestionService;
public QuizQuestionsController(QuizQuestionService quizQuestionService)
{
_quizQuestionService = quizQuestionService;
}
/// <summary>
/// Gets all quiz questions.
/// </summary>
/// <returns>List of all quiz questions</returns>
[HttpGet]
[AllowAnonymous]
public async Task<IActionResult> GetAllQuizQuestionsAsync(CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetAllQuizQuestionsAsync(cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets all active quiz questions for a specific lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>List of quiz questions for the specified lesson</returns>
[HttpGet("by-lesson/{lessonId}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizQuestionsByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetActiveQuizQuestionsByLessonAsync(lessonId, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets a specific quiz question by its ID.
/// </summary>
/// <param name="id">The quiz question ID</param>
/// <returns>The quiz question with the specified ID</returns>
[HttpGet("{id}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizQuestionByIdAsync(int id, CancellationToken cancellationToken = default)
{
var question = await _quizQuestionService.GetQuizQuestionByIdAsync(id, cancellationToken);
if (question == null)
return NotFound();
return Ok(question);
}
/// <summary>
/// Gets a random set of quiz questions for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <param name="count">Number of questions to return</param>
/// <returns>List of random quiz questions for the lesson</returns>
[HttpGet("random/{lessonId}/{count}")]
[AllowAnonymous]
public async Task<IActionResult> GetRandomQuizQuestionsForLessonAsync(int lessonId, int count, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetRandomQuizQuestionsForLessonAsync(lessonId, count, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets quiz questions by difficulty level.
/// </summary>
/// <param name="difficulty">The difficulty level (1-5)</param>
/// <returns>List of quiz questions with the specified difficulty</returns>
[HttpGet("by-difficulty/{difficulty}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizQuestionsByDifficultyAsync(int difficulty, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetQuizQuestionsByDifficultyAsync(difficulty, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets quiz questions by type.
/// </summary>
/// <param name="type">The question type</param>
/// <returns>List of quiz questions with the specified type</returns>
[HttpGet("by-type/{type}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizQuestionsByTypeAsync(QuestionType type, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetQuizQuestionsByTypeAsync(type, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Creates a new quiz question.
/// </summary>
/// <param name="dto">The quiz question data</param>
/// <returns>The created quiz question</returns>
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> CreateQuizQuestionAsync(CreateQuizQuestionDto dto, CancellationToken cancellationToken = default)
{
var question = await _quizQuestionService.CreateQuizQuestionAsync(dto, cancellationToken);
return CreatedAtAction(nameof(GetQuizQuestionByIdAsync), new { id = question.Id }, question);
}
/// <summary>
/// Updates an existing quiz question.
/// </summary>
/// <param name="id">The quiz question ID</param>
/// <param name="dto">The updated quiz question data</param>
/// <returns>The updated quiz question</returns>
[HttpPut("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateQuizQuestionAsync(int id, UpdateQuizQuestionDto dto, CancellationToken cancellationToken = default)
{
var question = await _quizQuestionService.UpdateQuizQuestionAsync(id, dto, cancellationToken);
if (question == null)
return NotFound();
return Ok(question);
}
/// <summary>
/// Deletes a quiz question by its ID.
/// </summary>
/// <param name="id">The quiz question ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteQuizQuestionAsync(int id, CancellationToken cancellationToken = default)
{
var result = await _quizQuestionService.DeleteQuizQuestionAsync(id, cancellationToken);
if (!result)
return NotFound();
return NoContent();
}
/// <summary>
/// Gets quiz options for a specific question.
/// </summary>
/// <param name="questionId">The question ID</param>
/// <returns>List of quiz options for the question</returns>
[HttpGet("options/{questionId}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizOptionsByQuestionAsync(int questionId, CancellationToken cancellationToken = default)
{
var options = await _quizQuestionService.GetQuizOptionsByQuestionAsync(questionId, cancellationToken);
return Ok(options);
}
/// <summary>
/// Gets the correct option for a question.
/// </summary>
/// <param name="questionId">The question ID</param>
/// <returns>The correct option for the question</returns>
[HttpGet("correct-option/{questionId}")]
[AllowAnonymous]
public async Task<IActionResult> GetCorrectOptionAsync(int questionId, CancellationToken cancellationToken = default)
{
var option = await _quizQuestionService.GetCorrectOptionAsync(questionId, cancellationToken);
if (option == null)
return NotFound();
return Ok(option);
}
/// <summary>
/// Gets all correct options for a question (for MultipleSelect type).
/// </summary>
/// <param name="questionId">The question ID</param>
/// <returns>List of correct options for the question</returns>
[HttpGet("correct-options/{questionId}")]
[AllowAnonymous]
public async Task<IActionResult> GetCorrectOptionsAsync(int questionId, CancellationToken cancellationToken = default)
{
var options = await _quizQuestionService.GetCorrectOptionsAsync(questionId, cancellationToken);
return Ok(options);
}
/// <summary>
/// Creates a new quiz option for a question.
/// </summary>
/// <param name="questionId">The question ID</param>
/// <param name="dto">The quiz option data</param>
/// <returns>The created quiz option</returns>
[HttpPost("options/{questionId}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> CreateQuizOptionAsync(int questionId, CreateQuizOptionDto dto, CancellationToken cancellationToken = default)
{
var option = await _quizQuestionService.CreateQuizOptionAsync(questionId, dto, cancellationToken);
return CreatedAtAction(nameof(GetQuizOptionsByQuestionAsync), new { questionId }, option);
}
/// <summary>
/// Updates an existing quiz option.
/// </summary>
/// <param name="id">The quiz option ID</param>
/// <param name="dto">The updated quiz option data</param>
/// <returns>The updated quiz option</returns>
[HttpPut("options/{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateQuizOptionAsync(int id, UpdateQuizOptionDto dto, CancellationToken cancellationToken = default)
{
var option = await _quizQuestionService.UpdateQuizOptionAsync(id, dto, cancellationToken);
if (option == null)
return NotFound();
return Ok(option);
}
/// <summary>
/// Deletes a quiz option by its ID.
/// </summary>
/// <param name="id">The quiz option ID</param>
/// <returns>No content on success</returns>
[HttpDelete("options/{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteQuizOptionAsync(int id, CancellationToken cancellationToken = default)
{
var result = await _quizQuestionService.DeleteQuizOptionAsync(id, cancellationToken);
if (!result)
return NotFound();
return NoContent();
}
/// <summary>
/// Submits quiz answers and returns the result.
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="dto">The quiz answers submission</param>
/// <returns>The quiz result</returns>
[HttpPost("submit/{userId}")]
[Authorize]
public async Task<IActionResult> SubmitQuizAnswersAsync(int userId, SubmitQuizAnswersDto dto, CancellationToken cancellationToken = default)
{
var result = await _quizQuestionService.SubmitQuizAnswersAsync(userId, dto, cancellationToken);
return Ok(result);
}
/// <summary>
/// Gets the number of quiz questions for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>The count of quiz questions for the lesson</returns>
[HttpGet("count/{lessonId}")]
[AllowAnonymous]
public async Task<IActionResult> GetQuizQuestionCountForLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var count = await _quizQuestionService.GetQuizQuestionCountForLessonAsync(lessonId, cancellationToken);
return Ok(new { lessonId, count });
}
/// <summary>
/// Gets the difficulty distribution for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>The difficulty distribution for the lesson</returns>
[HttpGet("difficulty-distribution/{lessonId}")]
[AllowAnonymous]
public async Task<IActionResult> GetDifficultyDistributionAsync(int lessonId, CancellationToken cancellationToken = default)
{
var distribution = await _quizQuestionService.GetDifficultyDistributionAsync(lessonId, cancellationToken);
return Ok(distribution);
}
/// <summary>
/// Gets the type distribution for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>The type distribution for the lesson</returns>
[HttpGet("type-distribution/{lessonId}")]
[AllowAnonymous]
public async Task<IActionResult> GetTypeDistributionAsync(int lessonId, CancellationToken cancellationToken = default)
{
var distribution = await _quizQuestionService.GetTypeDistributionAsync(lessonId, cancellationToken);
return Ok(distribution);
}
}