2025-08-25 18:01:57 +00:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
|
using Microsoft.AspNetCore.SignalR;
|
|
|
|
|
|
using Volo.Abp.Domain.Repositories;
|
|
|
|
|
|
using Kurs.Platform.Entities;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Volo.Abp.Guids;
|
2025-08-27 20:55:01 +00:00
|
|
|
|
using Volo.Abp.Users;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
|
|
|
|
|
namespace Kurs.Platform.SignalR.Hubs;
|
|
|
|
|
|
|
|
|
|
|
|
[Authorize]
|
|
|
|
|
|
public class ClassroomHub : Hub
|
|
|
|
|
|
{
|
2025-08-26 05:59:39 +00:00
|
|
|
|
private readonly IRepository<Classroom, Guid> _classSessionRepository;
|
2025-08-29 09:37:38 +00:00
|
|
|
|
private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
|
|
|
|
|
|
private readonly IRepository<ClassroomChat, Guid> _chatMessageRepository;
|
|
|
|
|
|
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
private readonly ILogger<ClassroomHub> _logger;
|
|
|
|
|
|
private readonly IGuidGenerator _guidGenerator;
|
2025-08-27 20:55:01 +00:00
|
|
|
|
private readonly ICurrentUser _currentUser;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
|
|
|
|
|
public ClassroomHub(
|
2025-08-26 05:59:39 +00:00
|
|
|
|
IRepository<Classroom, Guid> classSessionRepository,
|
2025-08-29 09:37:38 +00:00
|
|
|
|
IRepository<ClassroomParticipant, Guid> participantRepository,
|
|
|
|
|
|
IRepository<ClassroomChat, Guid> chatMessageRepository,
|
|
|
|
|
|
IRepository<ClassroomAttandance, Guid> attendanceRepository,
|
2025-08-25 18:01:57 +00:00
|
|
|
|
ILogger<ClassroomHub> logger,
|
2025-08-27 20:55:01 +00:00
|
|
|
|
IGuidGenerator guidGenerator,
|
|
|
|
|
|
ICurrentUser currentUser)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
|
|
|
|
|
_classSessionRepository = classSessionRepository;
|
|
|
|
|
|
_participantRepository = participantRepository;
|
|
|
|
|
|
_chatMessageRepository = chatMessageRepository;
|
2025-08-29 09:37:38 +00:00
|
|
|
|
_attendanceRepository = attendanceRepository;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
_logger = logger;
|
|
|
|
|
|
_guidGenerator = guidGenerator;
|
2025-08-27 20:55:01 +00:00
|
|
|
|
_currentUser = currentUser;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-27 20:55:01 +00:00
|
|
|
|
[HubMethodName("JoinClass")]
|
2025-08-29 09:37:38 +00:00
|
|
|
|
public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
2025-08-29 09:37:38 +00:00
|
|
|
|
x => x.SessionId == sessionId && x.UserId == userId
|
2025-08-25 18:01:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
if (participant == null)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
participant = new ClassroomParticipant(
|
|
|
|
|
|
_guidGenerator.Create(),
|
|
|
|
|
|
sessionId,
|
|
|
|
|
|
userId,
|
|
|
|
|
|
userName,
|
|
|
|
|
|
isTeacher
|
|
|
|
|
|
);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
participant.UpdateConnectionId(Context.ConnectionId);
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await _participantRepository.InsertAsync(participant, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
// 🔑 Katılımcı sayısını güncelle
|
|
|
|
|
|
var classroom = await _classSessionRepository.GetAsync(sessionId);
|
|
|
|
|
|
var participantCount = await _participantRepository.CountAsync(x => x.SessionId == sessionId);
|
|
|
|
|
|
classroom.ParticipantCount = participantCount;
|
|
|
|
|
|
await _classSessionRepository.UpdateAsync(classroom, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
participant.UpdateConnectionId(Context.ConnectionId);
|
|
|
|
|
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
// 🔑 Attendance kaydı aç
|
|
|
|
|
|
var attendance = new ClassroomAttandance(
|
|
|
|
|
|
_guidGenerator.Create(),
|
|
|
|
|
|
sessionId,
|
|
|
|
|
|
userId,
|
|
|
|
|
|
userName,
|
|
|
|
|
|
DateTime.UtcNow
|
|
|
|
|
|
);
|
|
|
|
|
|
await _attendanceRepository.InsertAsync(attendance, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
|
|
|
|
|
|
|
2025-08-25 18:01:57 +00:00
|
|
|
|
await Clients.Group(sessionId.ToString())
|
2025-08-29 09:37:38 +00:00
|
|
|
|
.SendAsync("ParticipantJoined", userId, userName);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
|
2025-08-27 20:55:01 +00:00
|
|
|
|
[HubMethodName("LeaveClass")]
|
2025-08-25 18:01:57 +00:00
|
|
|
|
public async Task LeaveClassAsync(Guid sessionId)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Groups.RemoveFromGroupAsync(Context.ConnectionId, sessionId.ToString());
|
2025-08-29 09:37:38 +00:00
|
|
|
|
|
|
|
|
|
|
var userId = _currentUser.Id;
|
|
|
|
|
|
if (userId.HasValue)
|
|
|
|
|
|
{
|
|
|
|
|
|
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.StudentId == userId.Value && x.LeaveTime == null
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (attendance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
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(sessionId.ToString())
|
|
|
|
|
|
.SendAsync("AttendanceUpdated", attendance);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-25 18:01:57 +00:00
|
|
|
|
await Clients.Group(sessionId.ToString())
|
2025-08-27 20:55:01 +00:00
|
|
|
|
.SendAsync("ParticipantLeft", _currentUser);
|
|
|
|
|
|
_logger.LogInformation($"User {_currentUser} left class {sessionId}");
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
[HubMethodName("MuteParticipant")]
|
|
|
|
|
|
public async Task MuteParticipantAsync(Guid sessionId, Guid userId, bool isMuted, bool isTeacher)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
var teacherParticipant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.UserId == _currentUser.Id
|
|
|
|
|
|
);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
if (teacherParticipant?.IsTeacher != true)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Clients.Caller.SendAsync("Error", "Only teachers can mute participants");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.UserId == userId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
if (participant != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (isMuted) participant.MuteAudio();
|
|
|
|
|
|
else participant.UnmuteAudio();
|
|
|
|
|
|
|
|
|
|
|
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
await Clients.Group(sessionId.ToString())
|
|
|
|
|
|
.SendAsync("ParticipantMuted", userId, isMuted);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
[HubMethodName("SendChatMessage")]
|
|
|
|
|
|
public async Task SendChatMessageAsync(
|
|
|
|
|
|
Guid sessionId,
|
|
|
|
|
|
Guid senderId,
|
|
|
|
|
|
string senderName,
|
|
|
|
|
|
string message,
|
|
|
|
|
|
bool isTeacher,
|
|
|
|
|
|
string messageType)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Save message to DB
|
|
|
|
|
|
var chatMessage = new ClassroomChat(
|
2025-08-25 18:01:57 +00:00
|
|
|
|
_guidGenerator.Create(),
|
|
|
|
|
|
sessionId,
|
2025-08-29 09:37:38 +00:00
|
|
|
|
senderId,
|
|
|
|
|
|
senderName,
|
2025-08-25 18:01:57 +00:00
|
|
|
|
message,
|
2025-08-29 09:37:38 +00:00
|
|
|
|
null,
|
|
|
|
|
|
null,
|
|
|
|
|
|
isTeacher,
|
|
|
|
|
|
messageType
|
2025-08-25 18:01:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
// Broadcast to group
|
|
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("ChatMessage", new
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = chatMessage.Id,
|
|
|
|
|
|
SenderId = senderId,
|
|
|
|
|
|
SenderName = senderName,
|
|
|
|
|
|
Message = chatMessage.Message,
|
|
|
|
|
|
Timestamp = chatMessage.Timestamp,
|
|
|
|
|
|
IsTeacher = isTeacher,
|
|
|
|
|
|
MessageType = messageType
|
|
|
|
|
|
});
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
[HubMethodName("SendPrivateMessage")]
|
|
|
|
|
|
public async Task SendPrivateMessageAsync(
|
|
|
|
|
|
Guid sessionId,
|
|
|
|
|
|
Guid senderId,
|
|
|
|
|
|
string senderName,
|
|
|
|
|
|
string message,
|
|
|
|
|
|
Guid recipientId,
|
|
|
|
|
|
string recipientName,
|
|
|
|
|
|
bool isTeacher,
|
|
|
|
|
|
string messageType)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
// Save message to DB
|
|
|
|
|
|
var chatMessage = new ClassroomChat(
|
|
|
|
|
|
_guidGenerator.Create(),
|
|
|
|
|
|
sessionId,
|
|
|
|
|
|
senderId,
|
|
|
|
|
|
senderName,
|
|
|
|
|
|
message,
|
|
|
|
|
|
recipientId,
|
|
|
|
|
|
recipientName,
|
|
|
|
|
|
isTeacher,
|
|
|
|
|
|
"private"
|
2025-08-25 18:01:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
await Clients.User(recipientId.ToString()).SendAsync("ChatMessage", new
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
SenderId = senderId,
|
|
|
|
|
|
SenderName = senderName,
|
|
|
|
|
|
Message = message,
|
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
|
IsTeacher = isTeacher,
|
|
|
|
|
|
RecipientId = recipientId,
|
|
|
|
|
|
RecipientName = recipientName,
|
|
|
|
|
|
MessageType = "private"
|
|
|
|
|
|
});
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await Clients.Caller.SendAsync("ChatMessage", new
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
SenderId = senderId,
|
|
|
|
|
|
SenderName = senderName,
|
|
|
|
|
|
Message = message,
|
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
|
IsTeacher = isTeacher,
|
|
|
|
|
|
RecipientId = recipientId,
|
|
|
|
|
|
RecipientName = recipientName,
|
|
|
|
|
|
MessageType = "private"
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HubMethodName("SendAnnouncement")]
|
|
|
|
|
|
public async Task SendAnnouncementAsync(Guid sessionId, Guid senderId, string senderName, string message, bool isTeacher)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Save message to DB
|
|
|
|
|
|
var chatMessage = new ClassroomChat(
|
|
|
|
|
|
_guidGenerator.Create(),
|
|
|
|
|
|
sessionId,
|
|
|
|
|
|
senderId,
|
|
|
|
|
|
senderName,
|
|
|
|
|
|
message,
|
|
|
|
|
|
null,
|
|
|
|
|
|
null,
|
|
|
|
|
|
isTeacher,
|
|
|
|
|
|
"announcement"
|
2025-08-25 18:01:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("ChatMessage", new
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
SenderId = senderId,
|
|
|
|
|
|
SenderName = senderName,
|
|
|
|
|
|
Message = message,
|
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
|
IsTeacher = isTeacher,
|
|
|
|
|
|
MessageType = "announcement"
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
[HubMethodName("RaiseHand")]
|
|
|
|
|
|
public async Task RaiseHandAsync(Guid sessionId, Guid studentId, string studentName)
|
|
|
|
|
|
{
|
2025-08-29 12:04:22 +00:00
|
|
|
|
// 🔑 Participant'ı bul
|
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.UserId == studentId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (participant != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
participant.IsHandRaised = true;
|
|
|
|
|
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseReceived", new
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
|
StudentId = studentId,
|
|
|
|
|
|
StudentName = studentName,
|
|
|
|
|
|
Timestamp = DateTime.UtcNow,
|
|
|
|
|
|
IsActive = true
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
[HubMethodName("KickParticipant")]
|
|
|
|
|
|
public async Task KickParticipantAsync(Guid sessionId, Guid participantId)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Attendance kapat
|
|
|
|
|
|
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (attendance != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
attendance.LeaveTime = DateTime.UtcNow;
|
|
|
|
|
|
attendance.TotalDurationMinutes = (int)Math.Max(
|
|
|
|
|
|
1,
|
|
|
|
|
|
(attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
await _attendanceRepository.UpdateAsync(attendance, autoSave: true);
|
|
|
|
|
|
|
|
|
|
|
|
// Katılım güncellemesini yayınla
|
|
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("AttendanceUpdated", new
|
|
|
|
|
|
{
|
|
|
|
|
|
attendance.Id,
|
|
|
|
|
|
attendance.SessionId,
|
|
|
|
|
|
attendance.StudentId,
|
|
|
|
|
|
attendance.StudentName,
|
|
|
|
|
|
attendance.JoinTime,
|
|
|
|
|
|
attendance.LeaveTime,
|
|
|
|
|
|
attendance.TotalDurationMinutes
|
|
|
|
|
|
});
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
2025-08-29 09:37:38 +00:00
|
|
|
|
|
|
|
|
|
|
// Katılımcı çıkışını bildir
|
|
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("ParticipantLeft", participantId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HubMethodName("ApproveHandRaise")]
|
2025-08-29 12:53:11 +00:00
|
|
|
|
public async Task ApproveHandRaiseAsync(Guid sessionId, Guid studentId)
|
2025-08-29 09:37:38 +00:00
|
|
|
|
{
|
2025-08-29 12:04:22 +00:00
|
|
|
|
// 🔑 Öğrencinin parmak kaldırma durumunu sıfırla
|
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.UserId == studentId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (participant != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
participant.IsHandRaised = false;
|
|
|
|
|
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 12:53:11 +00:00
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", new { studentId });
|
2025-08-29 09:37:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HubMethodName("DismissHandRaise")]
|
2025-08-29 12:53:11 +00:00
|
|
|
|
public async Task DismissHandRaiseAsync(Guid sessionId, Guid studentId)
|
2025-08-29 09:37:38 +00:00
|
|
|
|
{
|
2025-08-29 12:04:22 +00:00
|
|
|
|
// 🔑 Participant'ı bul ve elini indir
|
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
|
x => x.SessionId == sessionId && x.UserId == studentId
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (participant != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
participant.IsHandRaised = false;
|
|
|
|
|
|
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-29 12:53:11 +00:00
|
|
|
|
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", new { studentId });
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override async Task OnDisconnectedAsync(Exception exception)
|
|
|
|
|
|
{
|
2025-08-28 11:53:47 +00:00
|
|
|
|
try
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-28 11:53:47 +00:00
|
|
|
|
if (Context.ConnectionAborted.IsCancellationRequested)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
var userId = _currentUser.Id;
|
|
|
|
|
|
if (userId.HasValue)
|
2025-08-25 18:01:57 +00:00
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
// 🔑 1. Katılımcı listesi
|
2025-08-28 11:53:47 +00:00
|
|
|
|
var participants = await _participantRepository
|
2025-08-29 09:37:38 +00:00
|
|
|
|
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId);
|
2025-08-28 11:53:47 +00:00
|
|
|
|
|
|
|
|
|
|
foreach (var participant in participants)
|
|
|
|
|
|
{
|
2025-08-29 09:37:38 +00:00
|
|
|
|
// 🔑 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)
|
|
|
|
|
|
{
|
|
|
|
|
|
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
|
|
|
|
|
|
{
|
|
|
|
|
|
attendance.Id,
|
|
|
|
|
|
attendance.SessionId,
|
|
|
|
|
|
attendance.StudentId,
|
|
|
|
|
|
attendance.StudentName,
|
|
|
|
|
|
attendance.JoinTime,
|
|
|
|
|
|
attendance.LeaveTime,
|
|
|
|
|
|
attendance.TotalDurationMinutes
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 🔑 3. ParticipantLeft event’i
|
2025-08-28 11:53:47 +00:00
|
|
|
|
await Clients.Group(participant.SessionId.ToString())
|
2025-08-29 09:37:38 +00:00
|
|
|
|
.SendAsync("ParticipantLeft", userId.Value);
|
2025-08-28 11:53:47 +00:00
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-28 11:53:47 +00:00
|
|
|
|
catch (TaskCanceledException)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, "OnDisconnectedAsync hata");
|
|
|
|
|
|
}
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
2025-08-29 09:37:38 +00:00
|
|
|
|
await base.OnDisconnectedAsync(exception);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public class SignalingMessageDto
|
|
|
|
|
|
{
|
|
|
|
|
|
public string Type { get; set; } // offer, answer, ice-candidate
|
|
|
|
|
|
public string FromUserId { get; set; }
|
|
|
|
|
|
public string ToUserId { get; set; }
|
|
|
|
|
|
public object Data { get; set; }
|
|
|
|
|
|
}
|