diff --git a/api/src/Kurs.Platform.Domain/Classroom/Classroom.cs b/api/src/Kurs.Platform.Domain/Classroom/Classroom.cs index 4c1ce9a3..915a6315 100644 --- a/api/src/Kurs.Platform.Domain/Classroom/Classroom.cs +++ b/api/src/Kurs.Platform.Domain/Classroom/Classroom.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; using Volo.Abp.Domain.Entities.Auditing; namespace Kurs.Platform.Entities; @@ -20,6 +21,7 @@ public class Classroom : FullAuditedEntity public int ParticipantCount { get; set; } public string SettingsJson { get; set; } + [JsonIgnore] public virtual ICollection Participants { get; set; } public virtual ICollection AttendanceRecords { get; set; } public virtual ICollection ChatMessages { get; set; } diff --git a/api/src/Kurs.Platform.Domain/Classroom/ClassroomAttandance.cs b/api/src/Kurs.Platform.Domain/Classroom/ClassroomAttandance.cs index 270394e1..dcb5961f 100644 --- a/api/src/Kurs.Platform.Domain/Classroom/ClassroomAttandance.cs +++ b/api/src/Kurs.Platform.Domain/Classroom/ClassroomAttandance.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using Volo.Abp.Domain.Entities.Auditing; namespace Kurs.Platform.Entities; @@ -12,7 +13,7 @@ public class ClassroomAttandance : FullAuditedEntity public DateTime? LeaveTime { get; set; } public int TotalDurationMinutes { get; set; } - // Navigation properties + [JsonIgnore] public virtual Classroom Session { get; set; } protected ClassroomAttandance() diff --git a/api/src/Kurs.Platform.Domain/Classroom/ClassroomChat.cs b/api/src/Kurs.Platform.Domain/Classroom/ClassroomChat.cs index 4efbc63d..a4990a78 100644 --- a/api/src/Kurs.Platform.Domain/Classroom/ClassroomChat.cs +++ b/api/src/Kurs.Platform.Domain/Classroom/ClassroomChat.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using Volo.Abp.Domain.Entities.Auditing; namespace Kurs.Platform.Entities; @@ -15,7 +16,7 @@ public class ClassroomChat : FullAuditedEntity public bool IsTeacher { get; set; } public string MessageType { get; set; } - // Navigation properties + [JsonIgnore] public virtual Classroom Session { get; set; } protected ClassroomChat() diff --git a/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs b/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs index b0f83f0b..099ad619 100644 --- a/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs +++ b/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs @@ -1,4 +1,5 @@ using System; +using System.Text.Json.Serialization; using Volo.Abp.Domain.Entities.Auditing; namespace Kurs.Platform.Entities; @@ -17,7 +18,7 @@ public class ClassroomParticipant : FullAuditedEntity public DateTime JoinTime { get; set; } public string ConnectionId { get; set; } - // Navigation properties + [JsonIgnore] public virtual Classroom Session { get; set; } protected ClassroomParticipant() diff --git a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs index 2b9abbd1..89ed806c 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs @@ -9,6 +9,7 @@ using Volo.Abp.Guids; using Volo.Abp.Users; using System.Linq; using System.Text.Json; +using Volo.Abp.Uow; namespace Kurs.Platform.Classrooms; @@ -22,6 +23,7 @@ public class ClassroomHub : Hub private readonly ILogger _logger; private readonly IGuidGenerator _guidGenerator; private readonly ICurrentUser _currentUser; + private readonly UnitOfWorkManager _unitOfWorkManager; public ClassroomHub( IRepository classSessionRepository, @@ -30,7 +32,8 @@ public class ClassroomHub : Hub IRepository attendanceRepository, ILogger logger, IGuidGenerator guidGenerator, - ICurrentUser currentUser) + ICurrentUser currentUser, + UnitOfWorkManager unitOfWorkManager) { _classSessionRepository = classSessionRepository; _participantRepository = participantRepository; @@ -39,6 +42,7 @@ public class ClassroomHub : Hub _logger = logger; _guidGenerator = guidGenerator; _currentUser = currentUser; + _unitOfWorkManager = unitOfWorkManager; } #region Helper Methods @@ -392,7 +396,15 @@ public class ClassroomHub : Hub // yalnızca diğer katılımcılar görebilsin await Clients.Group(sessionId.ToString()) - .SendAsync("AttendanceUpdated", attendance); + .SendAsync("AttendanceUpdated", new { + attendance.Id, + attendance.SessionId, + attendance.StudentId, + attendance.StudentName, + attendance.JoinTime, + attendance.LeaveTime, + attendance.TotalDurationMinutes + }); } // 2. Participant bul @@ -512,7 +524,7 @@ public class ClassroomHub : Hub .SendAsync("ReceiveIceCandidate", _currentUser.Id?.ToString(), candidate); } - + [UnitOfWork] public override async Task OnDisconnectedAsync(Exception exception) { try @@ -524,9 +536,11 @@ public class ClassroomHub : Hub return; } - _logger.LogInformation("🔴 OnDisconnectedAsync: User={UserId}, ConnId={ConnId}, Exception={Exception}", userId, Context.ConnectionId, exception?.Message); + _logger.LogInformation("🔴 OnDisconnectedAsync: User={UserId}, ConnId={ConnId}, Exception={Exception}", + userId, Context.ConnectionId, exception?.Message); - // 🔑 Aynı anda birden fazla session olabilir (tab senaryosu) + // 🔑 Yeni scope aç (her disconnect için ayrı UoW) + using var uow = _unitOfWorkManager.Begin(requiresNew: true, isTransactional: true); var participants = await _participantRepository .GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId); @@ -537,41 +551,40 @@ public class ClassroomHub : Hub foreach (var participant in participants) { - _logger.LogInformation("OnDisconnectedAsync: User {UserId}, Session {SessionId} bağlantısı koptu.", userId, participant.SessionId); + _logger.LogInformation("OnDisconnectedAsync: User {UserId}, Session {SessionId} bağlantısı koptu.", + userId, participant.SessionId); - // 🔑 Attendance kapat + // 🔑 Attendance güncelle var attendances = await _attendanceRepository.GetListAsync( x => x.SessionId == participant.SessionId && x.StudentId == userId.Value && x.LeaveTime == null ); - if (attendances != null) + foreach (var attendance in attendances) { - foreach (var attendance in attendances) - { - attendance.LeaveTime = DateTime.UtcNow; - attendance.TotalDurationMinutes = (int)Math.Max( - 1, - (attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes - ); - await _attendanceRepository.UpdateAsync(attendance, autoSave: true); + attendance.LeaveTime = DateTime.UtcNow; + attendance.TotalDurationMinutes = (int)Math.Max( + 1, + (attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes + ); + await _attendanceRepository.UpdateAsync(attendance, autoSave: true); - await Clients.Group(participant.SessionId.ToString()) - .SendAsync("AttendanceUpdated", new - { - attendance.Id, - attendance.SessionId, - attendance.StudentId, - attendance.StudentName, - attendance.JoinTime, - attendance.LeaveTime, - attendance.TotalDurationMinutes - }); - } + // DTO gönder (entity yerine) + await Clients.Group(participant.SessionId.ToString()) + .SendAsync("AttendanceUpdated", new + { + attendance.Id, + attendance.SessionId, + attendance.StudentId, + attendance.StudentName, + attendance.JoinTime, + attendance.LeaveTime, + attendance.TotalDurationMinutes + }); } - // Eğer kullanıcı kick edilmemişse pasifleştir + // Eğer kick edilmemişse → pasif et if (!participant.IsKicked) { participant.IsActive = false; @@ -579,10 +592,16 @@ public class ClassroomHub : Hub await _participantRepository.UpdateAsync(participant, autoSave: true); await Clients.Group(participant.SessionId.ToString()) - .SendAsync("ParticipantLeft", new { UserId = userId.Value, SessionId = participant.SessionId }); + .SendAsync("ParticipantLeft", new + { + UserId = userId.Value, + SessionId = participant.SessionId + }); } - } + + // 🔑 Scope commit + await uow.CompleteAsync(); } catch (TaskCanceledException) { @@ -597,8 +616,6 @@ public class ClassroomHub : Hub } } - - public class SignalingMessageDto { public string Type { get; set; } // offer, answer, ice-candidate