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);
+ }
+}