Classroom güncellemeleri

This commit is contained in:
Sedat Öztürk 2025-08-31 01:29:11 +03:00
parent 09380b1e63
commit a9d2b33638
2 changed files with 74 additions and 66 deletions

View file

@ -188,15 +188,18 @@ public class ClassroomHub : Hub
var userId = _currentUser.Id; var userId = _currentUser.Id;
if (!userId.HasValue) return; 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 x => x.SessionId == sessionId && x.StudentId == userId.Value && x.LeaveTime == null
); );
if (attendance != null) if (attendances != null)
{
foreach (var attendance in attendances)
{ {
await CloseAttendanceAsync(attendance); await CloseAttendanceAsync(attendance);
await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance);
} }
}
var participant = await _participantRepository.FirstOrDefaultAsync( var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId x => x.SessionId == sessionId && x.UserId == userId
@ -356,18 +359,19 @@ public class ClassroomHub : Hub
{ {
try try
{ {
// 1. Attendance kapat var attendances = await _attendanceRepository.GetListAsync(
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null
); );
if (attendance != null) if (attendances != null)
{
foreach (var attendance in attendances)
{ {
await CloseAttendanceAsync(attendance); await CloseAttendanceAsync(attendance);
await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance); await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", attendance);
} }
}
// 2. Participant bul
var participant = await _participantRepository.FirstOrDefaultAsync( var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == participantId x => x.SessionId == sessionId && x.UserId == participantId
); );
@ -455,40 +459,53 @@ public class ClassroomHub : Hub
.SendAsync("ReceiveIceCandidate", _currentUser.Id?.ToString(), candidate); .SendAsync("ReceiveIceCandidate", _currentUser.Id?.ToString(), candidate);
} }
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
try try
{ {
// Eğer bağlantı zaten iptal edilmişse boşuna uğraşma
if (Context.ConnectionAborted.IsCancellationRequested) if (Context.ConnectionAborted.IsCancellationRequested)
return; return;
var userId = _currentUser.Id; var userId = _currentUser.Id;
if (userId.HasValue) if (!userId.HasValue)
{ {
// 🔑 1. Katılımcı listesi _logger.LogWarning("OnDisconnectedAsync çağrıldı fakat UserId bulunamadı. ConnId={ConnectionId}", Context.ConnectionId);
return;
}
// 🔑 Aynı anda birden fazla session olabilir (tab senaryosu)
var participants = await _participantRepository var participants = await _participantRepository
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId); .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) foreach (var participant in participants)
{ {
// 🔑 2. Attendance kaydını kapat _logger.LogInformation("OnDisconnectedAsync: User {UserId}, Session {SessionId} bağlantısı koptu.", userId, participant.SessionId);
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
// 🔑 Attendance kapat
var attendances = await _attendanceRepository.GetListAsync(
x => x.SessionId == participant.SessionId && x => x.SessionId == participant.SessionId &&
x.StudentId == userId.Value && x.StudentId == userId.Value &&
x.LeaveTime == null x.LeaveTime == null
); );
if (attendance != null) if (attendances != null)
{
foreach (var attendance in attendances)
{ {
attendance.LeaveTime = DateTime.UtcNow; attendance.LeaveTime = DateTime.UtcNow;
attendance.TotalDurationMinutes = (int)Math.Max( attendance.TotalDurationMinutes = (int)Math.Max(
1, 1,
(attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes (attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes
); );
await _attendanceRepository.UpdateAsync(attendance, autoSave: true); await _attendanceRepository.UpdateAsync(attendance, autoSave: true);
// Frontende bildir
await Clients.Group(participant.SessionId.ToString()) await Clients.Group(participant.SessionId.ToString())
.SendAsync("AttendanceUpdated", new .SendAsync("AttendanceUpdated", new
{ {
@ -501,24 +518,26 @@ public class ClassroomHub : Hub
attendance.TotalDurationMinutes attendance.TotalDurationMinutes
}); });
} }
}
// 🔑 Participant pasifleştir
participant.IsActive = false; participant.IsActive = false;
participant.ConnectionId = null; participant.ConnectionId = null;
await _participantRepository.UpdateAsync(participant, autoSave: true); await _participantRepository.UpdateAsync(participant, autoSave: true);
// 🔑 3. ParticipantLeft eventi // 🔑 Frontende bildir
await Clients.Group(participant.SessionId.ToString()) await Clients.Group(participant.SessionId.ToString())
.SendAsync("ParticipantLeft", userId.Value); .SendAsync("ParticipantLeft", userId.Value);
} }
} }
}
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted)."); _logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted). ConnId={ConnectionId}", Context.ConnectionId);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "OnDisconnectedAsync hata"); _logger.LogError(ex, "OnDisconnectedAsync hata. ConnId={ConnectionId}", Context.ConnectionId);
} }
await base.OnDisconnectedAsync(exception); await base.OnDisconnectedAsync(exception);

View file

