using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Courses; using UniVerse.Application.Interfaces; namespace UniVerse.Api.Controllers; /// Управление курсами (дисциплинами) и их тегами. [ApiController] [Route("api/v1/courses")] [Authorize] [Produces("application/json")] public class CoursesController : ControllerBase { private readonly ICourseService _courses; public CoursesController(ICourseService courses) => _courses = courses; /// Получить список курсов с фильтрацией и пагинацией. /// Фильтры: tagId, search, isSynced; параметры пагинации. /// Список курсов (пагинированный). /// Требуется аутентификация. [HttpGet] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task GetAll([FromQuery] CourseFilterRequest filter) => Ok(await _courses.GetAllAsync(filter)); /// Получить курс по ID (включая теги). /// ID курса. /// Данные курса с тегами. /// Требуется аутентификация. /// Курс не найден. [HttpGet("{id:int}")] [ProducesResponseType(typeof(CourseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Get(int id) => Ok(await _courses.GetByIdAsync(id)); /// Создать новый курс. /// Только Admin. /// Название и описание курса. /// Курс создан. /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpPost] [ProducesResponseType(typeof(CourseDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> Create([FromBody] CreateCourseRequest req) => CreatedAtAction(nameof(Get), new { id = 0 }, await _courses.CreateAsync(req)); /// Обновить курс по ID. /// Только Admin. /// ID курса. /// Новое название и/или описание. /// Обновлённые данные курса. /// Требуется аутентификация. /// Требуется роль Admin. /// Курс не найден. [Authorize(Roles = "Admin")] [HttpPut("{id:int}")] [ProducesResponseType(typeof(CourseDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Update(int id, [FromBody] UpdateCourseRequest req) => Ok(await _courses.UpdateAsync(id, req)); /// Удалить курс по ID. /// Только Admin. Удаление курса каскадно удаляет связанные лекции. /// ID курса. /// Курс удалён. /// Требуется аутентификация. /// Требуется роль Admin. /// Курс не найден. [Authorize(Roles = "Admin")] [HttpDelete("{id:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Delete(int id) { await _courses.DeleteAsync(id); return NoContent(); } /// Привязать тег к курсу. /// Только Admin. Тег должен существовать в системе. /// ID курса. /// ID тега. /// Тег привязан. /// Требуется аутентификация. /// Требуется роль Admin. /// Курс или тег не найден. /// Тег уже привязан к курсу. [Authorize(Roles = "Admin")] [HttpPost("{id:int}/tags")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task AddTag(int id, [FromBody] int tagId) { await _courses.AddTagAsync(id, tagId); return NoContent(); } /// Отвязать тег от курса. /// Только Admin. /// ID курса. /// ID тега. /// Тег отвязан. /// Требуется аутентификация. /// Требуется роль Admin. /// Курс или тег не найден, либо связь не существует. [Authorize(Roles = "Admin")] [HttpDelete("{id:int}/tags/{tagId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task RemoveTag(int id, int tagId) { await _courses.RemoveTagAsync(id, tagId); return NoContent(); } }