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

194 lines
6.6 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;
private readonly IRepository<ClassParticipant, Guid> _participantRepository;
private readonly IRepository<ClassChat, Guid> _chatMessageRepository;
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,
IRepository<ClassParticipant, Guid> participantRepository,
IRepository<ClassChat, Guid> chatMessageRepository,
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;
_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-25 18:01:57 +00:00
public async Task JoinClassAsync(Guid sessionId, string userName)
{
var classSession = await _classSessionRepository.GetAsync(sessionId);
// Add to SignalR group
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
// Update participant connection
var participant = await _participantRepository.FirstOrDefaultAsync(
2025-08-27 20:55:01 +00:00
x => x.SessionId == sessionId && x.UserId == _currentUser.Id
2025-08-25 18:01:57 +00:00
);
if (participant != null)
{
participant.UpdateConnectionId(Context.ConnectionId);
await _participantRepository.UpdateAsync(participant);
}
// Notify others
await Clients.Group(sessionId.ToString())
2025-08-27 20:55:01 +00:00
.SendAsync("ParticipantJoined", _currentUser.Id, userName);
2025-08-25 18:01:57 +00:00
_logger.LogInformation($"User {userName} joined class {sessionId}");
}
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());
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
}
public async Task SendSignalingMessageAsync(SignalingMessageDto message)
{
// Forward WebRTC signaling messages
await Clients.User(message.ToUserId)
.SendAsync("ReceiveSignalingMessage", message);
_logger.LogInformation($"Signaling message sent from {message.FromUserId} to {message.ToUserId}");
}
public async Task SendChatMessageAsync(Guid sessionId, string message)
{
2025-08-27 20:55:01 +00:00
var userName = _currentUser.UserName;
var userId = _currentUser.Id;
2025-08-25 18:01:57 +00:00
// Check if user is teacher
var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == userId
);
var isTeacher = participant?.IsTeacher ?? false;
// Save message to database
2025-08-26 05:59:39 +00:00
var chatMessage = new ClassChat(
2025-08-25 18:01:57 +00:00
_guidGenerator.Create(),
sessionId,
userId,
userName,
message,
isTeacher
);
await _chatMessageRepository.InsertAsync(chatMessage);
// Send to all participants
await Clients.Group(sessionId.ToString())
.SendAsync("ChatMessage", new
{
Id = chatMessage.Id,
SenderId = chatMessage.SenderId,
SenderName = chatMessage.SenderName,
Message = chatMessage.Message,
Timestamp = chatMessage.Timestamp,
IsTeacher = chatMessage.IsTeacher
});
}
public async Task MuteParticipantAsync(Guid sessionId, Guid participantId, bool isMuted)
{
var teacherParticipant = await _participantRepository.FirstOrDefaultAsync(
2025-08-27 20:55:01 +00:00
x => x.SessionId == sessionId && x.UserId == _currentUser.Id
2025-08-25 18:01:57 +00:00
);
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 == participantId
);
if (participant != null)
{
if (isMuted)
participant.MuteAudio();
else
participant.UnmuteAudio();
await _participantRepository.UpdateAsync(participant);
// Notify the participant and others
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantMuted", participantId, isMuted);
}
}
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
// bağlantı gerçekten iptal edilmişse DB sorgusu çalıştırma
if (Context.ConnectionAborted.IsCancellationRequested)
return;
var userId = _currentUser.Id;
if (userId.HasValue)
2025-08-25 18:01:57 +00:00
{
2025-08-28 11:53:47 +00:00
var participants = await _participantRepository
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId)
.ConfigureAwait(false);
foreach (var participant in participants)
{
await Clients.Group(participant.SessionId.ToString())
.SendAsync("ParticipantLeft", userId.Value)
.ConfigureAwait(false);
}
2025-08-25 18:01:57 +00:00
}
}
2025-08-28 11:53:47 +00:00
catch (TaskCanceledException)
{
// bağlantı kapandığında doğal, error yerine debug seviyesinde loglayın
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
}
catch (Exception ex)
{
// beklenmeyen hataları error olarak loglayın
_logger.LogError(ex, "OnDisconnectedAsync hata");
}
2025-08-25 18:01:57 +00:00
2025-08-28 11:53:47 +00:00
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
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; }
}