using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using UniVerse.Application.DTOs.Common; using UniVerse.Application.DTOs.Users; using UniVerse.Application.Interfaces; using UniVerse.Domain.Enums; using System.Security.Claims; namespace UniVerse.Api.Controllers; /// Управление пользователями, профилями и геймификацией. [ApiController] [Route("api/v1/users")] [Authorize] [Produces("application/json")] public class UsersController : ControllerBase { private readonly IUserService _users; private readonly IReviewService _reviews; private readonly IGamificationService _gamification; public UsersController(IUserService users, IReviewService reviews, IGamificationService gamification) { _users = users; _reviews = reviews; _gamification = gamification; } private int CurrentUserId => int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? "0"); private static CurrentUserDto ToCurrentUserDto(UserDto user) => new( user.Id, user.Email, user.DisplayName, user.AvatarUrl, user.Roles, user.Xp, user.Coins, user.Level, user.CreatedAt); /// Получить профиль текущего пользователя. /// Данные текущего пользователя. /// Требуется аутентификация. /// Пользователь не найден. [HttpGet("me")] [ProducesResponseType(typeof(CurrentUserDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetMe() => Ok(ToCurrentUserDto(await _users.GetByIdAsync(CurrentUserId))); /// Обновить профиль текущего пользователя (displayName, avatarUrl). /// Обновляемые поля профиля. /// Обновлённые данные текущего пользователя. /// Требуется аутентификация. /// Пользователь не найден. [HttpPut("me")] [ProducesResponseType(typeof(CurrentUserDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> UpdateMe([FromBody] UpdateUserRequest req) => Ok(ToCurrentUserDto(await _users.UpdateProfileAsync(CurrentUserId, req))); /// Получить статистику текущего пользователя. /// Статистика текущего пользователя. /// Требуется аутентификация. /// Пользователь не найден. [HttpGet("me/stats")] [ProducesResponseType(typeof(UserStatsDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> MyStats() => Ok(await _users.GetStatsAsync(CurrentUserId)); /// Получить список записей текущего пользователя на лекции. /// Параметры пагинации. /// Список записей (пагинированный). /// Требуется аутентификация. /// Пользователь не найден. [HttpGet("me/enrollments")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task MyEnrollments([FromQuery] PaginationRequest pagination) => Ok(await _users.GetEnrollmentsAsync(CurrentUserId, pagination)); /// Получить отзывы текущего пользователя. /// Параметры пагинации. /// Список отзывов (пагинированный). /// Требуется аутентификация. [HttpGet("me/reviews")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task MyReviews([FromQuery] PaginationRequest pagination) => Ok(await _reviews.GetByUserAsync(CurrentUserId, pagination)); /// Получить достижения текущего пользователя. /// Список полученных достижений. /// Требуется аутентификация. [HttpGet("me/achievements")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task MyAchievements() => Ok(await _gamification.GetUserAchievementsAsync(CurrentUserId)); /// Получить историю транзакций монет текущего пользователя. /// Параметры пагинации. /// История транзакций (пагинированная). /// Требуется аутентификация. [HttpGet("me/transactions")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public async Task MyTransactions([FromQuery] PaginationRequest pagination) => Ok(await _gamification.GetTransactionsAsync(CurrentUserId, pagination)); /// Получить профиль пользователя по ID. /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me. /// ID пользователя. /// Данные пользователя. /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [HttpGet("{id:int}")] [ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Get(int id) => Ok(await _users.GetByIdAsync(id)); /// Обновить профиль пользователя (displayName, avatarUrl). /// Только Admin. Для текущего пользователя используйте PUT /api/v1/users/me. /// ID пользователя. /// Обновляемые поля профиля. /// Обновлённые данные пользователя. /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [HttpPut("{id:int}")] [ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Update(int id, [FromBody] UpdateUserRequest req) => Ok(await _users.UpdateProfileAsync(id, req)); /// Получить статистику пользователя (XP, монеты, уровень, посещения). /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me/stats. /// ID пользователя. /// Статистика пользователя. /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [HttpGet("{id:int}/stats")] [ProducesResponseType(typeof(UserStatsDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> Stats(int id) => Ok(await _users.GetStatsAsync(id)); /// Получить статистику для админского дашборда. /// Только Admin. /// Агрегированная статистика дашборда. /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpGet("admin/stats")] [ProducesResponseType(typeof(AdminDashboardStatsDto), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task> AdminStats() => Ok(await _users.GetAdminDashboardStatsAsync()); /// Получить список записей пользователя на лекции. /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me/enrollments. /// ID пользователя. /// Параметры пагинации. /// Список записей (пагинированный). /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [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 _users.GetEnrollmentsAsync(id, pagination)); /// Получить отзывы пользователя. /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me/reviews. /// ID пользователя. /// Параметры пагинации. /// Список отзывов (пагинированный). /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpGet("{id:int}/reviews")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task Reviews(int id, [FromQuery] PaginationRequest pagination) => Ok(await _reviews.GetByUserAsync(id, pagination)); /// Получить достижения пользователя. /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me/achievements. /// ID пользователя. /// Список полученных достижений. /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpGet("{id:int}/achievements")] [ProducesResponseType(typeof(List), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task Achievements(int id) => Ok(await _gamification.GetUserAchievementsAsync(id)); /// Получить историю транзакций монет пользователя. /// Только Admin. Для текущего пользователя используйте GET /api/v1/users/me/transactions. /// ID пользователя. /// Параметры пагинации. /// История транзакций (пагинированная). /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpGet("{id:int}/transactions")] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task Transactions(int id, [FromQuery] PaginationRequest pagination) => Ok(await _gamification.GetTransactionsAsync(id, pagination)); /// Получить список всех пользователей с фильтрацией и пагинацией. /// Только Admin. /// Параметры фильтрации (поиск, роль, активность) и пагинации. /// Список пользователей (пагинированный). /// Требуется аутентификация. /// Требуется роль Admin. [Authorize(Roles = "Admin")] [HttpGet] [ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task GetAll([FromQuery] UserFilterRequest filter) => Ok(await _users.GetAllAsync(filter)); /// Изменить набор ролей пользователя. /// Только Admin. Доступные роли: Student, Teacher, Admin. /// ID пользователя. /// Новый набор ролей пользователя. /// Роли успешно изменены. /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [HttpPatch("{id:int}/role")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task SetRole(int id, [FromBody] IReadOnlyCollection roles) { if (roles.Count == 0) return BadRequest("At least one role is required."); await _users.SetRolesAsync(id, roles); return NoContent(); } /// Активировать или деактивировать аккаунт пользователя. /// Только Admin. Деактивированный пользователь не может войти в систему. /// ID пользователя. /// true — активировать, false — деактивировать. /// Статус успешно изменён. /// Требуется аутентификация. /// Требуется роль Admin. /// Пользователь не найден. [Authorize(Roles = "Admin")] [HttpPatch("{id:int}/active")] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task SetActive(int id, [FromBody] bool isActive) { await _users.SetActiveAsync(id, isActive); return NoContent(); } }