@ -4,12 +4,15 @@ import {
HandRaiseDto, HandRaiseDto,
MessageType, MessageType,
} from '@/proxy/classroom/models' } from '@/proxy/classroom/models'
import { ROUTES_ENUM } from '@/routes/route.constant'
import { store } from '@/store/store' import { store } from '@/store/store'
import * as signalR from '@microsoft/signalr' import * as signalR from '@microsoft/signalr'
export class SignalRService { export class SignalRService {
private connection!: signalR.HubConnection private connection!: signalR.HubConnection
private isConnected: boolean = false private isConnected: boolean = false
private currentSessionId?: string
private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
private onParticipantJoined?: ( private onParticipantJoined?: (
userId: string, userId: string,
@ -68,7 +71,6 @@ export class SignalRService {
this.onParticipantMuted?.(userId, isMuted) this.onParticipantMuted?.(userId, isMuted)
}) })
this.connection.on('HandRaiseReceived', (payload: any) => { this.connection.on('HandRaiseReceived', (payload: any) => {
// payload = { handRaiseId, studentId, studentName, ... } // payload = { handRaiseId, studentId, studentName, ... }
this.onHandRaiseReceived?.(payload.studentId) this.onHandRaiseReceived?.(payload.studentId)
@ -96,12 +98,19 @@ export class SignalRService {
this.connection.onreconnected(() => { this.connection.onreconnected(() => {
this.isConnected = true this.isConnected = true
console.log('SignalR reconnected')
}) })
this.connection.onclose(() => { this.connection.onclose(async () => {
this.isConnected = false 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) => { this.connection.on('Error', (message: string) => {
@ -112,7 +121,7 @@ export class SignalRService {
console.warn('⚠️ ForceDisconnect received:', message) console.warn('⚠️ ForceDisconnect received:', message)
await this.disconnect() await this.disconnect()
window.location.href = '/classrooms' window.location.href = ROUTES_ENUM.protected.admin.classroom.classes
}) })
} }
@ -120,7 +129,6 @@ export class SignalRService {
try { try {
await this.connection.start() await this.connection.start()
this.isConnected = true this.isConnected = true
console.log('SignalR connection started')
} catch (error) { } catch (error) {
console.error('Error starting SignalR connection:', error) console.error('Error starting SignalR connection:', error)
// Switch to demo mode if connection fails // Switch to demo mode if connection fails
@ -140,16 +148,8 @@ export class SignalRService {
return return
} }
console.log( //Global değişkene yazılıyor.
'Joining class session:', this.currentSessionId = sessionId
sessionId,
'as',
userName,
'isTeacher:',
isTeacher,
'isActive:',
isActive,
)
try { try {
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive) await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher, isActive)
@ -172,6 +172,9 @@ export class SignalRService {
try { try {
await this.connection.invoke('LeaveClass', sessionId) await this.connection.invoke('LeaveClass', sessionId)
//Global değişkene null atanıyor.
this.currentSessionId = undefined
} catch (error) { } catch (error) {
console.error('Error leaving class:', error) console.error('Error leaving class:', error)
} }
@ -185,7 +188,6 @@ export class SignalRService {
isTeacher: boolean, isTeacher: boolean,
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating chat message from', senderName)
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId, sessionId,
@ -227,12 +229,6 @@ export class SignalRService {
isTeacher: boolean, isTeacher: boolean,
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log(
'Error starting SignalR connection simulating private message from',
senderName,
'to',
recipientName,
)
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId, sessionId,
@ -276,7 +272,6 @@ export class SignalRService {
isTeacher: boolean, isTeacher: boolean,
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating announcement from', senderName)
const chatMessage: ClassroomChatDto = { const chatMessage: ClassroomChatDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
sessionId, sessionId,
@ -314,15 +309,12 @@ export class SignalRService {
isTeacher: boolean, isTeacher: boolean,
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating mute participant', userId, isMuted)
setTimeout(() => { setTimeout(() => {
this.onParticipantMuted?.(userId, isMuted) this.onParticipantMuted?.(userId, isMuted)
}, 100) }, 100)
return return
} }
console.log('Muting participant:', userId, 'Muted:', isMuted, 'isTeacher:', isTeacher)
try { try {
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher) await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
} catch (error) { } catch (error) {
@ -332,7 +324,6 @@ export class SignalRService {
async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> { async raiseHand(sessionId: string, studentId: string, studentName: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise from', studentName)
const handRaise: HandRaiseDto = { const handRaise: HandRaiseDto = {
id: crypto.randomUUID(), id: crypto.randomUUID(),
studentId, studentId,
@ -355,7 +346,6 @@ export class SignalRService {
async kickParticipant(sessionId: string, participantId: string): Promise<void> { async kickParticipant(sessionId: string, participantId: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating kick participant', participantId)
setTimeout(() => { setTimeout(() => {
this.onParticipantLeft?.(participantId) this.onParticipantLeft?.(participantId)
}, 100) }, 100)
@ -371,7 +361,6 @@ export class SignalRService {
async approveHandRaise(sessionId: string, studentId: string): Promise<void> { async approveHandRaise(sessionId: string, studentId: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Simulating hand raise approval for student', studentId)
setTimeout(() => { setTimeout(() => {
this.onHandRaiseDismissed?.(studentId) this.onHandRaiseDismissed?.(studentId)
}, 100) }, 100)
@ -387,7 +376,6 @@ export class SignalRService {
async dismissHandRaise(sessionId: string, studentId: string): Promise<void> { async dismissHandRaise(sessionId: string, studentId: string): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Simulating hand raise dismissal for student', studentId)
setTimeout(() => { setTimeout(() => {
this.onHandRaiseDismissed?.(studentId) this.onHandRaiseDismissed?.(studentId)
}, 100) }, 100)
@ -472,6 +460,7 @@ export class SignalRService {
if (this.isConnected && this.connection) { if (this.isConnected && this.connection) {
await this.connection.stop() await this.connection.stop()
this.isConnected = false this.isConnected = false
this.currentSessionId = undefined
} }
} }