diff --git a/GermanApp/Presentation/Controllers/LessonsController.cs b/GermanApp/Presentation/Controllers/LessonsController.cs new file mode 100644 index 0000000..3648942 --- /dev/null +++ b/GermanApp/Presentation/Controllers/LessonsController.cs @@ -0,0 +1,202 @@ +using GermanApp.Application.DTOs; +using GermanApp.Application.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace GermanApp.Presentation.Controllers; + +/// +/// API controller for managing lessons. +/// This is part of the Presentation layer. +/// +[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; + } + + /// + /// Gets all lessons. + /// + /// List of all lessons + [HttpGet] + [AllowAnonymous] + public async Task>> GetAllLessonsAsync(CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetAllLessonsAsync(cancellationToken); + return Ok(lessons); + } + + /// + /// Gets all lessons ordered by level and lesson order. + /// + /// List of all lessons in order + [HttpGet("ordered")] + [AllowAnonymous] + public async Task>> GetAllOrderedLessonsAsync(CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetAllOrderedLessonsAsync(cancellationToken); + return Ok(lessons); + } + + /// + /// Gets a specific lesson by its ID. + /// + /// The lesson ID + /// The lesson with the specified ID + [HttpGet("{id}")] + [AllowAnonymous] + public async Task> GetLessonByIdAsync(int id, CancellationToken cancellationToken = default) + { + var lesson = await _lessonService.GetLessonByIdAsync(id, cancellationToken); + if (lesson == null) + return NotFound(); + return Ok(lesson); + } + + /// + /// Gets lessons by level ID. + /// + /// The level ID + /// List of lessons for the specified level + [HttpGet("by-level/{levelId}")] + [AllowAnonymous] + public async Task>> GetLessonsByLevelAsync(int levelId, CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetLessonsByLevelAsync(levelId, cancellationToken); + return Ok(lessons); + } + + /// + /// Gets beginner lessons (A1 and A2 - levels 1 and 2). + /// + /// List of beginner lessons + [HttpGet("beginner")] + [AllowAnonymous] + public async Task>> GetBeginnerLessonsAsync(CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetBeginnerLessonsAsync(cancellationToken); + return Ok(lessons); + } + + /// + /// Gets advanced lessons (B2 and C1 - levels 4 and 5). + /// + /// List of advanced lessons + [HttpGet("advanced")] + [AllowAnonymous] + public async Task>> GetAdvancedLessonsAsync(CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetAdvancedLessonsAsync(cancellationToken); + return Ok(lessons); + } + + /// + /// Creates a new lesson. + /// + /// The lesson data + /// The created lesson + [HttpPost] + [Authorize(Roles = "Admin")] + public async Task> CreateLessonAsync(CreateLessonDto dto, CancellationToken cancellationToken = default) + { + var lesson = await _lessonService.CreateLessonAsync(dto, cancellationToken); + return CreatedAtAction(nameof(GetLessonByIdAsync), new { id = lesson.Id }, lesson); + } + + /// + /// Updates an existing lesson. + /// + /// The lesson ID + /// The updated lesson data + /// The updated lesson + [HttpPut("{id}")] + [Authorize(Roles = "Admin")] + public async Task> UpdateLessonAsync(int id, UpdateLessonDto dto, CancellationToken cancellationToken = default) + { + var lesson = await _lessonService.UpdateLessonAsync(id, dto, cancellationToken); + if (lesson == null) + return NotFound(); + return Ok(lesson); + } + + /// + /// Deletes a lesson by its ID. + /// + /// The lesson ID + /// No content on success + [HttpDelete("{id}")] + [Authorize(Roles = "Admin")] + public async Task DeleteLessonAsync(int id, CancellationToken cancellationToken = default) + { + var result = await _lessonService.DeleteLessonAsync(id, cancellationToken); + if (!result) + return NotFound(); + return NoContent(); + } + + /// + /// Gets the first lesson in a level. + /// + /// The level ID + /// The first lesson in the level + [HttpGet("first/{levelId}")] + [AllowAnonymous] + public async Task> GetFirstLessonInLevelAsync(int levelId, CancellationToken cancellationToken = default) + { + var lesson = await _lessonService.GetFirstLessonInLevelAsync(levelId, cancellationToken); + if (lesson == null) + return NotFound(); + return Ok(lesson); + } + + /// + /// Gets the next lesson after the specified one. + /// + /// The current lesson ID + /// The next lesson + [HttpGet("next/{currentLessonId}")] + [AllowAnonymous] + public async Task> GetNextLessonAsync(int currentLessonId, CancellationToken cancellationToken = default) + { + var lesson = await _lessonService.GetNextLessonAsync(currentLessonId, cancellationToken); + if (lesson == null) + return NotFound(); + return Ok(lesson); + } + + /// + /// Gets lessons that the user can access (based on completion of previous lessons). + /// + /// The user ID + /// List of accessible lessons for the user + [HttpGet("accessible/{userId}")] + [Authorize] + public async Task>> GetAccessibleLessonsAsync(int userId, CancellationToken cancellationToken = default) + { + var lessons = await _lessonService.GetAccessibleLessonsAsync(userId, cancellationToken); + return Ok(lessons); + } + + /// + /// Checks if the next lesson is unlocked for a user. + /// + /// The user ID + /// The current lesson ID + /// True if the next lesson is unlocked + [HttpGet("unlocked/{userId}/{currentLessonId}")] + [Authorize] + public async Task> IsNextLessonUnlockedAsync(int userId, int currentLessonId, CancellationToken cancellationToken = default) + { + var isUnlocked = await _progressService.IsNextLessonUnlockedAsync(userId, currentLessonId, cancellationToken); + return Ok(isUnlocked); + } +} diff --git a/GermanApp/Presentation/Controllers/LevelsController.cs b/GermanApp/Presentation/Controllers/LevelsController.cs new file mode 100644 index 0000000..7bc034b --- /dev/null +++ b/GermanApp/Presentation/Controllers/LevelsController.cs @@ -0,0 +1,138 @@ +using GermanApp.Application.DTOs; +using GermanApp.Application.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace GermanApp.Presentation.Controllers; + +/// +/// API controller for managing CEFR levels. +/// This is part of the Presentation layer. +/// +[ApiController] +[Route("api/[controller]")] +[Authorize] +public class LevelsController : ControllerBase +{ + private readonly LevelService _levelService; + + public LevelsController(LevelService levelService) + { + _levelService = levelService; + } + + /// + /// Gets all CEFR levels ordered by their sort order. + /// + /// List of all levels + [HttpGet] + [AllowAnonymous] + public async Task>> GetAllLevelsAsync(CancellationToken cancellationToken = default) + { + var levels = await _levelService.GetAllLevelsAsync(cancellationToken); + return Ok(levels); + } + + /// + /// Gets a specific level by its ID. + /// + /// The level ID + /// The level with the specified ID + [HttpGet("{id}")] + [AllowAnonymous] + public async Task> GetLevelByIdAsync(int id, CancellationToken cancellationToken = default) + { + var level = await _levelService.GetLevelByIdAsync(id, cancellationToken); + if (level == null) + return NotFound(); + return Ok(level); + } + + /// + /// Gets a level by its code (e.g., "A1", "B2"). + /// + /// The level code + /// The level with the specified code + [HttpGet("by-code/{code}")] + [AllowAnonymous] + public async Task> GetLevelByCodeAsync(string code, CancellationToken cancellationToken = default) + { + var level = await _levelService.GetLevelByCodeAsync(code, cancellationToken); + if (level == null) + return NotFound(); + return Ok(level); + } + + /// + /// Creates a new CEFR level. + /// + /// The level data + /// The created level + [HttpPost] + [Authorize(Roles = "Admin")] + public async Task> CreateLevelAsync(CreateLevelDto dto, CancellationToken cancellationToken = default) + { + var level = await _levelService.CreateLevelAsync(dto, cancellationToken); + return CreatedAtAction(nameof(GetLevelByIdAsync), new { id = level.Id }, level); + } + + /// + /// Updates an existing CEFR level. + /// + /// The level ID + /// The updated level data + /// The updated level + [HttpPut("{id}")] + [Authorize(Roles = "Admin")] + public async Task> UpdateLevelAsync(int id, UpdateLevelDto dto, CancellationToken cancellationToken = default) + { + var level = await _levelService.UpdateLevelAsync(id, dto, cancellationToken); + if (level == null) + return NotFound(); + return Ok(level); + } + + /// + /// Deletes a CEFR level by its ID. + /// + /// The level ID + /// No content on success + [HttpDelete("{id}")] + [Authorize(Roles = "Admin")] + public async Task DeleteLevelAsync(int id, CancellationToken cancellationToken = default) + { + var result = await _levelService.DeleteLevelAsync(id, cancellationToken); + if (!result) + return NotFound(); + return NoContent(); + } + + /// + /// Gets the first level (lowest order number) - typically A1. + /// + /// The first level + [HttpGet("first")] + [AllowAnonymous] + public async Task> GetFirstLevelAsync(CancellationToken cancellationToken = default) + { + var level = await _levelService.GetFirstLevelAsync(cancellationToken); + if (level == null) + return NotFound(); + return Ok(level); + } + + /// + /// Gets the next level after the specified one. + /// + /// The current level ID + /// The next level + [HttpGet("next/{currentLevelId}")] + [AllowAnonymous] + public async Task> GetNextLevelAsync(int currentLevelId, CancellationToken cancellationToken = default) + { + var level = await _levelService.GetNextLevelAsync(currentLevelId, cancellationToken); + if (level == null) + return NotFound(); + return Ok(level); + } +}