Classroom : Büyük güncellemee
This commit is contained in:
parent
b904b35506
commit
cf3cb50e1a
25 changed files with 2156 additions and 1856 deletions
|
|
@ -2,7 +2,7 @@ using System;
|
|||
|
||||
namespace Kurs.Platform.Classrooms;
|
||||
|
||||
public class ClassChatDto
|
||||
public class ClassroomChatDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid SessionId { get; set; }
|
||||
|
|
@ -10,5 +10,8 @@ public class ClassChatDto
|
|||
public string SenderName { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public Guid? RecipientId { get; set; }
|
||||
public string? RecipientName { get; set; }
|
||||
public bool IsTeacher { get; set; }
|
||||
public string MessageType { get; set; }
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ public class GetClassroomListDto : PagedAndSortedResultRequestDto
|
|||
public Guid? TeacherId { get; set; }
|
||||
}
|
||||
|
||||
public class ClassAttendanceDto : EntityDto<Guid>
|
||||
public class ClassroomAttendanceDto : EntityDto<Guid>
|
||||
{
|
||||
public Guid SessionId { get; set; }
|
||||
public Guid StudentId { get; set; }
|
||||
|
|
|
|||
|
|
@ -2,15 +2,15 @@ using System;
|
|||
|
||||
namespace Kurs.Platform.Classrooms;
|
||||
|
||||
public class ClassParticipantDto
|
||||
public class ClassroomParticipantDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid SessionId { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool IsTeacher { get; set; }
|
||||
public bool IsAudioMuted { get; set; }
|
||||
public bool IsVideoMuted { get; set; }
|
||||
public bool IsHandRaised { get; set; }
|
||||
public DateTime JoinTime { get; set; }
|
||||
}
|
||||
|
|
@ -17,5 +17,5 @@ public interface IClassroomAppService : IApplicationService
|
|||
Task EndClassAsync(Guid id);
|
||||
Task<ClassroomDto> JoinClassAsync(Guid id);
|
||||
Task LeaveClassAsync(Guid id);
|
||||
Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId);
|
||||
Task<List<ClassroomAttendanceDto>> GetAttendanceAsync(Guid sessionId);
|
||||
}
|
||||
|
|
@ -15,13 +15,13 @@ namespace Kurs.Platform.Classrooms;
|
|||
public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
||||
{
|
||||
private readonly IRepository<Classroom, Guid> _classSessionRepository;
|
||||
private readonly IRepository<ClassParticipant, Guid> _participantRepository;
|
||||
private readonly IRepository<ClassAttandance, Guid> _attendanceRepository;
|
||||
private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
|
||||
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
|
||||
|
||||
public ClassroomAppService(
|
||||
IRepository<Classroom, Guid> classSessionRepository,
|
||||
IRepository<ClassParticipant, Guid> participantRepository,
|
||||
IRepository<ClassAttandance, Guid> attendanceRepository)
|
||||
IRepository<ClassroomParticipant, Guid> participantRepository,
|
||||
IRepository<ClassroomAttandance, Guid> attendanceRepository)
|
||||
{
|
||||
_classSessionRepository = classSessionRepository;
|
||||
_participantRepository = participantRepository;
|
||||
|
|
@ -198,19 +198,18 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
|||
if (existingParticipant == null)
|
||||
{
|
||||
// Add participant
|
||||
var participant = new ClassParticipant(
|
||||
var participant = new ClassroomParticipant(
|
||||
GuidGenerator.Create(),
|
||||
id,
|
||||
CurrentUser.Id,
|
||||
CurrentUser.Name,
|
||||
CurrentUser.Email,
|
||||
false // isTeacher
|
||||
);
|
||||
|
||||
await _participantRepository.InsertAsync(participant);
|
||||
|
||||
// Create attendance record
|
||||
var attendance = new ClassAttandance(
|
||||
var attendance = new ClassroomAttandance(
|
||||
GuidGenerator.Create(),
|
||||
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);
|
||||
|
||||
|
|
@ -270,6 +269,6 @@ public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
|||
x => x.SessionId == sessionId
|
||||
);
|
||||
|
||||
return ObjectMapper.Map<List<ClassAttandance>, List<ClassAttendanceDto>>(attendanceRecords);
|
||||
return ObjectMapper.Map<List<ClassroomAttandance>, List<ClassroomAttendanceDto>>(attendanceRecords);
|
||||
}
|
||||
}
|
||||
|
|
@ -8,8 +8,8 @@ public class ClassroomAutoMapperProfile : Profile
|
|||
public ClassroomAutoMapperProfile()
|
||||
{
|
||||
CreateMap<Classroom, ClassroomDto>();
|
||||
CreateMap<ClassAttandance, ClassAttendanceDto>();
|
||||
CreateMap<ClassParticipant, ClassParticipantDto>();
|
||||
CreateMap<ClassChat, ClassChatDto>();
|
||||
CreateMap<ClassroomAttandance, ClassroomAttendanceDto>();
|
||||
CreateMap<ClassroomParticipant, ClassroomParticipantDto>();
|
||||
CreateMap<ClassroomChat, ClassroomChatDto>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ public class Classroom : FullAuditedEntity<Guid>
|
|||
public int ParticipantCount { get; set; }
|
||||
public string SettingsJson { get; set; }
|
||||
|
||||
public virtual ICollection<ClassParticipant> Participants { get; set; }
|
||||
public virtual ICollection<ClassAttandance> AttendanceRecords { get; set; }
|
||||
public virtual ICollection<ClassChat> ChatMessages { get; set; }
|
||||
public virtual ICollection<ClassroomParticipant> Participants { get; set; }
|
||||
public virtual ICollection<ClassroomAttandance> AttendanceRecords { get; set; }
|
||||
public virtual ICollection<ClassroomChat> ChatMessages { get; set; }
|
||||
|
||||
protected Classroom()
|
||||
{
|
||||
Participants = new HashSet<ClassParticipant>();
|
||||
AttendanceRecords = new HashSet<ClassAttandance>();
|
||||
ChatMessages = new HashSet<ClassChat>();
|
||||
Participants = new HashSet<ClassroomParticipant>();
|
||||
AttendanceRecords = new HashSet<ClassroomAttandance>();
|
||||
ChatMessages = new HashSet<ClassroomChat>();
|
||||
}
|
||||
|
||||
public Classroom(
|
||||
|
|
@ -56,8 +56,8 @@ public class Classroom : FullAuditedEntity<Guid>
|
|||
MaxParticipants = maxParticipants;
|
||||
SettingsJson = settingsJson;
|
||||
|
||||
Participants = new HashSet<ClassParticipant>();
|
||||
AttendanceRecords = new HashSet<ClassAttandance>();
|
||||
ChatMessages = new HashSet<ClassChat>();
|
||||
Participants = new HashSet<ClassroomParticipant>();
|
||||
AttendanceRecords = new HashSet<ClassroomAttandance>();
|
||||
ChatMessages = new HashSet<ClassroomChat>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using Volo.Abp.Domain.Entities.Auditing;
|
|||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ClassAttandance : FullAuditedEntity<Guid>
|
||||
public class ClassroomAttandance : FullAuditedEntity<Guid>
|
||||
{
|
||||
public Guid SessionId { get; set; }
|
||||
public Guid? StudentId { get; set; }
|
||||
|
|
@ -15,11 +15,11 @@ public class ClassAttandance : FullAuditedEntity<Guid>
|
|||
// Navigation properties
|
||||
public virtual Classroom Session { get; set; }
|
||||
|
||||
protected ClassAttandance()
|
||||
protected ClassroomAttandance()
|
||||
{
|
||||
}
|
||||
|
||||
public ClassAttandance(
|
||||
public ClassroomAttandance(
|
||||
Guid id,
|
||||
Guid sessionId,
|
||||
Guid? studentId,
|
||||
|
|
@ -3,36 +3,45 @@ using Volo.Abp.Domain.Entities.Auditing;
|
|||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ClassChat : FullAuditedEntity<Guid>
|
||||
public class ClassroomChat : FullAuditedEntity<Guid>
|
||||
{
|
||||
public Guid SessionId { get; set; }
|
||||
public Guid? SenderId { get; set; }
|
||||
public string SenderName { get; set; }
|
||||
public string Message { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public Guid? RecipientId { get; set; }
|
||||
public string? RecipientName { get; set; }
|
||||
public bool IsTeacher { get; set; }
|
||||
public string MessageType { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public virtual Classroom Session { get; set; }
|
||||
|
||||
protected ClassChat()
|
||||
protected ClassroomChat()
|
||||
{
|
||||
}
|
||||
|
||||
public ClassChat(
|
||||
public ClassroomChat(
|
||||
Guid id,
|
||||
Guid sessionId,
|
||||
Guid? senderId,
|
||||
string senderName,
|
||||
string message,
|
||||
bool isTeacher
|
||||
Guid? recipientId,
|
||||
string recipientName,
|
||||
bool isTeacher,
|
||||
string messageType
|
||||
) : base(id)
|
||||
{
|
||||
SessionId = sessionId;
|
||||
SenderId = senderId;
|
||||
SenderName = senderName;
|
||||
Message = message;
|
||||
RecipientId = recipientId;
|
||||
RecipientName = recipientName;
|
||||
IsTeacher = isTeacher;
|
||||
Timestamp = DateTime.UtcNow;
|
||||
MessageType = messageType;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,41 +3,40 @@ using Volo.Abp.Domain.Entities.Auditing;
|
|||
|
||||
namespace Kurs.Platform.Entities;
|
||||
|
||||
public class ClassParticipant : FullAuditedEntity<Guid>
|
||||
public class ClassroomParticipant : FullAuditedEntity<Guid>
|
||||
{
|
||||
public Guid SessionId { get; set; }
|
||||
public Guid? UserId { get; set; }
|
||||
public string UserName { get; set; }
|
||||
public string UserEmail { get; set; }
|
||||
public bool IsTeacher { get; set; }
|
||||
public bool IsAudioMuted { get; set; }
|
||||
public bool IsVideoMuted { get; set; }
|
||||
public bool IsAudioMuted { get; set; } = false;
|
||||
public bool IsVideoMuted { get; set; } = false;
|
||||
public bool IsHandRaised { get; set; } = false;
|
||||
public DateTime JoinTime { get; set; }
|
||||
public string ConnectionId { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public virtual Classroom Session { get; set; }
|
||||
|
||||
protected ClassParticipant()
|
||||
protected ClassroomParticipant()
|
||||
{
|
||||
}
|
||||
|
||||
public ClassParticipant(
|
||||
public ClassroomParticipant(
|
||||
Guid id,
|
||||
Guid sessionId,
|
||||
Guid? userId,
|
||||
string userName,
|
||||
string userEmail,
|
||||
bool isTeacher
|
||||
) : base(id)
|
||||
{
|
||||
SessionId = sessionId;
|
||||
UserId = userId;
|
||||
UserName = userName;
|
||||
UserEmail = userEmail;
|
||||
IsTeacher = isTeacher;
|
||||
IsAudioMuted = false;
|
||||
IsVideoMuted = false;
|
||||
IsHandRaised = false;
|
||||
JoinTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
|
|
@ -98,9 +98,9 @@ public class PlatformDbContext :
|
|||
public DbSet<Service> Services { get; set; }
|
||||
|
||||
public DbSet<Classroom> ClassSessions { get; set; }
|
||||
public DbSet<ClassParticipant> Participants { get; set; }
|
||||
public DbSet<ClassAttandance> AttendanceRecords { get; set; }
|
||||
public DbSet<ClassChat> ChatMessages { get; set; }
|
||||
public DbSet<ClassroomParticipant> Participants { get; set; }
|
||||
public DbSet<ClassroomAttandance> AttendanceRecords { get; set; }
|
||||
public DbSet<ClassroomChat> ChatMessages { get; set; }
|
||||
|
||||
#region Entities from the modules
|
||||
|
||||
|
|
@ -913,13 +913,12 @@ public class PlatformDbContext :
|
|||
});
|
||||
|
||||
// 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.Property(x => x.UserName).IsRequired().HasMaxLength(100);
|
||||
b.Property(x => x.UserEmail).HasMaxLength(200);
|
||||
b.Property(x => x.ConnectionId).HasMaxLength(100);
|
||||
|
||||
b.HasIndex(x => x.SessionId);
|
||||
|
|
@ -928,9 +927,9 @@ public class PlatformDbContext :
|
|||
});
|
||||
|
||||
// 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.Property(x => x.StudentName).IsRequired().HasMaxLength(100);
|
||||
|
|
@ -941,9 +940,9 @@ public class PlatformDbContext :
|
|||
});
|
||||
|
||||
// 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.Property(x => x.SenderName).IsRequired().HasMaxLength(100);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ using Volo.Abp.EntityFrameworkCore;
|
|||
namespace Kurs.Platform.Migrations
|
||||
{
|
||||
[DbContext(typeof(PlatformDbContext))]
|
||||
[Migration("20250828112303_Initial")]
|
||||
[Migration("20250829093104_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
|
|
@ -1559,219 +1559,6 @@ namespace Kurs.Platform.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -1861,6 +1648,227 @@ namespace Kurs.Platform.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -6558,7 +6566,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("AttendanceRecords")
|
||||
|
|
@ -6569,7 +6577,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Session");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("ChatMessages")
|
||||
|
|
@ -6580,7 +6588,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Session");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("Participants")
|
||||
|
|
@ -1922,7 +1922,7 @@ namespace Kurs.Platform.Migrations
|
|||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PClassAttandance",
|
||||
name: "PClassroomAttandance",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
|
|
@ -1942,9 +1942,9 @@ namespace Kurs.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PClassAttandance", x => x.Id);
|
||||
table.PrimaryKey("PK_PClassroomAttandance", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PClassAttandance_PClassroom_SessionId",
|
||||
name: "FK_PClassroomAttandance_PClassroom_SessionId",
|
||||
column: x => x.SessionId,
|
||||
principalTable: "PClassroom",
|
||||
principalColumn: "Id",
|
||||
|
|
@ -1952,7 +1952,7 @@ namespace Kurs.Platform.Migrations
|
|||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PClassChat",
|
||||
name: "PClassroomChat",
|
||||
columns: table => new
|
||||
{
|
||||
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),
|
||||
Message = table.Column<string>(type: "nvarchar(2000)", maxLength: 2000, 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),
|
||||
MessageType = table.Column<string>(type: "nvarchar(max)", nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
CreatorId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
LastModificationTime = table.Column<DateTime>(type: "datetime2", nullable: true),
|
||||
|
|
@ -1972,9 +1975,9 @@ namespace Kurs.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PClassChat", x => x.Id);
|
||||
table.PrimaryKey("PK_PClassroomChat", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PClassChat_PClassroom_SessionId",
|
||||
name: "FK_PClassroomChat_PClassroom_SessionId",
|
||||
column: x => x.SessionId,
|
||||
principalTable: "PClassroom",
|
||||
principalColumn: "Id",
|
||||
|
|
@ -1982,17 +1985,17 @@ namespace Kurs.Platform.Migrations
|
|||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PClassParticipant",
|
||||
name: "PClassroomParticipant",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
SessionId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||
UserId = table.Column<Guid>(type: "uniqueidentifier", nullable: true),
|
||||
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),
|
||||
IsAudioMuted = 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),
|
||||
ConnectionId = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: true),
|
||||
CreationTime = table.Column<DateTime>(type: "datetime2", nullable: false),
|
||||
|
|
@ -2005,9 +2008,9 @@ namespace Kurs.Platform.Migrations
|
|||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PClassParticipant", x => x.Id);
|
||||
table.PrimaryKey("PK_PClassroomParticipant", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_PClassParticipant_PClassroom_SessionId",
|
||||
name: "FK_PClassroomParticipant_PClassroom_SessionId",
|
||||
column: x => x.SessionId,
|
||||
principalTable: "PClassroom",
|
||||
principalColumn: "Id",
|
||||
|
|
@ -2991,53 +2994,6 @@ namespace Kurs.Platform.Migrations
|
|||
columns: new[] { "CountryCode", "Code" },
|
||||
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(
|
||||
name: "IX_PClassroom_ScheduledStartTime",
|
||||
table: "PClassroom",
|
||||
|
|
@ -3048,6 +3004,53 @@ namespace Kurs.Platform.Migrations
|
|||
table: "PClassroom",
|
||||
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(
|
||||
name: "IX_PCountry_Code",
|
||||
table: "PCountry",
|
||||
|
|
@ -3303,13 +3306,13 @@ namespace Kurs.Platform.Migrations
|
|||
name: "PChart");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PClassAttandance");
|
||||
name: "PClassroomAttandance");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PClassChat");
|
||||
name: "PClassroomChat");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PClassParticipant");
|
||||
name: "PClassroomParticipant");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PContact");
|
||||
|
|
@ -1556,219 +1556,6 @@ namespace Kurs.Platform.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -1858,6 +1645,227 @@ namespace Kurs.Platform.Migrations
|
|||
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 =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
|
|
@ -6555,7 +6563,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Country");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassAttandance", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomAttandance", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("AttendanceRecords")
|
||||
|
|
@ -6566,7 +6574,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Session");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassChat", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomChat", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("ChatMessages")
|
||||
|
|
@ -6577,7 +6585,7 @@ namespace Kurs.Platform.Migrations
|
|||
b.Navigation("Session");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassParticipant", b =>
|
||||
modelBuilder.Entity("Kurs.Platform.Entities.ClassroomParticipant", b =>
|
||||
{
|
||||
b.HasOne("Kurs.Platform.Entities.Classroom", "Session")
|
||||
.WithMany("Participants")
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ namespace Kurs.Platform.SignalR.Hubs;
|
|||
public class ClassroomHub : Hub
|
||||
{
|
||||
private readonly IRepository<Classroom, Guid> _classSessionRepository;
|
||||
private readonly IRepository<ClassParticipant, Guid> _participantRepository;
|
||||
private readonly IRepository<ClassChat, Guid> _chatMessageRepository;
|
||||
private readonly IRepository<ClassroomParticipant, Guid> _participantRepository;
|
||||
private readonly IRepository<ClassroomChat, Guid> _chatMessageRepository;
|
||||
private readonly IRepository<ClassroomAttandance, Guid> _attendanceRepository;
|
||||
private readonly ILogger<ClassroomHub> _logger;
|
||||
private readonly IGuidGenerator _guidGenerator;
|
||||
private readonly ICurrentUser _currentUser;
|
||||
|
||||
public ClassroomHub(
|
||||
IRepository<Classroom, Guid> classSessionRepository,
|
||||
IRepository<ClassParticipant, Guid> participantRepository,
|
||||
IRepository<ClassChat, Guid> chatMessageRepository,
|
||||
IRepository<ClassroomParticipant, Guid> participantRepository,
|
||||
IRepository<ClassroomChat, Guid> chatMessageRepository,
|
||||
IRepository<ClassroomAttandance, Guid> attendanceRepository,
|
||||
ILogger<ClassroomHub> logger,
|
||||
IGuidGenerator guidGenerator,
|
||||
ICurrentUser currentUser)
|
||||
|
|
@ -31,91 +33,93 @@ public class ClassroomHub : Hub
|
|||
_classSessionRepository = classSessionRepository;
|
||||
_participantRepository = participantRepository;
|
||||
_chatMessageRepository = chatMessageRepository;
|
||||
_attendanceRepository = attendanceRepository;
|
||||
_logger = logger;
|
||||
_guidGenerator = guidGenerator;
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
[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(
|
||||
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);
|
||||
await _participantRepository.UpdateAsync(participant);
|
||||
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
||||
}
|
||||
|
||||
// Notify others
|
||||
// 🔑 Attendance kaydı aç
|
||||
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())
|
||||
.SendAsync("ParticipantJoined", _currentUser.Id, userName);
|
||||
_logger.LogInformation($"User {userName} joined class {sessionId}");
|
||||
.SendAsync("ParticipantJoined", userId, userName);
|
||||
}
|
||||
|
||||
|
||||
[HubMethodName("LeaveClass")]
|
||||
public async Task LeaveClassAsync(Guid sessionId)
|
||||
{
|
||||
await Groups.RemoveFromGroupAsync(Context.ConnectionId, sessionId.ToString());
|
||||
|
||||
var userId = _currentUser.Id;
|
||||
if (userId.HasValue)
|
||||
{
|
||||
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
|
||||
x => x.SessionId == sessionId && x.StudentId == userId.Value && x.LeaveTime == null
|
||||
);
|
||||
|
||||
if (attendance != null)
|
||||
{
|
||||
attendance.LeaveTime = DateTime.UtcNow;
|
||||
attendance.TotalDurationMinutes = (int)Math.Max(
|
||||
1,
|
||||
(attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes
|
||||
);
|
||||
await _attendanceRepository.UpdateAsync(attendance, autoSave: true);
|
||||
|
||||
await Clients.Group(sessionId.ToString())
|
||||
.SendAsync("AttendanceUpdated", attendance);
|
||||
}
|
||||
}
|
||||
|
||||
await Clients.Group(sessionId.ToString())
|
||||
.SendAsync("ParticipantLeft", _currentUser);
|
||||
_logger.LogInformation($"User {_currentUser} left class {sessionId}");
|
||||
}
|
||||
|
||||
public async Task SendSignalingMessageAsync(SignalingMessageDto message)
|
||||
{
|
||||
// Forward WebRTC signaling messages
|
||||
await Clients.User(message.ToUserId)
|
||||
.SendAsync("ReceiveSignalingMessage", message);
|
||||
_logger.LogInformation($"Signaling message sent from {message.FromUserId} to {message.ToUserId}");
|
||||
}
|
||||
|
||||
public async Task SendChatMessageAsync(Guid sessionId, string message)
|
||||
{
|
||||
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)
|
||||
[HubMethodName("MuteParticipant")]
|
||||
public async Task MuteParticipantAsync(Guid sessionId, Guid userId, bool isMuted, bool isTeacher)
|
||||
{
|
||||
var teacherParticipant = await _participantRepository.FirstOrDefaultAsync(
|
||||
x => x.SessionId == sessionId && x.UserId == _currentUser.Id
|
||||
|
|
@ -128,59 +132,264 @@ public class ClassroomHub : Hub
|
|||
}
|
||||
|
||||
var participant = await _participantRepository.FirstOrDefaultAsync(
|
||||
x => x.SessionId == sessionId && x.UserId == participantId
|
||||
x => x.SessionId == sessionId && x.UserId == userId
|
||||
);
|
||||
|
||||
if (participant != null)
|
||||
{
|
||||
if (isMuted)
|
||||
participant.MuteAudio();
|
||||
else
|
||||
participant.UnmuteAudio();
|
||||
if (isMuted) participant.MuteAudio();
|
||||
else participant.UnmuteAudio();
|
||||
|
||||
await _participantRepository.UpdateAsync(participant);
|
||||
await _participantRepository.UpdateAsync(participant, autoSave: true);
|
||||
|
||||
// Notify the participant and others
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
// bağlantı gerçekten iptal edilmişse DB sorgusu çalıştırma
|
||||
if (Context.ConnectionAborted.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
var userId = _currentUser.Id;
|
||||
if (userId.HasValue)
|
||||
{
|
||||
// 🔑 1. Katılımcı listesi
|
||||
var participants = await _participantRepository
|
||||
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId)
|
||||
.ConfigureAwait(false);
|
||||
.GetListAsync(x => x.UserId == userId.Value && x.ConnectionId == Context.ConnectionId);
|
||||
|
||||
foreach (var participant in participants)
|
||||
{
|
||||
// 🔑 2. Attendance kaydını kapat
|
||||
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
|
||||
x => x.SessionId == participant.SessionId &&
|
||||
x.StudentId == userId.Value &&
|
||||
x.LeaveTime == null
|
||||
);
|
||||
|
||||
if (attendance != null)
|
||||
{
|
||||
attendance.LeaveTime = DateTime.UtcNow;
|
||||
attendance.TotalDurationMinutes = (int)Math.Max(
|
||||
1,
|
||||
(attendance.LeaveTime.Value - attendance.JoinTime).TotalMinutes
|
||||
);
|
||||
|
||||
await _attendanceRepository.UpdateAsync(attendance, autoSave: true);
|
||||
|
||||
// Frontend’e bildir
|
||||
await Clients.Group(participant.SessionId.ToString())
|
||||
.SendAsync("ParticipantLeft", userId.Value)
|
||||
.ConfigureAwait(false);
|
||||
.SendAsync("AttendanceUpdated", new
|
||||
{
|
||||
attendance.Id,
|
||||
attendance.SessionId,
|
||||
attendance.StudentId,
|
||||
attendance.StudentName,
|
||||
attendance.JoinTime,
|
||||
attendance.LeaveTime,
|
||||
attendance.TotalDurationMinutes
|
||||
});
|
||||
}
|
||||
|
||||
// 🔑 3. ParticipantLeft event’i
|
||||
await Clients.Group(participant.SessionId.ToString())
|
||||
.SendAsync("ParticipantLeft", userId.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
// bağlantı kapandığında doğal, error yerine debug seviyesinde loglayın
|
||||
_logger.LogDebug("OnDisconnectedAsync iptal edildi (connection aborted).");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// beklenmeyen hataları error olarak loglayın
|
||||
_logger.LogError(ex, "OnDisconnectedAsync hata");
|
||||
}
|
||||
|
||||
await base.OnDisconnectedAsync(exception).ConfigureAwait(false);
|
||||
await base.OnDisconnectedAsync(exception);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ define(['./workbox-54d0af47'], (function (workbox) { 'use strict';
|
|||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||
}, {
|
||||
"url": "index.html",
|
||||
"revision": "0.b9bfk61okp"
|
||||
"revision": "0.48gll4p3s3o"
|
||||
}], {});
|
||||
workbox.cleanupOutdatedCaches();
|
||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ClassAttendanceDto } from '@/proxy/classroom/models'
|
||||
import { ClassroomAttendanceDto } from '@/proxy/classroom/models'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { FaClock, FaUsers } from 'react-icons/fa'
|
||||
|
||||
interface AttendancePanelProps {
|
||||
attendanceRecords: ClassAttendanceDto[]
|
||||
attendanceRecords: ClassroomAttendanceDto[]
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { ClassChatDto } from '@/proxy/classroom/models'
|
||||
import { ClassroomChatDto } from '@/proxy/classroom/models'
|
||||
import { useStoreState } from '@/store/store'
|
||||
import React, { useState, useRef, useEffect } from 'react'
|
||||
import { FaPaperPlane, FaComments, FaTimes, FaUsers, FaUser, FaBullhorn } from 'react-icons/fa'
|
||||
|
||||
interface ChatPanelProps {
|
||||
messages: ClassChatDto[]
|
||||
messages: ClassroomChatDto[]
|
||||
isTeacher: boolean
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react'
|
||||
import { FaMicrophoneSlash, FaExpand, FaUserTimes } from 'react-icons/fa'
|
||||
import { VideoPlayer } from './VideoPlayer'
|
||||
import { ClassParticipantDto, VideoLayoutDto } from '@/proxy/classroom/models'
|
||||
import { ClassroomParticipantDto, VideoLayoutDto } from '@/proxy/classroom/models'
|
||||
|
||||
interface ParticipantGridProps {
|
||||
participants: ClassParticipantDto[]
|
||||
participants: ClassroomParticipantDto[]
|
||||
localStream?: MediaStream
|
||||
currentUserId: string
|
||||
currentUserName: string
|
||||
|
|
@ -14,7 +14,7 @@ interface ParticipantGridProps {
|
|||
onToggleAudio: () => void
|
||||
onToggleVideo: () => void
|
||||
onLeaveCall: () => void
|
||||
onMuteParticipant?: (participantId: string, isMuted: boolean) => void
|
||||
onMuteParticipant?: (participantId: string, isMuted: boolean, isTeacher: boolean) => void
|
||||
layout: VideoLayoutDto
|
||||
focusedParticipant?: string
|
||||
onParticipantFocus?: (participantId: string | undefined) => void
|
||||
|
|
@ -211,7 +211,7 @@ export const ParticipantGrid: React.FC<ParticipantGridProps> = ({
|
|||
}
|
||||
|
||||
const renderParticipant = (
|
||||
participant: ClassParticipantDto,
|
||||
participant: ClassroomParticipantDto,
|
||||
isMain: boolean = false,
|
||||
isSmall: boolean = false,
|
||||
) => (
|
||||
|
|
@ -243,7 +243,7 @@ export const ParticipantGrid: React.FC<ParticipantGridProps> = ({
|
|||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
onMuteParticipant?.(participant.id, !participant.isAudioMuted)
|
||||
onMuteParticipant?.(participant.id, !participant.isAudioMuted, isTeacher)
|
||||
}}
|
||||
className={`p-1 rounded-full text-white text-xs ${
|
||||
participant.isAudioMuted ? 'bg-red-600' : 'bg-gray-600 hover:bg-gray-700'
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export interface ClassroomSettingsDto {
|
|||
autoMuteNewParticipants: boolean
|
||||
}
|
||||
|
||||
export interface ClassAttendanceDto {
|
||||
export interface ClassroomAttendanceDto {
|
||||
id: string
|
||||
sessionId: string
|
||||
studentId: string
|
||||
|
|
@ -49,16 +49,7 @@ export interface ClassAttendanceDto {
|
|||
totalDurationMinutes: number
|
||||
}
|
||||
|
||||
export type MediaType = 'audio' | 'video' | 'screen'
|
||||
|
||||
export interface SignalingMessageDto {
|
||||
type: MediaType
|
||||
fromUserId: string
|
||||
toUserId: string
|
||||
data: any
|
||||
}
|
||||
|
||||
export interface ClassParticipantDto {
|
||||
export interface ClassroomParticipantDto {
|
||||
id: string
|
||||
name: string
|
||||
isTeacher: boolean
|
||||
|
|
@ -73,7 +64,7 @@ export interface ClassParticipantDto {
|
|||
|
||||
export type messageType = 'public' | 'private' | 'announcement'
|
||||
|
||||
export interface ClassChatDto {
|
||||
export interface ClassroomChatDto {
|
||||
id: string
|
||||
senderId: string
|
||||
senderName: string
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
import {
|
||||
ClassAttendanceDto,
|
||||
ClassChatDto,
|
||||
HandRaiseDto,
|
||||
SignalingMessageDto,
|
||||
} from '@/proxy/classroom/models'
|
||||
import { ClassroomAttendanceDto, ClassroomChatDto, HandRaiseDto } from '@/proxy/classroom/models'
|
||||
import { store } from '@/store/store'
|
||||
import * as signalR from '@microsoft/signalr'
|
||||
|
||||
export class SignalRService {
|
||||
private connection!: signalR.HubConnection
|
||||
private isConnected: boolean = false
|
||||
private onSignalingMessage?: (message: SignalingMessageDto) => void
|
||||
private onAttendanceUpdate?: (record: ClassAttendanceDto) => void
|
||||
private onAttendanceUpdate?: (record: ClassroomAttendanceDto) => void
|
||||
private onParticipantJoined?: (userId: string, name: 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 onHandRaiseReceived?: (handRaise: HandRaiseDto) => void
|
||||
private onHandRaiseDismissed?: (handRaiseId: string) => void
|
||||
|
|
@ -38,11 +32,7 @@ export class SignalRService {
|
|||
private setupEventHandlers() {
|
||||
if (!this.connection) return
|
||||
|
||||
this.connection.on('ReceiveSignalingMessage', (message: SignalingMessageDto) => {
|
||||
this.onSignalingMessage?.(message)
|
||||
})
|
||||
|
||||
this.connection.on('AttendanceUpdated', (record: ClassAttendanceDto) => {
|
||||
this.connection.on('AttendanceUpdated', (record: ClassroomAttendanceDto) => {
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
console.log('Joining class session:', sessionId, 'as', userName, 'isTeacher:', isTeacher)
|
||||
|
||||
try {
|
||||
await this.connection.invoke('JoinClass', sessionId, userName)
|
||||
await this.connection.invoke('JoinClass', sessionId, userId, userName, isTeacher)
|
||||
} catch (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(
|
||||
sessionId: string,
|
||||
senderId: string,
|
||||
|
|
@ -153,8 +133,8 @@ export class SignalRService {
|
|||
): Promise<void> {
|
||||
if (!this.isConnected) {
|
||||
console.log('Error starting SignalR connection simulating chat message from', senderName)
|
||||
const chatMessage: ClassChatDto = {
|
||||
id: `msg-${Date.now()}`,
|
||||
const chatMessage: ClassroomChatDto = {
|
||||
id: crypto.randomUUID(),
|
||||
senderId,
|
||||
senderName,
|
||||
message,
|
||||
|
|
@ -199,8 +179,8 @@ export class SignalRService {
|
|||
'to',
|
||||
recipientName,
|
||||
)
|
||||
const chatMessage: ClassChatDto = {
|
||||
id: `msg-${Date.now()}`,
|
||||
const chatMessage: ClassroomChatDto = {
|
||||
id: crypto.randomUUID(),
|
||||
senderId,
|
||||
senderName,
|
||||
message,
|
||||
|
|
@ -226,6 +206,7 @@ export class SignalRService {
|
|||
recipientId,
|
||||
recipientName,
|
||||
isTeacher,
|
||||
'private',
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Error sending private message:', error)
|
||||
|
|
@ -237,16 +218,17 @@ export class SignalRService {
|
|||
senderId: string,
|
||||
senderName: string,
|
||||
message: string,
|
||||
isTeacher: boolean,
|
||||
): Promise<void> {
|
||||
if (!this.isConnected) {
|
||||
console.log('Error starting SignalR connection simulating announcement from', senderName)
|
||||
const chatMessage: ClassChatDto = {
|
||||
id: `msg-${Date.now()}`,
|
||||
const chatMessage: ClassroomChatDto = {
|
||||
id: crypto.randomUUID(),
|
||||
senderId,
|
||||
senderName,
|
||||
message,
|
||||
timestamp: new Date().toISOString(),
|
||||
isTeacher: true,
|
||||
isTeacher,
|
||||
messageType: 'announcement',
|
||||
}
|
||||
setTimeout(() => {
|
||||
|
|
@ -256,13 +238,25 @@ export class SignalRService {
|
|||
}
|
||||
|
||||
try {
|
||||
await this.connection.invoke('SendAnnouncement', sessionId, senderId, senderName, message)
|
||||
await this.connection.invoke(
|
||||
'SendAnnouncement',
|
||||
sessionId,
|
||||
senderId,
|
||||
senderName,
|
||||
message,
|
||||
isTeacher,
|
||||
)
|
||||
} catch (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) {
|
||||
console.log('Error starting SignalR connection simulating mute participant', userId, isMuted)
|
||||
setTimeout(() => {
|
||||
|
|
@ -271,8 +265,10 @@ export class SignalRService {
|
|||
return
|
||||
}
|
||||
|
||||
console.log('Muting participant:', userId, 'Muted:', isMuted, 'isTeacher:', isTeacher)
|
||||
|
||||
try {
|
||||
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted)
|
||||
await this.connection.invoke('MuteParticipant', sessionId, userId, isMuted, isTeacher)
|
||||
} catch (error) {
|
||||
console.error('Error muting participant:', error)
|
||||
}
|
||||
|
|
@ -282,7 +278,7 @@ export class SignalRService {
|
|||
if (!this.isConnected) {
|
||||
console.log('Error starting SignalR connection simulating hand raise from', studentName)
|
||||
const handRaise: HandRaiseDto = {
|
||||
id: `hand-${Date.now()}`,
|
||||
id: crypto.randomUUID(),
|
||||
studentId,
|
||||
studentName,
|
||||
timestamp: new Date().toISOString(),
|
||||
|
|
@ -349,11 +345,7 @@ export class SignalRService {
|
|||
}
|
||||
}
|
||||
|
||||
setSignalingHandler(callback: (message: SignalingMessageDto) => void) {
|
||||
this.onSignalingMessage = callback
|
||||
}
|
||||
|
||||
setAttendanceUpdatedHandler(callback: (record: ClassAttendanceDto) => void) {
|
||||
setAttendanceUpdatedHandler(callback: (record: ClassroomAttendanceDto) => void) {
|
||||
this.onAttendanceUpdate = callback
|
||||
}
|
||||
|
||||
|
|
@ -365,7 +357,7 @@ export class SignalRService {
|
|||
this.onParticipantLeft = callback
|
||||
}
|
||||
|
||||
setChatMessageReceivedHandler(callback: (message: ClassChatDto) => void) {
|
||||
setChatMessageReceivedHandler(callback: (message: ClassroomChatDto) => void) {
|
||||
this.onChatMessage = callback
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function useClassroomLogic() {
|
|||
const handleCreateClass = (classData: Partial<ClassroomDto>) => {
|
||||
const newClass = {
|
||||
...classData,
|
||||
id: `class-${Date.now()}`,
|
||||
id: crypto.randomUUID(),
|
||||
teacherId: '',
|
||||
teacherName: '',
|
||||
isActive: false,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ import {
|
|||
startClassroom,
|
||||
updateClassroom,
|
||||
} from '@/services/classroom.service'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
export interface ClassProps {
|
||||
status: string
|
||||
|
|
@ -41,6 +43,7 @@ export interface ClassProps {
|
|||
const ClassList: React.FC = () => {
|
||||
const navigate = useNavigate()
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const newClassEntity: ClassroomDto = {
|
||||
id: crypto.randomUUID(),
|
||||
|
|
@ -254,6 +257,12 @@ const ClassList: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
<Container>
|
||||
{/* Main Content */}
|
||||
<div className="mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
|
|
@ -310,7 +319,9 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
<div className="ml-3 sm:ml-4">
|
||||
<p className="text-xs sm:text-sm font-medium text-gray-600">Katılıma Açık</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">{widgets().openCount}</p>
|
||||
<p className="text-xl sm:text-2xl font-bold text-gray-900">
|
||||
{widgets().openCount}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
|
@ -476,7 +487,9 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||
<p className="text-gray-600 text-sm sm:text-base">{classSession.subject}</p>
|
||||
<p className="text-gray-600 text-sm sm:text-base">
|
||||
{classSession.subject}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
|
||||
|
|
@ -546,7 +559,9 @@ const ClassList: React.FC = () => {
|
|||
className="p-4 sm:p-6 space-y-4 sm:space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">Sınıf Adı *</label>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Sınıf Adı *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
required
|
||||
|
|
@ -591,7 +606,9 @@ const ClassList: React.FC = () => {
|
|||
type="datetime-local"
|
||||
required
|
||||
value={
|
||||
classroom.scheduledStartTime ? classroom.scheduledStartTime.slice(0, 16) : ''
|
||||
classroom.scheduledStartTime
|
||||
? classroom.scheduledStartTime.slice(0, 16)
|
||||
: ''
|
||||
}
|
||||
onChange={(e) =>
|
||||
setClassroom({
|
||||
|
|
@ -863,7 +880,8 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<p className="text-gray-700 mb-6">
|
||||
<strong>"{classroom.name}"</strong> adlı sınıfı silmek istediğinizden emin misiniz?
|
||||
<strong>"{classroom.name}"</strong> adlı sınıfı silmek istediğinizden emin
|
||||
misiniz?
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-end space-x-4">
|
||||
|
|
@ -888,6 +906,7 @@ const ClassList: React.FC = () => {
|
|||
</div>
|
||||
)}
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@ import { Role } from '@/proxy/classroom/models'
|
|||
import { useStoreActions, useStoreState } from '@/store/store'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
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 navigate = useNavigate()
|
||||
const { translate } = useLocalization()
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
const { setUser } = useStoreActions((actions) => actions.auth.user)
|
||||
|
||||
|
|
@ -21,7 +25,13 @@ const Dashboard: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center p-4">
|
||||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
<Container>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
|
|
@ -70,7 +80,8 @@ const Dashboard: React.FC = () => {
|
|||
</motion.button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</Container>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -39,10 +39,10 @@ import {
|
|||
import { SignalRService } from '@/services/classroom/signalr'
|
||||
import { WebRTCService } from '@/services/classroom/webrtc'
|
||||
import {
|
||||
ClassAttendanceDto,
|
||||
ClassChatDto,
|
||||
ClassroomAttendanceDto,
|
||||
ClassroomChatDto,
|
||||
ClassDocumentDto,
|
||||
ClassParticipantDto,
|
||||
ClassroomParticipantDto,
|
||||
ClassroomDto,
|
||||
ClassroomSettingsDto,
|
||||
HandRaiseDto,
|
||||
|
|
@ -58,6 +58,8 @@ import { showDbDateAsIs } from '@/utils/dateUtils'
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { endClassroom } from '@/services/classroom.service'
|
||||
import { ROUTES_ENUM } from '@/routes/route.constant'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useLocalization } from '@/utils/hooks/useLocalization'
|
||||
|
||||
type SidePanelType =
|
||||
| 'chat'
|
||||
|
|
@ -85,15 +87,16 @@ const RoomDetail: React.FC = () => {
|
|||
const params = useParams()
|
||||
const navigate = useNavigate()
|
||||
const { user } = useStoreState((state) => state.auth)
|
||||
const { translate } = useLocalization()
|
||||
|
||||
const [classSession, setClassSession] = useState<ClassroomDto>(newClassSession)
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
|
||||
const [participants, setParticipants] = useState<ClassParticipantDto[]>([])
|
||||
const [participants, setParticipants] = useState<ClassroomParticipantDto[]>([])
|
||||
const [localStream, setLocalStream] = useState<MediaStream>()
|
||||
const [isAudioEnabled, setIsAudioEnabled] = useState(true)
|
||||
const [isVideoEnabled, setIsVideoEnabled] = useState(true)
|
||||
const [attendanceRecords, setAttendanceRecords] = useState<ClassAttendanceDto[]>([])
|
||||
const [chatMessages, setChatMessages] = useState<ClassChatDto[]>([])
|
||||
const [attendanceRecords, setAttendanceRecords] = useState<ClassroomAttendanceDto[]>([])
|
||||
const [chatMessages, setChatMessages] = useState<ClassroomChatDto[]>([])
|
||||
const [currentLayout, setCurrentLayout] = useState<VideoLayoutDto>({
|
||||
id: 'grid',
|
||||
name: 'Izgara Görünümü',
|
||||
|
|
@ -223,11 +226,11 @@ const RoomDetail: React.FC = () => {
|
|||
|
||||
// Setup SignalR event handlers
|
||||
signalRServiceRef.current.setParticipantJoinHandler((userId, name) => {
|
||||
console.log(`Participant joined: ${name}`)
|
||||
|
||||
// Eğer kendimsem, ekleme
|
||||
// 🔑 Eğer gelen participant bizsek, listeye ekleme
|
||||
if (userId === user.id) return
|
||||
|
||||
console.log(`Participant joined: ${name}`)
|
||||
|
||||
// Create WebRTC connection for new participant
|
||||
if (webRTCServiceRef.current) {
|
||||
webRTCServiceRef.current.createPeerConnection(userId)
|
||||
|
|
@ -287,7 +290,12 @@ const RoomDetail: React.FC = () => {
|
|||
})
|
||||
|
||||
// Join the class
|
||||
await signalRServiceRef.current.joinClass(classSession.id, user.name)
|
||||
await signalRServiceRef.current.joinClass(
|
||||
classSession.id,
|
||||
user.id,
|
||||
user.name,
|
||||
user.role === 'teacher',
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize services:', error)
|
||||
}
|
||||
|
|
@ -349,6 +357,7 @@ const RoomDetail: React.FC = () => {
|
|||
user.id,
|
||||
user.name,
|
||||
newMessage.trim(),
|
||||
user.role === 'teacher',
|
||||
)
|
||||
} else {
|
||||
await signalRServiceRef.current.sendChatMessage(
|
||||
|
|
@ -363,9 +372,18 @@ const RoomDetail: React.FC = () => {
|
|||
}
|
||||
}
|
||||
|
||||
const handleMuteParticipant = async (participantId: string, isMuted: boolean) => {
|
||||
const handleMuteParticipant = async (
|
||||
participantId: string,
|
||||
isMuted: boolean,
|
||||
isTeacher: boolean,
|
||||
) => {
|
||||
if (signalRServiceRef.current && user.role === 'teacher') {
|
||||
await signalRServiceRef.current.muteParticipant(classSession.id, participantId, isMuted)
|
||||
await signalRServiceRef.current.muteParticipant(
|
||||
classSession.id,
|
||||
participantId,
|
||||
isMuted,
|
||||
isTeacher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,6 +399,7 @@ const RoomDetail: React.FC = () => {
|
|||
classSession.id,
|
||||
participant.id,
|
||||
newMuteState,
|
||||
user.role === 'teacher',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -443,7 +462,7 @@ const RoomDetail: React.FC = () => {
|
|||
const handleUploadDocument = async (file: File) => {
|
||||
// In a real app, this would upload to a server
|
||||
const newDoc: ClassDocumentDto = {
|
||||
id: `doc-${Date.now()}`,
|
||||
id: crypto.randomUUID(),
|
||||
name: file.name,
|
||||
url: URL.createObjectURL(file),
|
||||
type: file.type,
|
||||
|
|
@ -518,7 +537,7 @@ const RoomDetail: React.FC = () => {
|
|||
}
|
||||
|
||||
// Demo: Simulate student joining
|
||||
const simulateStudentJoin = () => {
|
||||
const simulateStudentJoin = async () => {
|
||||
const studentNames = ['Ahmet Yılmaz', 'Fatma Demir', 'Mehmet Kaya', 'Ayşe Özkan', 'Ali Çelik']
|
||||
const availableNames = studentNames.filter((name) => !participants.some((p) => p.name === name))
|
||||
|
||||
|
|
@ -528,41 +547,63 @@ const RoomDetail: React.FC = () => {
|
|||
}
|
||||
|
||||
const randomName = availableNames[Math.floor(Math.random() * availableNames.length)]
|
||||
const studentId = `student-${Date.now()}`
|
||||
const studentId = crypto.randomUUID() // Guid formatında id üretiliyor
|
||||
|
||||
setParticipants((prev) => {
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: studentId,
|
||||
name: randomName,
|
||||
isTeacher: false,
|
||||
isAudioMuted: classSettings.autoMuteNewParticipants,
|
||||
isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// Add attendance record
|
||||
setAttendanceRecords((prev: any) => {
|
||||
// Check if student already has an active attendance record
|
||||
const existingRecord = prev.find((r: any) => r.studentId === studentId && !r.leaveTime)
|
||||
if (existingRecord) return prev
|
||||
|
||||
return [
|
||||
...prev,
|
||||
{
|
||||
id: `attendance-${Date.now()}`,
|
||||
sessionId: classSession.id,
|
||||
// SignalR üzerinden joinClass çağrılıyor
|
||||
await signalRServiceRef.current?.joinClass(
|
||||
classSession.id,
|
||||
studentId,
|
||||
studentName: randomName,
|
||||
joinTime: new Date().toISOString(),
|
||||
totalDurationMinutes: 0,
|
||||
},
|
||||
]
|
||||
})
|
||||
randomName,
|
||||
false, // öğrenci
|
||||
)
|
||||
}
|
||||
|
||||
// // Demo: Simulate student joining
|
||||
// const simulateStudentJoin = () => {
|
||||
// const studentNames = ['Ahmet Yılmaz', 'Fatma Demir', 'Mehmet Kaya', 'Ayşe Özkan', 'Ali Çelik']
|
||||
// const availableNames = studentNames.filter((name) => !participants.some((p) => p.name === name))
|
||||
|
||||
// if (availableNames.length === 0) {
|
||||
// alert('Tüm demo öğrenciler zaten sınıfta!')
|
||||
// return
|
||||
// }
|
||||
|
||||
// const randomName = availableNames[Math.floor(Math.random() * availableNames.length)]
|
||||
// const studentId = crypto.randomUUID()
|
||||
|
||||
// setParticipants((prev) => {
|
||||
// return [
|
||||
// ...prev,
|
||||
// {
|
||||
// id: studentId,
|
||||
// name: randomName,
|
||||
// isTeacher: false,
|
||||
// isAudioMuted: classSettings.autoMuteNewParticipants,
|
||||
// isVideoMuted: classSettings.defaultCameraState === 'off',
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
|
||||
// // Add attendance record
|
||||
// setAttendanceRecords((prev: any) => {
|
||||
// // Check if student already has an active attendance record
|
||||
// const existingRecord = prev.find((r: any) => r.studentId === studentId && !r.leaveTime)
|
||||
// if (existingRecord) return prev
|
||||
|
||||
// return [
|
||||
// ...prev,
|
||||
// {
|
||||
// id: crypto.randomUUID(),
|
||||
// sessionId: classSession.id,
|
||||
// studentId,
|
||||
// studentName: randomName,
|
||||
// joinTime: new Date().toISOString(),
|
||||
// totalDurationMinutes: 0,
|
||||
// },
|
||||
// ]
|
||||
// })
|
||||
// }
|
||||
|
||||
const handleSettingsChange = (newSettings: Partial<ClassroomSettingsDto>) => {
|
||||
setClassSettings((prev) => ({ ...prev, ...newSettings }))
|
||||
}
|
||||
|
|
@ -984,6 +1025,7 @@ const RoomDetail: React.FC = () => {
|
|||
await handleMuteParticipant(
|
||||
participant.id,
|
||||
!participant.isAudioMuted,
|
||||
user.role === 'teacher',
|
||||
)
|
||||
}}
|
||||
className={`p-1 rounded transition-colors ${participant.isAudioMuted ? 'text-green-600 hover:bg-green-50' : 'text-yellow-600 hover:bg-yellow-50'}`}
|
||||
|
|
@ -1500,6 +1542,13 @@ const RoomDetail: React.FC = () => {
|
|||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet
|
||||
titleTemplate="%s | Kurs Platform"
|
||||
title={translate('::' + 'App.Classroom')}
|
||||
defaultTitle="Kurs Platform"
|
||||
></Helmet>
|
||||
|
||||
<div className="min-h-screen h-screen flex flex-col bg-gray-900 text-white overflow-hidden">
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
|
|
@ -2016,6 +2065,7 @@ const RoomDetail: React.FC = () => {
|
|||
/>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue