feat: перелопатил синхронизацию преподавателей
Backend CI / build-and-test (push) Failing after 13m11s
🚀 Create and publish a Docker image / Detect changes in backend and frontend (push) Failing after 10m12s
Frontend CI / build-and-check (push) Failing after 16m9s
🚀 Create and publish a Docker image / Build & publish frontend image (push) Failing after 14m6s
🚀 Create and publish a Docker image / Build & publish backend image (push) Failing after 14m58s
🚀 Create and publish a Docker image / Update stack on Portainer (push) Failing after 14m58s

This commit is contained in:
2026-05-24 21:06:03 +03:00
parent 6aef5dd66f
commit 99d25adbb1
33 changed files with 1756 additions and 90 deletions
@@ -218,15 +218,42 @@ public class ScheduleSyncService : IScheduleSyncService
.Include(profile => profile.User)
.ThenInclude(user => user.Roles)
.FirstOrDefaultAsync(profile => profile.ModeusId == personId);
var subId = existingProfile?.User.MicrosoftId;
if (string.IsNullOrWhiteSpace(subId))
subId = await TryGetTeacherSubIdAsync(fullName);
User? ssoUser = null;
if (!string.IsNullOrWhiteSpace(subId))
{
ssoUser = await _db.Users
.Include(item => item.Roles)
.Include(item => item.TeacherProfile)
.FirstOrDefaultAsync(item => item.MicrosoftId == subId);
}
if (existingProfile != null && ssoUser != null && existingProfile.UserId != ssoUser.Id)
return await MergeTeacherPlaceholderAsync(existingProfile, ssoUser, fullName, subId);
if (existingProfile != null)
{
existingProfile.User.DisplayName = fullName;
if (!string.IsNullOrWhiteSpace(subId))
existingProfile.User.MicrosoftId = subId;
existingProfile.User.UpdatedAt = DateTime.UtcNow;
EnsureTeacherRole(existingProfile.User);
return existingProfile.User;
}
if (ssoUser != null)
{
ssoUser.DisplayName = fullName;
ssoUser.UpdatedAt = DateTime.UtcNow;
EnsureTeacherRole(ssoUser);
EnsureTeacherProfile(ssoUser, personId);
await _db.SaveChangesAsync();
return ssoUser;
}
var email = BuildModeusTeacherEmail(personId);
var user = await _db.Users
.Include(item => item.Roles)
@@ -239,6 +266,7 @@ public class ScheduleSyncService : IScheduleSyncService
{
Email = email,
DisplayName = fullName,
MicrosoftId = subId,
IsActive = true,
TeacherProfile = new TeacherProfile { ModeusId = personId }
};
@@ -249,6 +277,8 @@ public class ScheduleSyncService : IScheduleSyncService
}
user.DisplayName = fullName;
if (!string.IsNullOrWhiteSpace(subId))
user.MicrosoftId = subId;
user.UpdatedAt = DateTime.UtcNow;
if (user.TeacherProfile == null)
user.TeacherProfile = new TeacherProfile { UserId = user.Id, ModeusId = personId };
@@ -261,6 +291,76 @@ public class ScheduleSyncService : IScheduleSyncService
return user;
}
private async Task<string?> TryGetTeacherSubIdAsync(string fullName)
{
try
{
return await _modeus.GetSubIdByFullNameAsync(fullName);
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Could not resolve SSO sub id for teacher {TeacherFullName}. A placeholder teacher will be used until a future sync succeeds.", fullName);
return null;
}
}
private async Task<User> MergeTeacherPlaceholderAsync(
TeacherProfile placeholderProfile,
User targetUser,
string fullName,
string? subId)
{
var placeholderUser = placeholderProfile.User;
var lectures = await _db.Lectures
.Where(lecture => lecture.TeacherId == placeholderUser.Id)
.ToListAsync();
foreach (var lecture in lectures)
lecture.TeacherId = targetUser.Id;
targetUser.DisplayName = fullName;
if (!string.IsNullOrWhiteSpace(subId))
targetUser.MicrosoftId = subId;
targetUser.UpdatedAt = DateTime.UtcNow;
EnsureTeacherRole(targetUser);
if (targetUser.TeacherProfile == null)
{
placeholderProfile.UserId = targetUser.Id;
placeholderProfile.User = targetUser;
targetUser.TeacherProfile = placeholderProfile;
placeholderUser.TeacherProfile = null;
}
else
{
targetUser.TeacherProfile.ModeusId = placeholderProfile.ModeusId;
_db.TeacherProfiles.Remove(placeholderProfile);
}
if (await CanDeletePlaceholderUserAsync(placeholderUser.Id))
_db.Users.Remove(placeholderUser);
await _db.SaveChangesAsync();
return targetUser;
}
private async Task<bool> CanDeletePlaceholderUserAsync(int userId) =>
!await _db.StudentProfiles.AnyAsync(profile => profile.UserId == userId)
&& !await _db.RefreshTokens.AnyAsync(token => token.UserId == userId)
&& !await _db.LectureEnrollments.AnyAsync(enrollment => enrollment.UserId == userId)
&& !await _db.Reviews.AnyAsync(review => review.UserId == userId)
&& !await _db.UserAchievements.AnyAsync(achievement => achievement.UserId == userId)
&& !await _db.CoinTransactions.AnyAsync(transaction => transaction.UserId == userId)
&& !await _db.UserNotifications.AnyAsync(notification => notification.UserId == userId);
private static void EnsureTeacherProfile(User user, string modeusId)
{
if (user.TeacherProfile == null)
user.TeacherProfile = new TeacherProfile { UserId = user.Id, ModeusId = modeusId };
else
user.TeacherProfile.ModeusId = modeusId;
}
private static void EnsureTeacherRole(User user)
{
if (!user.Roles.Any(role => role.Role == UserRole.Teacher))