From 08f5d11f281e1df3afeb39469774d6b5ceabccfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sat, 30 Aug 2025 15:05:50 +0300 Subject: [PATCH] classroom isActive Participant --- .../Classroom/ClassroomParticipantDto.cs | 1 + .../Classroom/ClassroomAppService.cs | 12 ++++++++- .../Classroom/ClassroomParticipant.cs | 5 +++- ....cs => 20250830114522_Initial.Designer.cs} | 5 +++- ...4_Initial.cs => 20250830114522_Initial.cs} | 1 + .../PlatformDbContextModelSnapshot.cs | 3 +++ .../Classroom/ClassroomHub.cs | 25 ++++++++++++++++--- ui/src/proxy/classroom/models.ts | 1 + ui/src/services/classroom/signalr.ts | 8 +++--- ui/src/views/classroom/RoomDetail.tsx | 19 ++++++++------ 10 files changed, 61 insertions(+), 19 deletions(-) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250829093104_Initial.Designer.cs => 20250830114522_Initial.Designer.cs} (99%) rename api/src/Kurs.Platform.EntityFrameworkCore/Migrations/{20250829093104_Initial.cs => 20250830114522_Initial.cs} (99%) diff --git a/api/src/Kurs.Platform.Application.Contracts/Classroom/ClassroomParticipantDto.cs b/api/src/Kurs.Platform.Application.Contracts/Classroom/ClassroomParticipantDto.cs index e6923318..d81bff4c 100644 --- a/api/src/Kurs.Platform.Application.Contracts/Classroom/ClassroomParticipantDto.cs +++ b/api/src/Kurs.Platform.Application.Contracts/Classroom/ClassroomParticipantDto.cs @@ -13,4 +13,5 @@ public class ClassroomParticipantDto public bool IsVideoMuted { get; set; } public bool IsHandRaised { get; set; } public DateTime JoinTime { get; set; } + public bool IsActive { get; set; } } diff --git a/api/src/Kurs.Platform.Application/Classroom/ClassroomAppService.cs b/api/src/Kurs.Platform.Application/Classroom/ClassroomAppService.cs index 0c23be74..80f245a7 100644 --- a/api/src/Kurs.Platform.Application/Classroom/ClassroomAppService.cs +++ b/api/src/Kurs.Platform.Application/Classroom/ClassroomAppService.cs @@ -187,6 +187,12 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService public async Task JoinClassAsync(Guid id) { var classSession = await _classSessionRepository.GetAsync(id); + if (classSession == null) + { + throw new InvalidOperationException("Class not found"); + } + + var classroomSettings = JsonSerializer.Deserialize(classSession.SettingsJson); if (classSession.ParticipantCount >= classSession.MaxParticipants) { @@ -206,7 +212,11 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService id, CurrentUser.Id, CurrentUser.Name, - false // isTeacher + false, // isTeacher + classroomSettings?.DefaultMicrophoneState == "muted", + classroomSettings?.DefaultCameraState == "off", + false, // HandRaised + true // isActive ); await _participantRepository.InsertAsync(participant); diff --git a/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs b/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs index af950dbd..9eaaada7 100644 --- a/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs +++ b/api/src/Kurs.Platform.Domain/Classroom/ClassroomParticipant.cs @@ -12,6 +12,7 @@ public class ClassroomParticipant : FullAuditedEntity public bool IsAudioMuted { get; set; } = false; public bool IsVideoMuted { get; set; } = false; public bool IsHandRaised { get; set; } = false; + public bool IsActive { get; set; } = true; public DateTime JoinTime { get; set; } public string ConnectionId { get; set; } @@ -30,7 +31,8 @@ public class ClassroomParticipant : FullAuditedEntity bool isTeacher, bool isAudioMuted, bool isVideoMuted, - bool isHandRaised + bool isHandRaised, + bool isActive ) : base(id) { SessionId = sessionId; @@ -40,6 +42,7 @@ public class ClassroomParticipant : FullAuditedEntity IsAudioMuted = isAudioMuted; IsVideoMuted = isVideoMuted; IsHandRaised = isHandRaised; + IsActive = isActive; JoinTime = DateTime.UtcNow; } diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.Designer.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.Designer.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.Designer.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.Designer.cs index 231a2544..39c758a5 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.Designer.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.Designer.cs @@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Kurs.Platform.Migrations { [DbContext(typeof(PlatformDbContext))] - [Migration("20250829093104_Initial")] + [Migration("20250830114522_Initial")] partial class Initial { /// @@ -1816,6 +1816,9 @@ namespace Kurs.Platform.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("IsActive") + .HasColumnType("bit"); + b.Property("IsAudioMuted") .HasColumnType("bit"); diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.cs similarity index 99% rename from api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.cs rename to api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.cs index 33df3eee..fa497082 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250829093104_Initial.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/20250830114522_Initial.cs @@ -1996,6 +1996,7 @@ namespace Kurs.Platform.Migrations IsAudioMuted = table.Column(type: "bit", nullable: false), IsVideoMuted = table.Column(type: "bit", nullable: false), IsHandRaised = table.Column(type: "bit", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), JoinTime = table.Column(type: "datetime2", nullable: false), ConnectionId = table.Column(type: "nvarchar(100)", maxLength: 100, nullable: true), CreationTime = table.Column(type: "datetime2", nullable: false), diff --git a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs index bf37c225..533d4959 100644 --- a/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs +++ b/api/src/Kurs.Platform.EntityFrameworkCore/Migrations/PlatformDbContextModelSnapshot.cs @@ -1813,6 +1813,9 @@ namespace Kurs.Platform.Migrations .HasColumnType("datetime2") .HasColumnName("DeletionTime"); + b.Property("IsActive") + .HasColumnType("bit"); + b.Property("IsAudioMuted") .HasColumnType("bit"); diff --git a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs index f1fcb547..d7ac5977 100644 --- a/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs +++ b/api/src/Kurs.Platform.HttpApi.Host/Classroom/ClassroomHub.cs @@ -67,7 +67,8 @@ public class ClassroomHub : Hub isTeacher, classroomSettings.DefaultMicrophoneState == "muted", classroomSettings.DefaultCameraState == "off", - false + false, + true ); participant.UpdateConnectionId(Context.ConnectionId); await _participantRepository.InsertAsync(participant, autoSave: true); @@ -96,14 +97,19 @@ public class ClassroomHub : Hub await Groups.AddToGroupAsync(Context.ConnectionId, sessionId.ToString()); // 🔑 Yeni katılana mevcut katılımcıları gönder - var existingParticipants = await _participantRepository.GetListAsync(x => x.SessionId == sessionId); + // 🔑 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 + IsTeacher = x.IsTeacher, + IsActive = x.IsActive // ✅ aktiflik bilgisini de gönder }) .ToList(); @@ -111,7 +117,7 @@ public class ClassroomHub : Hub // 🔑 Grup üyelerine yeni katılanı öğretmen bilgisiyle bildir await Clients.Group(sessionId.ToString()) - .SendAsync("ParticipantJoined", userId, userName, isTeacher); + .SendAsync("ParticipantJoined", userId, userName, isTeacher, true); } [HubMethodName("LeaveClass")] @@ -138,6 +144,17 @@ public class ClassroomHub : Hub 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()) diff --git a/ui/src/proxy/classroom/models.ts b/ui/src/proxy/classroom/models.ts index ea563b22..5d2cadb3 100644 --- a/ui/src/proxy/classroom/models.ts +++ b/ui/src/proxy/classroom/models.ts @@ -57,6 +57,7 @@ export interface ClassroomParticipantDto { isAudioMuted?: boolean isVideoMuted?: boolean isHandRaised?: boolean + isActive?: boolean stream?: MediaStream screenStream?: MediaStream isScreenSharing?: boolean diff --git a/ui/src/services/classroom/signalr.ts b/ui/src/services/classroom/signalr.ts index 26713c9e..fd03f660 100644 --- a/ui/src/services/classroom/signalr.ts +++ b/ui/src/services/classroom/signalr.ts @@ -6,7 +6,7 @@ export class SignalRService { private connection!: signalR.HubConnection private isConnected: boolean = false private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void - private onParticipantJoined?: (userId: string, name: string, isTeacher: boolean) => void + private onParticipantJoined?: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void private onParticipantLeft?: (userId: string) => void private onChatMessage?: (message: ClassroomChatDto) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void @@ -39,8 +39,8 @@ export class SignalRService { this.onAttendanceUpdate?.(record) }) - this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean) => { - this.onParticipantJoined?.(userId, name, isTeacher) + this.connection.on('ParticipantJoined', (userId: string, name: string, isTeacher: boolean, isActive: boolean) => { + this.onParticipantJoined?.(userId, name, isTeacher, isActive) }) this.connection.on('ParticipantLeft', (userId: string) => { @@ -391,7 +391,7 @@ export class SignalRService { this.onAttendanceUpdate = callback } - setParticipantJoinHandler(callback: (userId: string, name: string, isTeacher: boolean) => void) { + setParticipantJoinHandler(callback: (userId: string, name: string, isTeacher: boolean, isActive: boolean) => void) { this.onParticipantJoined = callback } diff --git a/ui/src/views/classroom/RoomDetail.tsx b/ui/src/views/classroom/RoomDetail.tsx index 31f53b6f..a7c0c6a2 100644 --- a/ui/src/views/classroom/RoomDetail.tsx +++ b/ui/src/views/classroom/RoomDetail.tsx @@ -262,8 +262,9 @@ const RoomDetail: React.FC = () => { }) signalRServiceRef.current.setParticipantJoinHandler( - async (userId: string, name: string, isTeacher: boolean) => { + async (userId: string, name: string, isTeacher: boolean, isActive: boolean) => { if (userId === user.id) return + if (!isActive) return // ❌ pasif kullanıcıyı ekleme console.log(`Participant joined: ${name}, isTeacher: ${isTeacher}`) @@ -276,6 +277,7 @@ const RoomDetail: React.FC = () => { isTeacher, isAudioMuted: classSettings.defaultMicrophoneState === 'muted', isVideoMuted: classSettings.defaultCameraState === 'off', + isActive: isActive, // ✅ state’de tut }) } @@ -291,8 +293,6 @@ const RoomDetail: React.FC = () => { await signalRServiceRef.current?.sendOffer(classSession.id, userId, offer) } })() - } else { - console.log('Teacher yok, offer başlatılmadı.') } return updated @@ -302,11 +302,14 @@ const RoomDetail: React.FC = () => { // 🔑 ExistingParticipants handler signalRServiceRef.current.setExistingParticipantsHandler( - async (existing: { userId: string; userName: string; isTeacher: boolean }[]) => { + async ( + existing: { userId: string; userName: string; isTeacher: boolean; isActive: boolean }[], + ) => { setParticipants((prev) => { let updated = [...prev] for (const participant of existing) { + if (!participant.isActive) continue // ❌ pasif kullanıcıyı alma if (participant.userId === user.id) continue if (!updated.find((p) => p.id === participant.userId)) { updated.push({ @@ -315,6 +318,7 @@ const RoomDetail: React.FC = () => { isTeacher: participant.isTeacher, isAudioMuted: classSettings.defaultMicrophoneState === 'muted', isVideoMuted: classSettings.defaultCameraState === 'off', + isActive: participant.isActive, // ✅ state’de tut }) } } @@ -324,6 +328,7 @@ const RoomDetail: React.FC = () => { if (teacherExists) { ;(async () => { for (const participant of existing) { + if (!participant.isActive) continue // ❌ pasifleri bağlama if (participant.userId === user.id) continue if (!webRTCServiceRef.current?.getPeerConnection(participant.userId)) { await webRTCServiceRef.current?.createPeerConnection(participant.userId) @@ -338,8 +343,6 @@ const RoomDetail: React.FC = () => { } } })() - } else { - console.log('Teacher yok, offer başlatılmadı (existing).') } return updated @@ -764,7 +767,7 @@ const RoomDetail: React.FC = () => { useEffect(() => { window.addEventListener('beforeunload', handleLeaveCall) - window.addEventListener('pagehide', handleLeaveCall) + window.addEventListener('pagehide', handleLeaveCall) return () => { window.removeEventListener('beforeunload', handleLeaveCall) @@ -812,7 +815,7 @@ const RoomDetail: React.FC = () => { {/* Video Grid */}
p.isActive)} localStream={user.role === 'observer' ? undefined : localStream} currentUserId={user.id} currentUserName={user.name}