erp-platform/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs

436 lines
15 KiB
C#
Raw Normal View History

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ı
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")]
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);
}
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", new { studentId });
2025-08-29 09:37:38 +00:00
}
[HubMethodName("DismissHandRaise")]
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);
}
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);
// Frontende 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 eventi
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; }
}