Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3b51ebe
feat: add tab for course groups
semrosin Mar 2, 2026
15a5618
feat: add groups with names get method
semrosin Mar 4, 2026
8cb61aa
feat: add course groups api to apisingleton
semrosin Mar 4, 2026
446ed7a
feat: add course group tab prototype
semrosin Mar 4, 2026
d54c045
fix: groups ui
semrosin Mar 6, 2026
3e98254
feat: add group choose for homework
semrosin Mar 9, 2026
cb7da7a
refactor: separate group selector
semrosin Mar 10, 2026
e02de09
feat: add homework update group validation
semrosin Mar 13, 2026
707aaf3
feat: add apply filter subtractively method
semrosin Mar 13, 2026
623c274
feat: add global filter subtraction applying
semrosin Mar 13, 2026
1d255ab
feat: add filter updating for group homeworks method
semrosin Mar 13, 2026
7850406
feat: add updating filters for group homeworks
semrosin Mar 13, 2026
501c002
fix: student filters applying
semrosin Mar 22, 2026
85acdbf
fix: deleting homework
semrosin Mar 23, 2026
2a99494
feat: show non included in groups course students count
semrosin Mar 24, 2026
ae06821
refactor: union apply filter methods by creating apply filter type
semrosin Mar 26, 2026
cde8d1a
fix: parallel dbcontext access problem
semrosin Mar 27, 2026
c2b9a4b
feat: mark grouped students
semrosin Mar 27, 2026
19257f2
fix: add automap for groups
semrosin Mar 29, 2026
b6e2df0
fix: parallel access error in groups service
semrosin Mar 29, 2026
8d43277
fix: parallel access error in course filter service
semrosin Mar 29, 2026
adc7da6
fix: add hw to group mates filters on update
semrosin Mar 29, 2026
644ca77
feat: move creation groups to homework
semrosin Mar 29, 2026
be83d1a
feat: remove creation groups from groups tab
semrosin Mar 29, 2026
7d33478
fix: forbid remove student after group creation
semrosin Mar 30, 2026
3e07775
fix: only mentor can see all group homeworks
semrosin Mar 31, 2026
ebf7ba2
fix: group homework cell in stats
semrosin Apr 4, 2026
a815e53
fix: max homeworks rate counting
semrosin Apr 4, 2026
6a7f382
fix: sort homeworks after apply filter
semrosin Apr 4, 2026
03aa2a6
refactor: optimize sequantial db access for groups
semrosin Apr 4, 2026
33cf41f
feat: get many homeworks method
semrosin Apr 4, 2026
075adea
refactor: use many homeworks get method in filter service
semrosin Apr 4, 2026
9769fa9
fix: can choose only students without group
semrosin Apr 5, 2026
dc72916
refactor: students without group filter
semrosin Apr 5, 2026
1b47e5d
refactor: styles calculation
semrosin Apr 5, 2026
3a42690
feat: show max homework sum
semrosin Apr 5, 2026
686f6f6
fix: gray cell instead of red cross
semrosin Apr 5, 2026
f2dcc7d
refactor: remove excess api calls (groups in stats)
semrosin Apr 9, 2026
5377dc2
refactor: remove excess api calls (group selector)
semrosin Apr 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using HwProj.CoursesService.Client;
using HwProj.Models.CoursesService.ViewModels;
using HwProj.Models.CoursesService.DTO;
using HwProj.Models.Roles;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
Expand Down Expand Up @@ -30,6 +31,16 @@ public async Task<IActionResult> GetAllCourseGroups(long courseId)
: Ok(result);
}

[HttpGet("{courseId}/getAllWithNames")]
[ProducesResponseType(typeof(GroupWithNameDTO[]), (int)HttpStatusCode.OK)]
public async Task<IActionResult> GetAllCourseGroupsWithNames(long courseId)
{
var result = await _coursesClient.GetAllCourseGroupsWithNames(courseId);
return result == null
? NotFound()
: Ok(result);
}

