DeutschLernen/GermanApp/Presentation/Controllers/QuizzesController.cs
Lasse Rune Hansen 50f1b8a8dc feat(backend): Implement mandatory authentication and admin module
- Add Role property to User entity with migration
- Create BootstrapController for first admin user creation
- Remove [AllowAnonymous] from all learning content controllers
- Create AdminController with admin-only endpoints
- Create AdminService for user management
- Create UserReportService for progress reports
- Add UserRepository implementation
- Update AuthService with role support

feat(frontend): Implement authentication system
- Add AuthStore with React context for auth state management
- Create Login, Register, Landing, and Home pages
- Add ProtectedRoute and AdminRoute components
- Create Auth API types and client
- Configure Vite with @/ path alias
- Add comprehensive CSS styles for auth and landing pages

BREAKING CHANGE: All learning content now requires authentication.
Users must register and sign in before accessing lessons, quizzes, and stories.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-06-14 12:42:15 +02:00

327 lines
12 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 quizzes.
/// This is part of the Presentation layer.
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class QuizzesController : ControllerBase
{
private readonly QuizService _quizService;
private readonly QuizQuestionService _quizQuestionService;
public QuizzesController(
QuizService quizService,
QuizQuestionService quizQuestionService)
{
_quizService = quizService;
_quizQuestionService = quizQuestionService;
}
/// <summary>
/// Gets all quizzes.
/// </summary>
/// <returns>List of all quizzes</returns>
[HttpGet]
public async Task<IActionResult> GetAllQuizzesAsync(CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetAllQuizzesAsync(cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Gets all quizzes as list items (summary).
/// </summary>
/// <returns>List of quiz summary items</returns>
[HttpGet("list")]
public async Task<IActionResult> GetAllQuizListItemsAsync(CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetAllQuizListItemsAsync(cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Gets a specific quiz by its ID.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>The quiz with the specified ID</returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetQuizByIdAsync(int id, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.GetQuizByIdAsync(id, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Gets a quiz with its questions by ID.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>The quiz with questions</returns>
[HttpGet("{id}/with-questions")]
public async Task<IActionResult> GetQuizWithQuestionsAsync(int id, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.GetQuizWithQuestionsAsync(id, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Gets all quizzes for a specific lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>List of quizzes for the specified lesson</returns>
[HttpGet("by-lesson/{lessonId}")]
public async Task<IActionResult> GetQuizzesByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetQuizzesByLessonAsync(lessonId, cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Gets active quizzes for a specific lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>List of active quizzes for the specified lesson</returns>
[HttpGet("active/by-lesson/{lessonId}")]
public async Task<IActionResult> GetActiveQuizzesByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetActiveQuizzesByLessonAsync(lessonId, cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Gets the first quiz for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>The first quiz for the lesson</returns>
[HttpGet("first/by-lesson/{lessonId}")]
public async Task<IActionResult> GetFirstQuizByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.GetFirstQuizByLessonAsync(lessonId, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Gets all active quizzes.
/// </summary>
/// <returns>List of active quizzes</returns>
[HttpGet("active")]
public async Task<IActionResult> GetActiveQuizzesAsync(CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetActiveQuizzesAsync(cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Creates a new quiz.
/// </summary>
/// <param name="dto">The quiz data</param>
/// <returns>The created quiz</returns>
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> CreateQuizAsync(CreateQuizDto dto, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.CreateQuizAsync(dto, cancellationToken);
return CreatedAtAction(nameof(GetQuizByIdAsync), new { id = quiz.Id }, quiz);
}
/// <summary>
/// Updates an existing quiz.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <param name="dto">The updated quiz data</param>
/// <returns>The updated quiz</returns>
[HttpPut("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateQuizAsync(int id, UpdateQuizDto dto, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.UpdateQuizAsync(id, dto, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Deletes a quiz by its ID.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteQuizAsync(int id, CancellationToken cancellationToken = default)
{
var result = await _quizService.DeleteQuizAsync(id, cancellationToken);
if (!result)
return NotFound();
return NoContent();
}
/// <summary>
/// Activates a quiz.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>The activated quiz</returns>
[HttpPost("{id}/activate")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> ActivateQuizAsync(int id, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.ActivateQuizAsync(id, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Deactivates a quiz.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>The deactivated quiz</returns>
[HttpPost("{id}/deactivate")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeactivateQuizAsync(int id, CancellationToken cancellationToken = default)
{
var quiz = await _quizService.DeactivateQuizAsync(id, cancellationToken);
if (quiz == null)
return NotFound();
return Ok(quiz);
}
/// <summary>
/// Checks if a quiz exists by ID.
/// </summary>
/// <param name="id">The quiz ID</param>
/// <returns>True if the quiz exists</returns>
[HttpGet("exists/{id}")]
public async Task<IActionResult> ExistsAsync(int id, CancellationToken cancellationToken = default)
{
var exists = await _quizService.ExistsAsync(id, cancellationToken);
return Ok(new { exists });
}
/// <summary>
/// Checks if a quiz exists for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>True if a quiz exists for the lesson</returns>
[HttpGet("exists/by-lesson/{lessonId}")]
public async Task<IActionResult> ExistsByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var exists = await _quizService.ExistsByLessonAsync(lessonId, cancellationToken);
return Ok(new { lessonId, exists });
}
/// <summary>
/// Gets the total number of quizzes.
/// </summary>
/// <returns>The total count of quizzes</returns>
[HttpGet("count")]
public async Task<IActionResult> GetTotalQuizCountAsync(CancellationToken cancellationToken = default)
{
var count = await _quizService.GetTotalQuizCountAsync(cancellationToken);
return Ok(new { count });
}
/// <summary>
/// Gets the number of quizzes for a lesson.
/// </summary>
/// <param name="lessonId">The lesson ID</param>
/// <returns>The count of quizzes for the lesson</returns>
[HttpGet("count/by-lesson/{lessonId}")]
public async Task<IActionResult> GetQuizCountByLessonAsync(int lessonId, CancellationToken cancellationToken = default)
{
var count = await _quizService.GetQuizCountByLessonAsync(lessonId, cancellationToken);
return Ok(new { lessonId, count });
}
/// <summary>
/// Gets quizzes by passing score range.
/// </summary>
/// <param name="minScore">Minimum passing score</param>
/// <param name="maxScore">Maximum passing score</param>
/// <returns>List of quizzes in the score range</returns>
[HttpGet("by-passing-score/{minScore}/{maxScore}")]
public async Task<IActionResult> GetQuizzesByPassingScoreRangeAsync(int minScore, int maxScore, CancellationToken cancellationToken = default)
{
var quizzes = await _quizService.GetQuizzesByPassingScoreRangeAsync(minScore, maxScore, cancellationToken);
return Ok(quizzes);
}
/// <summary>
/// Gets quiz questions for a specific quiz.
/// </summary>
/// <param name="quizId">The quiz ID</param>
/// <returns>List of quiz questions for the quiz</returns>
[HttpGet("{quizId}/questions")]
public async Task<IActionResult> GetQuizQuestionsByQuizAsync(int quizId, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetQuizQuestionsByQuizAsync(quizId, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets active quiz questions for a specific quiz.
/// </summary>
/// <param name="quizId">The quiz ID</param>
/// <returns>List of active quiz questions for the quiz</returns>
[HttpGet("{quizId}/questions/active")]
public async Task<IActionResult> GetActiveQuizQuestionsByQuizAsync(int quizId, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetActiveQuizQuestionsByQuizAsync(quizId, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Gets random quiz questions for a quiz.
/// </summary>
/// <param name="quizId">The quiz ID</param>
/// <param name="count">Number of questions to return</param>
/// <returns>List of random quiz questions</returns>
[HttpGet("{quizId}/questions/random/{count}")]
public async Task<IActionResult> GetRandomQuizQuestionsForQuizAsync(int quizId, int count, CancellationToken cancellationToken = default)
{
var questions = await _quizQuestionService.GetRandomQuizQuestionsForQuizAsync(quizId, count, cancellationToken);
return Ok(questions);
}
/// <summary>
/// Submits quiz answers and returns the result.
/// </summary>
/// <param name="userId">The user ID</param>
/// <param name="quizId">The quiz ID</param>
/// <param name="dto">The quiz answers submission</param>
/// <returns>The quiz result</returns>
[HttpPost("submit/{userId}/{quizId}")]
[Authorize]
public async Task<IActionResult> SubmitQuizAnswersAsync(int userId, int quizId, SubmitQuizDto dto, CancellationToken cancellationToken = default)
{
// Update the submission to use the correct quizId
var submission = new SubmitQuizDto(quizId, dto.Answers);
var result = await _quizQuestionService.SubmitQuizAnswersForQuizAsync(userId, submission, cancellationToken);
return Ok(result);
}
/// <summary>
/// Gets the total points for a quiz.
/// </summary>
/// <param name="quizId">The quiz ID</param>
/// <returns>The total points for the quiz</returns>
[HttpGet("{quizId}/total-points")]
public async Task<IActionResult> GetTotalPointsForQuizAsync(int quizId, CancellationToken cancellationToken = default)
{
var points = await _quizQuestionService.GetTotalPointsForQuizAsync(quizId, cancellationToken);
return Ok(new { quizId, points });
}
}