fix: лекции когда возвращались не говорили записан ли студент уже или нет

This commit is contained in:
2026-05-13 20:01:43 +03:00
parent 65e3d1bf18
commit d29b52f824
10 changed files with 79 additions and 13 deletions
@@ -177,7 +177,7 @@ public class ApiWebApplicationFactory : WebApplicationFactory<Program>
var pagedLectures = PagedResult<LectureDto>.Create([lectureDto], 1, 1, 20);
var pagedEnrollments = PagedResult<EnrollmentDto>.Create([], 0, 1, 20);
stub.GetAllAsync(Arg.Any<LectureFilterRequest>()).Returns(pagedLectures);
stub.GetAllAsync(Arg.Any<LectureFilterRequest>(), Arg.Any<int?>()).Returns(pagedLectures);
stub.GetByIdAsync(Arg.Any<int>(), Arg.Any<int?>()).Returns(detailDto);
stub.CreateAsync(Arg.Any<CreateLectureRequest>()).Returns(lectureDto);
stub.UpdateAsync(Arg.Any<int>(), Arg.Any<UpdateLectureRequest>()).Returns(lectureDto);
@@ -0,0 +1,53 @@
using Microsoft.EntityFrameworkCore;
using NSubstitute;
using UniVerse.Application.DTOs.Lectures;
using UniVerse.Application.Interfaces;
using UniVerse.Domain.Entities;
using UniVerse.Infrastructure.Data;
using UniVerse.Infrastructure.Services;
using Xunit;
namespace UniVerse.Api.Tests.Lectures;
public class LectureServiceTests
{
[Fact]
public async Task GetAllAsync_MarksLecturesEnrolledByCurrentUser()
{
await using var db = CreateDbContext();
var service = new LectureService(db, Substitute.For<IGamificationService>());
var startsAt = DateTime.UtcNow.AddDays(1);
db.Users.Add(new User { Id = 1, Email = "student@test.local" });
db.Courses.Add(new Course { Id = 1, Name = "Course" });
db.Lectures.AddRange(
Lecture(1, startsAt),
Lecture(2, startsAt.AddDays(1)));
db.LectureEnrollments.Add(new LectureEnrollment { LectureId = 1, UserId = 1 });
await db.SaveChangesAsync();
var result = await service.GetAllAsync(new LectureFilterRequest(null, null, null, null, null, null, null, null), 1);
Assert.True(result.Items.Single(item => item.Id == 1).IsEnrolled);
Assert.False(result.Items.Single(item => item.Id == 2).IsEnrolled);
}
private static AppDbContext CreateDbContext()
{
var options = new DbContextOptionsBuilder<AppDbContext>()
.UseInMemoryDatabase($"LectureServiceTests_{Guid.NewGuid()}")
.Options;
return new AppDbContext(options);
}
private static Lecture Lecture(int id, DateTime startsAt) => new()
{
Id = id,
CourseId = 1,
Title = $"Lecture {id}",
StartsAt = startsAt,
EndsAt = startsAt.AddHours(2),
IsOpen = true,
MaxEnrollments = 30
};
}
@@ -31,13 +31,14 @@ public class LecturesController : ControllerBase
/// Фильтры: dateFrom, dateTo, courseId, teacherId, format (Online/Offline),
/// isOpen, tagId, search; параметры пагинации.
/// </param>
/// <remarks>Включает флаг `isEnrolled` — записан ли текущий пользователь на лекцию.</remarks>
/// <response code="200">Список лекций (пагинированный).</response>
/// <response code="401">Требуется аутентификация.</response>
[HttpGet]
[ProducesResponseType(typeof(PagedResult<LectureDto>), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task<ActionResult> GetAll([FromQuery] LectureFilterRequest filter) =>
Ok(await _lectures.GetAllAsync(filter));
Ok(await _lectures.GetAllAsync(filter, CurrentUserId));
/// <summary>Получить детальную карточку лекции по ID.</summary>
/// <remarks>
+4 -1
View File
@@ -1140,7 +1140,7 @@
"Lectures"
],
"summary": "Получить каталог лекций с фильтрацией и пагинацией.",
"description": "**Required:** any authenticated user",
"description": "Включает флаг `isEnrolled` — записан ли текущий пользователь на лекцию.\n\n**Required:** any authenticated user",
"parameters": [
{
"name": "DateFrom",
@@ -4745,6 +4745,9 @@
"createdAt": {
"type": "string",
"format": "date-time"
},
"isEnrolled": {
"type": "boolean"
}
},
"additionalProperties": false
@@ -19,7 +19,8 @@ public record LectureDto(
int MaxEnrollments,
int EnrollmentsCount,
string? OnlineUrl,
DateTime CreatedAt
DateTime CreatedAt,
bool IsEnrolled = false
);
public record LectureDetailDto(
@@ -5,7 +5,7 @@ namespace UniVerse.Application.Interfaces;
public interface ILectureService
{
Task<PagedResult<LectureDto>> GetAllAsync(LectureFilterRequest filter);
Task<PagedResult<LectureDto>> GetAllAsync(LectureFilterRequest filter, int? currentUserId = null);
Task<LectureDetailDto> GetByIdAsync(int id, int? currentUserId = null);
Task<LectureDto> CreateAsync(CreateLectureRequest request);
Task<LectureDto> UpdateAsync(int id, UpdateLectureRequest request);
@@ -46,14 +46,14 @@ public static class MappingExtensions
);
// --- Lecture ---
public static LectureDto ToDto(this Lecture lecture) => new(
public static LectureDto ToDto(this Lecture lecture, bool isEnrolled = false) => new(
lecture.Id, lecture.CourseId, lecture.Course?.Name ?? "",
lecture.TeacherId, lecture.Teacher?.DisplayName,
lecture.LocationId, lecture.Location?.Name,
lecture.Title, lecture.Description, lecture.Format,
lecture.StartsAt, lecture.EndsAt, lecture.IsOpen,
lecture.MaxEnrollments, lecture.Enrollments.Count,
lecture.OnlineUrl, lecture.CreatedAt
lecture.OnlineUrl, lecture.CreatedAt, isEnrolled
);
public static LectureDetailDto ToDetailDto(this Lecture lecture, bool isEnrolled) => new(
@@ -24,7 +24,7 @@ public class LectureService : ILectureService
.Include(l => l.Course).Include(l => l.Teacher)
.Include(l => l.Location).Include(l => l.Enrollments);
public async Task<PagedResult<LectureDto>> GetAllAsync(LectureFilterRequest filter)
public async Task<PagedResult<LectureDto>> GetAllAsync(LectureFilterRequest filter, int? currentUserId = null)
{
var query = BaseQuery();
if (filter.CourseId.HasValue) query = query.Where(l => l.CourseId == filter.CourseId);
@@ -43,7 +43,11 @@ public class LectureService : ILectureService
var total = await query.CountAsync();
var items = await query.OrderBy(l => l.StartsAt)
.Skip((filter.Page - 1) * filter.PageSize).Take(filter.PageSize).ToListAsync();
return PagedResult<LectureDto>.Create(items.Select(l => l.ToDto()).ToList(), total, filter.Page, filter.PageSize);
return PagedResult<LectureDto>.Create(
items.Select(l => l.ToDto(currentUserId.HasValue && l.Enrollments.Any(e => e.UserId == currentUserId.Value))).ToList(),
total,
filter.Page,
filter.PageSize);
}
public async Task<LectureDetailDto> GetByIdAsync(int id, int? currentUserId = null)
+1 -1
View File
@@ -94,7 +94,7 @@ export const useLecturesStore = defineStore('lectures', () => {
}
function isRegistered(lectureId: string) {
return registered.value.includes(lectureId)
return registered.value.includes(lectureId) || Boolean(lectures.value.find(item => item.id === lectureId)?.registered)
}
return {
+7 -3
View File
@@ -109,6 +109,10 @@ async function registerLecture(id: string) {
addToast?.(err instanceof Error ? err.message : 'Не удалось записаться на лекцию.', 'error')
}
}
function isRegistered(id: string) {
return lecturesStore.isRegistered(id)
}
</script>
<template>
@@ -204,7 +208,7 @@ async function registerLecture(id: string) {
v-for="l in filtered"
:key="l.id"
:lecture="l"
:registered="lecturesStore.registeredIds.includes(l.id)"
:registered="isRegistered(l.id)"
:show-rating="false"
@register="registerLecture"
/>
@@ -231,10 +235,10 @@ async function registerLecture(id: string) {
<template #action="{ row }">
<button
class="btn-primary btn-sm"
:disabled="row.freeSeats === 0 || row.registrationClosed || lecturesStore.registeredIds.includes(row.id)"
:disabled="row.freeSeats === 0 || row.registrationClosed || isRegistered(row.id)"
@click="registerLecture(row.id)"
>
{{ lecturesStore.registeredIds.includes(row.id) ? 'Записан' : 'Записаться' }}
{{ isRegistered(row.id) ? 'Записан' : 'Записаться' }}
</button>
</template>
</DataTable>