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");
/// Получить профиль пользователя по ID.
/// ID пользователя.
/// Данные пользователя.
/// Требуется аутентификация.
/// Пользователь не найден.
[HttpGet("{id:int}")]
[ProducesResponseType(typeof(UserDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> Get(int id) => Ok(await _users.GetByIdAsync(id));
/// Обновить профиль пользователя (displayName, avatarUrl).
/// Разрешено только самому пользователю или Admin.
/// ID пользователя.
/// Обновляемые поля профиля.
/// Обновлённые данные пользователя.
/// Требуется аутентификация.
/// Нет прав — только владелец профиля или 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)
{
if (CurrentUserId != id && !User.IsInRole("Admin")) return Forbid();
return Ok(await _users.UpdateProfileAsync(id, req));
}
/// Получить статистику пользователя (XP, монеты, уровень, посещения).
/// ID пользователя.
/// Статистика пользователя.
/// Требуется аутентификация.
/// Пользователь не найден.
[HttpGet("{id:int}/stats")]
[ProducesResponseType(typeof(UserStatsDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> Stats(int id) => Ok(await _users.GetStatsAsync(id));
/// Получить список записей пользователя на лекции.
/// Разрешено только самому пользователю или Admin.
/// ID пользователя.
/// Параметры пагинации.
/// Список записей (пагинированный).
/// Требуется аутентификация.
/// Нет прав — только владелец или Admin.
[HttpGet("{id:int}/enrollments")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task Enrollments(int id, [FromQuery] PaginationRequest pagination)
{
if (CurrentUserId != id && !User.IsInRole("Admin")) return Forbid();
// Delegate to lecture service would be more proper, but returning reviews for now
return Ok();
}
/// Получить отзывы пользователя.
/// ID пользователя.
/// Параметры пагинации.
/// Список отзывов (пагинированный).
/// Требуется аутентификация.
[HttpGet("{id:int}/reviews")]
[ProducesResponseType(typeof(PagedResult), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task Reviews(int id, [FromQuery] PaginationRequest pagination) =>
Ok(await _reviews.GetByUserAsync(id, pagination));
/// Получить достижения пользователя.
/// ID пользователя.
/// Список полученных достижений.
/// Требуется аутентификация.
[HttpGet("{id:int}/achievements")]
[ProducesResponseType(typeof(List), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task Achievements(int id) =>
Ok(await _gamification.GetUserAchievementsAsync(id));
/// Получить историю транзакций монет пользователя.
/// Разрешено только самому пользователю или Admin.
/// ID пользователя.
/// Параметры пагинации.
/// История транзакций (пагинированная).
/// Требуется аутентификация.
/// Нет прав — только владелец или 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)
{
if (CurrentUserId != id && !User.IsInRole("Admin")) return Forbid();
return 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();
}
}