- 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>
327 lines
12 KiB
C#
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 });
|
|
}
|
|
}
|