feat(backend/presentation): add LevelsController and LessonsController for Lesson Management
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
- Created LevelsController.cs with full CRUD operations
- GET /api/levels - all levels
- GET /api/levels/{id} - level by ID
- GET /api/levels/by-code/{code} - level by code
- GET /api/levels/first - first level
- GET /api/levels/next/{id} - next level
- POST /api/levels - create level (Admin)
- PUT /api/levels/{id} - update level (Admin)
- DELETE /api/levels/{id} - delete level (Admin)
- Created LessonsController.cs with full CRUD and filtering
- GET /api/lessons - all lessons
- GET /api/lessons/ordered - ordered lessons
- GET /api/lessons/{id} - lesson by ID
- GET /api/lessons/by-level/{levelId} - lessons by level
- GET /api/lessons/beginner - beginner lessons
- GET /api/lessons/advanced - advanced lessons
- GET /api/lessons/first/{levelId} - first lesson in level
- GET /api/lessons/next/{id} - next lesson
- GET /api/lessons/accessible/{userId} - accessible lessons
- GET /api/lessons/unlocked/{userId}/{lessonId} - check unlock
- POST /api/lessons - create lesson (Admin)
- PUT /api/lessons/{id} - update lesson (Admin)
- DELETE /api/lessons/{id} - delete lesson (Admin)
- Added authorization: [Authorize] for most endpoints, [AllowAnonymous] for read-only
- Added Admin role requirement for write operations
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
parent
c65214737e
commit
3caee4c21e
2 changed files with 340 additions and 0 deletions
202
GermanApp/Presentation/Controllers/LessonsController.cs
Normal file
202
GermanApp/Presentation/Controllers/LessonsController.cs
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
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 lessons.
|
||||||
|
/// This is part of the Presentation layer.
|
||||||
|
/// </summary>
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/[controller]")]
|
||||||
|
[Authorize]
|
||||||
|
public class LessonsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly LessonService _lessonService;
|
||||||
|
private readonly ProgressService _progressService;
|
||||||
|
|
||||||
|
public LessonsController(LessonService lessonService, ProgressService progressService)
|
||||||
|
{
|
||||||
|
_lessonService = lessonService;
|
||||||
|
_progressService = progressService;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all lessons.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of all lessons</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetAllLessonsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetAllLessonsAsync(cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all lessons ordered by level and lesson order.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of all lessons in order</returns>
|
||||||
|
[HttpGet("ordered")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetAllOrderedLessonsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetAllOrderedLessonsAsync(cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a specific lesson by its ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The lesson ID</param>
|
||||||
|
/// <returns>The lesson with the specified ID</returns>
|
||||||
|
[HttpGet("{id}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LessonDto>> GetLessonByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lesson = await _lessonService.GetLessonByIdAsync(id, cancellationToken);
|
||||||
|
if (lesson == null)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(lesson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets lessons by level ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="levelId">The level ID</param>
|
||||||
|
/// <returns>List of lessons for the specified level</returns>
|
||||||
|
[HttpGet("by-level/{levelId}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetLessonsByLevelAsync(int levelId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetLessonsByLevelAsync(levelId, cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets beginner lessons (A1 and A2 - levels 1 and 2).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of beginner lessons</returns>
|
||||||
|
[HttpGet("beginner")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetBeginnerLessonsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetBeginnerLessonsAsync(cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets advanced lessons (B2 and C1 - levels 4 and 5).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>List of advanced lessons</returns>
|
||||||
|
[HttpGet("advanced")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetAdvancedLessonsAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetAdvancedLessonsAsync(cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new lesson.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dto">The lesson data</param>
|
||||||
|
/// <returns>The created lesson</returns>
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<ActionResult<LessonDto>> CreateLessonAsync(CreateLessonDto dto, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lesson = await _lessonService.CreateLessonAsync(dto, cancellationToken);
|
||||||
|
return CreatedAtAction(nameof(GetLessonByIdAsync), new { id = lesson.Id }, lesson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an existing lesson.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The lesson ID</param>
|
||||||
|
/// <param name="dto">The updated lesson data</param>
|
||||||
|
/// <returns>The updated lesson</returns>
|
||||||
|
[HttpPut("{id}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<ActionResult<LessonDto>> UpdateLessonAsync(int id, UpdateLessonDto dto, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lesson = await _lessonService.UpdateLessonAsync(id, dto, cancellationToken);
|
||||||
|
if (lesson == null)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(lesson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a lesson by its ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The lesson ID</param>
|
||||||
|
/// <returns>No content on success</returns>
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize(Roles = "Admin")]
|
||||||
|
public async Task<ActionResult> DeleteLessonAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var result = await _lessonService.DeleteLessonAsync(id, cancellationToken);
|
||||||
|
if (!result)
|
||||||
|
return NotFound();
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the first lesson in a level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="levelId">The level ID</param>
|
||||||
|
/// <returns>The first lesson in the level</returns>
|
||||||
|
[HttpGet("first/{levelId}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LessonDto>> GetFirstLessonInLevelAsync(int levelId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lesson = await _lessonService.GetFirstLessonInLevelAsync(levelId, cancellationToken);
|
||||||
|
if (lesson == null)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(lesson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next lesson after the specified one.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="currentLessonId">The current lesson ID</param>
|
||||||
|
/// <returns>The next lesson</returns>
|
||||||
|
[HttpGet("next/{currentLessonId}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LessonDto>> GetNextLessonAsync(int currentLessonId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lesson = await _lessonService.GetNextLessonAsync(currentLessonId, cancellationToken);
|
||||||
|
if (lesson == null)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(lesson);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets lessons that the user can access (based on completion of previous lessons).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user ID</param>
|
||||||
|
/// <returns>List of accessible lessons for the user</returns>
|
||||||
|
[HttpGet("accessible/{userId}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LessonDto>>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var lessons = await _lessonService.GetAccessibleLessonsAsync(userId, cancellationToken);
|
||||||
|
return Ok(lessons);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the next lesson is unlocked for a user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user ID</param>
|
||||||
|
/// <param name="currentLessonId">The current lesson ID</param>
|
||||||
|
/// <returns>True if the next lesson is unlocked</returns>
|
||||||
|
[HttpGet("unlocked/{userId}/{currentLessonId}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<bool>> IsNextLessonUnlockedAsync(int userId, int currentLessonId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var isUnlocked = await _progressService.IsNextLessonUnlockedAsync(userId, currentLessonId, cancellationToken);
|
||||||
|
return Ok(isUnlocked);
|
||||||
|
}
|
||||||
|
}
|
||||||
138
GermanApp/Presentation/Controllers/LevelsController.cs
Normal file
138
GermanApp/Presentation/Controllers/LevelsController.cs
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
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]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<IReadOnlyList<LevelDto>>> 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}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LevelDto>> 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}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LevelDto>> 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<ActionResult<LevelDto>> 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<ActionResult<LevelDto>> 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<ActionResult> 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")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LevelDto>> 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}")]
|
||||||
|
[AllowAnonymous]
|
||||||
|
public async Task<ActionResult<LevelDto>> GetNextLevelAsync(int currentLevelId, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var level = await _levelService.GetNextLevelAsync(currentLevelId, cancellationToken);
|
||||||
|
if (level == null)
|
||||||
|
return NotFound();
|
||||||
|
return Ok(level);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue