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.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. Для текущего пользователя используйте 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();
}
}