Classroom : Büyük güncellemee

This commit is contained in:
Sedat ÖZTÜRK 2025-08-29 12:37:38 +03:00
parent b904b35506
commit cf3cb50e1a
25 changed files with 2156 additions and 1856 deletions

View file

@ -2,7 +2,7 @@ using System;
namespace Kurs.Platform.Classrooms; namespace Kurs.Platform.Classrooms;
public class ClassChatDto public class ClassroomChatDto
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
@ -10,5 +10,8 @@ public class ClassChatDto
public string SenderName { get; set; } public string SenderName { get; set; }
public string Message { get; set; } public string Message { get; set; }
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
public Guid? RecipientId { get; set; }
public string? RecipientName { get; set; }
public bool IsTeacher { get; set; } public bool IsTeacher { get; set; }
public string MessageType { get; set; }
} }

View file

@ -53,7 +53,7 @@ public class GetClassroomListDto : PagedAndSortedResultRequestDto
public Guid? TeacherId { get; set; } public Guid? TeacherId { get; set; }
} }
public class ClassAttendanceDto : EntityDto<Guid> public class ClassroomAttendanceDto : EntityDto<Guid>
{ {
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
public Guid StudentId { get; set; } public Guid StudentId { get; set; }

View file

@ -2,15 +2,15 @@ using System;
namespace Kurs.Platform.Classrooms; namespace Kurs.Platform.Classrooms;
public class ClassParticipantDto public class ClassroomParticipantDto
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
public Guid UserId { get; set; } public Guid UserId { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string UserEmail { get; set; }
public bool IsTeacher { get; set; } public bool IsTeacher { get; set; }
public bool IsAudioMuted { get; set; } public bool IsAudioMuted { get; set; }
public bool IsVideoMuted { get; set; } public bool IsVideoMuted { get; set; }
public bool IsHandRaised { get; set; }
public DateTime JoinTime { get; set; } public DateTime JoinTime { get; set; }
} }

View file

@ -17,5 +17,5 @@ public interface IClassroomAppService : IApplicationService
Task EndClassAsync(Guid id); Task EndClassAsync(Guid id);
Task<ClassroomDto> JoinClassAsync(Guid id); Task<ClassroomDto> JoinClassAsync(Guid id);
Task LeaveClassAsync(Guid id); Task LeaveClassAsync(Guid id);
Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId); Task<List<ClassroomAttendanceDto>> GetAttendanceAsync(Guid sessionId);
} }

View file

@ -15,13 +15,13 @@ namespace Kurs.Platform.Classrooms;
public class ClassroomAppService : PlatformAppService, IClassroomAppService public class ClassroomAppService : PlatformAppService, IClassroomAppService
{ {
private readonly IRepository<Classroom, Guid> _classSessionRepository; private readonly IRepository<Classroom, Guid> _classSessionRepository;
private readonly IRepository<ClassParticipant, Guid> _participantRepository; private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
private readonly IRepository<ClassAttandance, Guid> _attendanceRepository; private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
public ClassroomAppService( public ClassroomAppService(
IRepository<Classroom, Guid> classSessionRepository, IRepository<Classroom, Guid> classSessionRepository,
IRepository<ClassParticipant, Guid> participantRepository, IRepository<ClassroomParticipant, Guid> participantRepository,
IRepository<ClassAttandance, Guid> attendanceRepository) IRepository<ClassroomAttandance, Guid> attendanceRepository)
{ {
_classSessionRepository = classSessionRepository; _classSessionRepository = classSessionRepository;
_participantRepository = participantRepository; _participantRepository = participantRepository;
@ -198,19 +198,18 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
if (existingParticipant == null) if (existingParticipant == null)
{ {
// Add participant // Add participant
var participant = new ClassParticipant( var participant = new ClassroomParticipant(
GuidGenerator.Create(), GuidGenerator.Create(),
id, id,
CurrentUser.Id, CurrentUser.Id,
CurrentUser.Name, CurrentUser.Name,
CurrentUser.Email,
false // isTeacher false // isTeacher
); );
await _participantRepository.InsertAsync(participant); await _participantRepository.InsertAsync(participant);
// Create attendance record // Create attendance record
var attendance = new ClassAttandance( var attendance = new ClassroomAttandance(
GuidGenerator.Create(), GuidGenerator.Create(),
id, id,
CurrentUser.Id, CurrentUser.Id,
@ -257,7 +256,7 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
} }
} }
public async Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId) public async Task<List<ClassroomAttendanceDto>> GetAttendanceAsync(Guid sessionId)
{ {
var classSession = await _classSessionRepository.GetAsync(sessionId); var classSession = await _classSessionRepository.GetAsync(sessionId);
@ -270,6 +269,6 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
x => x.SessionId == sessionId x => x.SessionId == sessionId
); );
return ObjectMapper.Map<List<ClassAttandance>, List<ClassAttendanceDto>>(attendanceRecords); return ObjectMapper.Map<List<ClassroomAttandance>, List<ClassroomAttendanceDto>>(attendanceRecords);
} }
} }

View file

@ -8,8 +8,8 @@ public class ClassroomAutoMapperProfile : Profile
public ClassroomAutoMapperProfile() public ClassroomAutoMapperProfile()
{ {
CreateMap<Classroom, ClassroomDto>(); CreateMap<Classroom, ClassroomDto>();
CreateMap<ClassAttandance, ClassAttendanceDto>(); CreateMap<ClassroomAttandance, ClassroomAttendanceDto>();
CreateMap<ClassParticipant, ClassParticipantDto>(); CreateMap<ClassroomParticipant, ClassroomParticipantDto>();
CreateMap<ClassChat, ClassChatDto>(); CreateMap<ClassroomChat, ClassroomChatDto>();
} }
} }

View file

