2025-08-25 18:01:57 +00:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
2025-08-26 21:42:01 +00:00
|
|
|
using System.Text.Json;
|
2025-08-25 18:01:57 +00:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Kurs.Platform.Entities;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2025-08-27 15:00:22 +00:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2025-08-25 18:01:57 +00:00
|
|
|
using Volo.Abp.Application.Dtos;
|
|
|
|
|
using Volo.Abp.Domain.Repositories;
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
namespace Kurs.Platform.Classrooms;
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
|
|
|
[Authorize]
|
2025-08-26 05:59:39 +00:00
|
|
|
public class ClassroomAppService : PlatformAppService, IClassroomAppService
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
2025-08-26 05:59:39 +00:00
|
|
|
private readonly IRepository<Classroom, Guid> _classSessionRepository;
|
|
|
|
|
private readonly IRepository<ClassParticipant, Guid> _participantRepository;
|
|
|
|
|
private readonly IRepository<ClassAttandance, Guid> _attendanceRepository;
|
|
|
|
|
|
|
|
|
|
public ClassroomAppService(
|
|
|
|
|
IRepository<Classroom, Guid> classSessionRepository,
|
|
|
|
|
IRepository<ClassParticipant, Guid> participantRepository,
|
|
|
|
|
IRepository<ClassAttandance, Guid> attendanceRepository)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
_classSessionRepository = classSessionRepository;
|
|
|
|
|
_participantRepository = participantRepository;
|
|
|
|
|
_attendanceRepository = attendanceRepository;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:42:01 +00:00
|
|
|
public async Task<ClassroomDto> GetAsync(Guid id)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
2025-08-26 21:42:01 +00:00
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-28 12:28:42 +00:00
|
|
|
public async Task<PagedResultDto<ClassroomDto>> GetListAsync(ClassroomFilterInputDto input)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var query = await _classSessionRepository.GetQueryableAsync();
|
2025-08-28 12:28:42 +00:00
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(input.Search))
|
|
|
|
|
{
|
|
|
|
|
query = query.Where(x =>
|
|
|
|
|
x.Name.Contains(input.Search) ||
|
|
|
|
|
x.Description.Contains(input.Search) ||
|
|
|
|
|
x.Subject.Contains(input.Search) ||
|
|
|
|
|
x.TeacherName.Contains(input.Search)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(input.Status))
|
|
|
|
|
{
|
|
|
|
|
switch (input.Status)
|
|
|
|
|
{
|
|
|
|
|
case "Active":
|
|
|
|
|
query = query.Where(x => x.ActualStartTime == null && x.ActualEndTime == null);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "Open":
|
|
|
|
|
query = query.Where(x => x.ActualStartTime != null && x.ActualEndTime == null);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case "Passive":
|
|
|
|
|
query = query.Where(x => x.ActualEndTime != null);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-08-25 18:01:57 +00:00
|
|
|
var totalCount = query.Count();
|
|
|
|
|
var items = query
|
|
|
|
|
.OrderBy(x => x.ScheduledStartTime)
|
|
|
|
|
.Skip(input.SkipCount)
|
|
|
|
|
.Take(input.MaxResultCount)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
return new PagedResultDto<ClassroomDto>(
|
2025-08-25 18:01:57 +00:00
|
|
|
totalCount,
|
2025-08-26 05:59:39 +00:00
|
|
|
ObjectMapper.Map<List<Classroom>, List<ClassroomDto>>(items)
|
2025-08-25 18:01:57 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:42:01 +00:00
|
|
|
public async Task<ClassroomDto> CreateAsync(ClassroomDto input)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
2025-08-26 21:42:01 +00:00
|
|
|
var classSession = new Classroom(
|
|
|
|
|
GuidGenerator.Create(),
|
|
|
|
|
input.Name,
|
|
|
|
|
input.Description,
|
|
|
|
|
input.Subject,
|
|
|
|
|
CurrentUser.Id,
|
|
|
|
|
CurrentUser.Name,
|
|
|
|
|
input.ScheduledStartTime,
|
2025-08-28 11:53:47 +00:00
|
|
|
input.ScheduledStartTime.AddMinutes(input.Duration),
|
2025-08-26 21:42:01 +00:00
|
|
|
input.Duration,
|
|
|
|
|
input.MaxParticipants,
|
|
|
|
|
input.SettingsJson = JsonSerializer.Serialize(input.SettingsDto)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await _classSessionRepository.InsertAsync(classSession);
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-26 21:42:01 +00:00
|
|
|
public async Task<ClassroomDto> UpdateAsync(Guid id, ClassroomDto input)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
if (classSession.TeacherId != CurrentUser.Id)
|
|
|
|
|
{
|
|
|
|
|
throw new UnauthorizedAccessException("Only the teacher can update this class");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
classSession.Name = input.Name;
|
|
|
|
|
classSession.Description = input.Description;
|
|
|
|
|
classSession.Subject = input.Subject;
|
2025-08-27 15:00:22 +00:00
|
|
|
classSession.TeacherId = input.TeacherId;
|
|
|
|
|
classSession.TeacherName = input.TeacherName;
|
2025-08-25 18:01:57 +00:00
|
|
|
classSession.ScheduledStartTime = input.ScheduledStartTime;
|
2025-08-28 11:53:47 +00:00
|
|
|
classSession.ScheduledEndTime = input.ScheduledStartTime.AddMinutes(input.Duration);
|
2025-08-25 18:01:57 +00:00
|
|
|
classSession.Duration = input.Duration;
|
|
|
|
|
classSession.MaxParticipants = input.MaxParticipants;
|
2025-08-27 15:00:22 +00:00
|
|
|
classSession.SettingsJson = JsonSerializer.Serialize(input.SettingsDto);
|
2025-08-25 18:01:57 +00:00
|
|
|
|
|
|
|
|
await _classSessionRepository.UpdateAsync(classSession);
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task DeleteAsync(Guid id)
|
|
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
if (classSession.TeacherId != CurrentUser.Id)
|
|
|
|
|
{
|
|
|
|
|
throw new UnauthorizedAccessException("Only the teacher can delete this class");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await _classSessionRepository.DeleteAsync(id);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 15:00:22 +00:00
|
|
|
[HttpPut]
|
2025-08-27 20:55:01 +00:00
|
|
|
public async Task<ClassroomDto> StartClassAsync(Guid id)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
if (classSession.TeacherId != CurrentUser.Id)
|
|
|
|
|
{
|
|
|
|
|
throw new UnauthorizedAccessException("Only the teacher can start this class");
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-27 15:00:22 +00:00
|
|
|
classSession.ActualStartTime = DateTime.Now;
|
|
|
|
|
|
2025-08-25 18:01:57 +00:00
|
|
|
await _classSessionRepository.UpdateAsync(classSession);
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
2025-08-27 15:00:22 +00:00
|
|
|
[HttpPut]
|
2025-08-27 20:55:01 +00:00
|
|
|
public async Task EndClassAsync(Guid id)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
if (classSession.TeacherId != CurrentUser.Id)
|
|
|
|
|
{
|
|
|
|
|
throw new UnauthorizedAccessException("Only the teacher can end this class");
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-28 11:53:47 +00:00
|
|
|
classSession.ActualEndTime = DateTime.Now;
|
2025-08-27 15:00:22 +00:00
|
|
|
|
2025-08-25 18:01:57 +00:00
|
|
|
await _classSessionRepository.UpdateAsync(classSession);
|
|
|
|
|
|
|
|
|
|
// Update attendance records
|
|
|
|
|
var activeAttendances = await _attendanceRepository.GetListAsync(
|
|
|
|
|
x => x.SessionId == id && x.LeaveTime == null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
foreach (var attendance in activeAttendances)
|
|
|
|
|
{
|
2025-08-28 11:53:47 +00:00
|
|
|
attendance.LeaveTime = DateTime.Now;
|
2025-08-25 18:01:57 +00:00
|
|
|
attendance.CalculateDuration();
|
|
|
|
|
await _attendanceRepository.UpdateAsync(attendance);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
public async Task<ClassroomDto> JoinClassAsync(Guid id)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
|
|
|
|
|
if (classSession.ParticipantCount >= classSession.MaxParticipants)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Class is full");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user is already in the class
|
|
|
|
|
var existingParticipant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
x => x.SessionId == id && x.UserId == CurrentUser.Id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (existingParticipant == null)
|
|
|
|
|
{
|
|
|
|
|
// Add participant
|
2025-08-26 05:59:39 +00:00
|
|
|
var participant = new ClassParticipant(
|
2025-08-25 18:01:57 +00:00
|
|
|
GuidGenerator.Create(),
|
|
|
|
|
id,
|
|
|
|
|
CurrentUser.Id,
|
|
|
|
|
CurrentUser.Name,
|
|
|
|
|
CurrentUser.Email,
|
|
|
|
|
false // isTeacher
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await _participantRepository.InsertAsync(participant);
|
|
|
|
|
|
|
|
|
|
// Create attendance record
|
2025-08-26 05:59:39 +00:00
|
|
|
var attendance = new ClassAttandance(
|
2025-08-25 18:01:57 +00:00
|
|
|
GuidGenerator.Create(),
|
|
|
|
|
id,
|
|
|
|
|
CurrentUser.Id,
|
|
|
|
|
CurrentUser.Name,
|
2025-08-28 11:53:47 +00:00
|
|
|
DateTime.Now
|
2025-08-25 18:01:57 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await _attendanceRepository.InsertAsync(attendance);
|
|
|
|
|
|
|
|
|
|
// Update participant count
|
|
|
|
|
classSession.ParticipantCount++;
|
|
|
|
|
await _classSessionRepository.UpdateAsync(classSession);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<Classroom, ClassroomDto>(classSession);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task LeaveClassAsync(Guid id)
|
|
|
|
|
{
|
|
|
|
|
var participant = await _participantRepository.FirstOrDefaultAsync(
|
|
|
|
|
x => x.SessionId == id && x.UserId == CurrentUser.Id
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (participant != null)
|
|
|
|
|
{
|
|
|
|
|
await _participantRepository.DeleteAsync(participant);
|
|
|
|
|
|
|
|
|
|
// Update attendance record
|
|
|
|
|
var attendance = await _attendanceRepository.FirstOrDefaultAsync(
|
|
|
|
|
x => x.SessionId == id && x.StudentId == CurrentUser.Id && x.LeaveTime == null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (attendance != null)
|
|
|
|
|
{
|
|
|
|
|
attendance.LeaveTime = DateTime.UtcNow;
|
|
|
|
|
attendance.CalculateDuration();
|
|
|
|
|
await _attendanceRepository.UpdateAsync(attendance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update participant count
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(id);
|
|
|
|
|
classSession.ParticipantCount = Math.Max(0, classSession.ParticipantCount - 1);
|
|
|
|
|
await _classSessionRepository.UpdateAsync(classSession);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
public async Task<List<ClassAttendanceDto>> GetAttendanceAsync(Guid sessionId)
|
2025-08-25 18:01:57 +00:00
|
|
|
{
|
|
|
|
|
var classSession = await _classSessionRepository.GetAsync(sessionId);
|
|
|
|
|
|
|
|
|
|
if (classSession.TeacherId != CurrentUser.Id)
|
|
|
|
|
{
|
|
|
|
|
throw new UnauthorizedAccessException("Only the teacher can view attendance");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var attendanceRecords = await _attendanceRepository.GetListAsync(
|
|
|
|
|
x => x.SessionId == sessionId
|
|
|
|
|
);
|
|
|
|
|
|
2025-08-26 05:59:39 +00:00
|
|
|
return ObjectMapper.Map<List<ClassAttandance>, List<ClassAttendanceDto>>(attendanceRecords);
|
2025-08-25 18:01:57 +00:00
|
|
|
}
|
|
|
|
|
}
|