feat: мультироль
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Successful in 9s
🚀 Create and publish a Docker image / Build & publish backend image (push) Successful in 2m6s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Successful in 26s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Successful in 6s

This commit is contained in:
2026-05-11 21:29:16 +03:00
parent 3b0bbfc858
commit 6824d7ce7d
29 changed files with 1350 additions and 95 deletions
@@ -22,14 +22,18 @@ public class UserService : IUserService
public async Task<UserDto> GetByIdAsync(int id)
{
var user = await _db.Users.FindAsync(id)
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
return user.ToDto(_gamification.CalculateLevel(user.Xp));
}
public async Task<UserDto> UpdateProfileAsync(int id, UpdateUserRequest request)
{
var user = await _db.Users.FindAsync(id)
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
if (request.DisplayName != null) user.DisplayName = request.DisplayName;
@@ -68,8 +72,15 @@ public class UserService : IUserService
(u.DisplayName != null && u.DisplayName.ToLower().Contains(search)));
}
query = query.Include(u => u.Roles);
if (filter.Role.HasValue)
query = query.Where(u => u.Role == filter.Role.Value);
{
var role = filter.Role.Value;
query = query.Where(u =>
u.Roles.Count == 1 &&
u.Roles.Any(ur => ur.Role == role));
}
if (filter.IsActive.HasValue)
query = query.Where(u => u.IsActive == filter.IsActive.Value);
@@ -86,11 +97,27 @@ public class UserService : IUserService
return PagedResult<UserDto>.Create(items, total, filter.Page, filter.PageSize);
}
public async Task SetRoleAsync(int id, UserRole role)
public async Task SetRolesAsync(int id, IReadOnlyCollection<UserRole> roles)
{
var user = await _db.Users.FindAsync(id)
var normalizedRoles = roles.Distinct().ToList();
if (normalizedRoles.Count == 0)
throw new ForbiddenException("At least one role is required.");
var user = await _db.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == id)
?? throw new NotFoundException("User", id);
user.Role = role;
var existing = user.Roles.Select(r => r.Role).ToHashSet();
var toRemove = user.Roles.Where(r => !normalizedRoles.Contains(r.Role)).ToList();
foreach (var item in toRemove)
user.Roles.Remove(item);
var toAdd = normalizedRoles.Where(r => !existing.Contains(r)).ToList();
foreach (var role in toAdd)
user.Roles.Add(new Domain.Entities.UserRoleAssignment { UserId = user.Id, Role = role });
await EnsureProfilesForRolesAsync(user.Id, normalizedRoles);
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
@@ -103,4 +130,21 @@ public class UserService : IUserService
user.UpdatedAt = DateTime.UtcNow;
await _db.SaveChangesAsync();
}
private async Task EnsureProfilesForRolesAsync(int userId, IReadOnlyCollection<UserRole> roles)
{
if (roles.Contains(UserRole.Student))
{
var hasStudentProfile = await _db.StudentProfiles.AnyAsync(p => p.UserId == userId);
if (!hasStudentProfile)
_db.StudentProfiles.Add(new Domain.Entities.StudentProfile { UserId = userId });
}
if (roles.Contains(UserRole.Teacher))
{
var hasTeacherProfile = await _db.TeacherProfiles.AnyAsync(p => p.UserId == userId);
if (!hasTeacherProfile)
_db.TeacherProfiles.Add(new Domain.Entities.TeacherProfile { UserId = userId });
}
}
}