@ -20,15 +20,15 @@ public class Classroom : FullAuditedEntity<Guid>
public int ParticipantCount { get; set; } public int ParticipantCount { get; set; }
public string SettingsJson { get; set; } public string SettingsJson { get; set; }
public virtual ICollection<ClassParticipant> Participants { get; set; } public virtual ICollection<ClassroomParticipant> Participants { get; set; }
public virtual ICollection<ClassAttandance> AttendanceRecords { get; set; } public virtual ICollection<ClassroomAttandance> AttendanceRecords { get; set; }
public virtual ICollection<ClassChat> ChatMessages { get; set; } public virtual ICollection<ClassroomChat> ChatMessages { get; set; }
protected Classroom() protected Classroom()
{ {
Participants = new HashSet<ClassParticipant>(); Participants = new HashSet<ClassroomParticipant>();
AttendanceRecords = new HashSet<ClassAttandance>(); AttendanceRecords = new HashSet<ClassroomAttandance>();
ChatMessages = new HashSet<ClassChat>(); ChatMessages = new HashSet<ClassroomChat>();
} }
public Classroom( public Classroom(
@ -56,8 +56,8 @@ public class Classroom : FullAuditedEntity<Guid>
MaxParticipants = maxParticipants; MaxParticipants = maxParticipants;
SettingsJson = settingsJson; SettingsJson = settingsJson;
Participants = new HashSet<ClassParticipant>(); Participants = new HashSet<ClassroomParticipant>();
AttendanceRecords = new HashSet<ClassAttandance>(); AttendanceRecords = new HashSet<ClassroomAttandance>();
ChatMessages = new HashSet<ClassChat>(); ChatMessages = new HashSet<ClassroomChat>();
} }
} }

View file

