erp-platform/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs
2025-08-30 15:05:50 +03:00

502 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
using Volo.Abp.Users;
using System.Linq;
using System.Text.Json;
namespace Kurs.Platform.Classrooms;
[Authorize]
public class ClassroomHub : Hub
{
private readonly IRepository<Classroom, Guid> _classSessionRepository;
private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
private readonly IRepository<ClassroomChat, Guid> _chatMessageRepository;
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
private readonly ILogger<ClassroomHub> _logger;
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentUser _currentUser;
public ClassroomHub(
IRepository<Classroom, Guid> classSessionRepository,
IRepository<ClassroomParticipant, Guid> participantRepository,
IRepository<ClassroomChat, Guid> chatMessageRepository,
IRepository<ClassroomAttandance, Guid> attendanceRepository,
ILogger<ClassroomHub> logger,
IGuidGenerator guidGenerator,
ICurrentUser currentUser)
{
_classSessionRepository = classSessionRepository;
_participantRepository = participantRepository;
_chatMessageRepository = chatMessageRepository;
_attendanceRepository = attendanceRepository;
_logger = logger;
_guidGenerator = guidGenerator;
_currentUser = currentUser;
}
[HubMethodName("JoinClass")]
public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher)
{
var classroom = await _classSessionRepository.GetAsync(sessionId);
if (classroom == null)
{
await Clients.Caller.SendAsync("Error", "Classroom not found");
return;
}
var classroomSettings = JsonSerializer.Deserialize<ClassroomSettingsDto>(classroom.SettingsJson);
var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId
);
if (participant == null)
{
participant = new ClassroomParticipant(
_guidGenerator.Create(),
sessionId,
userId,
userName,
isTeacher,
classroomSettings.DefaultMicrophoneState == "muted",
classroomSettings.DefaultCameraState == "off",
false,
true
);
participant.UpdateConnectionId(Context.ConnectionId);
await _participantRepository.InsertAsync(participant, autoSave: true);
// 🔑 Katılımcı sayısını güncelle
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);
}
// 🔑 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());
// 🔑 Yeni katılana mevcut katılımcıları gönder
// 🔑 Yeni katılana mevcut aktif katılımcıları gönder
var existingParticipants = await _participantRepository.GetListAsync(
x => x.SessionId == sessionId && x.IsActive
);
var others = existingParticipants
.Where(x => x.ConnectionId != Context.ConnectionId)
.Select(x => new
{
UserId = x.UserId,
UserName = x.UserName,
IsTeacher = x.IsTeacher,
IsActive = x.IsActive // ✅ aktiflik bilgisini de gönder
})
.ToList();
await Clients.Caller.SendAsync("ExistingParticipants", others);
// 🔑 Grup üyelerine yeni katılanı öğretmen bilgisiyle bildir
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantJoined", userId, userName, isTeacher, true);
}
[HubMethodName("LeaveClass")]
public async Task LeaveClassAsync(Guid sessionId)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, sessionId.ToString());
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);
}
//Kullanıcıyı Pasife aldım.
var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId
);
if (participant == null)
{
participant.IsActive = false;
await _participantRepository.UpdateAsync(participant, autoSave: true);
}
}
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantLeft", _currentUser.Id.ToString());
_logger.LogInformation($"User {_currentUser} left class {sessionId}");
}
[HubMethodName("MuteParticipant")]
public async Task MuteParticipantAsync(Guid sessionId, Guid userId, bool isMuted, bool isTeacher)
{
var teacherParticipant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == _currentUser.Id
);
if (teacherParticipant?.IsTeacher != true)
{
await Clients.Caller.SendAsync("Error", "Only teachers can mute participants");
return;
}
var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId
);
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);
}
}
[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(
_guidGenerator.Create(),
sessionId,
senderId,
senderName,
message,
null,
null,
isTeacher,
messageType
);
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
// 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
});
}
[HubMethodName("SendPrivateMessage")]
public async Task SendPrivateMessageAsync(
Guid sessionId,
Guid senderId,
string senderName,
string message,
Guid recipientId,
string recipientName,
bool isTeacher,
string messageType)
{
// Save message to DB
var chatMessage = new ClassroomChat(
_guidGenerator.Create(),
sessionId,
senderId,
senderName,
message,
recipientId,
recipientName,
isTeacher,
"private"
);
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
await Clients.User(recipientId.ToString()).SendAsync("ChatMessage", new
{
Id = Guid.NewGuid(),
SenderId = senderId,
SenderName = senderName,
Message = message,
Timestamp = DateTime.UtcNow,
IsTeacher = isTeacher,
RecipientId = recipientId,
RecipientName = recipientName,
MessageType = "private"
});
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"
);
await _chatMessageRepository.InsertAsync(chatMessage, autoSave: true);
await Clients.Group(sessionId.ToString()).SendAsync("ChatMessage", new
{
Id = Guid.NewGuid(),
SenderId = senderId,
SenderName = senderName,
Message = message,
Timestamp = DateTime.UtcNow,
IsTeacher = isTeacher,
MessageType = "announcement"
});
}
[HubMethodName("RaiseHand")]
public async Task RaiseHandAsync(Guid sessionId, Guid studentId, string studentName)
{
// 🔑 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);
}
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseReceived", new
{
Id = Guid.NewGuid(),
StudentId = studentId,
StudentName = studentName,
Timestamp = DateTime.UtcNow,
IsActive = true
});
}
[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
});
}
// Katılımcı çıkışını bildir
await Clients.Group(sessionId.ToString()).SendAsync("ParticipantLeft", participantId);
}
[HubMethodName("ApproveHandRaise")]
public async Task ApproveHandRaiseAsync(Guid sessionId, Guid studentId)
{
// 🔑 Öğ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 });
}
[HubMethodName("DismissHandRaise")]
public async Task DismissHandRaiseAsync(Guid sessionId, Guid studentId)
{
// 🔑 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 });
}
[HubMethodName("SendOffer")]
public async Task SendOfferAsync(Guid sessionId, Guid targetUserId, object offer)
{
_logger.LogInformation("➡️ SendOffer to {TargetUserId}, from {CurrentUser}", targetUserId, _currentUser.Id);
await Clients.User(targetUserId.ToString())
.SendAsync("ReceiveOffer", _currentUser.Id?.ToString(), offer);
}
[HubMethodName("SendAnswer")]
public async Task SendAnswerAsync(Guid sessionId, Guid targetUserId, object answer)
{
await Clients.User(targetUserId.ToString())
.SendAsync("ReceiveAnswer", _currentUser.Id?.ToString(), answer);
}
[HubMethodName("SendIceCandidate")]
public async Task SendIceCandidateAsync(Guid sessionId, Guid targetUserId, object candidate)
{
await Clients.User(targetUserId.ToString())
.SendAsync("ReceiveIceCandidate", _currentUser.Id?.ToString(), candidate);
}
public override async Task OnDisconnectedAsync(Exception exception)
{
try
{
if (Context.ConnectionAborted.IsCancellationRequested)
return;
var userId = _currentUser.Id;
if (userId.HasValue)
{
// 🔑 1. Katılımcı listesi
var participants = await _participantRepository
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId);
foreach (var participant in participants)
{
// 🔑 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
await Clients.Group(participant.SessionId.ToString())
.SendAsync("ParticipantLeft", userId.Value);
}
}
}
catch (TaskCanceledException)
{
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
}
catch (Exception ex)
{
_logger.LogError(ex, "OnDisconnectedAsync hata");
}
await base.OnDisconnectedAsync(exception);
}
}
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; }
}