From a9d2b336384666bd5fea18f8caf174d4288e3ccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sun, 31 Aug 2025 01:29:11 +0300 Subject: [PATCH] =?UTF-8?q?Classroom=20g=C3=BCncellemeleri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Classroom/ClassroomHub.cs | 89 +++++++++++-------- ui/src/services/classroom/signalr.ts | 51 +++++------ 2 files changed, 74 insertions(+), 66 deletions(-) diff --git a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs index fec4fc53..2603cb87 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs @@ -188,14 +188,17 @@ public class ClassroomHub : Hub var userId = _currentUser.Id; if (!userId.HasValue) return; - var attendance = await _attendanceRepository.FirstOrDefaultAsync( + var attendances = await _attendanceRepository.GetListAsync( x => x.SessionId == sessionId && x.StudentId == userId.Value && x.LeaveTime == null ); - if (attendance != null) + if (attendances != null) { - await CloseAttendanceAsync(attendance); - await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); + foreach (var attendance in attendances) + { + await CloseAttendanceAsync(attendance); + await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); + } } var participant = await _participantRepository.FirstOrDefaultAsync( @@ -356,18 +359,19 @@ public class ClassroomHub : Hub { try { - // 1. Attendance kapat - var attendance = await _attendanceRepository.FirstOrDefaultAsync( + var attendances = await _attendanceRepository.GetListAsync( x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null ); - if (attendance != null) + if (attendances != null) { - await CloseAttendanceAsync(attendance); - await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); + foreach (var attendance in attendances) + { + await CloseAttendanceAsync(attendance); + await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); + } } - // 2. Participant bul var participant = await _participantRepository.FirstOrDefaultAsync( x => x.SessionId == sessionId && x.UserId == participantId ); @@ -455,40 +459,53 @@ public class ClassroomHub : Hub .SendAsync("ReceiveIceCandidate", _currentUser.Id?.ToString(), candidate); } + public override async Task OnDisconnectedAsync(Exception exception) { try { + // Eğer bağlantı zaten iptal edilmişse boşuna uğraşma if (Context.ConnectionAborted.IsCancellationRequested) return; var userId = _currentUser.Id; - if (userId.HasValue) + if (!userId.HasValue) { - // 🔑 1. Katılımcı listesi - var participants = await _participantRepository - .GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId); + _logger.LogWarning("OnDisconnectedAsync çağrıldı fakat UserId bulunamadı. ConnId={ConnectionId}", Context.ConnectionId); + return; + } - foreach (var participant in participants) + // 🔑 Aynı anda birden fazla session olabilir (tab senaryosu) + var participants = await _participantRepository + .GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId); + + if (!participants.Any()) + { + _logger.LogInformation("OnDisconnectedAsync: User {UserId} için aktif participant bulunamadı. ConnId={ConnectionId}", userId, Context.ConnectionId); + } + + foreach (var participant in participants) + { + _logger.LogInformation("OnDisconnectedAsync: User {UserId}, Session {SessionId} bağlantısı koptu.", userId, participant.SessionId); + + // 🔑 Attendance kapat + var attendances = await _attendanceRepository.GetListAsync( + x => x.SessionId == participant.SessionId && + x.StudentId == userId.Value && + x.LeaveTime == null + ); + + if (attendances != null) { - // 🔑 2. Attendance kaydını kapat - var attendance = await _attendanceRepository.FirstOrDefaultAsync( - x => x.SessionId == participant.SessionId && - x.StudentId == userId.Value && - x.LeaveTime == null - ); - - if (attendance != null) + 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); - // Frontend’e bildir await Clients.Group(participant.SessionId.ToString()) .SendAsync("AttendanceUpdated", new { @@ -501,24 +518,26 @@ public class ClassroomHub : Hub attendance.TotalDurationMinutes }); } - - participant.IsActive = false; - participant.ConnectionId = null; - await _participantRepository.UpdateAsync(participant, autoSave: true); - - // 🔑 3. ParticipantLeft event’i - await Clients.Group(participant.SessionId.ToString()) - .SendAsync("ParticipantLeft", userId.Value); } + + // 🔑 Participant pasifleştir + participant.IsActive = false; + participant.ConnectionId = null; + + await _participantRepository.UpdateAsync(participant, autoSave: true); + + // 🔑 Frontend’e bildir + await Clients.Group(participant.SessionId.ToString()) + .SendAsync("ParticipantLeft", userId.Value); } } catch (TaskCanceledException) { - _logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted)."); + _logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted). ConnId={ConnectionId}", Context.ConnectionId); } catch (Exception ex) { - _logger.LogError(ex, "OnDisconnectedAsync hata"); + _logger.LogError(ex, "OnDisconnectedAsync hata. ConnId={ConnectionId}", Context.ConnectionId); } await base.OnDisconnectedAsync(exception); diff --git a/ui/src/services/classroom/signalr.ts b/ui/src/services/classroom/signalr.ts index 0316870a..0d38490f 100644 --- a/ui/src/services/classroom/signalr.ts +++ b/ui/src/services/classroom/signalr.ts @@ -4,12 +4,15 @@ import { HandRaiseDto, MessageType, } from '@/proxy/classroom/models' +import { ROUTES_ENUM } from '@/routes/route.constant' import { store } from '@/store/store' import * as signalR from '@microsoft/signalr' export class SignalRService { private connection!: signalR.HubConnection private isConnected: boolean = false + private currentSessionId?: string + private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void private onParticipantJoined?: ( userId: string, @@ -67,7 +70,6 @@ export class SignalRService { this.connection.on('ParticipantMuted', (userId: string, isMuted: boolean) => { this.onParticipantMuted?.(userId, isMuted) }) - this.connection.on('HandRaiseReceived', (payload: any) => { // payload = { handRaiseId, studentId, studentName, ... } @@ -96,12 +98,19 @@ export class SignalRService { this.connection.onreconnected(() => { this.isConnected = true - console.log('SignalR reconnected') }) - this.connection.onclose(() => { + this.connection.onclose(async () => { this.isConnected = false - console.log('SignalR connection closed') + try { + if (this.currentSessionId) { + await this.connection.invoke('LeaveClass', this.currentSessionId) + } + } catch (err) { + console.warn('LeaveClass could not be sent, maybe server already closed', err) + } finally { + this.currentSessionId = undefined // ✅ garanti sıfırlama + } }) this.connection.on('Error', (message: string) => { @@ -112,7 +121,7 @@ export class SignalRService { console.warn('⚠️ ForceDisconnect received:', message) await this.disconnect() - window.location.href = '/classrooms' + window.location.href = ROUTES_ENUM.protected.admin.classroom.classes }) } @@ -120,7 +129,6 @@ export class SignalRService { try { await this.connection.start() this.isConnected = true - console.log('SignalR connection started') } catch (error) { console.error('Error starting SignalR connection:', error) // Switch to demo mode if connection fails @@ -140,16 +148,8 @@ export class SignalRService { return } - console.log( - 'Joining class session:', - sessionId, - 'as', - userName, - 'isTeacher:', - isTeacher, - 'isActive:', - isActive, - ) + //Global değişkene yazılıyor. + this.currentSessionId = sessionId try { await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive) @@ -172,6 +172,9 @@ export class SignalRService { try { await this.connection.invoke('LeaveClass', sessionId) + + //Global değişkene null atanıyor. + this.currentSessionId = undefined } catch (error) { console.error('Error leaving class:', error) } @@ -185,7 +188,6 @@ export class SignalRService { isTeacher: boolean, ): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating chat message from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, @@ -227,12 +229,6 @@ export class SignalRService { isTeacher: boolean, ): Promise { if (!this.isConnected) { - console.log( - 'Error starting SignalR connection simulating private message from', - senderName, - 'to', - recipientName, - ) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, @@ -276,7 +272,6 @@ export class SignalRService { isTeacher: boolean, ): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating announcement from', senderName) const chatMessage: ClassroomChatDto = { id: crypto.randomUUID(), sessionId, @@ -314,15 +309,12 @@ export class SignalRService { isTeacher: boolean, ): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating mute participant', userId, isMuted) setTimeout(() => { this.onParticipantMuted?.(userId, isMuted) }, 100) return } - console.log('Muting participant:', userId, 'Muted:', isMuted, 'isTeacher:', isTeacher) - try { await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher) } catch (error) { @@ -332,7 +324,6 @@ export class SignalRService { async raiseHand(sessionId: string, studentId: string, studentName: string): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating hand raise from', studentName) const handRaise: HandRaiseDto = { id: crypto.randomUUID(), studentId, @@ -355,7 +346,6 @@ export class SignalRService { async kickParticipant(sessionId: string, participantId: string): Promise { if (!this.isConnected) { - console.log('Error starting SignalR connection simulating kick participant', participantId) setTimeout(() => { this.onParticipantLeft?.(participantId) }, 100) @@ -371,7 +361,6 @@ export class SignalRService { async approveHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { - console.log('Simulating hand raise approval for student', studentId) setTimeout(() => { this.onHandRaiseDismissed?.(studentId) }, 100) @@ -387,7 +376,6 @@ export class SignalRService { async dismissHandRaise(sessionId: string, studentId: string): Promise { if (!this.isConnected) { - console.log('Simulating hand raise dismissal for student', studentId) setTimeout(() => { this.onHandRaiseDismissed?.(studentId) }, 100) @@ -472,6 +460,7 @@ export class SignalRService { if (this.isConnected && this.connection) { await this.connection.stop() this.isConnected = false + this.currentSessionId = undefined } }