@ -3,7 +3,7 @@ using Volo.Abp.Domain.Entities.Auditing;
namespace Kurs.Platform.Entities; namespace Kurs.Platform.Entities;
public class ClassAttandance : FullAuditedEntity<Guid> public class ClassroomAttandance : FullAuditedEntity<Guid>
{ {
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
public Guid? StudentId { get; set; } public Guid? StudentId { get; set; }
@ -15,11 +15,11 @@ public class ClassAttandance : FullAuditedEntity<Guid>
// Navigation properties // Navigation properties
public virtual Classroom Session { get; set; } public virtual Classroom Session { get; set; }
protected ClassAttandance() protected ClassroomAttandance()
{ {
} }
public ClassAttandance( public ClassroomAttandance(
Guid id, Guid id,
Guid sessionId, Guid sessionId,
Guid? studentId, Guid? studentId,

View file

@ -3,36 +3,45 @@ using Volo.Abp.Domain.Entities.Auditing;
namespace Kurs.Platform.Entities; namespace Kurs.Platform.Entities;
public class ClassChat : FullAuditedEntity<Guid> public class ClassroomChat : FullAuditedEntity<Guid>
{ {
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
public Guid? SenderId { get; set; } public Guid? SenderId { get; set; }
public string SenderName { get; set; } public string SenderName { get; set; }
public string Message { get; set; } public string Message { get; set; }
public DateTime Timestamp { get; set; } public DateTime Timestamp { get; set; }
public Guid? RecipientId { get; set; }
public string? RecipientName { get; set; }
public bool IsTeacher { get; set; } public bool IsTeacher { get; set; }
public string MessageType { get; set; }
// Navigation properties // Navigation properties
public virtual Classroom Session { get; set; } public virtual Classroom Session { get; set; }
protected ClassChat() protected ClassroomChat()
{ {
} }
public ClassChat( public ClassroomChat(
Guid id, Guid id,
Guid sessionId, Guid sessionId,
Guid? senderId, Guid? senderId,
string senderName, string senderName,
string message, string message,
bool isTeacher Guid? recipientId,
string recipientName,
bool isTeacher,
string messageType
) : base(id) ) : base(id)
{ {
SessionId = sessionId; SessionId = sessionId;
SenderId = senderId; SenderId = senderId;
SenderName = senderName; SenderName = senderName;
Message = message; Message = message;
RecipientId = recipientId;
RecipientName = recipientName;
IsTeacher = isTeacher; IsTeacher = isTeacher;
Timestamp = DateTime.UtcNow; Timestamp = DateTime.UtcNow;
MessageType = messageType;
} }
} }

View file

@ -3,41 +3,40 @@ using Volo.Abp.Domain.Entities.Auditing;
namespace Kurs.Platform.Entities; namespace Kurs.Platform.Entities;
public class ClassParticipant : FullAuditedEntity<Guid> public class ClassroomParticipant : FullAuditedEntity<Guid>
{ {
public Guid SessionId { get; set; } public Guid SessionId { get; set; }
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
public string UserName { get; set; } public string UserName { get; set; }
public string UserEmail { get; set; }
public bool IsTeacher { get; set; } public bool IsTeacher { get; set; }
public bool IsAudioMuted { get; set; } public bool IsAudioMuted { get; set; } = false;
public bool IsVideoMuted { get; set; } public bool IsVideoMuted { get; set; } = false;
public bool IsHandRaised { get; set; } = false;
public DateTime JoinTime { get; set; } public DateTime JoinTime { get; set; }
public string ConnectionId { get; set; } public string ConnectionId { get; set; }
// Navigation properties // Navigation properties
public virtual Classroom Session { get; set; } public virtual Classroom Session { get; set; }
protected ClassParticipant() protected ClassroomParticipant()
{ {
} }
public ClassParticipant( public ClassroomParticipant(
Guid id, Guid id,
Guid sessionId, Guid sessionId,
Guid? userId, Guid? userId,
string userName, string userName,
string userEmail,
bool isTeacher bool isTeacher
) : base(id) ) : base(id)
{ {
SessionId = sessionId; SessionId = sessionId;
UserId = userId; UserId = userId;
UserName = userName; UserName = userName;
UserEmail = userEmail;
IsTeacher = isTeacher; IsTeacher = isTeacher;
IsAudioMuted = false; IsAudioMuted = false;
IsVideoMuted = false; IsVideoMuted = false;
IsHandRaised = false;
JoinTime = DateTime.UtcNow; JoinTime = DateTime.UtcNow;
} }

View file

@ -98,9 +98,9 @@ public class PlatformDbContext :
public DbSet<Service> Services { get; set; } public DbSet<Service> Services { get; set; }
public DbSet<Classroom> ClassSessions { get; set; } public DbSet<Classroom> ClassSessions { get; set; }
public DbSet<ClassParticipant> Participants { get; set; } public DbSet<ClassroomParticipant> Participants { get; set; }
public DbSet<ClassAttandance> AttendanceRecords { get; set; } public DbSet<ClassroomAttandance> AttendanceRecords { get; set; }
public DbSet<ClassChat> ChatMessages { get; set; } public DbSet<ClassroomChat> ChatMessages { get; set; }
#region Entities from the modules #region Entities from the modules
@ -913,13 +913,12 @@ public class PlatformDbContext :
}); });
// Participant // Participant
builder.Entity<ClassParticipant>(b => builder.Entity<ClassroomParticipant>(b =>
{ {
b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassParticipant), PlatformConsts.DbSchema); b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassroomParticipant), PlatformConsts.DbSchema);
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.UserName).IsRequired().HasMaxLength(100); b.Property(x => x.UserName).IsRequired().HasMaxLength(100);
b.Property(x => x.UserEmail).HasMaxLength(200);
b.Property(x => x.ConnectionId).HasMaxLength(100); b.Property(x => x.ConnectionId).HasMaxLength(100);
b.HasIndex(x => x.SessionId); b.HasIndex(x => x.SessionId);
@ -928,9 +927,9 @@ public class PlatformDbContext :
}); });
// AttendanceRecord // AttendanceRecord
builder.Entity<ClassAttandance>(b => builder.Entity<ClassroomAttandance>(b =>
{ {
b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassAttandance), PlatformConsts.DbSchema); b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassroomAttandance), PlatformConsts.DbSchema);
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.StudentName).IsRequired().HasMaxLength(100); b.Property(x => x.StudentName).IsRequired().HasMaxLength(100);
@ -941,9 +940,9 @@ public class PlatformDbContext :
}); });
// ChatMessage // ChatMessage
builder.Entity<ClassChat>(b => builder.Entity<ClassroomChat>(b =>
{ {
b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassChat), PlatformConsts.DbSchema); b.ToTable(PlatformConsts.DbTablePrefix + nameof(ClassroomChat), PlatformConsts.DbSchema);
b.ConfigureByConvention(); b.ConfigureByConvention();
b.Property(x => x.SenderName).IsRequired().HasMaxLength(100); b.Property(x => x.SenderName).IsRequired().HasMaxLength(100);

View file

@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
namespace Kurs.Platform.Migrations namespace Kurs.Platform.Migrations
{ {
[DbContext(typeof(PlatformDbContext))] [DbContext(typeof(PlatformDbContext))]
[Migration("20250828112303_Initial")] [Migration("20250829093104_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@ -1559,219 +1559,6 @@ namespace Kurs.Platform.Migrations
b.ToTable("PCity", (string)null); b.ToTable("PCity", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("LeaveTime")
.HasColumnType("datetime2");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("StudentId")
.HasColumnType("uniqueidentifier");
b.Property<string>("StudentName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("TotalDurationMinutes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("JoinTime");
b.HasIndex("SessionId");
b.HasIndex("StudentId");
b.ToTable("PClassAttandance", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<Guid?>("SenderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SenderName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("SenderId");
b.HasIndex("SessionId");
b.HasIndex("Timestamp");
b.ToTable("PClassChat", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConnectionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<bool>("IsVideoMuted")
.HasColumnType("bit");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserEmail")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.HasIndex("SessionId", "UserId")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("PClassParticipant", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Classroom", b => modelBuilder.Entity("Kurs.Platform.Entities.Classroom", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -1861,6 +1648,227 @@ namespace Kurs.Platform.Migrations
b.ToTable("PClassroom", (string)null); b.ToTable("PClassroom", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("LeaveTime")
.HasColumnType("datetime2");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("StudentId")
.HasColumnType("uniqueidentifier");
b.Property<string>("StudentName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("TotalDurationMinutes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("JoinTime");
b.HasIndex("SessionId");
b.HasIndex("StudentId");
b.ToTable("PClassroomAttandance", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<string>("MessageType")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("RecipientId")
.HasColumnType("uniqueidentifier");
b.Property<string>("RecipientName")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("SenderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SenderName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("SenderId");
b.HasIndex("SessionId");
b.HasIndex("Timestamp");
b.ToTable("PClassroomChat", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConnectionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsHandRaised")
.HasColumnType("bit");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<bool>("IsVideoMuted")
.HasColumnType("bit");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.HasIndex("SessionId", "UserId")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("PClassroomParticipant", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b => modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -6558,7 +6566,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Country"); b.Navigation("Country");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("AttendanceRecords") .WithMany("AttendanceRecords")
@ -6569,7 +6577,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Session"); b.Navigation("Session");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("ChatMessages") .WithMany("ChatMessages")
@ -6580,7 +6588,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Session"); b.Navigation("Session");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("Participants") .WithMany("Participants")

View file

@ -1922,7 +1922,7 @@ namespace Kurs.Platform.Migrations
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "PClassAttandance", name: "PClassroomAttandance",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
@ -1942,9 +1942,9 @@ namespace Kurs.Platform.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_PClassAttandance", x => x.Id); table.PrimaryKey("PK_PClassroomAttandance", x => x.Id);
table.ForeignKey( table.ForeignKey(
name: "FK_PClassAttandance_PClassroom_SessionId", name: "FK_PClassroomAttandance_PClassroom_SessionId",
column: x => x.SessionId, column: x => x.SessionId,
principalTable: "PClassroom", principalTable: "PClassroom",
principalColumn: "Id", principalColumn: "Id",
@ -1952,7 +1952,7 @@ namespace Kurs.Platform.Migrations
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "PClassChat", name: "PClassroomChat",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
@ -1961,7 +1961,10 @@ namespace Kurs.Platform.Migrations
SenderName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), SenderName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
Message = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false), Message = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, nullable: false),
Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false), Timestamp = table.Column<DateTime>(type: "datetime2", nullable: false),
RecipientId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
RecipientName = table.Column<string>(type: "nvarchar(max)", nullable: true),
IsTeacher = table.Column<bool>(type: "bit", nullable: false), IsTeacher = table.Column<bool>(type: "bit", nullable: false),
MessageType = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true), LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
@ -1972,9 +1975,9 @@ namespace Kurs.Platform.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_PClassChat", x => x.Id); table.PrimaryKey("PK_PClassroomChat", x => x.Id);
table.ForeignKey( table.ForeignKey(
name: "FK_PClassChat_PClassroom_SessionId", name: "FK_PClassroomChat_PClassroom_SessionId",
column: x => x.SessionId, column: x => x.SessionId,
principalTable: "PClassroom", principalTable: "PClassroom",
principalColumn: "Id", principalColumn: "Id",
@ -1982,17 +1985,17 @@ namespace Kurs.Platform.Migrations
}); });
migrationBuilder.CreateTable( migrationBuilder.CreateTable(
name: "PClassParticipant", name: "PClassroomParticipant",
columns: table => new columns: table => new
{ {
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false), Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
SessionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false), SessionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true), UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
UserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false), UserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
UserEmail = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
IsTeacher = table.Column<bool>(type: "bit", nullable: false), IsTeacher = table.Column<bool>(type: "bit", nullable: false),
IsAudioMuted = table.Column<bool>(type: "bit", nullable: false), IsAudioMuted = table.Column<bool>(type: "bit", nullable: false),
IsVideoMuted = table.Column<bool>(type: "bit", nullable: false), IsVideoMuted = table.Column<bool>(type: "bit", nullable: false),
IsHandRaised = table.Column<bool>(type: "bit", nullable: false),
JoinTime = table.Column<DateTime>(type: "datetime2", nullable: false), JoinTime = table.Column<DateTime>(type: "datetime2", nullable: false),
ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true), ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false), CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
@ -2005,9 +2008,9 @@ namespace Kurs.Platform.Migrations
}, },
constraints: table => constraints: table =>
{ {
table.PrimaryKey("PK_PClassParticipant", x => x.Id); table.PrimaryKey("PK_PClassroomParticipant", x => x.Id);
table.ForeignKey( table.ForeignKey(
name: "FK_PClassParticipant_PClassroom_SessionId", name: "FK_PClassroomParticipant_PClassroom_SessionId",
column: x => x.SessionId, column: x => x.SessionId,
principalTable: "PClassroom", principalTable: "PClassroom",
principalColumn: "Id", principalColumn: "Id",
@ -2991,53 +2994,6 @@ namespace Kurs.Platform.Migrations
columns: new[] { "CountryCode", "Code" }, columns: new[] { "CountryCode", "Code" },
unique: true); unique: true);
migrationBuilder.CreateIndex(
name: "IX_PClassAttandance_JoinTime",
table: "PClassAttandance",
column: "JoinTime");
migrationBuilder.CreateIndex(
name: "IX_PClassAttandance_SessionId",
table: "PClassAttandance",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassAttandance_StudentId",
table: "PClassAttandance",
column: "StudentId");
migrationBuilder.CreateIndex(
name: "IX_PClassChat_SenderId",
table: "PClassChat",
column: "SenderId");
migrationBuilder.CreateIndex(
name: "IX_PClassChat_SessionId",
table: "PClassChat",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassChat_Timestamp",
table: "PClassChat",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_PClassParticipant_SessionId",
table: "PClassParticipant",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassParticipant_SessionId_UserId",
table: "PClassParticipant",
columns: new[] { "SessionId", "UserId" },
unique: true,
filter: "[UserId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_PClassParticipant_UserId",
table: "PClassParticipant",
column: "UserId");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_PClassroom_ScheduledStartTime", name: "IX_PClassroom_ScheduledStartTime",
table: "PClassroom", table: "PClassroom",
@ -3048,6 +3004,53 @@ namespace Kurs.Platform.Migrations
table: "PClassroom", table: "PClassroom",
column: "TeacherId"); column: "TeacherId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomAttandance_JoinTime",
table: "PClassroomAttandance",
column: "JoinTime");
migrationBuilder.CreateIndex(
name: "IX_PClassroomAttandance_SessionId",
table: "PClassroomAttandance",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomAttandance_StudentId",
table: "PClassroomAttandance",
column: "StudentId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomChat_SenderId",
table: "PClassroomChat",
column: "SenderId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomChat_SessionId",
table: "PClassroomChat",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomChat_Timestamp",
table: "PClassroomChat",
column: "Timestamp");
migrationBuilder.CreateIndex(
name: "IX_PClassroomParticipant_SessionId",
table: "PClassroomParticipant",
column: "SessionId");
migrationBuilder.CreateIndex(
name: "IX_PClassroomParticipant_SessionId_UserId",
table: "PClassroomParticipant",
columns: new[] { "SessionId", "UserId" },
unique: true,
filter: "[UserId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_PClassroomParticipant_UserId",
table: "PClassroomParticipant",
column: "UserId");
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_PCountry_Code", name: "IX_PCountry_Code",
table: "PCountry", table: "PCountry",
@ -3303,13 +3306,13 @@ namespace Kurs.Platform.Migrations
name: "PChart"); name: "PChart");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "PClassAttandance"); name: "PClassroomAttandance");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "PClassChat"); name: "PClassroomChat");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "PClassParticipant"); name: "PClassroomParticipant");
migrationBuilder.DropTable( migrationBuilder.DropTable(
name: "PContact"); name: "PContact");

View file

@ -1556,219 +1556,6 @@ namespace Kurs.Platform.Migrations
b.ToTable("PCity", (string)null); b.ToTable("PCity", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("LeaveTime")
.HasColumnType("datetime2");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("StudentId")
.HasColumnType("uniqueidentifier");
b.Property<string>("StudentName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("TotalDurationMinutes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("JoinTime");
b.HasIndex("SessionId");
b.HasIndex("StudentId");
b.ToTable("PClassAttandance", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<Guid?>("SenderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SenderName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("SenderId");
b.HasIndex("SessionId");
b.HasIndex("Timestamp");
b.ToTable("PClassChat", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConnectionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<bool>("IsVideoMuted")
.HasColumnType("bit");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserEmail")
.HasMaxLength(200)
.HasColumnType("nvarchar(200)");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.HasIndex("SessionId", "UserId")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("PClassParticipant", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Classroom", b => modelBuilder.Entity("Kurs.Platform.Entities.Classroom", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -1858,6 +1645,227 @@ namespace Kurs.Platform.Migrations
b.ToTable("PClassroom", (string)null); b.ToTable("PClassroom", (string)null);
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<DateTime?>("LeaveTime")
.HasColumnType("datetime2");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("StudentId")
.HasColumnType("uniqueidentifier");
b.Property<string>("StudentName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<int>("TotalDurationMinutes")
.HasColumnType("int");
b.HasKey("Id");
b.HasIndex("JoinTime");
b.HasIndex("SessionId");
b.HasIndex("StudentId");
b.ToTable("PClassroomAttandance", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<string>("Message")
.IsRequired()
.HasMaxLength(2000)
.HasColumnType("nvarchar(2000)");
b.Property<string>("MessageType")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("RecipientId")
.HasColumnType("uniqueidentifier");
b.Property<string>("RecipientName")
.HasColumnType("nvarchar(max)");
b.Property<Guid?>("SenderId")
.HasColumnType("uniqueidentifier");
b.Property<string>("SenderName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<DateTime>("Timestamp")
.HasColumnType("datetime2");
b.HasKey("Id");
b.HasIndex("SenderId");
b.HasIndex("SessionId");
b.HasIndex("Timestamp");
b.ToTable("PClassroomChat", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
{
b.Property<Guid>("Id")
.HasColumnType("uniqueidentifier");
b.Property<string>("ConnectionId")
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.Property<DateTime>("CreationTime")
.HasColumnType("datetime2")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnType("uniqueidentifier")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnType("uniqueidentifier")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnType("datetime2")
.HasColumnName("DeletionTime");
b.Property<bool>("IsAudioMuted")
.HasColumnType("bit");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnType("bit")
.HasDefaultValue(false)
.HasColumnName("IsDeleted");
b.Property<bool>("IsHandRaised")
.HasColumnType("bit");
b.Property<bool>("IsTeacher")
.HasColumnType("bit");
b.Property<bool>("IsVideoMuted")
.HasColumnType("bit");
b.Property<DateTime>("JoinTime")
.HasColumnType("datetime2");
b.Property<DateTime?>("LastModificationTime")
.HasColumnType("datetime2")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnType("uniqueidentifier")
.HasColumnName("LastModifierId");
b.Property<Guid>("SessionId")
.HasColumnType("uniqueidentifier");
b.Property<Guid?>("UserId")
.HasColumnType("uniqueidentifier");
b.Property<string>("UserName")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("nvarchar(100)");
b.HasKey("Id");
b.HasIndex("SessionId");
b.HasIndex("UserId");
b.HasIndex("SessionId", "UserId")
.IsUnique()
.HasFilter("[UserId] IS NOT NULL");
b.ToTable("PClassroomParticipant", (string)null);
});
modelBuilder.Entity("Kurs.Platform.Entities.Contact", b => modelBuilder.Entity("Kurs.Platform.Entities.Contact", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@ -6555,7 +6563,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Country"); b.Navigation("Country");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("AttendanceRecords") .WithMany("AttendanceRecords")
@ -6566,7 +6574,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Session"); b.Navigation("Session");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("ChatMessages") .WithMany("ChatMessages")
@ -6577,7 +6585,7 @@ namespace Kurs.Platform.Migrations
b.Navigation("Session"); b.Navigation("Session");
}); });
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b => modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
{ {
b.HasOne("Kurs.Platform.Entities.Classroom", "Session") b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
.WithMany("Participants") .WithMany("Participants")

View file

@ -14,16 +14,18 @@ namespace Kurs.Platform.SignalR.Hubs;
public class ClassroomHub : Hub public class ClassroomHub : Hub
{ {
private readonly IRepository<Classroom, Guid> _classSessionRepository; private readonly IRepository<Classroom, Guid> _classSessionRepository;
private readonly IRepository<ClassParticipant, Guid> _participantRepository; private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
private readonly IRepository<ClassChat, Guid> _chatMessageRepository; private readonly IRepository<ClassroomChat, Guid> _chatMessageRepository;
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
private readonly ILogger<ClassroomHub> _logger; private readonly ILogger<ClassroomHub> _logger;
private readonly IGuidGenerator _guidGenerator; private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentUser _currentUser; private readonly ICurrentUser _currentUser;
public ClassroomHub( public ClassroomHub(
IRepository<Classroom, Guid> classSessionRepository, IRepository<Classroom, Guid> classSessionRepository,
IRepository<ClassParticipant, Guid> participantRepository, IRepository<ClassroomParticipant, Guid> participantRepository,
IRepository<ClassChat, Guid> chatMessageRepository, IRepository<ClassroomChat, Guid> chatMessageRepository,
IRepository<ClassroomAttandance, Guid> attendanceRepository,
ILogger<ClassroomHub> logger, ILogger<ClassroomHub> logger,
IGuidGenerator guidGenerator, IGuidGenerator guidGenerator,
ICurrentUser currentUser) ICurrentUser currentUser)
@ -31,91 +33,93 @@ public class ClassroomHub : Hub
_classSessionRepository = classSessionRepository; _classSessionRepository = classSessionRepository;
_participantRepository = participantRepository; _participantRepository = participantRepository;
_chatMessageRepository = chatMessageRepository; _chatMessageRepository = chatMessageRepository;
_attendanceRepository = attendanceRepository;
_logger = logger; _logger = logger;
_guidGenerator = guidGenerator; _guidGenerator = guidGenerator;
_currentUser = currentUser; _currentUser = currentUser;
} }
[HubMethodName("JoinClass")] [HubMethodName("JoinClass")]
public async Task JoinClassAsync(Guid sessionId, string userName) public async Task JoinClassAsync(Guid sessionId, Guid userId, string userName, bool isTeacher)
{ {
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( var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == _currentUser.Id x => x.SessionId == sessionId && x.UserId == userId
); );
if (participant != null) if (participant == null)
{
participant = new ClassroomParticipant(
_guidGenerator.Create(),
sessionId,
userId,
userName,
isTeacher
);
participant.UpdateConnectionId(Context.ConnectionId);
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); participant.UpdateConnectionId(Context.ConnectionId);
await _participantRepository.UpdateAsync(participant); await _participantRepository.UpdateAsync(participant, autoSave: true);
} }
// Notify others // 🔑 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());
await Clients.Group(sessionId.ToString()) await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantJoined", _currentUser.Id, userName); .SendAsync("ParticipantJoined", userId, userName);
_logger.LogInformation($"User {userName} joined class {sessionId}");
} }
[HubMethodName("LeaveClass")] [HubMethodName("LeaveClass")]
public async Task LeaveClassAsync(Guid sessionId) public async Task LeaveClassAsync(Guid sessionId)
{ {
await Groups.RemoveFromGroupAsync(Context.ConnectionId, sessionId.ToString()); 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);
}
}
await Clients.Group(sessionId.ToString()) await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantLeft", _currentUser); .SendAsync("ParticipantLeft", _currentUser);
_logger.LogInformation($"User {_currentUser} left class {sessionId}"); _logger.LogInformation($"User {_currentUser} left class {sessionId}");
} }
public async Task SendSignalingMessageAsync(SignalingMessageDto message) [HubMethodName("MuteParticipant")]
{ public async Task MuteParticipantAsync(Guid sessionId, Guid userId, bool isMuted, bool isTeacher)
// 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)
{
var userName = _currentUser.UserName;
var userId = _currentUser.Id;
// 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
var chatMessage = new ClassChat(
_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( var teacherParticipant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == _currentUser.Id x => x.SessionId == sessionId && x.UserId == _currentUser.Id
@ -128,59 +132,264 @@ public class ClassroomHub : Hub
} }
var participant = await _participantRepository.FirstOrDefaultAsync( var participant = await _participantRepository.FirstOrDefaultAsync(
x => x.SessionId == sessionId && x.UserId == participantId x => x.SessionId == sessionId && x.UserId == userId
); );
if (participant != null) if (participant != null)
{ {
if (isMuted) if (isMuted) participant.MuteAudio();
participant.MuteAudio(); else participant.UnmuteAudio();
else
participant.UnmuteAudio();
await _participantRepository.UpdateAsync(participant); await _participantRepository.UpdateAsync(participant, autoSave: true);
// Notify the participant and others
await Clients.Group(sessionId.ToString()) await Clients.Group(sessionId.ToString())
.SendAsync("ParticipantMuted", participantId, isMuted); .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)
{
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 handRaiseId)
{
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", handRaiseId);
}
[HubMethodName("DismissHandRaise")]
public async Task DismissHandRaiseAsync(Guid sessionId, Guid handRaiseId)
{
await Clients.Group(sessionId.ToString()).SendAsync("HandRaiseDismissed", handRaiseId);
}
public override async Task OnDisconnectedAsync(Exception exception) public override async Task OnDisconnectedAsync(Exception exception)
{ {
try try
{ {
// bağlantı gerçekten iptal edilmişse DB sorgusu çalıştırma
if (Context.ConnectionAborted.IsCancellationRequested) if (Context.ConnectionAborted.IsCancellationRequested)
return; return;
var userId = _currentUser.Id; var userId = _currentUser.Id;
if (userId.HasValue) if (userId.HasValue)
{ {
// 🔑 1. Katılımcı listesi
var participants = await _participantRepository var participants = await _participantRepository
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId) .GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId);
.ConfigureAwait(false);
foreach (var participant in participants) 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()) await Clients.Group(participant.SessionId.ToString())
.SendAsync("ParticipantLeft", userId.Value) .SendAsync("ParticipantLeft", userId.Value);
.ConfigureAwait(false);
} }
} }
} }
catch (TaskCanceledException) catch (TaskCanceledException)
{ {
// bağlantı kapandığında doğal, error yerine debug seviyesinde loglayın
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted)."); _logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
} }
catch (Exception ex) catch (Exception ex)
{ {
// beklenmeyen hataları error olarak loglayın
_logger.LogError(ex, "OnDisconnectedAsync hata"); _logger.LogError(ex, "OnDisconnectedAsync hata");
} }
await base.OnDisconnectedAsync(exception).ConfigureAwait(false); await base.OnDisconnectedAsync(exception);
} }
} }