[HttpPost("{courseId}/create")]
[Authorize(Roles = Roles.LecturerRole)]
[ProducesResponseType(typeof(long), (int)HttpStatusCode.OK)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace HwProj.Models.CoursesService.DTO
{
public class GroupWithNameDTO
{
public long Id { get; set; }
public string Name { get; set; }
public string[] StudentsIds { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ public class CreateHomeworkViewModel
public List<PostTaskViewModel> Tasks { get; set; } = new List<PostTaskViewModel>();

public ActionOptions? ActionOptions { get; set; }

public long? GroupId { get; set; }
}

public class HomeworkViewModel
Expand Down Expand Up @@ -58,5 +60,7 @@ public class HomeworkViewModel
public List<string> Tags { get; set; } = new List<string>();

public List<HomeworkTaskViewModel> Tasks { get; set; } = new List<HomeworkTaskViewModel>();

public long? GroupId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public AutomapperProfile()

CreateMap<CreateCourseFilterDTO, CreateCourseFilterModel>();
CreateMap<Filter, CourseFilterDTO>();

CreateMap<UpdateGroupViewModel, Group>().ReverseMap();

CreateMap<GroupMateViewModel, GroupMate>().ReverseMap();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Linq;
using System;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using HwProj.CoursesService.API.Filters;
using HwProj.CoursesService.API.Models;
using HwProj.CoursesService.API.Services;
using HwProj.Models.CoursesService.DTO;
using HwProj.Models.CoursesService.ViewModels;
using Microsoft.AspNetCore.Mvc;

Expand Down Expand Up @@ -36,6 +38,19 @@ public async Task<GroupViewModel[]> GetAll(long courseId)
return result;
}

[HttpGet("{courseId}/getAllWithNames")]
public async Task<GroupWithNameDTO[]> GetAllWithNames(long courseId)
{
var groups = await _groupsService.GetAllAsync(courseId);
var result = groups.Select(t => new GroupWithNameDTO
{
Id = t.Id,
Name = t.Name,
StudentsIds = t.GroupMates.Select(s => s.StudentId).ToArray()
}).ToArray();
return result;
}

[HttpPost("{courseId}/create")]
public async Task<IActionResult> CreateGroup([FromBody] CreateGroupViewModel groupViewModel)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public static HomeworkViewModel ToHomeworkViewModel(this Homework homework)
IsDeferred = DateTime.UtcNow < homework.PublicationDate,
Tasks = homework.Tasks.Select(t => t.ToHomeworkTaskViewModel()).ToList(),
Tags = tags.ToList(),
GroupId = homework.GroupId,
};
}

Expand Down Expand Up @@ -147,6 +148,7 @@ public static Homework ToHomework(this CreateHomeworkViewModel homework)
PublicationDate = homework.PublicationDate,
Tasks = homework.Tasks.Select(t => t.ToHomeworkTask()).ToList(),
Tags = string.Join(";", homework.Tags),
GroupId = homework.GroupId,
};

public static CourseTemplate ToCourseTemplate(this CreateCourseViewModel createCourseViewModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ public static List<string> ValidateHomework(CreateHomeworkViewModel homework, Ho
errors.Add("Нельзя изменить дату публикации домашнего задания, если она уже показана студента");
}

if (previousState.GroupId != homework.GroupId)
{
errors.Add("Нельзя изменить группу для домашнего задания, если оно уже опубликовано");
}

return errors;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ public class Homework : IEntity<long>
public long CourseId { get; set; }

public List<HomeworkTask> Tasks { get; set; }

public long? GroupId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public async Task<Homework[]> GetAllWithTasksByCourseAsync(long courseId)
.ToArrayAsync();
}

public async Task<Homework[]> GetWithTasksAsync(long[] homeworkIds, bool withCriteria = false)
{
var query = Context.Set<Homework>().AsNoTracking().Include(h => h.Tasks);
return withCriteria
? await query.ThenInclude(x => x.Criteria).Where(h => homeworkIds.Contains(h.Id)).ToArrayAsync()
: await query.Where(h => homeworkIds.Contains(h.Id)).ToArrayAsync();
}

