using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Lectures; using UniVerse.Application.Interfaces; using System.Security.Claims; namespace UniVerse.Api.Controllers; /// Каталог лекций — просмотр, управление, запись и отзывы. [ApiController] [Route("api/v1/lectures")] [Authorize] [Produces("application/json")] public class LecturesController : ControllerBase { private readonly ILectureService _lectures; private readonly IReviewService _reviews; public LecturesController(ILectureService lectures, IReviewService reviews) { _lectures = lectures; _reviews = reviews; } private int CurrentUserId => int.Parse( User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? "0"); private bool CurrentUserIsAdmin => User.IsInRole("Admin"); /// Получить каталог лекций с фильтрацией и пагинацией. /// /// Фильтры: dateFrom, dateTo, courseId, teacherId, format (Online/Offline), /// isOpen, tagId, search; параметры пагинации. /// /// Включает флаг `isEnrolled` — записан ли текущий пользователь на лекцию. /// Список лекций (пагинированный). /// Требуется аутентификация. [HttpGet] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task GetAll([FromQuery] LectureFilterRequest filter) => Ok(await _lectures.GetAllAsync(filter, CurrentUserId)); /// Получить детальную карточку лекции по ID. /// /// Включает флаг `isEnrolled` — записан ли текущий пользователь на эту лекцию. /// /// ID лекции. /// Детальные данные лекции. /// Требуется аутентификация. /// Лекция не найдена. [HttpGet("{id:int}")] [ProducesResponseType(typeof(LectureDetailDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Get(int id) => Ok(await _lectures.GetByIdAsync(id, CurrentUserId)); /// Создать новую лекцию. /// Только Admin. Курс задаётся при создании и не может быть изменён. /// Данные лекции: курс, преподаватель, локация, время, формат, вместимость. /// Лекция создана. /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpPost] [ProducesResponseType(typeof(LectureDto), StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> Create([FromBody] CreateLectureRequest req) => CreatedAtAction(nameof(Get), new { id = 0 }, await _lectures.CreateAsync(req)); /// Обновить лекцию по ID. /// Admin или Teacher. CourseId изменить нельзя. /// ID лекции. /// Обновляемые поля: преподаватель, локация, время, формат, описание. /// Обновлённые данные лекции. /// Требуется аутентификация. /// Требуется роль Admin или Teacher. /// Лекция не найдена. [Authorize(Roles = "Admin,Teacher")] [HttpPut("{id:int}")] [ProducesResponseType(typeof(LectureDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Update(int id, [FromBody] UpdateLectureRequest req) => Ok(await _lectures.UpdateAsync(id, req, CurrentUserId, CurrentUserIsAdmin)); /// Удалить лекцию по 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 _lectures.DeleteAsync(id); return NoContent(); } /// Записаться на лекцию. /// /// Только Student. Проверяет наличие свободных мест и отсутствие повторной записи. /// После посещения начисляются монеты через gamification. /// /// ID лекции. /// Запись выполнена. /// Требуется аутентификация. /// Требуется роль Student. /// Лекция не найдена. /// Студент уже записан или мест нет. [Authorize(Roles = "Student")] [HttpPost("{id:int}/enroll")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status409Conflict)] public async Task Enroll(int id) { await _lectures.EnrollAsync(id, CurrentUserId); return NoContent(); } /// Отменить запись на лекцию. /// Только Student. Отменить можно только свою запись. /// ID лекции. /// Запись отменена. /// Требуется аутентификация. /// Требуется роль Student. /// Лекция или запись не найдена. [Authorize(Roles = "Student")] [HttpDelete("{id:int}/enroll")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Unenroll(int id) { await _lectures.UnenrollAsync(id, CurrentUserId); return NoContent(); } /// Отметить посещение студента на лекции. /// /// Admin или Teacher. При отметке `attended=true` начисляются монеты за посещение /// через gamification service. /// /// ID лекции. /// ID студента. /// true — посетил, false — не посетил. /// Посещение отмечено. /// Требуется аутентификация. /// Требуется роль Admin или Teacher. /// Лекция или запись студента не найдена. [Authorize(Roles = "Admin,Teacher")] [HttpPatch("{id:int}/attendance/{userId:int}")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Attendance(int id, int userId, [FromBody] bool attended) { await _lectures.MarkAttendanceAsync(id, userId, attended, CurrentUserId, CurrentUserIsAdmin); return NoContent(); } /// Получить список записавшихся студентов на лекцию. /// Только Admin или Teacher. Включает флаг посещения (`attended`). /// ID лекции. /// Параметры пагинации. /// Список записей (пагинированный). /// Требуется аутентификация. /// Требуется роль Admin или Teacher. /// Лекция не найдена. [Authorize(Roles = "Admin,Teacher")] [HttpGet("{id:int}/enrollments")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Enrollments(int id, [FromQuery] PaginationRequest pagination) => Ok(await _lectures.GetEnrollmentsAsync(id, pagination, CurrentUserId, CurrentUserIsAdmin)); /// Получить отзывы к лекции. /// Только Admin или Teacher. /// ID лекции. /// Параметры пагинации. /// Список отзывов (пагинированный). /// Требуется аутентификация. /// Требуется роль Admin или Teacher. /// Лекция не найдена. [Authorize(Roles = "Admin,Teacher")] [HttpGet("{id:int}/reviews")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task Reviews(int id, [FromQuery] PaginationRequest pagination) => Ok(await _reviews.GetByLectureAsync(id, pagination, CurrentUserId, CurrentUserIsAdmin)); }