diff --git a/Entities/Contracts/Assignment.cs b/Entities/Contracts/Assignment.cs index 9da5515..aac37c0 100644 --- a/Entities/Contracts/Assignment.cs +++ b/Entities/Contracts/Assignment.cs @@ -54,7 +54,7 @@ namespace Entities.Contracts public bool IsDeleted { get; set; } = false; // Navigation Properties - + [ForeignKey(nameof(CreatorId))] public User Creator { get; set; } public ICollection AssignmentClasses { get; set; } @@ -73,4 +73,44 @@ namespace Entities.Contracts AssignmentAttachments = new HashSet(); } } + + + public static class AssignmentExt + { + public static Submission ConvertToSubmission(this Assignment assignment, Guid studentId, Guid GraderId) + { + if (assignment == null) return new Submission(); + var submission = new Submission(); + + submission.StudentId = studentId; + submission.SubmissionTime = DateTime.Now; + submission.Status = SubmissionStatus.Pending; + submission.GraderId = GraderId; + submission.AssignmentId = assignment.Id; + + ConvertExamSturctToSubmissionDetails(assignment.ExamStruct, studentId, submission.SubmissionDetails); + + return submission; + } + + + public static void ConvertExamSturctToSubmissionDetails(AssignmentQuestion examStruct, Guid studentId, ICollection submissions) + { + if (examStruct == null) return; + submissions.Add(new SubmissionDetail + { + StudentId = studentId, + AssignmentQuestionId = examStruct.Id, + IsCorrect = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + Status = SubmissionStatus.Pending, + }); + + examStruct.ChildrenAssignmentQuestion?.ToList().ForEach(s => + { + ConvertExamSturctToSubmissionDetails(s, studentId, submissions); + }); + } + } } diff --git a/Entities/Contracts/Submission.cs b/Entities/Contracts/Submission.cs index 636816c..0b1ad3e 100644 --- a/Entities/Contracts/Submission.cs +++ b/Entities/Contracts/Submission.cs @@ -28,7 +28,7 @@ namespace Entities.Contracts [Required] [Column("attempt_number")] - public Guid AttemptNumber { get; set; } + public byte AttemptNumber { get; set; } [Column("submission_time")] public DateTime SubmissionTime { get; set; } diff --git a/Entities/Contracts/SubmissionDetail.cs b/Entities/Contracts/SubmissionDetail.cs index 6fc580a..3a8812d 100644 --- a/Entities/Contracts/SubmissionDetail.cs +++ b/Entities/Contracts/SubmissionDetail.cs @@ -51,6 +51,12 @@ namespace Entities.Contracts [Column("deleted")] public bool IsDeleted { get; set; } + [Required] + [Column("status")] + public SubmissionStatus Status { get; set; } + + + [ForeignKey(nameof(StudentId))] public User Student { get; set; } diff --git a/Entities/DTO/AssigExamToStudentsDto.cs b/Entities/DTO/AssigExamToStudentsDto.cs new file mode 100644 index 0000000..cf57528 --- /dev/null +++ b/Entities/DTO/AssigExamToStudentsDto.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Entities.DTO +{ + public class AssigExamToStudentsDto + { + public Guid CreaterId { get; set; } + public Guid AssignmentId { get; set; } + public List StudentIds { get; set; } = new List(); + } +} diff --git a/Entities/DTO/SubmissionDetailDto.cs b/Entities/DTO/SubmissionDetailDto.cs index 1a7aba9..d12702d 100644 --- a/Entities/DTO/SubmissionDetailDto.cs +++ b/Entities/DTO/SubmissionDetailDto.cs @@ -1,4 +1,5 @@ -using System; +using Entities.Contracts; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -15,5 +16,6 @@ namespace Entities.DTO public bool? IsCorrect { get; set; } public float? PointsAwarded { get; set; } public string? TeacherFeedback { get; set; } + public SubmissionStatus Status { get; set; } = SubmissionStatus.Graded; } } diff --git a/Entities/DTO/UserClassRoleDto.cs b/Entities/DTO/UserClassRoleDto.cs new file mode 100644 index 0000000..6f4d7d4 --- /dev/null +++ b/Entities/DTO/UserClassRoleDto.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Entities.DTO +{ + public class UserClassRoleDto + { + public List<(byte, byte)> ClassInfo { get; set; } = new List<(byte, byte)> (); + public string Role { get; set; } = string.Empty; + } +} diff --git a/TechHelper.Client/Pages/Author/RestoreUserRole.razor b/TechHelper.Client/Pages/Author/RestoreUserRole.razor new file mode 100644 index 0000000..4db71f4 --- /dev/null +++ b/TechHelper.Client/Pages/Author/RestoreUserRole.razor @@ -0,0 +1,34 @@ +@using TechHelper.Client.Services + + + Restore Role + + +@code { + [Inject] + public IAuthenticationClientService Authentication { get; set; } + [Inject] + public IUserServices UserServices { get; set; } + [Inject] + public ISnackbar Snackbar { get; set; } + private async Task Restore() + { + var result = await UserServices.RestoreUserInfo(); + if (result.Status) + { + + Snackbar.Add("更新成功", Severity.Success); + + } + else + Snackbar.Add("更新失败", Severity.Error); + var token = await Authentication.RefreshTokenAsync(); + if (token != null) + Snackbar.Add("刷新令牌成功", Severity.Success); + else + Snackbar.Add("刷新令牌失败,你可以手动刷新", Severity.Warning); + StateHasChanged(); + } + + +} diff --git a/TechHelper.Client/Pages/Exam/ExamCheck.razor b/TechHelper.Client/Pages/Exam/ExamCheck.razor index 07b8286..fe601c5 100644 --- a/TechHelper.Client/Pages/Exam/ExamCheck.razor +++ b/TechHelper.Client/Pages/Exam/ExamCheck.razor @@ -24,7 +24,7 @@ else if (_questionsForTable.Any() && _students.Any()) @student.DisplayName + @onclick="() => ToggleStudentAllAnswers(student.Id)" /> } @@ -200,7 +200,7 @@ else newSubmission.StudentId = student.Id; newSubmission.AssignmentId = Assignment.Id; newSubmission.SubmissionTime = DateTime.Now; - newSubmission.Status = Entities.Contracts.SubmissionStatus.Submitted; + newSubmission.Status = Entities.Contracts.SubmissionStatus.Graded; foreach (var row in _questionsForTable) @@ -225,11 +225,15 @@ else submissionDto.ForEach(async s => { Snackbar?.Add($"正在提交: {_students.FirstOrDefault(std => std.Id == s.StudentId)?.DisplayName} 的试卷", Severity.Info); - await ExamService.SubmissionAssignment(s); + var submidResult = await ExamService.SubmissionAssignment(s); + if (submidResult.Status) + Snackbar?.Add($"批改结果已提交 {_students.FirstOrDefault(st => st.Id == s.StudentId)?.DisplayName}", Severity.Success); + else + Snackbar?.Add("批改结果提交失败", Severity.Error); + }); - Snackbar?.Add("批改结果已提交(模拟)", Severity.Success); } } \ No newline at end of file diff --git a/TechHelper.Client/Pages/Exam/ExamManager.razor b/TechHelper.Client/Pages/Exam/ExamManager.razor index 9105f17..352c196 100644 --- a/TechHelper.Client/Pages/Exam/ExamManager.razor +++ b/TechHelper.Client/Pages/Exam/ExamManager.razor @@ -50,9 +50,16 @@ else isloding = true; Snackbar.Add("正在加载", Severity.Info); var result = await ExamService.GetAllExam(); - examDtos = result.Result as List ?? new List(); + if (result.Status) + { + examDtos = result.Result as List ?? new List(); + Snackbar.Add("加载成功", Severity.Info); + } + else + { + Snackbar.Add($"加载失败 {result.Message}", Severity.Error); + } isloding = false; - Snackbar.Add("加载成功", Severity.Info); StateHasChanged(); } } diff --git a/TechHelper.Client/Pages/Manage/Class.razor b/TechHelper.Client/Pages/Manage/Class.razor index 66727a1..e524ecd 100644 --- a/TechHelper.Client/Pages/Manage/Class.razor +++ b/TechHelper.Client/Pages/Manage/Class.razor @@ -55,4 +55,5 @@ else 年级 : @authenticationStateTask.Result.User.FindFirst("Grade")?.Value.ToString() 班级 : @authenticationStateTask.Result.User.FindFirst("Class")?.Value.ToString() -} \ No newline at end of file +} + \ No newline at end of file diff --git a/TechHelper.Client/Program.cs b/TechHelper.Client/Program.cs index e6b8e82..25a67a7 100644 --- a/TechHelper.Client/Program.cs +++ b/TechHelper.Client/Program.cs @@ -45,6 +45,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddHttpClient("WebApiClient", client => { var baseAddress = builder.Configuration.GetSection("ApiConfiguration:BaseAddress").Value; diff --git a/TechHelper.Client/Services/ExamService.cs b/TechHelper.Client/Services/ExamService.cs index ce06297..cdc4b97 100644 --- a/TechHelper.Client/Services/ExamService.cs +++ b/TechHelper.Client/Services/ExamService.cs @@ -167,7 +167,7 @@ namespace TechHelper.Client.Services // 直接使用注入的 _client 实例 var response = await _client.PostAsJsonAsync("exam/add", assiDto); - if (response.IsSuccessStatusCode) // 检查是否是成功的状态码,例如 200 OK, 201 Created 等 + if (response.IsSuccessStatusCode) { return ApiResponse.Success(message: "试题保存成功。"); } diff --git a/TechHelper.Client/Services/IUserServices.cs b/TechHelper.Client/Services/IUserServices.cs new file mode 100644 index 0000000..9159fab --- /dev/null +++ b/TechHelper.Client/Services/IUserServices.cs @@ -0,0 +1,9 @@ +using TechHelper.Services; + +namespace TechHelper.Client.Services +{ + public interface IUserServices + { + public Task RestoreUserInfo(); + } +} diff --git a/TechHelper.Client/Services/UserServices.cs b/TechHelper.Client/Services/UserServices.cs new file mode 100644 index 0000000..3096755 --- /dev/null +++ b/TechHelper.Client/Services/UserServices.cs @@ -0,0 +1,24 @@ +using TechHelper.Services; + +namespace TechHelper.Client.Services +{ + public class UserServices : IUserServices + { + private readonly HttpClient _client; + + public UserServices(HttpClient httpClient) + { + _client = httpClient; + } + + public async Task RestoreUserInfo() + { + var result = await _client.GetAsync("user/restoreUserRole"); + if (result.IsSuccessStatusCode) + { + return ApiResponse.Success(); + } + return ApiResponse.Error(); + } + } +} diff --git a/TechHelper.Server/Context/AutoMapperProFile.cs b/TechHelper.Server/Context/AutoMapperProFile.cs index 4d31504..9cb9c2a 100644 --- a/TechHelper.Server/Context/AutoMapperProFile.cs +++ b/TechHelper.Server/Context/AutoMapperProFile.cs @@ -32,7 +32,8 @@ namespace TechHelper.Context .ForMember(dest => dest.EmailConfirmed, opt => opt.Ignore()); CreateMap() - .ForMember(d => d.Number, o => o.MapFrom(src => src.Class)).ReverseMap(); + .ForMember(d => d.Number, o => o.MapFrom(src => src.Class)) + .ReverseMap(); diff --git a/TechHelper.Server/Controllers/ExamController.cs b/TechHelper.Server/Controllers/ExamController.cs index 1ce660c..b457d0a 100644 --- a/TechHelper.Server/Controllers/ExamController.cs +++ b/TechHelper.Server/Controllers/ExamController.cs @@ -11,14 +11,12 @@ using TechHelper.Services; namespace TechHelper.Server.Controllers { - [Route("api/exam")] + [Route("api/[controller]")] [ApiController] [Authorize] - - public class ExamController : ControllerBase { - private IExamService _examService; + private readonly IExamService _examService; private readonly UserManager _userManager; public ExamController(IExamService examService, UserManager userManager) @@ -27,117 +25,202 @@ namespace TechHelper.Server.Controllers _userManager = userManager; } + /// + /// 创建一个新的考试/作业。 + /// + /// 考试/作业的数据传输对象。 + /// 新创建的考试/作业信息或错误信息。 [HttpPost("add")] - public async Task AddExam( - [FromBody] AssignmentDto examDto) + public async Task AddExam([FromBody] AssignmentDto examDto) { - var user = await _userManager.FindByEmailAsync(User.Identity?.Name ?? ""); - if (user == null) return BadRequest("无效的用户"); - + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) return NotFound("没有找到用户"); examDto.CreatorId = user.Id; var result = await _examService.CreateExamAsync(examDto); + if (result.Status) { - return Ok(result); + return StatusCode(201, result.Result); } else { - return BadRequest(); + return BadRequest(result.Message); } } + /// + /// 提交作业。 + /// + /// 提交的数据传输对象。 + /// 提交结果或错误信息。 [HttpPost("submission")] - public async Task SubmissionAssignment( - [FromBody] SubmissionDto submissionDto) + [Authorize(Roles = "Student")] + public async Task SubmissionAssignment([FromBody] SubmissionDto submissionDto) { - if (User == null) return BadRequest("无效的用户"); - if (User.IsInRole("Teacher")) + var result = await _examService.SubmissionAssignment(submissionDto); + + if (result.Status) { - var result = await _examService.SubmissionAssignment(submissionDto); - if (result.Status) - { - return Ok(result); - } - else - { - return BadRequest(result.Message); - } + return Ok(result.Result); } else { - return BadRequest("你没有权限修改"); + return BadRequest(result.Message); } } - [HttpGet("get")] + /// + /// 根据ID获取考试/作业详情。 + /// + /// 考试/作业ID。 + /// 考试/作业详情或未找到错误。 + [HttpGet("{id:guid}")] public async Task GetExamById(Guid id) { - var result = await _examService.GetAsync(id); + if (result.Status) + { + if (result.Result == null) + { + return NotFound("未找到指定的考试/作业。"); + } return Ok(result.Result); + } else - return BadRequest("查找失败"); + { + if (result.Message.Contains("未找到") || result.Message.Contains("not found", StringComparison.OrdinalIgnoreCase)) + { + return NotFound(result.Message); + } + return BadRequest(result.Message); + } } - + /// + /// 获取所有考试/作业的预览信息(教师获取自己创建的,学生获取自己需要提交的)。 + /// + /// 考试/作业预览列表或错误信息。 [HttpGet("getAllPreview")] public async Task GetAllExamPreview() { - if (User == null) return BadRequest("用户验证失败, 无效用户"); + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) return NotFound("没有找到用户"); - var userid = await _userManager.FindByEmailAsync(User.Identity.Name); + ApiResponse result; - - var result = new ApiResponse(); if (User.IsInRole("Teacher")) { - result = await _examService.GetAllExamPreviewsAsync(userid.Id); + result = await _examService.GetAllExamPreviewsAsync(user.Id); } else if (User.IsInRole("Student")) { - result = await _examService.GetAllSubmissionAsync(userid.Id); + result = await _examService.GetAllSubmissionAsync(user.Id); } else { - return BadRequest("你没有相应的权限"); + return Forbid("你没有查看考试预览的权限。"); } if (result.Status) { return Ok(result.Result); } - return BadRequest(result); + else + { + return BadRequest(result.Message); + } } - - [HttpGet("getAllSubmission")] + /// + /// 获取学生的所有提交记录。 + /// + /// 提交记录列表或错误信息。 + [HttpGet("getAllSubmissions")] + [Authorize(Roles = "Student")] public async Task GetAllSubmission() { - if (User == null) return BadRequest("用户验证失败, 无效用户"); + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) return NotFound("没有找到用户"); - var userid = await _userManager.FindByEmailAsync(User.Identity.Name); - - var result = await _examService.GetAllSubmissionAsync(userid.Id); + var result = await _examService.GetAllSubmissionAsync(user.Id); if (result.Status) { return Ok(result.Result); } - return BadRequest(result); + else + { + return BadRequest(result.Message); + } } - + /// + /// 逻辑删除指定ID的考试/作业。 + /// + /// 要删除的考试/作业ID。 + /// 操作结果。 + [HttpDelete("delete/{id:guid}")] [Authorize(Roles = "Teacher")] - [HttpDelete("{guid}")] - public async Task DeleteAsync(Guid guid) + public async Task DeleteAsync(Guid id) { - var deleteResult = await _examService.DeleteAsync(guid); + var deleteResult = await _examService.DeleteAsync(id); + if (deleteResult.Status) { - return Ok(); + return NoContent(); + } + else + { + if (deleteResult.Message.Contains("未找到") || deleteResult.Message.Contains("not found", StringComparison.OrdinalIgnoreCase)) + { + return NotFound(deleteResult.Message); + } + return BadRequest(deleteResult.Message); + } + } + + + /// + /// 为指定学生指派作业 + /// + /// + /// + [HttpPost("assignmentExamToStudent")] + [Authorize(Roles = "Teacher")] + public async Task AssignmentExamToStudent([FromBody] AssigExamToStudentsDto AETSdto) + { + var result = await _examService.AssignmentToStudentsAsync(AETSdto); + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + + + /// + /// 为所有学生指派作业 + /// + /// + /// + [HttpPost("assignmentExamToStudent/{id:guid}")] + [Authorize(Roles = "Teacher")] + public async Task AssignmentExamToAllStudentsAsync(Guid id) + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name ?? ""); + var result = await _examService.AssignmentToAllStudentsAsync(id, user.Id); + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); } - return BadRequest(); } } -} +} \ No newline at end of file diff --git a/TechHelper.Server/Controllers/SubmissionController.cs b/TechHelper.Server/Controllers/SubmissionController.cs new file mode 100644 index 0000000..3519d2a --- /dev/null +++ b/TechHelper.Server/Controllers/SubmissionController.cs @@ -0,0 +1,279 @@ +using Entities.Contracts; +using Entities.DTO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using TechHelper.Server.Services; +using TechHelper.Services; + +namespace TechHelper.Server.Controllers +{ + [Route("api/submission")] + [ApiController] + [Authorize] + + public class SubmissionController : ControllerBase + { + private readonly UserManager _userManager; + private readonly ISubmissionServices _submissionServices; + + public SubmissionController(UserManager userManager, ISubmissionServices submissionServices) + { + _userManager = userManager; + _submissionServices = submissionServices; + } + + + /// + /// 获取当前用户的所有错题。 + /// + /// 错题列表或错误信息。 + [HttpGet("getAllErrorQuestions")] + public async Task GetAllErrorQuestionsAsync() + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + { + return NotFound("未找到当前用户信息。"); + } + + var result = await _submissionServices.GetAllErrorQuestionsAsync(user.Id); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定作业中当前用户的错题。 + /// + /// 作业ID。 + /// 错题列表或错误信息。 + [HttpGet("getAssignmentErrorQuestions/{assignmentId:guid}")] + public async Task GetAssignmentErrorQuestionsAsync(Guid assignmentId) + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + { + return NotFound("未找到当前用户信息。"); + } + + var result = await _submissionServices.GetAssignmentErrorQuestionsAsync(assignmentId, user.Id); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定作业中当前用户的错题类型分布。 + /// + /// 作业ID。 + /// 错题类型分布数据。 + [HttpGet("getAssignmentErrorQuestionTypeDistribution/{assignmentId:guid}")] + public async Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId) + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + { + return NotFound("未找到当前用户信息。"); + } + + var result = await _submissionServices.GetAssignmentErrorQuestionTypeDisAsync(assignmentId, user.Id); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定作业中所有学生的错题情况概述。 + /// + /// 作业ID。 + /// 每个学生的错题统计信息。 + [HttpGet("getAssignmentAllStudentsError/{assignmentId:guid}")] + [Authorize(Roles = "Teacher")] + public async Task GetAssignmentAllStudentsError(Guid assignmentId) + { + // 假设当前用户是教师,如果需要验证,可以使用UserManager.IsInRoleAsync + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + { + return NotFound("未找到当前用户信息。"); + } + // TODO: 根据实际业务需求,可能需要验证当前用户是否为该作业的教师。 + // 例如: var isTeacherOfAssignment = await _assignmentService.IsTeacherOfAssignment(assignmentId, user.Id); + + var result = await _submissionServices.GetAssignmentAllStudentsError(assignmentId, user.Id); // 传入当前教师ID + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定作业中哪些学生做错了哪些题目。 + /// + /// 作业ID。 + /// 按题目分组的学生错题列表。 + [HttpGet("getQuestionErrorStudents/{assignmentId:guid}")] + [Authorize(Roles = "Teacher")] + public async Task GetQuestionErrorStudents(Guid assignmentId) + { + var result = await _submissionServices.GetQuestionErrorStudents(assignmentId); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 添加一次提交记录。 + /// + /// 提交的数据模型。 + /// 新创建的提交记录或错误信息。 + [HttpPost("add")] + public async Task AddAsync([FromBody] Submission model) + { + // 可以在这里获取当前用户ID并赋值给 model.StudentId,确保提交人信息正确 + // var user = await _userManager.FindByEmailAsync(User.Identity.Name); + // if (user == null) return NotFound("未找到当前用户信息。"); + // model.StudentId = user.Id; + + var result = await _submissionServices.AddAsync(model); + + if (result.Status) + { + // 如果成功,通常返回 201 Created,并包含新资源的URI + // 但如果服务层只返回数据,也可以直接 Ok + return StatusCode(201, result); // 建议返回 201 Created + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 逻辑删除指定ID的提交记录。 + /// + /// 提交ID。 + /// 操作结果。 + [HttpDelete("delete/{id:guid}")] + public async Task DeleteAsync(Guid id) + { + // TODO: 在服务层或控制器层添加权限检查:确保删除者有权删除此提交(例如是提交者本人、相关教师或管理员) + // var user = await _userManager.FindByEmailAsync(User.Identity.Name); + // if (user == null) return Unauthorized(); // 或 Forbidden + + var result = await _submissionServices.DeleteAsync(id); + + if (result.Status) + { + return NoContent(); // 204 No Content,表示删除成功但无内容返回 + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取所有提交记录(支持分页和查询)。 + /// + /// 查询参数,包含分页信息。 + /// 分页的提交记录列表。 + [HttpGet("getAll")] + [Authorize(Roles = "Admin,Teacher")] + public async Task GetAllAsync([FromQuery] QueryParameter query) + { + var result = await _submissionServices.GetAllAsync(query); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 根据ID获取单个提交记录。 + /// + /// 提交ID。 + /// 单个提交记录或未找到错误。 + [HttpGet("{id:guid}")] + public async Task GetAsync(Guid id) + { + var result = await _submissionServices.GetAsync(id); + + if (result.Status) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 更新提交记录。 + /// + /// 提交ID。 + /// 要更新的提交数据。 + /// 更新后的提交记录或错误信息。 + [HttpPut("update/{id:guid}")] + public async Task UpdateAsync(Guid id, [FromBody] Submission model) + { + if (id != model.Id) // 确保路径中的ID和模型中的ID一致 + { + return BadRequest("路由ID与请求体中的ID不匹配。"); + } + + // TODO: 权限检查:确保更新者有权更新此提交 + // var user = await _userManager.FindByEmailAsync(User.Identity.Name); + // if (user == null) return Unauthorized(); + + var result = await _submissionServices.UpdateAsync(model); + + if (result.Status) + { + return Ok(result); + } + else + { + + return BadRequest(result.Message); + } + } + } +} diff --git a/TechHelper.Server/Controllers/UserController.cs b/TechHelper.Server/Controllers/UserController.cs index 4121dde..6b18c5c 100644 --- a/TechHelper.Server/Controllers/UserController.cs +++ b/TechHelper.Server/Controllers/UserController.cs @@ -3,6 +3,7 @@ using Entities.DTO; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using TechHelper.Server.Services; using TechHelper.Services; namespace TechHelper.Server.Controllers @@ -11,12 +12,14 @@ namespace TechHelper.Server.Controllers [ApiController] public class UserController : ControllerBase { + private IUserSerivces _userSerivces; private IClassService _classService; private UserManager _userManager; - public UserController(IClassService classService, UserManager userManager) + public UserController(IClassService classService, UserManager userManager, IUserSerivces userSerivces) { _classService = classService; _userManager = userManager; + _userSerivces = userSerivces; } @@ -26,5 +29,22 @@ namespace TechHelper.Server.Controllers { return Ok(); } + + + + [HttpGet("restoreUserRole")] + public async Task RestoreUserRole() + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + + if (user == null) return NotFound(); + if (User.IsInRole("Teacher") || User.IsInRole("Student")) + return Ok(); + var result = await _userSerivces.RestoreUserRoleInformation(user); + if (result.Status) + return Ok(); + else + return Unauthorized(); + } } } diff --git a/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.Designer.cs b/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.Designer.cs new file mode 100644 index 0000000..1a4119e --- /dev/null +++ b/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.Designer.cs @@ -0,0 +1,1254 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TechHelper.Context; + +#nullable disable + +namespace TechHelper.Server.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20250630090135__update_submisstion_detail")] + partial class _update_submisstion_detail + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.16") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("created_by"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("description"); + + b.Property("DueDate") + .HasColumnType("datetime(6)") + .HasColumnName("due_date"); + + b.Property("ExamStructId") + .HasColumnType("char(36)") + .HasColumnName("exam_struct_id"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("Score") + .HasColumnType("float") + .HasColumnName("score"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("title"); + + b.Property("TotalQuestions") + .HasColumnType("tinyint unsigned") + .HasColumnName("total_points"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("ExamStructId") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("assignments", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("file_name"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("file_path"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)") + .HasColumnName("uploaded_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.ToTable("assignment_attachments"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentClass", b => + { + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id") + .HasColumnOrder(0); + + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id") + .HasColumnOrder(1); + + b.Property("AssignedAt") + .HasColumnType("datetime(6)") + .HasColumnName("assigned_at"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.HasKey("AssignmentId", "ClassId"); + + b.HasIndex("ClassId"); + + b.ToTable("assignment_class", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + b.Property("Index") + .HasColumnType("tinyint unsigned") + .HasColumnName("question_number"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("ParentAssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("parent_question_group_id"); + + b.Property("QuestionContextId") + .HasColumnType("char(36)") + .HasColumnName("description"); + + b.Property("QuestionId") + .HasColumnType("char(36)") + .HasColumnName("question_id"); + + b.Property("Score") + .HasColumnType("float") + .HasColumnName("score"); + + b.Property("Sequence") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("sequence"); + + b.Property("StructType") + .HasColumnType("tinyint unsigned") + .HasColumnName("group_state"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)") + .HasColumnName("title"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("ParentAssignmentQuestionId"); + + b.HasIndex("QuestionContextId"); + + b.HasIndex("QuestionId"); + + b.ToTable("assignment_questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("Description") + .HasColumnType("longtext") + .HasColumnName("description"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned") + .HasColumnName("grade"); + + b.Property("HeadTeacherId") + .HasColumnType("char(36)") + .HasColumnName("head_teacher_id"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("Number") + .HasColumnType("tinyint unsigned") + .HasColumnName("class"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("HeadTeacherId"); + + b.ToTable("classes", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.ClassStudent", b => + { + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id") + .HasColumnOrder(0); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id") + .HasColumnOrder(1); + + b.Property("EnrollmentDate") + .HasColumnType("datetime(6)") + .HasColumnName("enrollment_date"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.HasKey("ClassId", "StudentId"); + + b.HasIndex("StudentId"); + + b.ToTable("class_student", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.ClassTeacher", b => + { + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id"); + + b.Property("TeacherId") + .HasColumnType("char(36)") + .HasColumnName("teacher_id"); + + b.Property("SubjectTaught") + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_taught"); + + b.HasKey("ClassId", "TeacherId"); + + b.HasIndex("TeacherId"); + + b.ToTable("class_teachers", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("LessonID") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LessonID"); + + b.ToTable("key_point"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TextbookID") + .HasColumnType("char(36)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("TextbookID"); + + b.ToTable("lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.LessonQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("LessonID") + .HasColumnType("char(36)"); + + b.Property("Question") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LessonID"); + + b.ToTable("lesson_question"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("Answer") + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("correct_answer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("created_by"); + + b.Property("DifficultyLevel") + .HasMaxLength(10) + .HasColumnType("tinyint unsigned") + .HasColumnName("difficulty_level"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("KeyPointId") + .HasColumnType("char(36)") + .HasColumnName("key_point"); + + b.Property("LessonId") + .HasColumnType("char(36)") + .HasColumnName("lesson"); + + b.Property("Options") + .HasColumnType("longtext") + .HasColumnName("options"); + + b.Property("SubjectArea") + .HasMaxLength(100) + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("question_text"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("tinyint unsigned") + .HasColumnName("question_type"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("KeyPointId"); + + b.HasIndex("LessonId"); + + b.HasIndex("Title") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 20 }); + + b.ToTable("questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("QuestionContexts"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("AttemptNumber") + .HasColumnType("char(36)") + .HasColumnName("attempt_number"); + + b.Property("GradedAt") + .HasColumnType("datetime(6)") + .HasColumnName("graded_at"); + + b.Property("GraderId") + .HasColumnType("char(36)") + .HasColumnName("graded_by"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("OverallFeedback") + .HasColumnType("longtext") + .HasColumnName("overall_feedback"); + + b.Property("OverallGrade") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("overall_grade"); + + b.Property("Status") + .HasMaxLength(15) + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionTime") + .HasColumnType("datetime(6)") + .HasColumnName("submission_time"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("GraderId"); + + b.HasIndex("StudentId"); + + b.ToTable("submissions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("assignment_question_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("IsCorrect") + .HasColumnType("tinyint(1)") + .HasColumnName("is_correct"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("PointsAwarded") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("points_awarded"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentAnswer") + .HasColumnType("longtext") + .HasColumnName("student_answer"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionId") + .HasColumnType("char(36)") + .HasColumnName("submission_id"); + + b.Property("TeacherFeedback") + .HasColumnType("longtext") + .HasColumnName("teacher_feedback"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentQuestionId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubmissionId"); + + b.ToTable("submission_details", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned"); + + b.Property("Publisher") + .HasColumnType("tinyint unsigned"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DisplayName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("69c3cc0c-b284-433e-8493-9b1e7bd1eb1f"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("14b8854f-a38b-4e72-878e-31ba2f7528b2"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("6ef4d2bb-05da-4c17-9152-4467d86939fc"), + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.HasOne("Entities.Contracts.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.AssignmentQuestion", "ExamStruct") + .WithOne() + .HasForeignKey("Entities.Contracts.Assignment", "ExamStructId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", null) + .WithMany("CreatedAssignments") + .HasForeignKey("UserId"); + + b.Navigation("Creator"); + + b.Navigation("ExamStruct"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentAttachment", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("AssignmentAttachments") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentClass", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("AssignmentClasses") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("AssignmentClasses") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Class"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany() + .HasForeignKey("AssignmentId"); + + b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion") + .WithMany("ChildrenAssignmentQuestion") + .HasForeignKey("ParentAssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") + .WithMany("Questions") + .HasForeignKey("QuestionContextId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.Question", "Question") + .WithMany("AssignmentQuestions") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Assignment"); + + b.Navigation("ParentAssignmentQuestion"); + + b.Navigation("Question"); + + b.Navigation("QuestionContext"); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.HasOne("Entities.Contracts.User", "HeadTeacher") + .WithMany() + .HasForeignKey("HeadTeacherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("HeadTeacher"); + }); + + modelBuilder.Entity("Entities.Contracts.ClassStudent", b => + { + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("ClassStudents") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("EnrolledClassesLink") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Class"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Entities.Contracts.ClassTeacher", b => + { + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("ClassTeachers") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Teacher") + .WithMany("TaughtClassesLink") + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Class"); + + b.Navigation("Teacher"); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("KeyPoints") + .HasForeignKey("LessonID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.HasOne("Entities.Contracts.Textbook", "Textbook") + .WithMany("Lessons") + .HasForeignKey("TextbookID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.LessonQuestion", b => + { + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("LessonQuestions") + .HasForeignKey("LessonID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.HasOne("Entities.Contracts.User", "Creator") + .WithMany("CreatedQuestions") + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Entities.Contracts.KeyPoint", "KeyPoint") + .WithMany("Questions") + .HasForeignKey("KeyPointId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("Questions") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Creator"); + + b.Navigation("KeyPoint"); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("Submissions") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Grader") + .WithMany("GradedSubmissions") + .HasForeignKey("GraderId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("SubmissionsAsStudent") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Grader"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.HasOne("Entities.Contracts.AssignmentQuestion", "AssignmentQuestion") + .WithMany("SubmissionDetails") + .HasForeignKey("AssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("SubmissionDetails") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.Submission", "Submission") + .WithMany("SubmissionDetails") + .HasForeignKey("SubmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignmentQuestion"); + + b.Navigation("Student"); + + b.Navigation("Submission"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.Navigation("AssignmentAttachments"); + + b.Navigation("AssignmentClasses"); + + b.Navigation("Submissions"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.Navigation("ChildrenAssignmentQuestion"); + + b.Navigation("SubmissionDetails"); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.Navigation("AssignmentClasses"); + + b.Navigation("ClassStudents"); + + b.Navigation("ClassTeachers"); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.Navigation("KeyPoints"); + + b.Navigation("LessonQuestions"); + + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.Navigation("AssignmentQuestions"); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Navigation("SubmissionDetails"); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Navigation("CreatedAssignments"); + + b.Navigation("CreatedQuestions"); + + b.Navigation("EnrolledClassesLink"); + + b.Navigation("GradedSubmissions"); + + b.Navigation("SubmissionDetails"); + + b.Navigation("SubmissionsAsStudent"); + + b.Navigation("TaughtClassesLink"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.cs b/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.cs new file mode 100644 index 0000000..70d6ba4 --- /dev/null +++ b/TechHelper.Server/Migrations/20250630090135__update_submisstion_detail.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class _update_submisstion_detail : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("cf16c215-63f8-4962-8ad0-058274ecf944")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849")); + + migrationBuilder.AddColumn( + name: "status", + table: "submission_details", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("14b8854f-a38b-4e72-878e-31ba2f7528b2"), null, "Teacher", "TEACHER" }, + { new Guid("69c3cc0c-b284-433e-8493-9b1e7bd1eb1f"), null, "Student", "STUDENT" }, + { new Guid("6ef4d2bb-05da-4c17-9152-4467d86939fc"), null, "Administrator", "ADMINISTRATOR" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("14b8854f-a38b-4e72-878e-31ba2f7528b2")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("69c3cc0c-b284-433e-8493-9b1e7bd1eb1f")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("6ef4d2bb-05da-4c17-9152-4467d86939fc")); + + migrationBuilder.DropColumn( + name: "status", + table: "submission_details"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"), null, "Student", "STUDENT" }, + { new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"), null, "Teacher", "TEACHER" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.Designer.cs b/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.Designer.cs new file mode 100644 index 0000000..fd51de4 --- /dev/null +++ b/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.Designer.cs @@ -0,0 +1,1254 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TechHelper.Context; + +#nullable disable + +namespace TechHelper.Server.Migrations +{ + [DbContext(typeof(ApplicationContext))] + [Migration("20250701095424_atemp_number_convert_to_byte")] + partial class atemp_number_convert_to_byte + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.16") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("created_by"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("description"); + + b.Property("DueDate") + .HasColumnType("datetime(6)") + .HasColumnName("due_date"); + + b.Property("ExamStructId") + .HasColumnType("char(36)") + .HasColumnName("exam_struct_id"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("Score") + .HasColumnType("float") + .HasColumnName("score"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("title"); + + b.Property("TotalQuestions") + .HasColumnType("tinyint unsigned") + .HasColumnName("total_points"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("ExamStructId") + .IsUnique(); + + b.HasIndex("UserId"); + + b.ToTable("assignments", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentAttachment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("FileName") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("file_name"); + + b.Property("FilePath") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)") + .HasColumnName("file_path"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("UploadedAt") + .HasColumnType("datetime(6)") + .HasColumnName("uploaded_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.ToTable("assignment_attachments"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentClass", b => + { + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id") + .HasColumnOrder(0); + + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id") + .HasColumnOrder(1); + + b.Property("AssignedAt") + .HasColumnType("datetime(6)") + .HasColumnName("assigned_at"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.HasKey("AssignmentId", "ClassId"); + + b.HasIndex("ClassId"); + + b.ToTable("assignment_class", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + b.Property("Index") + .HasColumnType("tinyint unsigned") + .HasColumnName("question_number"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("ParentAssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("parent_question_group_id"); + + b.Property("QuestionContextId") + .HasColumnType("char(36)") + .HasColumnName("description"); + + b.Property("QuestionId") + .HasColumnType("char(36)") + .HasColumnName("question_id"); + + b.Property("Score") + .HasColumnType("float") + .HasColumnName("score"); + + b.Property("Sequence") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("sequence"); + + b.Property("StructType") + .HasColumnType("tinyint unsigned") + .HasColumnName("group_state"); + + b.Property("Title") + .HasMaxLength(1024) + .HasColumnType("varchar(1024)") + .HasColumnName("title"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("ParentAssignmentQuestionId"); + + b.HasIndex("QuestionContextId"); + + b.HasIndex("QuestionId"); + + b.ToTable("assignment_questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("Description") + .HasColumnType("longtext") + .HasColumnName("description"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned") + .HasColumnName("grade"); + + b.Property("HeadTeacherId") + .HasColumnType("char(36)") + .HasColumnName("head_teacher_id"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("Number") + .HasColumnType("tinyint unsigned") + .HasColumnName("class"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + MySqlPropertyBuilderExtensions.UseMySqlComputedColumn(b.Property("UpdatedAt")); + + b.HasKey("Id"); + + b.HasIndex("HeadTeacherId"); + + b.ToTable("classes", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.ClassStudent", b => + { + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id") + .HasColumnOrder(0); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id") + .HasColumnOrder(1); + + b.Property("EnrollmentDate") + .HasColumnType("datetime(6)") + .HasColumnName("enrollment_date"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.HasKey("ClassId", "StudentId"); + + b.HasIndex("StudentId"); + + b.ToTable("class_student", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.ClassTeacher", b => + { + b.Property("ClassId") + .HasColumnType("char(36)") + .HasColumnName("class_id"); + + b.Property("TeacherId") + .HasColumnType("char(36)") + .HasColumnName("teacher_id"); + + b.Property("SubjectTaught") + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_taught"); + + b.HasKey("ClassId", "TeacherId"); + + b.HasIndex("TeacherId"); + + b.ToTable("class_teachers", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.Property("LessonID") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("LessonID"); + + b.ToTable("key_point"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TextbookID") + .HasColumnType("char(36)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("varchar(255)"); + + b.HasKey("Id"); + + b.HasIndex("TextbookID"); + + b.ToTable("lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.LessonQuestion", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("LessonID") + .HasColumnType("char(36)"); + + b.Property("Question") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("LessonID"); + + b.ToTable("lesson_question"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("Answer") + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("correct_answer"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("CreatorId") + .HasColumnType("char(36)") + .HasColumnName("created_by"); + + b.Property("DifficultyLevel") + .HasMaxLength(10) + .HasColumnType("tinyint unsigned") + .HasColumnName("difficulty_level"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("KeyPointId") + .HasColumnType("char(36)") + .HasColumnName("key_point"); + + b.Property("LessonId") + .HasColumnType("char(36)") + .HasColumnName("lesson"); + + b.Property("Options") + .HasColumnType("longtext") + .HasColumnName("options"); + + b.Property("SubjectArea") + .HasMaxLength(100) + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("question_text"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("tinyint unsigned") + .HasColumnName("question_type"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("KeyPointId"); + + b.HasIndex("LessonId"); + + b.HasIndex("Title") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 20 }); + + b.ToTable("questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("QuestionContexts"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("AttemptNumber") + .HasColumnType("tinyint unsigned") + .HasColumnName("attempt_number"); + + b.Property("GradedAt") + .HasColumnType("datetime(6)") + .HasColumnName("graded_at"); + + b.Property("GraderId") + .HasColumnType("char(36)") + .HasColumnName("graded_by"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("OverallFeedback") + .HasColumnType("longtext") + .HasColumnName("overall_feedback"); + + b.Property("OverallGrade") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("overall_grade"); + + b.Property("Status") + .HasMaxLength(15) + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionTime") + .HasColumnType("datetime(6)") + .HasColumnName("submission_time"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("GraderId"); + + b.HasIndex("StudentId"); + + b.ToTable("submissions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("assignment_question_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("IsCorrect") + .HasColumnType("tinyint(1)") + .HasColumnName("is_correct"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("PointsAwarded") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("points_awarded"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentAnswer") + .HasColumnType("longtext") + .HasColumnName("student_answer"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionId") + .HasColumnType("char(36)") + .HasColumnName("submission_id"); + + b.Property("TeacherFeedback") + .HasColumnType("longtext") + .HasColumnName("teacher_feedback"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentQuestionId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubmissionId"); + + b.ToTable("submission_details", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned"); + + b.Property("Publisher") + .HasColumnType("tinyint unsigned"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DisplayName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + + b.HasData( + new + { + Id = new Guid("6d49bb08-97d6-4a38-88a7-8080925b589b"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("e330c745-f422-43e3-bcdf-1439ace3c52f"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("379143a2-8d7f-4ef7-b7c0-14701b710f87"), + Name = "Administrator", + NormalizedName = "ADMINISTRATOR" + }); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("Id")); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.HasOne("Entities.Contracts.User", "Creator") + .WithMany() + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.AssignmentQuestion", "ExamStruct") + .WithOne() + .HasForeignKey("Entities.Contracts.Assignment", "ExamStructId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", null) + .WithMany("CreatedAssignments") + .HasForeignKey("UserId"); + + b.Navigation("Creator"); + + b.Navigation("ExamStruct"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentAttachment", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("AssignmentAttachments") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentClass", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("AssignmentClasses") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("AssignmentClasses") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Class"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany() + .HasForeignKey("AssignmentId"); + + b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion") + .WithMany("ChildrenAssignmentQuestion") + .HasForeignKey("ParentAssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") + .WithMany("Questions") + .HasForeignKey("QuestionContextId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.Question", "Question") + .WithMany("AssignmentQuestions") + .HasForeignKey("QuestionId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("Assignment"); + + b.Navigation("ParentAssignmentQuestion"); + + b.Navigation("Question"); + + b.Navigation("QuestionContext"); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.HasOne("Entities.Contracts.User", "HeadTeacher") + .WithMany() + .HasForeignKey("HeadTeacherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("HeadTeacher"); + }); + + modelBuilder.Entity("Entities.Contracts.ClassStudent", b => + { + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("ClassStudents") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("EnrolledClassesLink") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Class"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Entities.Contracts.ClassTeacher", b => + { + b.HasOne("Entities.Contracts.Class", "Class") + .WithMany("ClassTeachers") + .HasForeignKey("ClassId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Teacher") + .WithMany("TaughtClassesLink") + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Class"); + + b.Navigation("Teacher"); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("KeyPoints") + .HasForeignKey("LessonID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.HasOne("Entities.Contracts.Textbook", "Textbook") + .WithMany("Lessons") + .HasForeignKey("TextbookID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.LessonQuestion", b => + { + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("LessonQuestions") + .HasForeignKey("LessonID") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.HasOne("Entities.Contracts.User", "Creator") + .WithMany("CreatedQuestions") + .HasForeignKey("CreatorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Entities.Contracts.KeyPoint", "KeyPoint") + .WithMany("Questions") + .HasForeignKey("KeyPointId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.Lesson", "Lesson") + .WithMany("Questions") + .HasForeignKey("LessonId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("Creator"); + + b.Navigation("KeyPoint"); + + b.Navigation("Lesson"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.HasOne("Entities.Contracts.Assignment", "Assignment") + .WithMany("Submissions") + .HasForeignKey("AssignmentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Grader") + .WithMany("GradedSubmissions") + .HasForeignKey("GraderId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("SubmissionsAsStudent") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Assignment"); + + b.Navigation("Grader"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.HasOne("Entities.Contracts.AssignmentQuestion", "AssignmentQuestion") + .WithMany("SubmissionDetails") + .HasForeignKey("AssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", "Student") + .WithMany("SubmissionDetails") + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.Submission", "Submission") + .WithMany("SubmissionDetails") + .HasForeignKey("SubmissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignmentQuestion"); + + b.Navigation("Student"); + + b.Navigation("Submission"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Entities.Contracts.User", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Entities.Contracts.Assignment", b => + { + b.Navigation("AssignmentAttachments"); + + b.Navigation("AssignmentClasses"); + + b.Navigation("Submissions"); + }); + + modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => + { + b.Navigation("ChildrenAssignmentQuestion"); + + b.Navigation("SubmissionDetails"); + }); + + modelBuilder.Entity("Entities.Contracts.Class", b => + { + b.Navigation("AssignmentClasses"); + + b.Navigation("ClassStudents"); + + b.Navigation("ClassTeachers"); + }); + + modelBuilder.Entity("Entities.Contracts.KeyPoint", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Lesson", b => + { + b.Navigation("KeyPoints"); + + b.Navigation("LessonQuestions"); + + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Question", b => + { + b.Navigation("AssignmentQuestions"); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Navigation("Questions"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Navigation("SubmissionDetails"); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Navigation("Lessons"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Navigation("CreatedAssignments"); + + b.Navigation("CreatedQuestions"); + + b.Navigation("EnrolledClassesLink"); + + b.Navigation("GradedSubmissions"); + + b.Navigation("SubmissionDetails"); + + b.Navigation("SubmissionsAsStudent"); + + b.Navigation("TaughtClassesLink"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.cs b/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.cs new file mode 100644 index 0000000..d218e43 --- /dev/null +++ b/TechHelper.Server/Migrations/20250701095424_atemp_number_convert_to_byte.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class atemp_number_convert_to_byte : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("14b8854f-a38b-4e72-878e-31ba2f7528b2")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("69c3cc0c-b284-433e-8493-9b1e7bd1eb1f")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("6ef4d2bb-05da-4c17-9152-4467d86939fc")); + + migrationBuilder.AlterColumn( + name: "attempt_number", + table: "submissions", + type: "tinyint unsigned", + nullable: false, + oldClrType: typeof(Guid), + oldType: "char(36)") + .OldAnnotation("Relational:Collation", "ascii_general_ci"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("379143a2-8d7f-4ef7-b7c0-14701b710f87"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("6d49bb08-97d6-4a38-88a7-8080925b589b"), null, "Student", "STUDENT" }, + { new Guid("e330c745-f422-43e3-bcdf-1439ace3c52f"), null, "Teacher", "TEACHER" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("379143a2-8d7f-4ef7-b7c0-14701b710f87")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("6d49bb08-97d6-4a38-88a7-8080925b589b")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("e330c745-f422-43e3-bcdf-1439ace3c52f")); + + migrationBuilder.AlterColumn( + name: "attempt_number", + table: "submissions", + type: "char(36)", + nullable: false, + collation: "ascii_general_ci", + oldClrType: typeof(byte), + oldType: "tinyint unsigned"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("14b8854f-a38b-4e72-878e-31ba2f7528b2"), null, "Teacher", "TEACHER" }, + { new Guid("69c3cc0c-b284-433e-8493-9b1e7bd1eb1f"), null, "Student", "STUDENT" }, + { new Guid("6ef4d2bb-05da-4c17-9152-4467d86939fc"), null, "Administrator", "ADMINISTRATOR" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs index 140ae48..4edc338 100644 --- a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs +++ b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs @@ -500,8 +500,8 @@ namespace TechHelper.Server.Migrations .HasColumnType("char(36)") .HasColumnName("assignment_id"); - b.Property("AttemptNumber") - .HasColumnType("char(36)") + b.Property("AttemptNumber") + .HasColumnType("tinyint unsigned") .HasColumnName("attempt_number"); b.Property("GradedAt") @@ -584,6 +584,10 @@ namespace TechHelper.Server.Migrations .HasColumnType("float") .HasColumnName("points_awarded"); + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + b.Property("StudentAnswer") .HasColumnType("longtext") .HasColumnName("student_answer"); @@ -751,19 +755,19 @@ namespace TechHelper.Server.Migrations b.HasData( new { - Id = new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"), + Id = new Guid("6d49bb08-97d6-4a38-88a7-8080925b589b"), Name = "Student", NormalizedName = "STUDENT" }, new { - Id = new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"), + Id = new Guid("e330c745-f422-43e3-bcdf-1439ace3c52f"), Name = "Teacher", NormalizedName = "TEACHER" }, new { - Id = new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"), + Id = new Guid("379143a2-8d7f-4ef7-b7c0-14701b710f87"), Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); diff --git a/TechHelper.Server/Program.cs b/TechHelper.Server/Program.cs index 653fc68..c960287 100644 --- a/TechHelper.Server/Program.cs +++ b/TechHelper.Server/Program.cs @@ -87,6 +87,8 @@ builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/TechHelper.Server/Repositories/ExamRepository.cs b/TechHelper.Server/Repositories/ExamRepository.cs index 5753726..c9df66a 100644 --- a/TechHelper.Server/Repositories/ExamRepository.cs +++ b/TechHelper.Server/Repositories/ExamRepository.cs @@ -101,7 +101,7 @@ namespace TechHelper.Server.Repositories var submissions = await _unitOfWork.GetRepository().GetAllAsync(predicate: s => s.StudentId == id, include: i => i.Include(s => s.Assignment)); if (submissions == null || !submissions.Any()) return Enumerable.Empty(); - return submissions.ToList().Select(s => s.Assignment).Where(a => a != null).Distinct().ToList(); + return submissions.ToList().Select(s => s.Assignment).Where(a => a != null).ToList(); } } } diff --git a/TechHelper.Server/Services/ClassService.cs b/TechHelper.Server/Services/ClassService.cs index bb3b3e0..cae8a87 100644 --- a/TechHelper.Server/Services/ClassService.cs +++ b/TechHelper.Server/Services/ClassService.cs @@ -150,7 +150,7 @@ namespace TechHelper.Services var std = await _work.GetRepository().GetAllAsync(predicate: user => user.StudentId == id, include: i => i .Include(t => t.Class)); - if (tch == null && std == null) return new ApiResponse("你没有加入任何班级。"); + if (tch == null && std == null) return ApiResponse.Error("你没有加入任何班级。"); List result = new List(); @@ -159,6 +159,25 @@ namespace TechHelper.Services return ApiResponse.Success(result: result); } + public async Task GetUserClassRole(Guid id) + { + var tch = await _work.GetRepository().GetAllAsync(predicate: user => user.TeacherId == id, include: i => i +.Include(t => t.Class)); + var std = await _work.GetRepository().GetAllAsync(predicate: user => user.StudentId == id, include: i => i + .Include(t => t.Class)); + + if (tch == null && std == null) return ApiResponse.Error("你没有加入任何班级。"); + + + UserClassRoleDto result = new UserClassRoleDto(); + tch?.ToList().ForEach(c => result.ClassInfo.Add((c.Class.Number, c.Class.Grade))); + std?.ToList().ForEach(c => result.ClassInfo.Add((c.Class.Number, c.Class.Grade))); + if (tch?.Count > 0) result.Role = "Teacher"; else result.Role = "Student"; + + return ApiResponse.Success(result: result); + + } + // 实现 IBaseService.UpdateAsync public async Task UpdateAsync(ClassDto model) { diff --git a/TechHelper.Server/Services/ExamService.cs b/TechHelper.Server/Services/ExamService.cs index 88af79c..21ff45a 100644 --- a/TechHelper.Server/Services/ExamService.cs +++ b/TechHelper.Server/Services/ExamService.cs @@ -14,13 +14,17 @@ namespace TechHelper.Server.Services { private readonly IUnitOfWork _unitOfWork; private readonly IExamRepository _examRepository; + private readonly ISubmissionServices _submissionService; + private readonly IClassService _classService; private readonly IMapper _mapper; - public ExamService(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper) + public ExamService(IUnitOfWork unitOfWork, IExamRepository examRepository, IMapper mapper, IClassService classService, ISubmissionServices submissionService) { _unitOfWork = unitOfWork; _examRepository = examRepository; _mapper = mapper; + _classService = classService; + _submissionService = submissionService; } public async Task CreateExamAsync(AssignmentDto assignmentDto) @@ -128,7 +132,7 @@ namespace TechHelper.Server.Services } catch (Exception ex) { - return ApiResponse.Error("内部问题"); + return ApiResponse.Error($"内部问题,{ex.Message}, InerException{ex.InnerException}"); } } @@ -152,14 +156,65 @@ namespace TechHelper.Server.Services } } - public Task AssignmentToAllStudentsAsync(Guid id) + public async Task AssignmentToAllStudentsAsync(Guid assignmentId, Guid TeacherId) { - throw new NotImplementedException(); + try + { + var classes = await _classService.GetUserClass(TeacherId); + var classUsrClass = classes.Result as List; + var classDto = _mapper.Map(classUsrClass?.FirstOrDefault()); + var cla = await _classService.GetClassStudents(classDto); + var assignment = await _examRepository.GetFullExamByIdAsync(assignmentId); + if (assignment == null) return ApiResponse.Error("没有找到该试卷"); + + var cs = cla.Result as ICollection; + cs?.ToList().ForEach(async s => + { + var subCount = _unitOfWork.GetRepository().GetAll(predicate: su => su.AssignmentId == assignmentId && su.StudentId == s.StudentId); + + var submission = assignment.ConvertToSubmission(s.StudentId, TeacherId); + submission.AttemptNumber = (byte)(subCount.Count() + 1); + await _unitOfWork.GetRepository().InsertAsync(submission); + + }); + + if (await _unitOfWork.SaveChangesAsync() > 0) + { + return ApiResponse.Success(); + } + return ApiResponse.Error(); + } + catch (Exception ex) + { + return ApiResponse.Error($"内部错误, {ex.Message}"); + } } - public Task AssignmentToStudentsAsync(Guid assignementId, Guid studentId) + public async Task AssignmentToStudentsAsync(AssigExamToStudentsDto examToStudentsDto) { - throw new NotImplementedException(); + try + { + var assignment = await _examRepository.GetFullExamByIdAsync(examToStudentsDto.AssignmentId); + if (assignment == null) return ApiResponse.Error("没有找到该试卷"); + + examToStudentsDto.StudentIds?.ForEach(async s => + { + var subCount = _unitOfWork.GetRepository().GetAll(predicate: su => su.AssignmentId == examToStudentsDto.AssignmentId && su.StudentId == s); + var submission = assignment.ConvertToSubmission(s, examToStudentsDto.CreaterId); + submission.AttemptNumber = (byte)(subCount.Count() + 1); + await _unitOfWork.GetRepository().InsertAsync(submission); + }); + + if (await _unitOfWork.SaveChangesAsync() > 0) + { + return ApiResponse.Success(); + } + return ApiResponse.Error(); + } + catch (Exception ex) + { + return ApiResponse.Error($"内部错误, {ex.Message}"); + } } public async Task GetAllSubmissionAsync(Guid id) diff --git a/TechHelper.Server/Services/IClassService.cs b/TechHelper.Server/Services/IClassService.cs index 4b2582a..5ce0db2 100644 --- a/TechHelper.Server/Services/IClassService.cs +++ b/TechHelper.Server/Services/IClassService.cs @@ -7,7 +7,8 @@ namespace TechHelper.Services public interface IClassService : IBaseService { public Task UserRegister(UserRegistrationToClassDto user); - public Task GetUserClass(Guid user); - public Task GetClassStudents(ClassDto classDto); + public Task GetUserClass(Guid user); // List + public Task GetUserClassRole(Guid user); // List + public Task GetClassStudents(ClassDto classDto); // Class } } diff --git a/TechHelper.Server/Services/IExamService.cs b/TechHelper.Server/Services/IExamService.cs index ab7bcb7..5395665 100644 --- a/TechHelper.Server/Services/IExamService.cs +++ b/TechHelper.Server/Services/IExamService.cs @@ -23,16 +23,36 @@ namespace TechHelper.Server.Services Task CreateExamAsync(AssignmentDto examDto); - + /// + /// 提交一份试卷 + /// + /// 提交试卷的完整信息 + /// Task SubmissionAssignment(SubmissionDto submissionDto); + /// + /// 为所有学生指定试卷 + /// + /// 试卷ID + /// + Task AssignmentToAllStudentsAsync(Guid assignmentId, Guid TeacherId); - Task AssignmentToAllStudentsAsync(Guid id); - - Task AssignmentToStudentsAsync(Guid assignementId, Guid studentId); + /// + /// 为指定学生指派一个试卷 + /// + /// + /// + /// + Task AssignmentToStudentsAsync(AssigExamToStudentsDto examToStudentsDto); + /// + /// 获取学生所有提交过的试卷 + /// + /// + /// Task GetAllSubmissionAsync(Guid id); + } } diff --git a/TechHelper.Server/Services/ISubmissionServices.cs b/TechHelper.Server/Services/ISubmissionServices.cs index bf4ff01..386adf2 100644 --- a/TechHelper.Server/Services/ISubmissionServices.cs +++ b/TechHelper.Server/Services/ISubmissionServices.cs @@ -5,12 +5,60 @@ namespace TechHelper.Server.Services { public interface ISubmissionServices : IBaseService { + /// + /// 异步获取指定用户的指定试题的错题。 + /// + /// 作业ID。 + /// 用户ID。 + /// 包含操作结果的ApiResponse。 Task GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId); + + /// + /// 异步获取指定用户的所有错题。 + /// + /// 用户ID。 + /// 包含操作结果的ApiResponse。 Task GetAllErrorQuestionsAsync(Guid userId); + + /// + /// 异步获取指定作业和用户的错题类型分布。 + /// + /// 作业ID。 + /// 用户ID。 + /// 包含操作结果的ApiResponse。 Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId); + + /// + /// 异步获取指定作业中所有错题的类型分布。(注意:原始方法签名GetAllErrorQuestionTypeDisAsync参数中含有assignmentId,结合方法名推断此处可能应为获取所有错题的类型分布,而非特定作业的,请根据实际业务需求确认是否需要移除assignmentId参数或修改方法名。) + /// + /// 作业ID。 + /// 用户ID。 + /// 包含操作结果的ApiResponse。 Task GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId); + /// + /// 异步获取指定作业中所有学生的错题情况。 + /// + /// 作业ID。 + /// 教师ID。 + /// 包含操作结果的ApiResponse。 Task GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId); + + /// + /// 异步获取指定作业中出现错题的学生列表。 + /// + /// 作业ID。 + /// 包含操作结果的ApiResponse。 Task GetQuestionErrorStudents(Guid assignmentId); + + + + /// + /// 判断是否已经存在Submission + /// + /// + /// + /// + Task IsHasSubmissionAsync(Guid assignment, Guid studentId); } } diff --git a/TechHelper.Server/Services/IUserSerivces.cs b/TechHelper.Server/Services/IUserSerivces.cs index 51c2e12..e15e59e 100644 --- a/TechHelper.Server/Services/IUserSerivces.cs +++ b/TechHelper.Server/Services/IUserSerivces.cs @@ -6,5 +6,7 @@ namespace TechHelper.Server.Services public interface IUserSerivces : IBaseService { Task GetStudentDetailInfo(Guid userId); + Task RestoreUserRoleInformation(User user); + Task VerifyUserInformation(Guid userId); } } diff --git a/TechHelper.Server/Services/SubmissionServices.cs b/TechHelper.Server/Services/SubmissionServices.cs index f723aa5..c1a732b 100644 --- a/TechHelper.Server/Services/SubmissionServices.cs +++ b/TechHelper.Server/Services/SubmissionServices.cs @@ -1,7 +1,9 @@ using AutoMapper; using Entities.Contracts; +using Entities.DTO; using Microsoft.EntityFrameworkCore; using SharedDATA.Api; +using SharedDATA.Context; using TechHelper.Services; namespace TechHelper.Server.Services @@ -21,71 +23,329 @@ namespace TechHelper.Server.Services _submissionDetailRepository = _unitOfWork.GetRepository(); } - public Task AddAsync(Submission model) + public async Task AddAsync(Submission model) { - throw new NotImplementedException(); + try + { + model.SubmissionTime = DateTime.Now; + model.IsDeleted = false; + + await _submissionRepository.InsertAsync(model); + await _unitOfWork.SaveChangesAsync(); + + var result = _mapper.Map(model); + return ApiResponse.Success("提交成功。", result); + } + catch (Exception ex) + { + return ApiResponse.Error($"添加提交失败: {ex.Message}"); + } } - public Task DeleteAsync(Guid id) + public async Task DeleteAsync(Guid id) { - throw new NotImplementedException(); + try + { + var submission = await _submissionRepository.GetFirstOrDefaultAsync(predicate: s => s.Id == id); + if (submission == null) + { + return ApiResponse.Error("未找到要删除的提交。", 404); + } + + submission.IsDeleted = true; + _submissionRepository.Update(submission); + + var submissionDetails = await _submissionDetailRepository.GetPagedListAsync(predicate: sd => sd.SubmissionId == id); + foreach (var detail in submissionDetails.Items) + { + detail.IsDeleted = true; + _submissionDetailRepository.Update(detail); + } + + await _unitOfWork.SaveChangesAsync(); + return ApiResponse.Success("提交及相关详情删除成功。", null); + } + catch (Exception ex) + { + return ApiResponse.Error($"删除提交失败: {ex.Message}"); + } } - public Task GetAllAsync(QueryParameter query) + public async Task GetAllAsync(QueryParameter query) { - throw new NotImplementedException(); + try + { + var pagedSubmissions = await _submissionRepository.GetPagedListAsync( + pageIndex: query.PageIndex, + pageSize: query.PageSize, + orderBy: s => s.OrderByDescending(s => s.SubmissionTime), + predicate: s => !s.IsDeleted, + include: i => i.Include(s => s.Student) + .Include(s => s.Assignment)); + + var submissionDtos = _mapper.Map>(pagedSubmissions.Items); + + return ApiResponse.Success("获取所有提交成功。", new PagedList + { + PageIndex = pagedSubmissions.PageIndex, + PageSize = pagedSubmissions.PageSize, + TotalCount = pagedSubmissions.TotalCount, + TotalPages = pagedSubmissions.TotalPages, + Items = submissionDtos + }); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取所有提交失败: {ex.Message}"); + } } public async Task GetAllErrorQuestionsAsync(Guid userId) { try { - var errorSDs = await _submissionDetailRepository.GetPagedListAsync(predicate: sd => sd.StudentId == userId && sd.IsCorrect == false, + + var errorSDs = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.StudentId == userId && sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), include: i => i - .Include(s => s.AssignmentQuestion) - .ThenInclude(aq => aq.Question)); - var errorQuestion = errorSDs.Items.Select(sd => sd.AssignmentQuestion).ToList(); - return ApiResponse.Success(); + .Include(s => s.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + var errorQuestions = errorSDs.Items.Select(sd => sd.AssignmentQuestion.Question) + .Where(q => q != null) + .DistinctBy(q => q.Id) + .ToList(); + + + var result = _mapper.Map>(errorQuestions); + return ApiResponse.Success("获取所有错题成功。", result); } catch (Exception ex) { - return ApiResponse.Error(); + return ApiResponse.Error($"获取所有错题失败: {ex.Message}"); } } - public Task GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) + public async Task GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) { - throw new NotImplementedException(); + try + { + var errorSDs = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.Submission.AssignmentId == assignmentId && + sd.StudentId == userId && + sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), + include: i => i + .Include(s => s.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + // 对错题按类型进行分组计数 + var errorTypeDistribution = errorSDs.Items + .Where(sd => sd.AssignmentQuestion?.Question != null) + .GroupBy(sd => sd.AssignmentQuestion.Question.Type) + .Select(g => new + { + QuestionType = g.Key.ToString(), + Count = g.Count() + }) + .ToList(); + + return ApiResponse.Success("获取错题类型分布成功。", errorTypeDistribution); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取错题类型分布失败: {ex.Message}"); + } } - public Task GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId) + public async Task GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId) { - throw new NotImplementedException(); + try + { + var submissionDetails = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.Submission.AssignmentId == assignmentId && + sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), + include: i => i.Include(sd => sd.Student)); + + + var studentsErrorSummary = submissionDetails.Items + .Where(sd => sd.Student != null) + .GroupBy(sd => new { sd.StudentId, sd.Student.UserName }) + .Select(g => new + { + StudentId = g.Key.StudentId, + StudentName = g.Key.UserName, + ErrorQuestionCount = g.Count() + }) + .ToList(); + + return ApiResponse.Success("获取作业中所有学生的错题情况成功。", studentsErrorSummary); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取作业中所有学生的错题情况失败: {ex.Message}"); + } } - public Task GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId) + public async Task GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId) { - throw new NotImplementedException(); + try + { + var errorSDs = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.Submission.AssignmentId == assignmentId && + sd.StudentId == userId && + sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), + include: i => i + .Include(s => s.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + var errorQuestions = errorSDs.Items.Select(sd => sd.AssignmentQuestion.Question) + .Where(q => q != null) + .DistinctBy(q => q.Id) + .ToList(); + + var result = _mapper.Map>(errorQuestions); + return ApiResponse.Success("获取指定作业错题成功。", result); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取指定作业错题失败: {ex.Message}"); + } } - public Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) + public async Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) { - throw new NotImplementedException(); + try + { + var errorSDs = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.Submission.AssignmentId == assignmentId && + sd.StudentId == userId && + sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), + include: i => i + .Include(s => s.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + var errorTypeDistribution = errorSDs.Items + .Where(sd => sd.AssignmentQuestion?.Question != null) + .GroupBy(sd => sd.AssignmentQuestion.Question.Type) + .Select(g => new + { + QuestionType = g.Key.ToString(), + Count = g.Count() + }) + .ToList(); + + return ApiResponse.Success("获取指定作业错题类型分布成功。", errorTypeDistribution); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取指定作业错题类型分布失败: {ex.Message}"); + } } - public Task GetAsync(Guid id) + public async Task GetAsync(Guid id) { - throw new NotImplementedException(); + try + { + var submission = await _submissionRepository.GetFirstOrDefaultAsync( + predicate: s => s.Id == id && !s.IsDeleted, + include: i => i.Include(s => s.Student) + .Include(s => s.Assignment) + .Include(s => s.Grader) + .Include(s => s.SubmissionDetails) + .ThenInclude(sd => sd.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + if (submission == null) + { + return ApiResponse.Error("未找到提交。", 404); + } + + var submissionDto = _mapper.Map(submission); + return ApiResponse.Success("获取提交成功。", submissionDto); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取提交失败: {ex.Message}"); + } } - public Task GetQuestionErrorStudents(Guid assignmentId) + public async Task GetQuestionErrorStudents(Guid assignmentQuestionId) { - throw new NotImplementedException(); + try + { + var errorSubmissionDetails = await _submissionDetailRepository.GetPagedListAsync( + predicate: sd => sd.AssignmentQuestionId == assignmentQuestionId && + sd.IsCorrect == false && + (sd.Status == SubmissionStatus.Submitted || sd.Status == SubmissionStatus.Graded), + include: i => i + .Include(sd => sd.Student) + .Include(sd => sd.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + + + var errorStudentsByQuestion = errorSubmissionDetails.Items + .Where(sd => sd.AssignmentQuestion?.Question != null && sd.Student != null) + .GroupBy(sd => new { sd.AssignmentQuestionId, sd.AssignmentQuestion.Question.Title }) + .Select(g => new + { + AssignmentQuestionId = g.Key.AssignmentQuestionId, + QuestionTitle = g.Key.Title, + ErrorStudents = g.Select(sd => new + { + StudentId = sd.StudentId, + StudentName = sd.Student.UserName + }).Distinct().ToList() + }) + .ToList(); + + return ApiResponse.Success("获取出现错题的学生成功。", errorStudentsByQuestion); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取出现错题的学生失败: {ex.Message}"); + } } - public Task UpdateAsync(Submission model) + public async Task IsHasSubmissionAsync(Guid assignment, Guid studentId) { - throw new NotImplementedException(); + try + { + var result = await _unitOfWork.GetRepository().GetAllAsync(predicate: s => s.AssignmentId == assignment && s.StudentId == studentId); + return (byte)result.Count; + } + catch (Exception ex) + { + throw; + } + } + + public async Task UpdateAsync(Submission model) + { + try + { + var existingSubmission = await _submissionRepository.GetFirstOrDefaultAsync(predicate: s => s.Id == model.Id && !s.IsDeleted); + if (existingSubmission == null) + { + return ApiResponse.Error("未找到要更新的提交。", 404); + } + _mapper.Map(model, existingSubmission); + + + _submissionRepository.Update(existingSubmission); + await _unitOfWork.SaveChangesAsync(); + + var result = _mapper.Map(existingSubmission); + return ApiResponse.Success("更新提交成功。", result); + } + catch (Exception ex) + { + return ApiResponse.Error($"更新提交失败: {ex.Message}"); + } } } } diff --git a/TechHelper.Server/Services/UserServices.cs b/TechHelper.Server/Services/UserServices.cs index 2065faa..9e8891e 100644 --- a/TechHelper.Server/Services/UserServices.cs +++ b/TechHelper.Server/Services/UserServices.cs @@ -1,10 +1,25 @@ using Entities.Contracts; +using Entities.DTO; +using Microsoft.AspNetCore.Identity; +using SharedDATA.Api; using TechHelper.Services; namespace TechHelper.Server.Services { public class UserServices : IUserSerivces { + + private readonly IUnitOfWork _unitOfWork; + private readonly IClassService _classService; + private readonly UserManager _userManager; + + public UserServices(IUnitOfWork unitOfWork, IClassService classService, UserManager userManager) + { + _unitOfWork = unitOfWork; + _classService = classService; + _userManager = userManager; + } + public Task AddAsync(User model) { throw new NotImplementedException(); @@ -30,9 +45,33 @@ namespace TechHelper.Server.Services throw new NotImplementedException(); } + public async Task RestoreUserRoleInformation(User user) + { + var result = await _classService.GetUserClassRole(user.Id); + if (result.Status) + { + var classRole = result.Result as UserClassRoleDto; + if (classRole != null) + { + if (!await _userManager.IsInRoleAsync(user, classRole.Role)) + { + await _userManager.AddToRoleAsync(user, classRole.Role); + + return ApiResponse.Success(); + } + } + } + return ApiResponse.Error(); + } + public Task UpdateAsync(User model) { throw new NotImplementedException(); } + + public Task VerifyUserInformation(Guid userId) + { + throw new NotImplementedException(); + } } }