classroom düzeltmeler

This commit is contained in:
Sedat Öztürk 2025-08-31 21:35:43 +03:00
parent 8acbd2e257
commit dfd05cbf57
3 changed files with 77 additions and 59 deletions

View file

@ -117,7 +117,6 @@ public class ClassroomHub : Hub
[HubMethodName("JoinClass")]
public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher, bool isActive)
{
bool initialMuteState;
var classroom = await _classSessionRepository.GetAsync(sessionId);
if (classroom == null)
{
@ -129,7 +128,7 @@ public class ClassroomHub : Hub
? new ClassroomSettingsDto()
: JsonSerializer.Deserialize<ClassroomSettingsDto>(classroom.SettingsJson);
initialMuteState = !isTeacher && classroomSettings.AutoMuteNewParticipants
bool initialMuteState = !isTeacher && classroomSettings.AutoMuteNewParticipants
? true
: classroomSettings.DefaultMicrophoneState == "muted";
@ -137,15 +136,14 @@ public class ClassroomHub : Hub
x => x.SessionId == sessionId && x.UserId == userId
);
// 🚨 Kick edilmiş kullanıcı tekrar giriş yapamaz
// ❌ Kicklenmiş kullanıcı tekrar giremez
if (participant != null && participant.IsKicked)
{
await Clients.Caller.SendAsync("Error", "You are not allowed to rejoin this class.");
Context.Abort(); // bağlantıyı anında kapat
Context.Abort();
return;
}
if (participant == null)
{
participant = new ClassroomParticipant(
@ -161,15 +159,20 @@ public class ClassroomHub : Hub
isActive
);
await _participantRepository.InsertAsync(participant, autoSave: true);
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
await UpdateParticipantCountAsync(sessionId, classroom);
}
else
{
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
}
await UpdateParticipantConnectionAsync(participant, Context.ConnectionId, isActive);
// ✅ Attendance sadece yoksa aç
var existingOpenAttendance = await _attendanceRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.StudentId == userId && x.LeaveTime == null
);
if (existingOpenAttendance == null)
{
await CreateAttendanceAsync(sessionId, userId, userName);
}
await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString());
@ -189,9 +192,11 @@ public class ClassroomHub : Hub
.ToList();
await Clients.Caller.SendAsync("ExistingParticipants", others);
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantJoined", userId, userName, isTeacher, isActive);
}
[HubMethodName("LeaveClass")]
public async Task LeaveClassAsync(Guid sessionId)
{
@ -372,20 +377,21 @@ public class ClassroomHub : Hub
{
try
{
// 1. Attendance kapat
// 1. Attendance kapat (sadece kicklenen için)
var attendances = await _attendanceRepository.GetListAsync(
x => x.SessionId == sessionId && x.StudentId == participantId && x.LeaveTime == null
x => x.SessionId == sessionId &&
x.StudentId == participantId &&
x.LeaveTime == null
);
if (attendances != null)
{
foreach (var attendance in attendances)
{
await CloseAttendanceAsync(attendance);
// yalnızca diğer katılımcılar görebilsin
await Clients.Group(sessionId.ToString())
.SendAsync("AttendanceUpdated", attendance);
}
}
// 2. Participant bul
var participant = await _participantRepository.FirstOrDefaultAsync(
@ -394,22 +400,23 @@ public class ClassroomHub : Hub
if (participant == null)
{
_logger.LogWarning("KickParticipant: Session {SessionId} için participant {ParticipantId} bulunamadı",
_logger.LogWarning(
"KickParticipant: Session {SessionId} için participant {ParticipantId} bulunamadı",
sessionId, participantId);
return;
}
// ConnectionId'yi kaydet (DB'ye null yazmadan önce)
// ConnectionId'yi cache et (null yazmadan önce)
var connectionId = participant.ConnectionId;
// 3. DB'de kick flag setle
// 3. DB güncelle
participant.IsActive = false;
participant.IsKicked = true;
participant.ConnectionId = null;
await _participantRepository.UpdateAsync(participant, autoSave: true);
// 4. Hedef kullanıcıya bildir
// 4. Hedef kullanıcıya bildir (ForceDisconnect)
if (!string.IsNullOrEmpty(connectionId))
{
await Clients.Client(connectionId)
@ -418,9 +425,13 @@ public class ClassroomHub : Hub
await Groups.RemoveFromGroupAsync(connectionId, sessionId.ToString());
}
// 5. Diğer katılımcılara bildir
// 5. Diğer katılımcılara duyur
await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantLeft", new { UserId = participantId, SessionId = sessionId });
.SendAsync("ParticipantLeft", new
{
UserId = participantId,
SessionId = sessionId
});
// 6. Log
_logger.LogInformation("👢 Participant {ParticipantId} kicked from session {SessionId}",
@ -428,7 +439,8 @@ public class ClassroomHub : Hub
}
catch (Exception ex)
{
_logger.LogError(ex, "❌ KickParticipant hata verdi (Session={SessionId}, Participant={ParticipantId})",
_logger.LogError(ex,
"❌ KickParticipant hata verdi (Session={SessionId}, Participant={ParticipantId})",
sessionId, participantId);
await Clients.Caller.SendAsync("Error", "Kick işlemi başarısız oldu.");

View file

@ -39,7 +39,7 @@ export class SignalRService {
.withUrl(`${import.meta.env.VITE_API_URL}/classroomhub`, {
accessTokenFactory: () => auth.session.token || '',
})
.withAutomaticReconnect()
//.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build()

View file

@ -1,9 +1,7 @@
import React, { useState, useEffect, useRef } from 'react'
import { motion } from 'framer-motion'
import {
FaUsers,
FaComments,
FaUserPlus,
FaExpand,
FaHandPaper,
FaVolumeMute,
@ -19,7 +17,6 @@ import {
FaCompress,
FaUserFriends,
FaLayerGroup,
FaWrench,
FaFilePdf,
FaFileWord,
FaFileImage,
@ -278,40 +275,45 @@ const RoomDetail: React.FC = () => {
await webRTCServiceRef.current?.addIceCandidate(fromUserId, candidate)
})
// 🔑 Yeni katılan birini gördüğünde
signalRServiceRef.current.setParticipantJoinHandler(
async (userId: string, name: string, isTeacher: boolean, isActive: boolean) => {
if (userId === user.id) return
async (remoteUserId: string, name: string, isTeacher: boolean, isActive: boolean) => {
if (remoteUserId === user.id) return
if (!isActive) return
console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`)
// Statee ekle
setParticipants((prev) => {
const updated = [...prev]
if (!updated.find((p) => p.id === userId)) {
updated.push({
id: userId,
if (prev.find((p) => p.id === remoteUserId)) return prev
return [
...prev,
{
id: remoteUserId,
name,
sessionId: classSession.id,
isTeacher,
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
isVideoMuted: classSettings.defaultCameraState === 'off',
isActive: true,
})
}
return updated
},
]
})
// 🔑 Mesh: Herkes herkese offer gönderir
if (!webRTCServiceRef.current?.getPeerConnection(userId)) {
await webRTCServiceRef.current?.createPeerConnection(userId)
// PeerConnection hazırla
if (!webRTCServiceRef.current?.getPeerConnection(remoteUserId)) {
await webRTCServiceRef.current?.createPeerConnection(remoteUserId)
}
const offer = await webRTCServiceRef.current!.createOffer(userId)
await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer)
// 🔑 Çakışmayı önle: sadece idsi küçük olan offer başlatır
if (user.id < remoteUserId) {
const offer = await webRTCServiceRef.current!.createOffer(remoteUserId)
await signalRServiceRef.current?.sendOffer(classSession.id, remoteUserId, offer)
}
},
)
// 🔑 ExistingParticipants handler
// 🔑 Odaya girdiğinde var olan katılımcılar
signalRServiceRef.current.setExistingParticipantsHandler(
async (
existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[],
@ -320,10 +322,12 @@ const RoomDetail: React.FC = () => {
if (!participant.isActive) continue
if (participant.userId === user.id) continue
// Statee ekle
setParticipants((prev) => {
const updated = [...prev]
if (!updated.find((p) => p.id === participant.userId)) {
updated.push({
if (prev.find((p) => p.id === participant.userId)) return prev
return [
...prev,
{
id: participant.userId,
name: participant.userName,
sessionId: classSession.id,
@ -331,19 +335,21 @@ const RoomDetail: React.FC = () => {
isAudioMuted: classSettings.defaultMicrophoneState === 'muted',
isVideoMuted: classSettings.defaultCameraState === 'off',
isActive: true,
})
}
return updated
},
]
})
// PeerConnection hazırla
if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) {
await webRTCServiceRef.current?.createPeerConnection(participant.userId)
}
// 🔑 Mesh: yeni gelen user da karşıya offer yollar
// 🔑 Çakışmayı önle
if (user.id < participant.userId) {
const offer = await webRTCServiceRef.current!.createOffer(participant.userId)
await signalRServiceRef.current?.sendOffer(classSession.id, participant.userId, offer)
}
}
},
)