fix: скрыл отзывы от студентов
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 11s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m17s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 29s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 9s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 11s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 1m17s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 29s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 9s
This commit is contained in:
@@ -47,10 +47,13 @@ public class EndpointAuthorizationTests : IClassFixture<ApiWebApplicationFactory
|
|||||||
body: """{"displayName":"Test","avatarUrl":null}""");
|
body: """{"displayName":"Test","avatarUrl":null}""");
|
||||||
yield return E("users/{id}/stats [AnyAuth]", "GET", "api/v1/users/1/stats", "Student");
|
yield return E("users/{id}/stats [AnyAuth]", "GET", "api/v1/users/1/stats", "Student");
|
||||||
yield return E("users/{id}/enrollments [AnyAuth]", "GET", "api/v1/users/1/enrollments", "Student");
|
yield return E("users/{id}/enrollments [AnyAuth]", "GET", "api/v1/users/1/enrollments", "Student");
|
||||||
yield return E("users/{id}/reviews [AnyAuth]", "GET", "api/v1/users/1/reviews","Student");
|
|
||||||
yield return E("users/{id}/achievements [AnyAuth]","GET", "api/v1/users/1/achievements","Student");
|
yield return E("users/{id}/achievements [AnyAuth]","GET", "api/v1/users/1/achievements","Student");
|
||||||
yield return E("users/{id}/transactions [AnyAuth/self]","GET","api/v1/users/1/transactions","Student");
|
yield return E("users/{id}/transactions [AnyAuth/self]","GET","api/v1/users/1/transactions","Student");
|
||||||
|
|
||||||
|
// ── Users — Admin OR Teacher ─────────────────────────────────────────
|
||||||
|
yield return E("users/{id}/reviews [Admin]", "GET", "api/v1/users/1/reviews","Admin", forbidden: ["Student"]);
|
||||||
|
yield return E("users/{id}/reviews [Teacher]", "GET", "api/v1/users/1/reviews","Teacher", forbidden: ["Student"]);
|
||||||
|
|
||||||
// ── Users — Admin only ────────────────────────────────────────────────
|
// ── Users — Admin only ────────────────────────────────────────────────
|
||||||
yield return E("users GET [Admin]", "GET", "api/v1/users", "Admin", forbidden: ["Student", "Teacher"]);
|
yield return E("users GET [Admin]", "GET", "api/v1/users", "Admin", forbidden: ["Student", "Teacher"]);
|
||||||
yield return E("users/{id}/role PATCH [Admin]", "PATCH", "api/v1/users/1/role", "Admin", forbidden: ["Student", "Teacher"],
|
yield return E("users/{id}/role PATCH [Admin]", "PATCH", "api/v1/users/1/role", "Admin", forbidden: ["Student", "Teacher"],
|
||||||
@@ -75,7 +78,6 @@ public class EndpointAuthorizationTests : IClassFixture<ApiWebApplicationFactory
|
|||||||
// ── Lectures — any auth ───────────────────────────────────────────────
|
// ── Lectures — any auth ───────────────────────────────────────────────
|
||||||
yield return E("lectures GET [AnyAuth]", "GET", "api/v1/lectures", "Student");
|
yield return E("lectures GET [AnyAuth]", "GET", "api/v1/lectures", "Student");
|
||||||
yield return E("lectures/{id} GET [AnyAuth]", "GET", "api/v1/lectures/1", "Student");
|
yield return E("lectures/{id} GET [AnyAuth]", "GET", "api/v1/lectures/1", "Student");
|
||||||
yield return E("lectures/{id}/reviews GET [AnyAuth]","GET", "api/v1/lectures/1/reviews","Student");
|
|
||||||
|
|
||||||
// ── Lectures — Admin only ─────────────────────────────────────────────
|
// ── Lectures — Admin only ─────────────────────────────────────────────
|
||||||
yield return E("lectures POST [Admin]", "POST", "api/v1/lectures", "Admin", forbidden: ["Student", "Teacher"],
|
yield return E("lectures POST [Admin]", "POST", "api/v1/lectures", "Admin", forbidden: ["Student", "Teacher"],
|
||||||
@@ -93,17 +95,22 @@ public class EndpointAuthorizationTests : IClassFixture<ApiWebApplicationFactory
|
|||||||
body: "true");
|
body: "true");
|
||||||
yield return E("lectures/{id}/enrollments GET [Admin]", "GET","api/v1/lectures/1/enrollments","Admin", forbidden: ["Student"]);
|
yield return E("lectures/{id}/enrollments GET [Admin]", "GET","api/v1/lectures/1/enrollments","Admin", forbidden: ["Student"]);
|
||||||
yield return E("lectures/{id}/enrollments GET [Teacher]","GET","api/v1/lectures/1/enrollments","Teacher",forbidden: ["Student"]);
|
yield return E("lectures/{id}/enrollments GET [Teacher]","GET","api/v1/lectures/1/enrollments","Teacher",forbidden: ["Student"]);
|
||||||
|
yield return E("lectures/{id}/reviews GET [Admin]", "GET","api/v1/lectures/1/reviews","Admin", forbidden: ["Student"]);
|
||||||
|
yield return E("lectures/{id}/reviews GET [Teacher]", "GET","api/v1/lectures/1/reviews","Teacher",forbidden: ["Student"]);
|
||||||
|
|
||||||
// ── Lectures — Student only ───────────────────────────────────────────
|
// ── Lectures — Student only ───────────────────────────────────────────
|
||||||
yield return E("lectures/{id}/enroll POST [Student]", "POST", "api/v1/lectures/1/enroll", "Student", forbidden: ["Admin", "Teacher"]);
|
yield return E("lectures/{id}/enroll POST [Student]", "POST", "api/v1/lectures/1/enroll", "Student", forbidden: ["Admin", "Teacher"]);
|
||||||
yield return E("lectures/{id}/enroll DELETE [Student]", "DELETE","api/v1/lectures/1/enroll", "Student", forbidden: ["Admin", "Teacher"]);
|
yield return E("lectures/{id}/enroll DELETE [Student]", "DELETE","api/v1/lectures/1/enroll", "Student", forbidden: ["Admin", "Teacher"]);
|
||||||
|
|
||||||
// ── Reviews — any auth ────────────────────────────────────────────────
|
// ── Reviews — any auth ────────────────────────────────────────────────
|
||||||
yield return E("reviews/{id} GET [AnyAuth]", "GET", "api/v1/reviews/1", "Student");
|
|
||||||
yield return E("reviews/{id} PUT [AnyAuth]", "PUT", "api/v1/reviews/1", "Student",
|
yield return E("reviews/{id} PUT [AnyAuth]", "PUT", "api/v1/reviews/1", "Student",
|
||||||
body: """{"rating":"Like","text":"Updated"}""");
|
body: """{"rating":"Like","text":"Updated"}""");
|
||||||
yield return E("reviews/{id} DELETE [AnyAuth]", "DELETE", "api/v1/reviews/1", "Student");
|
yield return E("reviews/{id} DELETE [AnyAuth]", "DELETE", "api/v1/reviews/1", "Student");
|
||||||
|
|
||||||
|
// ── Reviews — Admin OR Teacher ───────────────────────────────────────
|
||||||
|
yield return E("reviews/{id} GET [Admin]", "GET", "api/v1/reviews/1", "Admin", forbidden: ["Student"]);
|
||||||
|
yield return E("reviews/{id} GET [Teacher]", "GET", "api/v1/reviews/1", "Teacher", forbidden: ["Student"]);
|
||||||
|
|
||||||
// ── Reviews — Student only ────────────────────────────────────────────
|
// ── Reviews — Student only ────────────────────────────────────────────
|
||||||
yield return E("reviews POST [Student]", "POST", "api/v1/reviews", "Student", forbidden: ["Admin", "Teacher"],
|
yield return E("reviews POST [Student]", "POST", "api/v1/reviews", "Student", forbidden: ["Admin", "Teacher"],
|
||||||
body: """{"lectureId":1,"rating":"Like","text":"Great lecture!"}""");
|
body: """{"lectureId":1,"rating":"Like","text":"Great lecture!"}""");
|
||||||
|
|||||||
@@ -189,14 +189,18 @@ public class LecturesController : ControllerBase
|
|||||||
Ok(await _lectures.GetEnrollmentsAsync(id, pagination));
|
Ok(await _lectures.GetEnrollmentsAsync(id, pagination));
|
||||||
|
|
||||||
/// <summary>Получить отзывы к лекции.</summary>
|
/// <summary>Получить отзывы к лекции.</summary>
|
||||||
|
/// <remarks>Только Admin или Teacher.</remarks>
|
||||||
/// <param name="id">ID лекции.</param>
|
/// <param name="id">ID лекции.</param>
|
||||||
/// <param name="pagination">Параметры пагинации.</param>
|
/// <param name="pagination">Параметры пагинации.</param>
|
||||||
/// <response code="200">Список отзывов (пагинированный).</response>
|
/// <response code="200">Список отзывов (пагинированный).</response>
|
||||||
/// <response code="401">Требуется аутентификация.</response>
|
/// <response code="401">Требуется аутентификация.</response>
|
||||||
|
/// <response code="403">Требуется роль Admin или Teacher.</response>
|
||||||
/// <response code="404">Лекция не найдена.</response>
|
/// <response code="404">Лекция не найдена.</response>
|
||||||
|
[Authorize(Roles = "Admin,Teacher")]
|
||||||
[HttpGet("{id:int}/reviews")]
|
[HttpGet("{id:int}/reviews")]
|
||||||
[ProducesResponseType(typeof(PagedResult<UniVerse.Application.DTOs.Reviews.ReviewDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(PagedResult<UniVerse.Application.DTOs.Reviews.ReviewDto>), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult> Reviews(int id, [FromQuery] PaginationRequest pagination) =>
|
public async Task<ActionResult> Reviews(int id, [FromQuery] PaginationRequest pagination) =>
|
||||||
Ok(await _reviews.GetByLectureAsync(id, pagination));
|
Ok(await _reviews.GetByLectureAsync(id, pagination));
|
||||||
|
|||||||
@@ -44,13 +44,17 @@ public class ReviewsController : ControllerBase
|
|||||||
CreatedAtAction(nameof(Get), new { id = 0 }, await _reviews.CreateAsync(CurrentUserId, req));
|
CreatedAtAction(nameof(Get), new { id = 0 }, await _reviews.CreateAsync(CurrentUserId, req));
|
||||||
|
|
||||||
/// <summary>Получить отзыв по ID.</summary>
|
/// <summary>Получить отзыв по ID.</summary>
|
||||||
|
/// <remarks>Только Admin или Teacher.</remarks>
|
||||||
/// <param name="id">ID отзыва.</param>
|
/// <param name="id">ID отзыва.</param>
|
||||||
/// <response code="200">Данные отзыва (включая LLM-статус и сентимент).</response>
|
/// <response code="200">Данные отзыва (включая LLM-статус и сентимент).</response>
|
||||||
/// <response code="401">Требуется аутентификация.</response>
|
/// <response code="401">Требуется аутентификация.</response>
|
||||||
|
/// <response code="403">Требуется роль Admin или Teacher.</response>
|
||||||
/// <response code="404">Отзыв не найден.</response>
|
/// <response code="404">Отзыв не найден.</response>
|
||||||
|
[Authorize(Roles = "Admin,Teacher")]
|
||||||
[HttpGet("{id:int}")]
|
[HttpGet("{id:int}")]
|
||||||
[ProducesResponseType(typeof(ReviewDto), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(ReviewDto), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||||
public async Task<ActionResult<ReviewDto>> Get(int id) => Ok(await _reviews.GetByIdAsync(id));
|
public async Task<ActionResult<ReviewDto>> Get(int id) => Ok(await _reviews.GetByIdAsync(id));
|
||||||
|
|
||||||
|
|||||||
@@ -86,13 +86,17 @@ public class UsersController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Получить отзывы пользователя.</summary>
|
/// <summary>Получить отзывы пользователя.</summary>
|
||||||
|
/// <remarks>Только Admin или Teacher.</remarks>
|
||||||
/// <param name="id">ID пользователя.</param>
|
/// <param name="id">ID пользователя.</param>
|
||||||
/// <param name="pagination">Параметры пагинации.</param>
|
/// <param name="pagination">Параметры пагинации.</param>
|
||||||
/// <response code="200">Список отзывов (пагинированный).</response>
|
/// <response code="200">Список отзывов (пагинированный).</response>
|
||||||
/// <response code="401">Требуется аутентификация.</response>
|
/// <response code="401">Требуется аутентификация.</response>
|
||||||
|
/// <response code="403">Требуется роль Admin или Teacher.</response>
|
||||||
|
[Authorize(Roles = "Admin,Teacher")]
|
||||||
[HttpGet("{id:int}/reviews")]
|
[HttpGet("{id:int}/reviews")]
|
||||||
[ProducesResponseType(typeof(PagedResult<UniVerse.Application.DTOs.Reviews.ReviewDto>), StatusCodes.Status200OK)]
|
[ProducesResponseType(typeof(PagedResult<UniVerse.Application.DTOs.Reviews.ReviewDto>), StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||||
|
[ProducesResponseType(StatusCodes.Status403Forbidden)]
|
||||||
public async Task<ActionResult> Reviews(int id, [FromQuery] PaginationRequest pagination) =>
|
public async Task<ActionResult> Reviews(int id, [FromQuery] PaginationRequest pagination) =>
|
||||||
Ok(await _reviews.GetByUserAsync(id, pagination));
|
Ok(await _reviews.GetByUserAsync(id, pagination));
|
||||||
|
|
||||||
|
|||||||
@@ -1827,7 +1827,7 @@
|
|||||||
"Lectures"
|
"Lectures"
|
||||||
],
|
],
|
||||||
"summary": "Получить отзывы к лекции.",
|
"summary": "Получить отзывы к лекции.",
|
||||||
"description": "**Required:** any authenticated user",
|
"description": "Только Admin или Teacher.\n\n**Required roles:** Admin, Teacher",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -1877,6 +1877,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Требуется роль Admin или Teacher.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Лекция не найдена.",
|
"description": "Лекция не найдена.",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -2501,7 +2511,7 @@
|
|||||||
"Reviews"
|
"Reviews"
|
||||||
],
|
],
|
||||||
"summary": "Получить отзыв по ID.",
|
"summary": "Получить отзыв по ID.",
|
||||||
"description": "**Required:** any authenticated user",
|
"description": "Только Admin или Teacher.\n\n**Required roles:** Admin, Teacher",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -2535,6 +2545,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Требуется роль Admin или Teacher.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"description": "Отзыв не найден.",
|
"description": "Отзыв не найден.",
|
||||||
"content": {
|
"content": {
|
||||||
@@ -3679,7 +3699,7 @@
|
|||||||
"Users"
|
"Users"
|
||||||
],
|
],
|
||||||
"summary": "Получить отзывы пользователя.",
|
"summary": "Получить отзывы пользователя.",
|
||||||
"description": "**Required:** any authenticated user",
|
"description": "Только Admin или Teacher.\n\n**Required roles:** Admin, Teacher",
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "id",
|
||||||
@@ -3728,6 +3748,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "Требуется роль Admin или Teacher.",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/ProblemDetails"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": [
|
"security": [
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import AppIcon from '@/components/ui/AppIcon.vue'
|
|||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
lecture: Lecture
|
lecture: Lecture
|
||||||
registered?: boolean
|
registered?: boolean
|
||||||
|
showRating?: boolean
|
||||||
}>()
|
}>()
|
||||||
const emit = defineEmits<{ register: [id: string] }>()
|
const emit = defineEmits<{ register: [id: string] }>()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -78,7 +79,7 @@ function goDetail() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div class="rating">
|
<div v-if="showRating !== false" class="rating">
|
||||||
<span class="stars">{{ starsHtml(lecture.rating) }}</span>
|
<span class="stars">{{ starsHtml(lecture.rating) }}</span>
|
||||||
<span class="rating-value">{{ lecture.rating }}</span>
|
<span class="rating-value">{{ lecture.rating }}</span>
|
||||||
<span class="text-secondary text-sm">({{ lecture.reviewCount }})</span>
|
<span class="text-secondary text-sm">({{ lecture.reviewCount }})</span>
|
||||||
@@ -178,6 +179,9 @@ function goDetail() {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
.card-footer button {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
.rating {
|
.rating {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ async function registerLecture(id: string) {
|
|||||||
:key="l.id"
|
:key="l.id"
|
||||||
:lecture="l"
|
:lecture="l"
|
||||||
:registered="lecturesStore.registeredIds.includes(l.id)"
|
:registered="lecturesStore.registeredIds.includes(l.id)"
|
||||||
|
:show-rating="false"
|
||||||
@register="registerLecture"
|
@register="registerLecture"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user