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
🚀 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:
@@ -85,20 +85,21 @@ public class AuthService : IAuthService
|
||||
throw new UnauthorizedException("Email не найден в токене Microsoft.");
|
||||
|
||||
// Automatically provision user
|
||||
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email);
|
||||
var user = await _db.Users
|
||||
.Include(u => u.Roles)
|
||||
.FirstOrDefaultAsync(u => u.Email == email);
|
||||
if (user == null)
|
||||
{
|
||||
user = new User
|
||||
{
|
||||
Email = email,
|
||||
DisplayName = name ?? email.Split('@')[0],
|
||||
Role = UserRole.Student, // Default role
|
||||
IsActive = true
|
||||
};
|
||||
_db.Users.Add(user);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
// Create corresponding profile
|
||||
user.Roles.Add(new UserRoleAssignment { UserId = user.Id, Role = UserRole.Student });
|
||||
_db.StudentProfiles.Add(new StudentProfile { UserId = user.Id });
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
@@ -107,6 +108,13 @@ public class AuthService : IAuthService
|
||||
throw new ForbiddenException("Аккаунт деактивирован.");
|
||||
}
|
||||
|
||||
if (user.Roles.Count == 0)
|
||||
{
|
||||
user.Roles.Add(new UserRoleAssignment { UserId = user.Id, Role = UserRole.Student });
|
||||
await EnsureProfilesForRolesAsync(user.Id, [UserRole.Student]);
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
var accessToken = GenerateAccessToken(user);
|
||||
var refreshToken = await GenerateRefreshTokenAsync(user.Id);
|
||||
await TrySendLoginNotificationAsync(user, ipAddress);
|
||||
@@ -121,9 +129,12 @@ public class AuthService : IAuthService
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<AuthResult> DevLoginAsync(string email, string? displayName, UserRole role, string? ipAddress = null)
|
||||
public async Task<AuthResult> DevLoginAsync(string email, string? displayName, IReadOnlyCollection<UserRole> roles, string? ipAddress = null)
|
||||
{
|
||||
var user = await _db.Users.FirstOrDefaultAsync(u => u.Email == email);
|
||||
var normalizedRoles = (roles.Count > 0 ? roles : [UserRole.Student]).Distinct().ToList();
|
||||
var user = await _db.Users
|
||||
.Include(u => u.Roles)
|
||||
.FirstOrDefaultAsync(u => u.Email == email);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
@@ -131,25 +142,23 @@ public class AuthService : IAuthService
|
||||
{
|
||||
Email = email,
|
||||
DisplayName = displayName ?? email.Split('@')[0],
|
||||
Role = role,
|
||||
IsActive = true
|
||||
};
|
||||
_db.Users.Add(user);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
// Create profile based on role
|
||||
if (role == UserRole.Student)
|
||||
{
|
||||
_db.StudentProfiles.Add(new StudentProfile { UserId = user.Id });
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
else if (role == UserRole.Teacher)
|
||||
{
|
||||
_db.TeacherProfiles.Add(new TeacherProfile { UserId = user.Id });
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
foreach (var role in normalizedRoles.Where(r => !existing.Contains(r)))
|
||||
user.Roles.Add(new UserRoleAssignment { UserId = user.Id, Role = role });
|
||||
|
||||
await EnsureProfilesForRolesAsync(user.Id, normalizedRoles);
|
||||
await _db.SaveChangesAsync();
|
||||
|
||||
if (!user.IsActive)
|
||||
throw new ForbiddenException("Аккаунт деактивирован.");
|
||||
|
||||
@@ -171,6 +180,7 @@ public class AuthService : IAuthService
|
||||
{
|
||||
var token = await _db.RefreshTokens
|
||||
.Include(rt => rt.User)
|
||||
.ThenInclude(u => u.Roles)
|
||||
.FirstOrDefaultAsync(rt => rt.Token == refreshToken);
|
||||
|
||||
if (token == null || !token.IsActive)
|
||||
@@ -207,7 +217,9 @@ public class AuthService : IAuthService
|
||||
|
||||
public async Task<UserDto> GetCurrentUserAsync(int userId)
|
||||
{
|
||||
var user = await _db.Users.FindAsync(userId)
|
||||
var user = await _db.Users
|
||||
.Include(u => u.Roles)
|
||||
.FirstOrDefaultAsync(u => u.Id == userId)
|
||||
?? throw new NotFoundException("User", userId);
|
||||
return user.ToDto(_gamification.CalculateLevel(user.Xp));
|
||||
}
|
||||
@@ -245,13 +257,15 @@ public class AuthService : IAuthService
|
||||
Encoding.UTF8.GetBytes(_config["Jwt:Secret"]!));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||
|
||||
var claims = new[]
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new Claim(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new Claim(JwtRegisteredClaimNames.Email, user.Email),
|
||||
new Claim(ClaimTypes.Role, user.Role.ToString()),
|
||||
new Claim("display_name", user.DisplayName ?? "")
|
||||
new(JwtRegisteredClaimNames.Sub, user.Id.ToString()),
|
||||
new(JwtRegisteredClaimNames.Email, user.Email),
|
||||
new("display_name", user.DisplayName ?? "")
|
||||
};
|
||||
var roles = user.Roles.Select(r => r.Role).Distinct().ToList();
|
||||
if (roles.Count == 0) roles.Add(UserRole.Student);
|
||||
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role.ToString())));
|
||||
|
||||
var token = new JwtSecurityToken(
|
||||
issuer: _config["Jwt:Issuer"],
|
||||
@@ -264,6 +278,23 @@ public class AuthService : IAuthService
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
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 StudentProfile { UserId = userId });
|
||||
}
|
||||
|
||||
if (roles.Contains(UserRole.Teacher))
|
||||
{
|
||||
var hasTeacherProfile = await _db.TeacherProfiles.AnyAsync(p => p.UserId == userId);
|
||||
if (!hasTeacherProfile)
|
||||
_db.TeacherProfiles.Add(new TeacherProfile { UserId = userId });
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GenerateRefreshTokenAsync(int userId)
|
||||
{
|
||||
var randomBytes = RandomNumberGenerator.GetBytes(64);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user