View file

@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
"revision": "3ca0b8505b4bec776b69afdba2768812" "revision": "3ca0b8505b4bec776b69afdba2768812"
}, { }, {
"url": "index.html", "url": "index.html",
"revision": "0.b9bfk61okp" "revision": "0.48gll4p3s3o"
}], {}); }], {});
workbox.cleanupOutdatedCaches(); workbox.cleanupOutdatedCaches();
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {

View file

@ -1,9 +1,9 @@
import { ClassAttendanceDto } from '@/proxy/classroom/models' import { ClassroomAttendanceDto } from '@/proxy/classroom/models'
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { FaClock, FaUsers } from 'react-icons/fa' import { FaClock, FaUsers } from 'react-icons/fa'
interface AttendancePanelProps { interface AttendancePanelProps {
attendanceRecords: ClassAttendanceDto[] attendanceRecords: ClassroomAttendanceDto[]
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void
} }

View file

@ -1,10 +1,10 @@
import { ClassChatDto } from '@/proxy/classroom/models' import { ClassroomChatDto } from '@/proxy/classroom/models'
import { useStoreState } from '@/store/store' import { useStoreState } from '@/store/store'
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useRef, useEffect } from 'react'
import { FaPaperPlane, FaComments, FaTimes, FaUsers, FaUser, FaBullhorn } from 'react-icons/fa' import { FaPaperPlane, FaComments, FaTimes, FaUsers, FaUser, FaBullhorn } from 'react-icons/fa'
interface ChatPanelProps { interface ChatPanelProps {
messages: ClassChatDto[] messages: ClassroomChatDto[]
isTeacher: boolean isTeacher: boolean
isOpen: boolean isOpen: boolean
onClose: () => void onClose: () => void

View file

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
import { FaMicrophoneSlash, FaExpand, FaUserTimes } from 'react-icons/fa' import { FaMicrophoneSlash, FaExpand, FaUserTimes } from 'react-icons/fa'
import { VideoPlayer } from './VideoPlayer' import { VideoPlayer } from './VideoPlayer'
import { ClassParticipantDto, VideoLayoutDto } from '@/proxy/classroom/models' import { ClassroomParticipantDto, VideoLayoutDto } from '@/proxy/classroom/models'
interface ParticipantGridProps { interface ParticipantGridProps {
participants: ClassParticipantDto[] participants: ClassroomParticipantDto[]
localStream?: MediaStream localStream?: MediaStream
currentUserId: string currentUserId: string
currentUserName: string currentUserName: string
@ -14,7 +14,7 @@ interface ParticipantGridProps {
onToggleAudio: () => void onToggleAudio: () => void
onToggleVideo: () => void onToggleVideo: () => void
onLeaveCall: () => void onLeaveCall: () => void
onMuteParticipant?: (participantId: string, isMuted: boolean) => void onMuteParticipant?: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
layout: VideoLayoutDto layout: VideoLayoutDto
focusedParticipant?: string focusedParticipant?: string
onParticipantFocus?: (participantId: string | undefined) => void onParticipantFocus?: (participantId: string | undefined) => void
@ -211,7 +211,7 @@ export const ParticipantGrid: React.FC<ParticipantGridProps> = ({
} }
const renderParticipant = ( const renderParticipant = (
participant: ClassParticipantDto, participant: ClassroomParticipantDto,
isMain: boolean = false, isMain: boolean = false,
isSmall: boolean = false, isSmall: boolean = false,
) => ( ) => (
@ -243,7 +243,7 @@ export const ParticipantGrid: React.FC<ParticipantGridProps> = ({
<button <button
onClick={(e) => { onClick={(e) => {
e.stopPropagation() e.stopPropagation()
onMuteParticipant?.(participant.id, !participant.isAudioMuted) onMuteParticipant?.(participant.id, !participant.isAudioMuted, isTeacher)
}} }}
className={`p-1 rounded-full text-white text-xs ${ className={`p-1 rounded-full text-white text-xs ${
participant.isAudioMuted ? 'bg-red-600' : 'bg-gray-600 hover:bg-gray-700' participant.isAudioMuted ? 'bg-red-600' : 'bg-gray-600 hover:bg-gray-700'

View file

@ -39,7 +39,7 @@ export interface ClassroomSettingsDto {
autoMuteNewParticipants: boolean autoMuteNewParticipants: boolean
} }
export interface ClassAttendanceDto { export interface ClassroomAttendanceDto {
id: string id: string
sessionId: string sessionId: string
studentId: string studentId: string
@ -49,16 +49,7 @@ export interface ClassAttendanceDto {
totalDurationMinutes: number totalDurationMinutes: number
} }
export type MediaType = 'audio' | 'video' | 'screen' export interface ClassroomParticipantDto {
export interface SignalingMessageDto {
type: MediaType
fromUserId: string
toUserId: string
data: any
}
export interface ClassParticipantDto {
id: string id: string
name: string name: string
isTeacher: boolean isTeacher: boolean
@ -73,7 +64,7 @@ export interface ClassParticipantDto {
export type messageType = 'public' | 'private' | 'announcement' export type messageType = 'public' | 'private' | 'announcement'
export interface ClassChatDto { export interface ClassroomChatDto {
id: string id: string
senderId: string senderId: string
senderName: string senderName: string

View file

@ -1,20 +1,14 @@
import { import { ClassroomAttendanceDto, ClassroomChatDto, HandRaiseDto } from '@/proxy/classroom/models'
ClassAttendanceDto,
ClassChatDto,
HandRaiseDto,
SignalingMessageDto,
} from '@/proxy/classroom/models'
import { store } from '@/store/store' import { store } from '@/store/store'
import * as signalR from '@microsoft/signalr' import * as signalR from '@microsoft/signalr'
export class SignalRService { export class SignalRService {
private connection!: signalR.HubConnection private connection!: signalR.HubConnection
private isConnected: boolean = false private isConnected: boolean = false
private onSignalingMessage?: (message: SignalingMessageDto) => void private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
private onAttendanceUpdate?: (record: ClassAttendanceDto) => void
private onParticipantJoined?: (userId: string, name: string) => void private onParticipantJoined?: (userId: string, name: string) => void
private onParticipantLeft?: (userId: string) => void private onParticipantLeft?: (userId: string) => void
private onChatMessage?: (message: ClassChatDto) => void private onChatMessage?: (message: ClassroomChatDto) => void
private onParticipantMuted?: (userId: string, isMuted: boolean) => void private onParticipantMuted?: (userId: string, isMuted: boolean) => void
private onHandRaiseReceived?: (handRaise: HandRaiseDto) => void private onHandRaiseReceived?: (handRaise: HandRaiseDto) => void
private onHandRaiseDismissed?: (handRaiseId: string) => void private onHandRaiseDismissed?: (handRaiseId: string) => void
@ -38,11 +32,7 @@ export class SignalRService {
private setupEventHandlers() { private setupEventHandlers() {
if (!this.connection) return if (!this.connection) return
this.connection.on('ReceiveSignalingMessage', (message: SignalingMessageDto) => { this.connection.on('AttendanceUpdated', (record: ClassroomAttendanceDto) => {
this.onSignalingMessage?.(message)
})
this.connection.on('AttendanceUpdated', (record: ClassAttendanceDto) => {
this.onAttendanceUpdate?.(record) this.onAttendanceUpdate?.(record)
}) })
@ -95,16 +85,21 @@ export class SignalRService {
} }
} }
async joinClass(sessionId: string, userName: string): Promise<void> { async joinClass(
sessionId: string,
userId: string,
userName: string,
isTeacher: boolean,
): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection join class for', userName) console.log('Error starting SignalR connection join class for', userName)
// Simulate successful join in demo mode
// Don't auto-add participants in demo mode - let manual simulation handle this
return return
} }
console.log('Joining class session:', sessionId, 'as', userName, 'isTeacher:', isTeacher)
try { try {
await this.connection.invoke('JoinClass', sessionId, userName) await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher)
} catch (error) { } catch (error) {
console.error('Error joining class:', error) console.error('Error joining class:', error)
} }
@ -129,21 +124,6 @@ export class SignalRService {
} }
} }
async sendSignalingMessage(message: SignalingMessageDto): Promise<void> {
if (!this.isConnected) {
console.log('Error starting SignalR connection signaling message', message.type)
// In demo mode, we can't send real signaling messages
// WebRTC will need to work in local-only mode
return
}
try {
await this.connection.invoke('SendSignalingMessage', message)
} catch (error) {
console.error('Error sending signaling message:', error)
}
}
async sendChatMessage( async sendChatMessage(
sessionId: string, sessionId: string,
senderId: string, senderId: string,
@ -153,8 +133,8 @@ export class SignalRService {
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating chat message from', senderName) console.log('Error starting SignalR connection simulating chat message from', senderName)
const chatMessage: ClassChatDto = { const chatMessage: ClassroomChatDto = {
id: `msg-${Date.now()}`, id: crypto.randomUUID(),
senderId, senderId,
senderName, senderName,
message, message,
@ -199,8 +179,8 @@ export class SignalRService {
'to', 'to',
recipientName, recipientName,
) )
const chatMessage: ClassChatDto = { const chatMessage: ClassroomChatDto = {
id: `msg-${Date.now()}`, id: crypto.randomUUID(),
senderId, senderId,
senderName, senderName,
message, message,
@ -226,6 +206,7 @@ export class SignalRService {
recipientId, recipientId,
recipientName, recipientName,
isTeacher, isTeacher,
'private',
) )
} catch (error) { } catch (error) {
console.error('Error sending private message:', error) console.error('Error sending private message:', error)
@ -237,16 +218,17 @@ export class SignalRService {
senderId: string, senderId: string,
senderName: string, senderName: string,
message: string, message: string,
isTeacher: boolean,
): Promise<void> { ): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating announcement from', senderName) console.log('Error starting SignalR connection simulating announcement from', senderName)
const chatMessage: ClassChatDto = { const chatMessage: ClassroomChatDto = {
id: `msg-${Date.now()}`, id: crypto.randomUUID(),
senderId, senderId,
senderName, senderName,
message, message,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
isTeacher: true, isTeacher,
messageType: 'announcement', messageType: 'announcement',
} }
setTimeout(() => { setTimeout(() => {
@ -256,13 +238,25 @@ export class SignalRService {
} }
try { try {
await this.connection.invoke('SendAnnouncement', sessionId, senderId, senderName, message) await this.connection.invoke(
'SendAnnouncement',
sessionId,
senderId,
senderName,
message,
isTeacher,
)
} catch (error) { } catch (error) {
console.error('Error sending chat message:', error) console.error('Error sending chat message:', error)
} }
} }
async muteParticipant(sessionId: string, userId: string, isMuted: boolean): Promise<void> { async muteParticipant(
sessionId: string,
userId: string,
isMuted: boolean,
isTeacher: boolean,
): Promise<void> {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating mute participant', userId, isMuted) console.log('Error starting SignalR connection simulating mute participant', userId, isMuted)
setTimeout(() => { setTimeout(() => {
@ -271,8 +265,10 @@ export class SignalRService {
return return
} }
console.log('Muting participant:', userId, 'Muted:', isMuted, 'isTeacher:', isTeacher)
try { try {
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted) await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
} catch (error) { } catch (error) {
console.error('Error muting participant:', error) console.error('Error muting participant:', error)
} }
@ -282,7 +278,7 @@ export class SignalRService {
if (!this.isConnected) { if (!this.isConnected) {
console.log('Error starting SignalR connection simulating hand raise from', studentName) console.log('Error starting SignalR connection simulating hand raise from', studentName)
const handRaise: HandRaiseDto = { const handRaise: HandRaiseDto = {
id: `hand-${Date.now()}`, id: crypto.randomUUID(),
studentId, studentId,
studentName, studentName,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
@ -349,11 +345,7 @@ export class SignalRService {
} }
} }
setSignalingHandler(callback: (message: SignalingMessageDto) => void) { setAttendanceUpdatedHandler(callback: (record: ClassroomAttendanceDto) => void) {
this.onSignalingMessage = callback
}
setAttendanceUpdatedHandler(callback: (record: ClassAttendanceDto) => void) {
this.onAttendanceUpdate = callback this.onAttendanceUpdate = callback
} }
@ -365,7 +357,7 @@ export class SignalRService {
this.onParticipantLeft = callback this.onParticipantLeft = callback
} }
setChatMessageReceivedHandler(callback: (message: ClassChatDto) => void) { setChatMessageReceivedHandler(callback: (message: ClassroomChatDto) => void) {
this.onChatMessage = callback this.onChatMessage = callback
} }

View file

@ -21,7 +21,7 @@ export function useClassroomLogic() {
const handleCreateClass = (classData: Partial<ClassroomDto>) => { const handleCreateClass = (classData: Partial<ClassroomDto>) => {
const newClass = { const newClass = {
...classData, ...classData,
id: `class-${Date.now()}`, id: crypto.randomUUID(),
teacherId: '', teacherId: '',
teacherName: '', teacherName: '',
isActive: false, isActive: false,

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,13 @@ import { Role } from '@/proxy/classroom/models'
import { useStoreActions, useStoreState } from '@/store/store' import { useStoreActions, useStoreState } from '@/store/store'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { ROUTES_ENUM } from '@/routes/route.constant' import { ROUTES_ENUM } from '@/routes/route.constant'
import { Helmet } from 'react-helmet'
import { useLocalization } from '@/utils/hooks/useLocalization'
import { Container } from '@/components/shared'
const Dashboard: React.FC = () => { const Dashboard: React.FC = () => {
const navigate = useNavigate() const navigate = useNavigate()
const { translate } = useLocalization()
const { user } = useStoreState((state) => state.auth) const { user } = useStoreState((state) => state.auth)
const { setUser } = useStoreActions((actions) => actions.auth.user) const { setUser } = useStoreActions((actions) => actions.auth.user)
@ -21,56 +25,63 @@ const Dashboard: React.FC = () => {
} }
return ( return (
<div className="flex items-center justify-center p-4"> <>
<motion.div <Helmet
initial={{ opacity: 0, y: 20 }} titleTemplate="%s | Kurs Platform"
animate={{ opacity: 1, y: 0 }} title={translate('::' + 'App.Classroom')}
className="text-center w-full max-w-4xl" defaultTitle="Kurs Platform"
> ></Helmet>
<p className="text-lg sm:text-xl text-gray-600 mb-8 sm:mb-12">Lütfen rolünüzü seçin</p> <Container>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="text-center w-full max-w-4xl"
>
<p className="text-lg sm:text-xl text-gray-600 mb-8 sm:mb-12">Lütfen rolünüzü seçin</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 sm:gap-8">
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={() => handleRoleSelect('teacher')} onClick={() => handleRoleSelect('teacher')}
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500" className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-blue-500"
> >
<FaGraduationCap size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" /> <FaGraduationCap size={48} className="mx-auto text-blue-600 mb-4 sm:mb-4" />
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğretmen</h2> <h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğretmen</h2>
<p className="text-gray-600 text-sm sm:text-base"> <p className="text-gray-600 text-sm sm:text-base">
Ders başlatın, öğrencilerle iletişim kurun ve katılım raporlarını görün Ders başlatın, öğrencilerle iletişim kurun ve katılım raporlarını görün
</p> </p>
</motion.button> </motion.button>
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={() => handleRoleSelect('student')} onClick={() => handleRoleSelect('student')}
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500" className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-green-500"
> >
<FaUserCheck size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" /> <FaUserCheck size={48} className="mx-auto text-green-600 mb-4 sm:mb-4" />
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğrenci</h2> <h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Öğrenci</h2>
<p className="text-gray-600 text-sm sm:text-base"> <p className="text-gray-600 text-sm sm:text-base">
Aktif derslere katılın, öğretmeniniz ve diğer öğrencilerle etkileşim kurun Aktif derslere katılın, öğretmeniniz ve diğer öğrencilerle etkileşim kurun
</p> </p>
</motion.button> </motion.button>
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={() => handleRoleSelect('observer')} onClick={() => handleRoleSelect('observer')}
className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 md:col-span-2 lg:col-span-1" className="bg-white rounded-lg shadow-lg p-6 sm:p-8 hover:shadow-xl transition-all duration-300 border-2 border-transparent hover:border-purple-500 md:col-span-2 lg:col-span-1"
> >
<FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" /> <FaEye size={48} className="mx-auto text-purple-600 mb-4 sm:mb-4" />
<h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Gözlemci</h2> <h2 className="text-xl sm:text-2xl font-bold text-gray-800 mb-2">Gözlemci</h2>
<p className="text-gray-600 text-sm sm:text-base"> <p className="text-gray-600 text-sm sm:text-base">
Sınıfı gözlemleyin, eğitim sürecini takip edin (ses/video paylaşımı yok) Sınıfı gözlemleyin, eğitim sürecini takip edin (ses/video paylaşımı yok)
</p> </p>
</motion.button> </motion.button>
</div> </div>
</motion.div> </motion.div>
</div> </Container>
</>
) )
} }

File diff suppressed because it is too large Load diff