public async Task<Homework> GetWithTasksAsync(long id, bool withCriteria = false)
{
var query = Context.Set<Homework>().AsNoTracking().Include(h => h.Tasks);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public interface IHomeworksRepository : ICrudRepository<Homework, long>
{
Task<Homework[]> GetAllWithTasksAsync();
Task<Homework[]> GetAllWithTasksByCourseAsync(long courseId);
Task<Homework[]> GetWithTasksAsync(long[] homeworkIds, bool withCriteria = false);
Task<Homework> GetWithTasksAsync(long id, bool withCriteria = false);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,28 @@
using HwProj.Models.CoursesService.DTO;
using HwProj.Models.CoursesService.ViewModels;
using HwProj.Models.Result;
using System.Collections.Generic;
using HwProj.CoursesService.API.Domains;

namespace HwProj.CoursesService.API.Services
{
public enum ApplyFilterType
{
Intersect,
Union,
Subtract
}
public class CourseFilterService : ICourseFilterService
{
private readonly ICourseFilterRepository _courseFilterRepository;
private readonly IHomeworksService _homeworksService;

public CourseFilterService(
ICourseFilterRepository courseFilterRepository)
ICourseFilterRepository courseFilterRepository,
IHomeworksService homeworksService)
{
_courseFilterRepository = courseFilterRepository;
_homeworksService = homeworksService;
}

public async Task<Result<long>> CreateOrUpdateCourseFilter(CreateCourseFilterModel courseFilterModel)
Expand Down Expand Up @@ -59,16 +70,12 @@ public async Task<CourseDTO[]> ApplyFiltersToCourses(string userId, CourseDTO[]
{
var courseIds = courses.Select(c => c.Id).ToArray();

var filters = (await _courseFilterRepository.GetAsync(userId, courseIds))
.ToDictionary(x => x.CourseId, x => x.CourseFilter);

return courses
.Select(course =>
{
filters.TryGetValue(course.Id, out var courseFilter);
return ApplyFilterInternal(course, courseFilter);
})
.ToArray();
var result = new List<CourseDTO>();
foreach (var course in courses)
{
result.Add(await ApplyFilter(course, userId));
}
return result.ToArray();
}

public async Task<CourseDTO> ApplyFilter(CourseDTO courseDto, string userId)
Expand All @@ -83,8 +90,21 @@ public async Task<CourseDTO> ApplyFilter(CourseDTO courseDto, string userId)
(await _courseFilterRepository.GetAsync(findFiltersFor, courseDto.Id))
.ToDictionary(x => x.UserId, x => x.CourseFilter);

if (!isMentor)
{
var studentCourse = courseDto;
var groupFilter = await _courseFilterRepository.GetAsync("", courseDto.Id); // Глобальный фильтр для вычитания групповых домашних заданий
if (groupFilter != null)
{
studentCourse = await ApplyFilterInternal(courseDto, groupFilter, ApplyFilterType.Subtract);
}
return courseFilters.TryGetValue(userId, out var studentFilter)
? await ApplyFilterInternal(studentCourse, studentFilter, ApplyFilterType.Union)
: studentCourse;
}

var course = courseFilters.TryGetValue(userId, out var userFilter)
? ApplyFilterInternal(courseDto, userFilter)
? await ApplyFilterInternal(courseDto, userFilter, ApplyFilterType.Intersect)
: courseDto;
if (isMentor || !isCourseStudent) return course;

Expand Down Expand Up @@ -123,7 +143,7 @@ private async Task<long> AddCourseFilter(Filter filter, long courseId, string us
return courseFilterId;
}

private CourseDTO ApplyFilterInternal(CourseDTO courseDto, CourseFilter? courseFilter)
private async Task<CourseDTO> ApplyFilterInternal(CourseDTO courseDto, CourseFilter? courseFilter, ApplyFilterType filterType)
{
var filter = courseFilter?.Filter;

Expand All @@ -132,6 +152,29 @@ private CourseDTO ApplyFilterInternal(CourseDTO courseDto, CourseFilter? courseF
return courseDto;
}

var homeworks = filter.HomeworkIds.Any()
? filterType switch
{
ApplyFilterType.Intersect => courseDto.Homeworks
.Where(hw => filter.HomeworkIds.Contains(hw.Id))
.ToArray(),

ApplyFilterType.Subtract => courseDto.Homeworks
.Where(hw => !filter.HomeworkIds.Contains(hw.Id))
.ToArray(),

ApplyFilterType.Union => courseDto.Homeworks
.Concat((await _homeworksService.GetHomeworksAsync(filter.HomeworkIds.ToArray()))
.Where(hw => hw != null)
.Select(hw => hw.ToHomeworkViewModel()))
.GroupBy(hw => hw.Id)
.Select(g => g.First())
.ToArray(),

_ => courseDto.Homeworks
}
: courseDto.Homeworks;

return new CourseDTO
{
Id = courseDto.Id,
Expand Down Expand Up @@ -164,10 +207,7 @@ private CourseDTO ApplyFilterInternal(CourseDTO courseDto, CourseFilter? courseF
? courseDto.CourseMates
.Where(mate => !mate.IsAccepted || filter.StudentIds.Contains(mate.StudentId)).ToArray()
: courseDto.CourseMates,
Homeworks =
filter.HomeworkIds.Any()
? courseDto.Homeworks.Where(hw => filter.HomeworkIds.Contains(hw.Id)).ToArray()
: courseDto.Homeworks
Homeworks = homeworks.OrderBy(hw => hw.PublicationDate).ToArray()
};
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using AutoMapper;
using HwProj.CoursesService.API.Models;
Expand Down Expand Up @@ -28,7 +30,10 @@ public GroupsService(IGroupsRepository groupsRepository,

public async Task<Group[]> GetAllAsync(long courseId)
{
return await _groupsRepository.GetGroupsWithGroupMatesByCourse(courseId).ToArrayAsync().ConfigureAwait(false);
return await _groupsRepository.GetGroupsWithGroupMatesByCourse(courseId)
.AsNoTracking()
.ToArrayAsync()
.ConfigureAwait(false);
}

public async Task<Group[]> GetGroupsAsync(params long[] groupIds)
Expand Down Expand Up @@ -63,19 +68,33 @@ public async Task DeleteGroupAsync(long groupId)

public async Task UpdateAsync(long groupId, Group updated)
{
var group = await _groupsRepository.GetAsync(groupId);
group.GroupMates.RemoveAll(cm => true);
group.Tasks.RemoveAll(cm => true);
var group = (await _groupsRepository.GetGroupsWithGroupMatesAsync(new[] { groupId }))
.FirstOrDefault() ?? throw new InvalidOperationException($"Group with id {groupId} not found");

updated.GroupMates.ForEach(cm => cm.GroupId = groupId);
updated.Tasks.ForEach(cm => cm.GroupId = groupId);
var mateTasks = updated.GroupMates.Select(cm => _groupMatesRepository.AddAsync(cm));
var idTasks = updated.Tasks.Select(cm => _taskModelsRepository.AddAsync(cm));
foreach (var groupMate in group.GroupMates.ToList())
{
await _groupMatesRepository.DeleteAsync(groupMate.Id);
}

foreach (var task in group.Tasks.ToList())
{
await _taskModelsRepository.DeleteAsync(task.Id);
}

updated.GroupMates?.ForEach(cm => cm.GroupId = groupId);
updated.Tasks?.ForEach(cm => cm.GroupId = groupId);

group.Name = updated.Name;

await Task.WhenAll(mateTasks);
await Task.WhenAll(idTasks);
if (updated.GroupMates != null && updated.GroupMates.Count > 0)
{
await _groupMatesRepository.AddRangeAsync(updated.GroupMates).ConfigureAwait(false);
}

if (updated.Tasks != null && updated.Tasks.Count > 0)
{
await _taskModelsRepository.AddRangeAsync(updated.Tasks).ConfigureAwait(false);
}
}

public async Task<bool> DeleteGroupMateAsync(long groupId, string studentId)
Expand Down Expand Up @@ -107,13 +126,14 @@ public async Task<UserGroupDescription[]> GetStudentGroupsAsync(long courseId, s
.ToArrayAsync()
.ConfigureAwait(false);

var getStudentGroupsTask = studentGroupsIds
.Select(async id => await _groupsRepository.GetAsync(id).ConfigureAwait(false))
.Where(cm => cm.Result.CourseId == courseId)
.ToArray();
var studentGroups = await Task.WhenAll(getStudentGroupsTask).ConfigureAwait(false);
var studentGroups = await _groupsRepository
.GetGroupsWithGroupMatesAsync(studentGroupsIds)
.ConfigureAwait(false);

return studentGroups.Select(c => _mapper.Map<UserGroupDescription>(c)).ToArray();
return studentGroups
.Where(g => g.CourseId == courseId)
.Select(c => _mapper.Map<UserGroupDescription>(c))
.ToArray();
}
}
}
Loading
Loading