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,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);
// Frontende 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 eventi
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);
// 🔑 Frontende 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);

View file

@ -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,
@ -68,7 +71,6 @@ export class SignalRService {
this.onParticipantMuted?.(userId, isMuted)
})
this.connection.on('HandRaiseReceived', (payload: any) => {
// payload = { handRaiseId, studentId, studentName, ... }
this.onHandRaiseReceived?.(payload.studentId)
@ -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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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
}
}