feat: добавил интеграционные тесты
This commit is contained in:
@@ -7,40 +7,123 @@ using System.Security.Claims;
|
||||
|
||||
namespace UniVerse.Api.Controllers;
|
||||
|
||||
/// <summary>Отзывы студентов на лекции с LLM-анализом и модерацией.</summary>
|
||||
[ApiController]
|
||||
[Route("api/v1/reviews")]
|
||||
[Authorize]
|
||||
[Produces("application/json")]
|
||||
public class ReviewsController : ControllerBase
|
||||
{
|
||||
private readonly IReviewService _reviews;
|
||||
public ReviewsController(IReviewService reviews) => _reviews = reviews;
|
||||
private int CurrentUserId => int.Parse(User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? "0");
|
||||
|
||||
public ReviewsController(IReviewService reviews) => _reviews = reviews;
|
||||
|
||||
private int CurrentUserId => int.Parse(
|
||||
User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub") ?? "0");
|
||||
|
||||
/// <summary>Создать отзыв к лекции.</summary>
|
||||
/// <remarks>
|
||||
/// Только Student. После создания отзыв помещается в очередь LLM-анализа
|
||||
/// (статус `Pending`). LLM оценивает содержательность и начисляет монеты
|
||||
/// скрытно от пользователя.
|
||||
/// </remarks>
|
||||
/// <param name="req">ID лекции, оценка (Like/Neutral/Dislike) и текст отзыва.</param>
|
||||
/// <response code="201">Отзыв создан и поставлен в очередь на LLM-анализ.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Требуется роль Student.</response>
|
||||
/// <response code="404">Лекция не найдена.</response>
|
||||
/// <response code="409">Студент уже оставил отзыв к этой лекции.</response>
|
||||
[Authorize(Roles = "Student")]
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(ReviewDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status409Conflict)]
|
||||
public async Task<ActionResult<ReviewDto>> Create([FromBody] CreateReviewRequest req) =>
|
||||
CreatedAtAction(nameof(Get), new { id = 0 }, await _reviews.CreateAsync(CurrentUserId, req));
|
||||
|
||||
/// <summary>Получить отзыв по ID.</summary>
|
||||
/// <param name="id">ID отзыва.</param>
|
||||
/// <response code="200">Данные отзыва (включая LLM-статус и сентимент).</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="404">Отзыв не найден.</response>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(ReviewDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ReviewDto>> Get(int id) => Ok(await _reviews.GetByIdAsync(id));
|
||||
|
||||
/// <summary>Обновить отзыв.</summary>
|
||||
/// <remarks>
|
||||
/// Разрешено любому авторизованному пользователю, но сервис проверяет владельца.
|
||||
/// Изменение текста сбрасывает LLM-статус в `Pending` (повторный анализ).
|
||||
/// </remarks>
|
||||
/// <param name="id">ID отзыва.</param>
|
||||
/// <param name="req">Новая оценка и/или текст.</param>
|
||||
/// <response code="200">Обновлённые данные отзыва.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Отзыв принадлежит другому пользователю.</response>
|
||||
/// <response code="404">Отзыв не найден.</response>
|
||||
[HttpPut("{id:int}")]
|
||||
[ProducesResponseType(typeof(ReviewDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<ReviewDto>> Update(int id, [FromBody] UpdateReviewRequest req) =>
|
||||
Ok(await _reviews.UpdateAsync(id, CurrentUserId, req));
|
||||
|
||||
/// <summary>Удалить отзыв.</summary>
|
||||
/// <remarks>Владелец может удалить свой отзыв. Admin может удалить любой.</remarks>
|
||||
/// <param name="id">ID отзыва.</param>
|
||||
/// <response code="204">Отзыв удалён.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Нет прав на удаление (не владелец и не Admin).</response>
|
||||
/// <response code="404">Отзыв не найден.</response>
|
||||
[HttpDelete("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
await _reviews.DeleteAsync(id, CurrentUserId, User.IsInRole("Admin"));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>Получить список отзывов, ожидающих LLM-анализа.</summary>
|
||||
/// <remarks>Только Admin. Используется для мониторинга очереди обработки.</remarks>
|
||||
/// <param name="pagination">Параметры пагинации.</param>
|
||||
/// <response code="200">Список отзывов со статусом Pending (пагинированный).</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Требуется роль Admin.</response>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpGet("pending")]
|
||||
[ProducesResponseType(typeof(PagedResult<ReviewDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
public async Task<ActionResult> Pending([FromQuery] PaginationRequest pagination) =>
|
||||
Ok(await _reviews.GetPendingAsync(pagination));
|
||||
|
||||
/// <summary>Запустить повторный LLM-анализ отзыва.</summary>
|
||||
/// <remarks>
|
||||
/// Только Admin. Сбрасывает статус отзыва на `Pending` и ставит его
|
||||
/// в очередь на повторную обработку фоновым сервисом.
|
||||
/// </remarks>
|
||||
/// <param name="id">ID отзыва.</param>
|
||||
/// <response code="204">Повторный анализ запланирован.</response>
|
||||
/// <response code="401">Требуется аутентификация.</response>
|
||||
/// <response code="403">Требуется роль Admin.</response>
|
||||
/// <response code="404">Отзыв не найден.</response>
|
||||
[Authorize(Roles = "Admin")]
|
||||
[HttpPost("{id:int}/reanalyze")]
|
||||
public async Task<IActionResult> Reanalyze(int id) { await _reviews.ReanalyzeAsync(id); return NoContent(); }
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Reanalyze(int id)
|
||||
{
|
||||
await _reviews.ReanalyzeAsync(id);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user