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

133 lines
4.5 KiB
C#

using GermanApp.Application.DTOs;
using GermanApp.Application.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace GermanApp.Presentation.Controllers;
/// <summary>
/// API controller for managing CEFR levels.
/// This is part of the Presentation layer.
/// </summary>
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class LevelsController : ControllerBase
{
private readonly LevelService _levelService;
public LevelsController(LevelService levelService)
{
_levelService = levelService;
}
/// <summary>
/// Gets all CEFR levels ordered by their sort order.
/// </summary>
/// <returns>List of all levels</returns>
[HttpGet]
public async Task<IActionResult> GetAllLevelsAsync(CancellationToken cancellationToken = default)
{
var levels = await _levelService.GetAllLevelsAsync(cancellationToken);
return Ok(levels);
}
/// <summary>
/// Gets a specific level by its ID.
/// </summary>
/// <param name="id">The level ID</param>
/// <returns>The level with the specified ID</returns>
[HttpGet("{id}")]
public async Task<IActionResult> GetLevelByIdAsync(int id, CancellationToken cancellationToken = default)
{
var level = await _levelService.GetLevelByIdAsync(id, cancellationToken);
if (level == null)
return NotFound();
return Ok(level);
}
/// <summary>
/// Gets a level by its code (e.g., "A1", "B2").
/// </summary>
/// <param name="code">The level code</param>
/// <returns>The level with the specified code</returns>
[HttpGet("by-code/{code}")]
public async Task<IActionResult> GetLevelByCodeAsync(string code, CancellationToken cancellationToken = default)
{
var level = await _levelService.GetLevelByCodeAsync(code, cancellationToken);
if (level == null)
return NotFound();
return Ok(level);
}
/// <summary>
/// Creates a new CEFR level.
/// </summary>
/// <param name="dto">The level data</param>
/// <returns>The created level</returns>
[HttpPost]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> CreateLevelAsync(CreateLevelDto dto, CancellationToken cancellationToken = default)
{
var level = await _levelService.CreateLevelAsync(dto, cancellationToken);
return CreatedAtAction(nameof(GetLevelByIdAsync), new { id = level.Id }, level);
}
/// <summary>
/// Updates an existing CEFR level.
/// </summary>
/// <param name="id">The level ID</param>
/// <param name="dto">The updated level data</param>
/// <returns>The updated level</returns>
[HttpPut("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> UpdateLevelAsync(int id, UpdateLevelDto dto, CancellationToken cancellationToken = default)
{
var level = await _levelService.UpdateLevelAsync(id, dto, cancellationToken);
if (level == null)
return NotFound();
return Ok(level);
}
/// <summary>
/// Deletes a CEFR level by its ID.
/// </summary>
/// <param name="id">The level ID</param>
/// <returns>No content on success</returns>
[HttpDelete("{id}")]
[Authorize(Roles = "Admin")]
public async Task<IActionResult> DeleteLevelAsync(int id, CancellationToken cancellationToken = default)
{
var result = await _levelService.DeleteLevelAsync(id, cancellationToken);
if (!result)
return NotFound();
return NoContent();
}
/// <summary>
/// Gets the first level (lowest order number) - typically A1.
/// </summary>
/// <returns>The first level</returns>
[HttpGet("first")]
public async Task<IActionResult> GetFirstLevelAsync(CancellationToken cancellationToken = default)
{
var level = await _levelService.GetFirstLevelAsync(cancellationToken);
if (level == null)
return NotFound();
return Ok(level);
}
/// <summary>
/// Gets the next level after the specified one.
/// </summary>
/// <param name="currentLevelId">The current level ID</param>
/// <returns>The next level</returns>
[HttpGet("next/{currentLevelId}")]
public async Task<IActionResult> GetNextLevelAsync(int currentLevelId, CancellationToken cancellationToken = default)
{
var level = await _levelService.GetNextLevelAsync(currentLevelId, cancellationToken);
if (level == null)
return NotFound();
return Ok(level);
}
}