diff --git a/Entities/Contracts/AppMainStruct.cs b/Entities/Contracts/AppMainStruct.cs index 5246b5c..9ca0285 100644 --- a/Entities/Contracts/AppMainStruct.cs +++ b/Entities/Contracts/AppMainStruct.cs @@ -76,4 +76,15 @@ namespace Entities.Contracts Option } + + + public enum SubmissionStatus + { + Pending, // 待提交/未开始 + Submitted, // 已提交 + Graded, // 已批改 + Resubmission, // 待重新提交 (如果允许) + Late, // 迟交 + Draft, // 草稿 + } } diff --git a/Entities/Contracts/AssignmentQuestion.cs b/Entities/Contracts/AssignmentQuestion.cs index 232244e..cfbaf91 100644 --- a/Entities/Contracts/AssignmentQuestion.cs +++ b/Entities/Contracts/AssignmentQuestion.cs @@ -31,6 +31,9 @@ namespace Entities.Contracts [Column("question_number")] public byte Index { get; set; } + [Column("sequence")] + public string Sequence { get; set; } = string.Empty; + [Column("parent_question_group_id")] public Guid? ParentAssignmentQuestionId { get; set; } diff --git a/Entities/Contracts/Submission.cs b/Entities/Contracts/Submission.cs index 832da6f..636816c 100644 --- a/Entities/Contracts/Submission.cs +++ b/Entities/Contracts/Submission.cs @@ -37,7 +37,7 @@ namespace Entities.Contracts public float? OverallGrade { get; set; } [Column("overall_feedback")] - public string OverallFeedback { get; set; } + public string? OverallFeedback { get; set; } [Column("graded_by")] [ForeignKey("Grader")] @@ -66,13 +66,4 @@ namespace Entities.Contracts } } - public enum SubmissionStatus - { - Pending, // 待提交/未开始 - Submitted, // 已提交 - Graded, // 已批改 - Resubmission, // 待重新提交 (如果允许) - Late, // 迟交 - Draft, // 草稿 - } } diff --git a/Entities/Contracts/SubmissionDetail.cs b/Entities/Contracts/SubmissionDetail.cs index 121b78f..6fc580a 100644 --- a/Entities/Contracts/SubmissionDetail.cs +++ b/Entities/Contracts/SubmissionDetail.cs @@ -31,16 +31,16 @@ namespace Entities.Contracts public Guid AssignmentQuestionId { get; set; } [Column("student_answer")] - public string StudentAnswer { get; set; } + public string? StudentAnswer { get; set; } [Column("is_correct")] public bool? IsCorrect { get; set; } [Column("points_awarded")] - public float? PointsAwarded { get; set; } + public float? PointsAwarded { get; set; } // score [Column("teacher_feedback")] - public string TeacherFeedback { get; set; } + public string? TeacherFeedback { get; set; } [Column("created_at")] public DateTime CreatedAt { get; set; } @@ -54,8 +54,10 @@ namespace Entities.Contracts [ForeignKey(nameof(StudentId))] public User Student { get; set; } + [ForeignKey(nameof(SubmissionId))] public Submission Submission { get; set; } + [ForeignKey(nameof(AssignmentQuestionId))] public AssignmentQuestion AssignmentQuestion { get; set; } public SubmissionDetail() diff --git a/Entities/DTO/AssignmentClassDto.cs b/Entities/DTO/AssignmentClassDto.cs new file mode 100644 index 0000000..e6a0a99 --- /dev/null +++ b/Entities/DTO/AssignmentClassDto.cs @@ -0,0 +1,23 @@ +using Entities.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Xml.Serialization; + +namespace Entities.DTO +{ + + + public class AssignmentClassDto + { + public AssignmentDto Assignment { get; set; } + public Class ClassId { get; set; } + public DateTime AssignedAt { get; set; } + } + + + + +} diff --git a/Entities/DTO/AssignmentDto.cs b/Entities/DTO/AssignmentDto.cs index 9a9af91..4a32497 100644 --- a/Entities/DTO/AssignmentDto.cs +++ b/Entities/DTO/AssignmentDto.cs @@ -1,4 +1,5 @@ -using System; +using Entities.Contracts; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -6,4 +7,20 @@ using System.Threading.Tasks; namespace Entities.DTO { + public class AssignmentDto + { + public Guid Id { get; set; } = Guid.Empty; + public string Title { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + + public byte TotalQuestions { get; set; } + public float Score { get; set; } = 0; + public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown; + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public DateTime DueDate { get; set; } + public Guid CreatorId { get; set; } + + public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto(); + } } diff --git a/Entities/DTO/AssignmentQuestionDto.cs b/Entities/DTO/AssignmentQuestionDto.cs index 5cb66a9..702e130 100644 --- a/Entities/DTO/AssignmentQuestionDto.cs +++ b/Entities/DTO/AssignmentQuestionDto.cs @@ -15,6 +15,7 @@ namespace Entities.DTO public byte Index { get; set; } = 0; public float Score { get; set; } = 0; + public string Sequence { get; set; } = string.Empty; public Layout Layout { get; set; } = Layout.horizontal; public AssignmentStructType StructType { get; set; } = AssignmentStructType.Question; @@ -24,4 +25,6 @@ namespace Entities.DTO public QuestionDto? Question { get; set; } } + + } diff --git a/Entities/DTO/ExamDto.cs b/Entities/DTO/ExamDto.cs deleted file mode 100644 index 1c675dd..0000000 --- a/Entities/DTO/ExamDto.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Entities.Contracts; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Xml.Serialization; - -namespace Entities.DTO -{ - public class AssignmentDto - { - public Guid Id { get; set; } = Guid.Empty; - public string Title { get; set; } = string.Empty; - public string Description { get; set; } = string.Empty; - - public byte TotalQuestions { get; set; } - public float Score { get; set; } = 0; - public SubjectAreaEnum SubjectArea { get; set; } = SubjectAreaEnum.Unknown; - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public DateTime DueDate { get; set; } - public Guid CreatorId { get; set; } - - public AssignmentQuestionDto ExamStruct { get; set; } = new AssignmentQuestionDto(); - } - - public class AssignmentClassDto - { - public AssignmentDto Assignment { get; set; } - public Class ClassId { get; set; } - public DateTime AssignedAt { get; set; } - } - - public class QuestionContextDto - { - public Guid Id { get; set; } = Guid.Empty; - public string Description { get; set; } = string.Empty; - } - - public class OptionDto - { - public string? Value { get; set; } = string.Empty; - } - -} diff --git a/Entities/DTO/QuestionContextDto.cs b/Entities/DTO/QuestionContextDto.cs new file mode 100644 index 0000000..523fe79 --- /dev/null +++ b/Entities/DTO/QuestionContextDto.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 QuestionContextDto + { + public Guid Id { get; set; } = Guid.Empty; + public string Description { get; set; } = string.Empty; + } +} diff --git a/Entities/DTO/QuestionDto.cs b/Entities/DTO/QuestionDto.cs index d30ab1d..a35ff20 100644 --- a/Entities/DTO/QuestionDto.cs +++ b/Entities/DTO/QuestionDto.cs @@ -36,4 +36,13 @@ namespace Entities.DTO public DateTime UpdatedAt { get; set; } = DateTime.Now; } + + + /// + /// Can be removed because the class isn't used + /// + public class OptionDto + { + public string? Value { get; set; } = string.Empty; + } } diff --git a/Entities/DTO/StudentDto.cs b/Entities/DTO/StudentDto.cs new file mode 100644 index 0000000..2bbb3bc --- /dev/null +++ b/Entities/DTO/StudentDto.cs @@ -0,0 +1,20 @@ +using Entities.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Entities.DTO +{ + public class StudentDto + { + public Guid Id { get; set; } + public string? DisplayName { get; set; } + + public UInt32 ErrorQuestionNum { get; set; } + public Dictionary ErrorQuestionTypes { get; set; } = new Dictionary(); + public Dictionary SubjectAreaErrorQuestionDis { get; set; } = new Dictionary(); + public Dictionary LessonErrorDis { get; set; } = new Dictionary(); + } +} diff --git a/Entities/DTO/SubmissionDetailDto.cs b/Entities/DTO/SubmissionDetailDto.cs new file mode 100644 index 0000000..1a7aba9 --- /dev/null +++ b/Entities/DTO/SubmissionDetailDto.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Entities.DTO +{ + public class SubmissionDetailDto + { + public Guid Id { get; set; } = Guid.Empty; + public Guid StudentId { get; set; } + public Guid AssignmentQuestionId { get; set; } + public string? StudentAnswer { get; set; } + public bool? IsCorrect { get; set; } + public float? PointsAwarded { get; set; } + public string? TeacherFeedback { get; set; } + } +} diff --git a/Entities/DTO/SubmissionDto.cs b/Entities/DTO/SubmissionDto.cs new file mode 100644 index 0000000..14343fb --- /dev/null +++ b/Entities/DTO/SubmissionDto.cs @@ -0,0 +1,23 @@ +using Entities.Contracts; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Entities.DTO +{ + public class SubmissionDto + { + public Guid Id { get; set; } = Guid.Empty; + public Guid AssignmentId { get; set; } + public Guid StudentId { get; set; } + public DateTime SubmissionTime { get; set; } + public float OverallGrade { get; set; } = 0; + public string OverallFeedback { get; set; } = string.Empty; + public Guid? GraderId { get; set; } + public DateTime? GradedAt { get; set; } + public SubmissionStatus Status { get; set; } + public List SubmissionDetails { get; set; } = new List(); + } +} diff --git a/Entities/DTO/UserDto.cs b/Entities/DTO/UserDto.cs new file mode 100644 index 0000000..502d1d6 --- /dev/null +++ b/Entities/DTO/UserDto.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 UserDto + { + public Guid Id { get; set; } + public string? DisplayName { get; set; } + + } +} diff --git a/TechHelper.Client/Content/AutoMapperProFile.cs b/TechHelper.Client/Content/AutoMapperProFile.cs index aff8517..22d9846 100644 --- a/TechHelper.Client/Content/AutoMapperProFile.cs +++ b/TechHelper.Client/Content/AutoMapperProFile.cs @@ -15,6 +15,9 @@ namespace TechHelper.Context CreateMap() .ForMember(d=>d.Description, o=>o.Ignore()); CreateMap(); + + + CreateMap(); } } diff --git a/TechHelper.Client/Exam/ExamStruct.cs b/TechHelper.Client/Exam/AssignmentCheckData.cs similarity index 52% rename from TechHelper.Client/Exam/ExamStruct.cs rename to TechHelper.Client/Exam/AssignmentCheckData.cs index f6b1627..0510537 100644 --- a/TechHelper.Client/Exam/ExamStruct.cs +++ b/TechHelper.Client/Exam/AssignmentCheckData.cs @@ -2,18 +2,20 @@ namespace TechHelper.Client.Exam { - public class ExamStruct + public class AssignmentCheckData { - public string Title { get; set; } - public List Questions { get; set; } = new List(); + public string Title { get; set; } + public Guid AssignmentId { get; set; } + public Guid StudentId { get; set; } + public List Questions { get; set; } = new List(); + } - public class QuestionItem - { - public string Sequence { get; set; } = string.Empty; - public string QuestionText { get; set; } = string.Empty; - public float Score { get; set; } - } + public class AssignmentCheckQuestion + { + public string Sequence { get; set; } = string.Empty; + public AssignmentQuestionDto AssignmentQuestionDto { get; set; } = new AssignmentQuestionDto(); + public float Score { get; set; } } public class Student @@ -24,32 +26,32 @@ namespace TechHelper.Client.Exam public class QuestionAnswerStatus { - public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1" - public string QuestionText { get; set; } = string.Empty; // 题目文本 - public float QuestionScore { get; set; } // 题目分值 + public string QuestionSequence { get; set; } = string.Empty; // 题目序号,例如 "1.1" + public string QuestionText { get; set; } = string.Empty; // 题目文本 + public float QuestionScore { get; set; } // 题目分值 public Dictionary StudentCorrectStatus { get; set; } = new Dictionary(); // Key: Student.Id, Value: true 表示正确,false 表示错误 } public class QuestionRowData { - public ExamStruct.QuestionItem QuestionItem { get; set; } // 原始题目信息 + public AssignmentCheckQuestion QuestionItem { get; set; } // 原始题目信息 public Dictionary StudentAnswers { get; set; } = new Dictionary(); } public static class ExamStructExtensions { - public static ExamStruct GetStruct(this AssignmentDto dto) + public static AssignmentCheckData GetStruct(this AssignmentDto dto) { if (dto == null) { - return new ExamStruct { Title = "无效试卷", Questions = new List() }; + return new AssignmentCheckData { Title = "无效试卷", Questions = new List() }; } - var examStruct = new ExamStruct + var examStruct = new AssignmentCheckData { - Title = dto.Title + Title = dto.Title }; GetSeqRecursive(dto.ExamStruct, null, examStruct.Questions); @@ -65,8 +67,8 @@ namespace TechHelper.Client.Exam /// 用于收集所有生成题目项的列表。 private static void GetSeqRecursive( AssignmentQuestionDto currentGroup, - string? parentSequence, - List allQuestions) + string? parentSequence, + List allQuestions) { string currentGroupSequence = parentSequence != null ? $"{parentSequence}.{currentGroup.Index}" @@ -76,6 +78,17 @@ namespace TechHelper.Client.Exam { GetSeqRecursive(subGroup, currentGroupSequence, allQuestions); } + if (!string.IsNullOrEmpty(currentGroup.Sequence)) + { + + allQuestions.Add(new AssignmentCheckQuestion + { + AssignmentQuestionDto = currentGroup, + //Sequence = currentGroupSequence, + Sequence = currentGroup.Sequence, + Score = currentGroup.Score, + }); + } } } } diff --git a/TechHelper.Client/Exam/ExamPaperExtensions .cs b/TechHelper.Client/Exam/ExamPaperExtensions .cs index 699b1f5..e082727 100644 --- a/TechHelper.Client/Exam/ExamPaperExtensions .cs +++ b/TechHelper.Client/Exam/ExamPaperExtensions .cs @@ -7,7 +7,7 @@ using AutoMapper; namespace TechHelper.Client.Exam { - public static class ExamPaperExtensions + public static class AssignmentExtensions { public static List ParseOptionsFromText(this string optionsText) @@ -25,9 +25,9 @@ namespace TechHelper.Client.Exam public static void SeqQGroupIndex(this AssignmentQuestionDto dto) { - foreach(var sqg in dto.ChildrenAssignmentQuestion) + foreach (var sqg in dto.ChildrenAssignmentQuestion) { - sqg.Index = (byte)dto.ChildrenAssignmentQuestion.IndexOf(sqg); + sqg.Index = (byte)(dto.ChildrenAssignmentQuestion.IndexOf(sqg) + 1); sqg.SeqQGroupIndex(); } diff --git a/TechHelper.Client/Exam/ExamParse.cs b/TechHelper.Client/Exam/ExamParse.cs index a59dad0..69b9770 100644 --- a/TechHelper.Client/Exam/ExamParse.cs +++ b/TechHelper.Client/Exam/ExamParse.cs @@ -56,6 +56,7 @@ namespace TechHelper.Client.Exam public string Description { get; set; } = string.Empty; public byte Index { get; set; } = 0; public float Score { get; set; } + public string Sequence { get; set; } = string.Empty; public QuestionEx? Question { get; set; } public AssignmentStructType Type { get; set; } public List ChildrenAssignmentQuestion { get; set; } = new List(); @@ -344,6 +345,8 @@ namespace TechHelper.Client.Exam assignmentQuestionStack.Pop(); } + string sequence = assignmentQuestionStack.Count > 0 ? assignmentQuestionStack.Peek().Sequence : string.Empty; + // 验证捕获组:Group 1 是编号,Group 2 是题目内容 if (pm.RegexMatch.Groups.Count < 3 || !pm.RegexMatch.Groups[1].Success || string.IsNullOrWhiteSpace(pm.RegexMatch.Groups[2].Value)) { @@ -372,6 +375,8 @@ namespace TechHelper.Client.Exam // 提取标题,这里使用 Group 2 的值,它不包含分数 string title = pm.RegexMatch.Groups[2].Value.Trim(); + string seq = pm.RegexMatch.Groups[1].Value.Trim(); + seq = string.IsNullOrEmpty(seq) || string.IsNullOrEmpty(sequence) ? seq : " ." + seq; AssignmentQuestionEx newAssignmentQuestion; if (pm.PatternConfig.Type == AssignmentStructType.Struct) @@ -380,6 +385,7 @@ namespace TechHelper.Client.Exam { Title = title, Score = score, + Sequence = sequence + seq, Priority = pm.PatternConfig.Priority, Type = pm.PatternConfig.Type }; @@ -390,6 +396,7 @@ namespace TechHelper.Client.Exam { Priority = pm.PatternConfig.Priority, Type = pm.PatternConfig.Type, + Sequence = sequence + seq, Score = score, Question = new QuestionEx { diff --git a/TechHelper.Client/HttpRepository/AuthenticationClientService.cs b/TechHelper.Client/HttpRepository/AuthenticationClientService.cs index 5dc1436..311acdc 100644 --- a/TechHelper.Client/HttpRepository/AuthenticationClientService.cs +++ b/TechHelper.Client/HttpRepository/AuthenticationClientService.cs @@ -92,7 +92,6 @@ namespace TechHelper.Client.HttpRepository public async Task RegisterUserAsync(UserForRegistrationDto userForRegistrationDto) { - // 移除 using (_client = _clientFactory.CreateClient("Default")) userForRegistrationDto.ClientURI = Path.Combine( _navigationManager.BaseUri, "emailconfirmation"); diff --git a/TechHelper.Client/Pages/Exam/ExamCheck.razor b/TechHelper.Client/Pages/Exam/ExamCheck.razor index 0314b32..07b8286 100644 --- a/TechHelper.Client/Pages/Exam/ExamCheck.razor +++ b/TechHelper.Client/Pages/Exam/ExamCheck.razor @@ -9,216 +9,227 @@ @if (_isLoading) { - - 正在加载试卷和学生数据... + + 正在加载试卷和学生数据... } else if (_questionsForTable.Any() && _students.Any()) { - - - 序号 - 分值 - @foreach (var student in _students) - { - - @student.Name - - - - - } - - - @context.QuestionItem.Sequence - @context.QuestionItem.Score - @foreach (var student in _students) - { - - @if (context.StudentAnswers.ContainsKey(student.Id)) - { - - } - else - { - N/A - } - - } - - - - - + + + 序号 + 分值 + @foreach (var student in _students) + { + + @student.DisplayName + + + + + } + + + @context.QuestionItem.Sequence + @context.QuestionItem.Score + @foreach (var student in _students) + { + + @if (context.StudentAnswers.ContainsKey(student.Id)) + { + + } + else + { + N/A + } + + } + + + + + - - 学生总分预览: - @foreach (var student in _students) - { - - @student.Name: @GetStudentTotalScore(student.Id) - - } - - 提交批改结果 (模拟) - - + + 学生总分预览: + @foreach (var student in _students) + { + + @student.DisplayName: @GetStudentTotalScore(student.Id) + + } + + 提交批改结果 (模拟) + + } else { - 无法加载试卷或题目信息。 - 返回试卷列表 + 无法加载试卷或题目信息。 + 返回试卷列表 } @code { - [Parameter] - public string ExamId { get; set; } + [Parameter] + public string ExamId { get; set; } - [Inject] - public IExamService ExamService { get; set; } + [Inject] + public IExamService ExamService { get; set; } - [Inject] - private ISnackbar Snackbar { get; set; } + [Inject] + private ISnackbar Snackbar { get; set; } - [Inject] - private NavigationManager Navigation { get; set; } + [Inject] + private NavigationManager Navigation { get; set; } - private MudTable _table = new(); - private AssignmentDto Assignment { get; set; } = new AssignmentDto(); - private ExamStruct _examStruct = new ExamStruct(); + private MudTable _table = new(); + private AssignmentDto Assignment { get; set; } = new AssignmentDto(); + private AssignmentCheckData _examStruct = new AssignmentCheckData(); - private List _students = new List(); - private List _questionsForTable = new List(); + private List _students = new List(); + private List _questionsForTable = new List(); - private bool _isLoading = true; + private bool _isLoading = true; + + [Inject] + public IClassServices ClassServices { get; set; } + + protected override async Task OnInitializedAsync() + { + _isLoading = true; + await LoadExamData(); + + var result = await ClassServices.GetClassStudents(); + if (!result.Status) Snackbar.Add($"获取学生失败, {result.Message}", Severity.Error); + _students = result.Result as List ?? new List(); + BuildTable(); + _isLoading = false; + } + + private void BuildTable() + { + _questionsForTable = _examStruct.Questions.Select(q => + { + var rowData = new QuestionRowData + { + QuestionItem = q, + StudentAnswers = new Dictionary() + }; + foreach (var student in _students) + { + rowData.StudentAnswers[student.Id] = false; + } + return rowData; + }).ToList(); + } + + private async Task LoadExamData() + { + if (Guid.TryParse(ExamId, out Guid parsedExamId)) + { + try + { + var result = await ExamService.GetExam(parsedExamId); + if (result.Status) + { + Assignment = result.Result as AssignmentDto ?? new AssignmentDto(); + _examStruct = Assignment.GetStruct(); + } + else + { + Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error); + Navigation.NavigateTo("/exam/manager"); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}"); + Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error); + Navigation.NavigateTo("/exam/manager"); + } + } + else + { + Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。"); + Snackbar?.Add("无效的试卷ID,无法加载。", Severity.Error); + Navigation.NavigateTo("/exam/manager"); + } + } - protected override async Task OnInitializedAsync() - { - _isLoading = true; - await LoadExamData(); - GenerateTemporaryStudentsAndAnswers(); - _isLoading = false; - } + + private float GetStudentTotalScore(Guid studentId) + { + float totalScore = 0; + foreach (var row in _questionsForTable) + { + if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect) + { + totalScore += row.QuestionItem.Score; + } + } + return totalScore; + } + + private void ToggleStudentAllAnswers(Guid studentId) + { + bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]); + + foreach (var row in _questionsForTable) + { + if (row.StudentAnswers.ContainsKey(studentId)) + { + row.StudentAnswers[studentId] = !allCorrect; + } + } + StateHasChanged(); + } + + private void SubmitGrading() + { + + List submissionDto = new List(); - private async Task LoadExamData() - { - if (Guid.TryParse(ExamId, out Guid parsedExamId)) - { - try - { - var result = await ExamService.GetExam(parsedExamId); - if (result.Status) - { - Assignment = result.Result as AssignmentDto ?? new AssignmentDto(); - _examStruct = Assignment.GetStruct(); - } - else - { - Snackbar?.Add($"获取试卷失败: {result.Message}", Severity.Error); - Navigation.NavigateTo("/exam/manager"); - } - } - catch (Exception ex) - { - Console.Error.WriteLine($"获取试卷时发生错误: {ex.Message}"); - Snackbar?.Add($"获取试卷失败: {ex.Message}", Severity.Error); - Navigation.NavigateTo("/exam/manager"); - } - } - else - { - Console.Error.WriteLine($"错误:路由参数 ExamId '{ExamId}' 不是一个有效的 GUID 格式。"); - Snackbar?.Add("无效的试卷ID,无法加载。", Severity.Error); - Navigation.NavigateTo("/exam/manager"); - } - } + foreach (var student in _students) + { + var newSubmission = new SubmissionDto(); + newSubmission.StudentId = student.Id; + newSubmission.AssignmentId = Assignment.Id; + newSubmission.SubmissionTime = DateTime.Now; + newSubmission.Status = Entities.Contracts.SubmissionStatus.Submitted; - // 生成临时学生和作答数据 - private void GenerateTemporaryStudentsAndAnswers() - { - _students = new List(); - // 生成 40 个学生 - for (int i = 1; i <= 40; i++) - { - _students.Add(new Student { Name = $"学生{i}" }); - } - _questionsForTable = _examStruct.Questions.Select(qItem => - { - var rowData = new QuestionRowData - { - QuestionItem = qItem, - StudentAnswers = new Dictionary() - }; + foreach (var row in _questionsForTable) + { + if (row.QuestionItem.AssignmentQuestionDto.StructType == Entities.Contracts.AssignmentStructType.Struct) continue; + if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect)) + { + newSubmission.SubmissionDetails.Add(new SubmissionDetailDto + { + IsCorrect = isCorrect, + StudentId = student.Id, + AssignmentQuestionId = row.QuestionItem.AssignmentQuestionDto.Id, + PointsAwarded = isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0 + }); - // 为每个学生随机生成初始的对错状态 - var random = new Random(); - foreach (var student in _students) - { - // 模拟随机对错,50%的概率 - rowData.StudentAnswers[student.Id] = random.Next(0, 2) == 1; - } - return rowData; - }).ToList(); - } + newSubmission.OverallGrade += isCorrect ? row.QuestionItem.AssignmentQuestionDto.Score : 0; + } + } + submissionDto.Add(newSubmission); + } - // 当某个学生的某个题目的作答状态改变时触发 - private void OnAnswerChanged(string questionSequence, Guid studentId, bool isCorrect) - { - // 可以在这里添加额外的逻辑,例如记录更改 - Console.WriteLine($"题目 {questionSequence}, 学生 {studentId} 的答案变为: {isCorrect}"); - // 由于是 @bind-Checked,数据模型已经自动更新,这里只是日志 - } + submissionDto.ForEach(async s => + { + Snackbar?.Add($"正在提交: {_students.FirstOrDefault(std => std.Id == s.StudentId)?.DisplayName} 的试卷", Severity.Info); + await ExamService.SubmissionAssignment(s); + }); - // 计算某个学生的总分 - private float GetStudentTotalScore(Guid studentId) - { - float totalScore = 0; - foreach (var row in _questionsForTable) - { - if (row.StudentAnswers.TryGetValue(studentId, out bool isCorrect) && isCorrect) - { - totalScore += row.QuestionItem.Score; - } - } - return totalScore; - } - // 切换某个学生所有题目的对错状态 (用于快速批改) - private void ToggleStudentAllAnswers(Guid studentId) - { - bool allCorrect = _questionsForTable.All(row => row.StudentAnswers.ContainsKey(studentId) && row.StudentAnswers[studentId]); - foreach (var row in _questionsForTable) - { - if (row.StudentAnswers.ContainsKey(studentId)) - { - row.StudentAnswers[studentId] = !allCorrect; // 全部取反 - } - } - StateHasChanged(); // 手动通知 Blazor 刷新 UI - } - - // 提交批改结果(模拟) - private void SubmitGrading() - { - Console.WriteLine("--- 提交批改结果 ---"); - foreach (var student in _students) - { - Console.WriteLine($"学生: {student.Name}, 总分: {GetStudentTotalScore(student.Id)}"); - foreach (var row in _questionsForTable) - { - if (row.StudentAnswers.TryGetValue(student.Id, out bool isCorrect)) - { - Console.WriteLine($" - 题目 {row.QuestionItem.Sequence}: {(isCorrect ? "正确" : "错误")}"); - } - } - } - Snackbar?.Add("批改结果已提交(模拟)", Severity.Success); - // 实际应用中,这里会将 _questionsForTable 和 _students 的数据发送到后端API - } + 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 bcdaad1..9105f17 100644 --- a/TechHelper.Client/Pages/Exam/ExamManager.razor +++ b/TechHelper.Client/Pages/Exam/ExamManager.razor @@ -49,7 +49,7 @@ else { isloding = true; Snackbar.Add("正在加载", Severity.Info); - var result = await ExamService.GetAllExam(authenticationStateTask.Result.User.Identity.Name); + var result = await ExamService.GetAllExam(); examDtos = result.Result as List ?? new List(); isloding = false; Snackbar.Add("加载成功", Severity.Info); diff --git a/TechHelper.Client/Pages/Exam/ExamPreview.razor b/TechHelper.Client/Pages/Exam/ExamPreview.razor index 0021fd2..05d2839 100644 --- a/TechHelper.Client/Pages/Exam/ExamPreview.razor +++ b/TechHelper.Client/Pages/Exam/ExamPreview.razor @@ -12,7 +12,11 @@ 详情 - + @if (bteacher) + { + + } + @@ -21,6 +25,10 @@ @code { + [CascadingParameter] + private Task authenticationStateTask { get; set; } + + private bool bteacher = false; [Inject] public NavigationManager navigationManager { get; set; } @@ -45,6 +53,12 @@ public string? MaxHeight { get; set; } = "64"; + protected override Task OnInitializedAsync() + { + bteacher = authenticationStateTask.Result.User.IsInRole("Teacher"); + return base.OnInitializedAsync(); + } + private void ExamClick() { navigationManager.NavigateTo($"exam/edit/{AssignmentDto.Id}"); diff --git a/TechHelper.Client/Pages/Home.razor b/TechHelper.Client/Pages/Home.razor index ab98721..8b26af7 100644 --- a/TechHelper.Client/Pages/Home.razor +++ b/TechHelper.Client/Pages/Home.razor @@ -1,118 +1,10 @@ @page "/" @using Microsoft.AspNetCore.Authorization - + - Hello @context.User.Identity.Name - @foreach (var item in context.User.Claims) - { - - @item.Value - @item.Issuer - @item.Subject - @item.Properties - @item.ValueType - - } - Welcome to your new app. - - -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello -Hello +@code { + [CascadingParameter] + private Task authenticationStateTask { get; set; } +} \ No newline at end of file diff --git a/TechHelper.Client/Pages/Teacher/StudentsView.razor b/TechHelper.Client/Pages/Teacher/StudentsView.razor new file mode 100644 index 0000000..5c89b17 --- /dev/null +++ b/TechHelper.Client/Pages/Teacher/StudentsView.razor @@ -0,0 +1,28 @@ +@using Entities.Contracts +@using Entities.DTO +@using TechHelper.Client.Services +

StudentsView

+ + +@foreach(var cs in ClassStudents) +{ + @cs.DisplayName +} + + +@code { + [CascadingParameter] + private Task authenticationStateTask { get; set; } + + private List ClassStudents { get; set; } = new List(); + + [Inject] + public IClassServices ClassServices { get; set; } + + protected override async Task OnInitializedAsync() + { + var result = await ClassServices.GetClassStudents(); + ClassStudents = result.Result as List ?? new List(); + StateHasChanged(); + } +} diff --git a/TechHelper.Client/Services/ClasssServices.cs b/TechHelper.Client/Services/ClasssServices.cs index 5a93d29..89bb362 100644 --- a/TechHelper.Client/Services/ClasssServices.cs +++ b/TechHelper.Client/Services/ClasssServices.cs @@ -1,6 +1,9 @@ -using Entities.DTO; +using Entities.Contracts; +using Entities.DTO; +using Newtonsoft.Json; using System.Net.Http.Json; using TechHelper.Client.HttpRepository; +using TechHelper.Services; namespace TechHelper.Client.Services { @@ -20,6 +23,21 @@ namespace TechHelper.Client.Services throw new NotImplementedException(); } + public async Task GetClassStudents() + { + try + { + var result = await _client.PostAsJsonAsync("class/getClassStudents",""); + var content = await result.Content.ReadAsStringAsync(); + var users = JsonConvert.DeserializeObject>(content); + return ApiResponse.Success(result: users); + } + catch(Exception ex) + { + return ApiResponse.Error($"获取失败,{ex.Message}, InnerException: {ex.InnerException}"); + } + } + public async Task UserRegister(UserRegistrationToClassDto userRegistrationToClassDto) { try diff --git a/TechHelper.Client/Services/ExamService.cs b/TechHelper.Client/Services/ExamService.cs index 4c0812b..ce06297 100644 --- a/TechHelper.Client/Services/ExamService.cs +++ b/TechHelper.Client/Services/ExamService.cs @@ -9,13 +9,13 @@ namespace TechHelper.Client.Services { public class ExamService : IExamService { - private readonly IAIService _aIService; - private readonly HttpClient _client; + private readonly IAIService _aIService; + private readonly HttpClient _client; - public ExamService(IAIService aIService, HttpClient client) + public ExamService(IAIService aIService, HttpClient client) { _aIService = aIService; - _client = client; + _client = client; } public ApiResponse ConvertToXML(string xmlContent) @@ -86,9 +86,9 @@ namespace TechHelper.Client.Services } } - public async Task GetAllExam(string user) + public async Task GetAllExam() { - var response = await _client.GetAsync($"exam/getAllPreview?user={user}"); + var response = await _client.GetAsync($"exam/getAllPreview"); if (response.IsSuccessStatusCode) { @@ -104,6 +104,25 @@ namespace TechHelper.Client.Services } } + public async Task GetAllSubmission() + { + try + { + var response = await _client.GetAsync($"exam/getAllSubmission"); + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var exam = JsonConvert.DeserializeObject(content); + return ApiResponse.Success(); + } + return ApiResponse.Error(message: "获取失败"); + } + catch (Exception ex) + { + return ApiResponse.Error(message: $"内部错误{ex.Message}, InerEx{ex.InnerException}"); + } + } + public async Task GetExam(Guid guid) { @@ -158,5 +177,18 @@ namespace TechHelper.Client.Services return ApiResponse.Error(message: $"保存试题失败: {response.StatusCode} - {errorContent}"); } } + + public async Task SubmissionAssignment(SubmissionDto submission) + { + var response = await _client.PostAsJsonAsync("exam/submission", submission); + if (response.IsSuccessStatusCode) + { + return ApiResponse.Success("提交成功"); + } + else + { + return ApiResponse.Error("提交失败"); + } + } } } \ No newline at end of file diff --git a/TechHelper.Client/Services/IClassServices.cs b/TechHelper.Client/Services/IClassServices.cs index 84a081b..3123b40 100644 --- a/TechHelper.Client/Services/IClassServices.cs +++ b/TechHelper.Client/Services/IClassServices.cs @@ -8,5 +8,6 @@ namespace TechHelper.Client.Services { public Task UserRegister(UserRegistrationToClassDto userRegistrationToClassDto); public Task CreateClass(UserRegistrationToClassDto userClass); + public Task GetClassStudents(); } } diff --git a/TechHelper.Client/Services/IExamService.cs b/TechHelper.Client/Services/IExamService.cs index 7b4364c..f04c733 100644 --- a/TechHelper.Client/Services/IExamService.cs +++ b/TechHelper.Client/Services/IExamService.cs @@ -11,7 +11,9 @@ namespace TechHelper.Client.Services public Task ParseSingleQuestionGroup(string examContent); public ApiResponse ConvertToXML(string xmlContent); - public Task GetAllExam(string user); + public Task GetAllExam(); public Task GetExam(Guid guid); + public Task SubmissionAssignment(SubmissionDto submission); + public Task GetAllSubmission(); } } diff --git a/TechHelper.Server/Context/AutoMapperProFile.cs b/TechHelper.Server/Context/AutoMapperProFile.cs index 60c799c..4d31504 100644 --- a/TechHelper.Server/Context/AutoMapperProFile.cs +++ b/TechHelper.Server/Context/AutoMapperProFile.cs @@ -36,20 +36,22 @@ namespace TechHelper.Context + // Assignment + CreateMap().ReverseMap(); - CreateMap(); + CreateMap().ReverseMap(); + + CreateMap().ReverseMap(); CreateMap().ReverseMap(); - CreateMap(); - CreateMap(); - CreateMap(); - CreateMap(); + // Submission + CreateMap().ReverseMap(); - CreateMap(); + CreateMap().ReverseMap(); } } diff --git a/TechHelper.Server/Context/Configuration/AssignmentConfiguration.cs b/TechHelper.Server/Context/Configuration/AssignmentConfiguration.cs index 9d159c9..4192749 100644 --- a/TechHelper.Server/Context/Configuration/AssignmentConfiguration.cs +++ b/TechHelper.Server/Context/Configuration/AssignmentConfiguration.cs @@ -57,26 +57,13 @@ namespace TechHelper.Context.Configuration .HasForeignKey(a => a.CreatorId) .IsRequired(); // CreatedBy 是必填的,对应 [Required] 在外键属性上 - // 关系: Assignment (一) 到 AssignmentClass (多) - // 假设 AssignmentClass 实体包含一个名为 AssignmentId 的外键属性 - builder.HasMany(a => a.AssignmentClasses) - .WithOne(ac => ac.Assignment) // AssignmentClass 没有指向 Assignment 的导航属性 (或我们不知道) - .HasForeignKey("AssignmentId") // 指定外键名称为 AssignmentId - .OnDelete(DeleteBehavior.Cascade); // 如果 Assignment 被删除,关联的 AssignmentClass 也会被删除 - // 关系: Assignment (一) 到 AssignmentAttachment (多) - // 假设 AssignmentAttachment 实体包含一个名为 AssignmentId 的外键属性 - builder.HasMany(a => a.AssignmentAttachments) - .WithOne(aa => aa.Assignment) - .HasForeignKey("AssignmentId") - .OnDelete(DeleteBehavior.Cascade); - // 关系: Assignment (一) 到 Submission (多) - // 假设 Submission 实体包含一个名为 AssignmentId 的外键属性 - builder.HasMany(a => a.Submissions) - .WithOne(s => s.Assignment) - .HasForeignKey("AssignmentId") - .OnDelete(DeleteBehavior.Cascade); + builder.HasOne(a=>a.ExamStruct) + .WithOne() + .HasForeignKey(a=>a.ExamStructId) + .OnDelete(DeleteBehavior.Cascade); + } } } diff --git a/TechHelper.Server/Context/Configuration/AssignmentQuestionConfiguration.cs b/TechHelper.Server/Context/Configuration/AssignmentQuestionConfiguration.cs index e999dc1..0d435f2 100644 --- a/TechHelper.Server/Context/Configuration/AssignmentQuestionConfiguration.cs +++ b/TechHelper.Server/Context/Configuration/AssignmentQuestionConfiguration.cs @@ -42,35 +42,24 @@ namespace TechHelper.Context.Configuration .HasColumnName("deleted") .HasDefaultValue(false); // 适用于软删除策略 - // 4. 配置导航属性和外键关系 - // --- - // 配置 AssignmentQuestion 到 Question 的关系 (多对一) - // 一个 AssignmentQuestion 属于一个 Question。 - // - // 假设 `Question` 实体中有一个名为 `AssignmentQuestions` 的 `ICollection` 集合属性。 - builder.HasOne(aq => aq.Question) // 当前 AssignmentQuestion 有一个 Question - .WithMany(q => q.AssignmentQuestions) // 那个 Question 可以有多个 AssignmentQuestion - .HasForeignKey(aq => aq.QuestionId) // 外键是 AssignmentQuestion.QuestionId - .OnDelete(DeleteBehavior.Cascade); // 当 Question 被删除时,相关的 AssignmentQuestion 也级联删除。 + builder.HasOne(aq => aq.Question) + .WithMany(q => q.AssignmentQuestions) + .HasForeignKey(aq => aq.QuestionId) + .OnDelete(DeleteBehavior.Cascade); + builder.HasOne(aq => aq.ParentAssignmentQuestion) + .WithMany(aq => aq.ChildrenAssignmentQuestion) + .HasForeignKey(aq => aq.ParentAssignmentQuestionId) + .OnDelete(DeleteBehavior.Cascade); builder.HasOne(aq => aq.QuestionContext) .WithMany(qc => qc.Questions) .HasForeignKey(aq => aq.QuestionContextId) .OnDelete(DeleteBehavior.SetNull); - // --- - // 配置 AssignmentQuestion 到 SubmissionDetail 的关系 (一对多) - // 一个 AssignmentQuestion 可以有多个 SubmissionDetail。 - // - // 这个关系通常从 "多" 的一方(`SubmissionDetail` 实体)来配置外键。 - // 假设 `SubmissionDetail` 实体有一个 `AssignmentQuestionId` 外键和 `AssignmentQuestion` 导航属性。 - builder.HasMany(aq => aq.SubmissionDetails) // 当前 AssignmentQuestion 有多个 SubmissionDetail - .WithOne(sd => sd.AssignmentQuestion); // 每一个 SubmissionDetail 都有一个 AssignmentQuestion - // .HasForeignKey(sd => sd.AssignmentQuestionId); // 外键的配置应在 `SubmissionDetailConfiguration` 中进行 - } - } - + } + + } } diff --git a/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs b/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs index 2db8c42..800fd1a 100644 --- a/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs +++ b/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs @@ -75,16 +75,13 @@ namespace TechHelper.Context.Configuration builder.HasOne(s => s.Student) // 当前 Submission 有一个 Student (User) .WithMany(u => u.SubmissionsAsStudent) // 那个 User (Student) 可以有多个 Submission .HasForeignKey(s => s.StudentId) // 外键是 Submission.StudentId - .OnDelete(DeleteBehavior.Restrict); // 当 User (Student) 被删除时,如果还有其提交的 Submission,则会阻止删除。 + .OnDelete(DeleteBehavior.Cascade); // 当 User (Student) 被删除时,如果还有其提交的 Submission,则会阻止删除。 builder.HasOne(s => s.Grader) // 当前 Submission 有一个 Grader (User),可以是空的 .WithMany(u => u.GradedSubmissions) // 那个 User (Grader) 可以批改多个 Submission .HasForeignKey(s => s.GraderId) // 外键是 Submission.GradedBy .OnDelete(DeleteBehavior.SetNull); // 当 User (Grader) 被删除时,如果 GradedBy 是可空的,则将其设置为 NULL。 - - builder.HasMany(s => s.SubmissionDetails) // 当前 Submission 有多个 SubmissionDetail - .WithOne(sd => sd.Submission); // 每一个 SubmissionDetail 都有一个 Submission } } } diff --git a/TechHelper.Server/Context/Configuration/SubmissionDetailConfiguration.cs b/TechHelper.Server/Context/Configuration/SubmissionDetailConfiguration.cs index ffcf3b6..3d495fb 100644 --- a/TechHelper.Server/Context/Configuration/SubmissionDetailConfiguration.cs +++ b/TechHelper.Server/Context/Configuration/SubmissionDetailConfiguration.cs @@ -64,7 +64,7 @@ namespace TechHelper.Context.Configuration builder.HasOne(sd => sd.Student) // 当前 SubmissionDetail 有一个 User (作为学生) .WithMany(u => u.SubmissionDetails) .HasForeignKey(sd => sd.StudentId) // 外键是 SubmissionDetail.StudentId - .OnDelete(DeleteBehavior.Restrict); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。 + .OnDelete(DeleteBehavior.Cascade); // 当 User (学生) 被删除时,如果他/她还有提交详情,则会阻止删除。 // 这是一个更安全的选择,以防止意外数据丢失。 builder.HasOne(sd => sd.AssignmentQuestion) // 当前 SubmissionDetail 有一个 AssignmentQuestion diff --git a/TechHelper.Server/Controllers/ClassController.cs b/TechHelper.Server/Controllers/ClassController.cs index f157b83..424b8e2 100644 --- a/TechHelper.Server/Controllers/ClassController.cs +++ b/TechHelper.Server/Controllers/ClassController.cs @@ -1,7 +1,10 @@ -using Entities.DTO; +using Entities.Contracts; +using Entities.DTO; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.Net; +using System.Security.Claims; using TechHelper.Services; namespace TechHelper.Server.Controllers @@ -11,25 +14,76 @@ namespace TechHelper.Server.Controllers public class ClassController : ControllerBase { private IClassService _classService; - - public ClassController(IClassService classService) + private UserManager _userManager; + public ClassController(IClassService classService, UserManager userManager) { _classService = classService; + _userManager = userManager; } [HttpPost("userRegiste")] public async Task UserRegisterToClass( [FromBody] UserRegistrationToClassDto toClass) { - + var result = await _classService.UserRegister(toClass); - if(!result.Status) return BadRequest(result.Message); + if (!result.Status) return BadRequest(result.Message); - return Ok(); + return Ok(); } + + + [HttpPost("getClassStudents")] + public async Task GetClassStudents() + { + if (User.IsInRole("Teacher")) + { + var gradeClaim = User.FindFirst("Grade")?.Value; + var classClaim = User.FindFirst("Class")?.Value; + + if (string.IsNullOrEmpty(gradeClaim) || string.IsNullOrEmpty(classClaim)) + { + return BadRequest("未识别到你加入的班级信息(年级或班级声明缺失)。"); + } + + if (!byte.TryParse(gradeClaim, out byte grade) || !byte.TryParse(classClaim, out byte cla)) + { + return BadRequest("你班级或年级信息格式不正确。"); + } + + var classDto = new ClassDto + { + Grade = grade, + Class = cla + }; + + var result = await _classService.GetClassStudents(classDto); + var css = result.Result as ICollection; + if(css == null) return BadRequest("你还没有学生"); + + + List sts = new List(); + css?.ToList().ForEach(s => sts.Add(new StudentDto + { + DisplayName = s.Student.DisplayName, + Id = s.Student.Id, + })); + + if (!result.Status) + { + return BadRequest(result.Message); + } + return Ok(sts); + } + else + { + return BadRequest("你没有权限查看,未识别到你的教师身份。"); + } + } + [HttpPost("Create")] public async Task Create( [FromBody] ClassDto classDto) diff --git a/TechHelper.Server/Controllers/ExamController.cs b/TechHelper.Server/Controllers/ExamController.cs index 3676c2b..1ce660c 100644 --- a/TechHelper.Server/Controllers/ExamController.cs +++ b/TechHelper.Server/Controllers/ExamController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using TechHelper.Server.Services; using System.Security.Claims; +using TechHelper.Services; namespace TechHelper.Server.Controllers @@ -31,7 +32,7 @@ namespace TechHelper.Server.Controllers [FromBody] AssignmentDto examDto) { var user = await _userManager.FindByEmailAsync(User.Identity?.Name ?? ""); - if(user == null) return BadRequest("无效的用户"); + if (user == null) return BadRequest("无效的用户"); examDto.CreatorId = user.Id; var result = await _examService.CreateExamAsync(examDto); @@ -45,6 +46,29 @@ namespace TechHelper.Server.Controllers } } + [HttpPost("submission")] + 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) + { + return Ok(result); + } + else + { + return BadRequest(result.Message); + } + } + else + { + return BadRequest("你没有权限修改"); + } + } + [HttpGet("get")] public async Task GetExamById(Guid id) { @@ -58,16 +82,26 @@ namespace TechHelper.Server.Controllers [HttpGet("getAllPreview")] - public async Task GetAllExamPreview(string user) + public async Task GetAllExamPreview() { - string? userId = User.Identity.Name; + if (User == null) return BadRequest("用户验证失败, 无效用户"); - var userid = await _userManager.FindByEmailAsync(user); - if (userid == null) return BadRequest("用户验证失败, 无效用户"); + var userid = await _userManager.FindByEmailAsync(User.Identity.Name); - - var result = await _examService.GetAllExamPreviewsAsync(userid.Id); + var result = new ApiResponse(); + if (User.IsInRole("Teacher")) + { + result = await _examService.GetAllExamPreviewsAsync(userid.Id); + } + else if (User.IsInRole("Student")) + { + result = await _examService.GetAllSubmissionAsync(userid.Id); + } + else + { + return BadRequest("你没有相应的权限"); + } if (result.Status) { @@ -76,5 +110,34 @@ namespace TechHelper.Server.Controllers return BadRequest(result); } + + [HttpGet("getAllSubmission")] + public async Task GetAllSubmission() + { + if (User == null) return BadRequest("用户验证失败, 无效用户"); + + var userid = await _userManager.FindByEmailAsync(User.Identity.Name); + + var result = await _examService.GetAllSubmissionAsync(userid.Id); + + if (result.Status) + { + return Ok(result.Result); + } + return BadRequest(result); + } + + + [Authorize(Roles = "Teacher")] + [HttpDelete("{guid}")] + public async Task DeleteAsync(Guid guid) + { + var deleteResult = await _examService.DeleteAsync(guid); + if (deleteResult.Status) + { + return Ok(); + } + return BadRequest(); + } } } diff --git a/TechHelper.Server/Controllers/UserController.cs b/TechHelper.Server/Controllers/UserController.cs new file mode 100644 index 0000000..4121dde --- /dev/null +++ b/TechHelper.Server/Controllers/UserController.cs @@ -0,0 +1,30 @@ +using Entities.Contracts; +using Entities.DTO; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using TechHelper.Services; + +namespace TechHelper.Server.Controllers +{ + [Route("api/user")] + [ApiController] + public class UserController : ControllerBase + { + private IClassService _classService; + private UserManager _userManager; + public UserController(IClassService classService, UserManager userManager) + { + _classService = classService; + _userManager = userManager; + } + + + [HttpPost("get")] + public async Task GetAsync( + [FromBody] UserRegistrationToClassDto toClass) + { + return Ok(); + } + } +} diff --git a/TechHelper.Server/Migrations/20250625032845_up.cs b/TechHelper.Server/Migrations/20250625032845_up.cs deleted file mode 100644 index 5f073a2..0000000 --- a/TechHelper.Server/Migrations/20250625032845_up.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; - -#nullable disable - -#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional - -namespace TechHelper.Server.Migrations -{ - /// - public partial class up : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_class_teachers_AspNetUsers_teacher_id", - table: "class_teachers"); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22")); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1")); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8")); - - migrationBuilder.InsertData( - table: "AspNetRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), null, "Teacher", "TEACHER" }, - { new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), null, "Administrator", "ADMINISTRATOR" }, - { new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), null, "Student", "STUDENT" } - }); - - migrationBuilder.AddForeignKey( - name: "FK_class_teachers_AspNetUsers_teacher_id", - table: "class_teachers", - column: "teacher_id", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropForeignKey( - name: "FK_class_teachers_AspNetUsers_teacher_id", - table: "class_teachers"); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3")); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9")); - - migrationBuilder.DeleteData( - table: "AspNetRoles", - keyColumn: "Id", - keyValue: new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3")); - - migrationBuilder.InsertData( - table: "AspNetRoles", - columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, - values: new object[,] - { - { new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), null, "Administrator", "ADMINISTRATOR" }, - { new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), null, "Student", "STUDENT" }, - { new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), null, "Teacher", "TEACHER" } - }); - - migrationBuilder.AddForeignKey( - name: "FK_class_teachers_AspNetUsers_teacher_id", - table: "class_teachers", - column: "teacher_id", - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Restrict); - } - } -} diff --git a/TechHelper.Server/Migrations/20250625032845_up.Designer.cs b/TechHelper.Server/Migrations/20250626073834_init.Designer.cs similarity index 99% rename from TechHelper.Server/Migrations/20250625032845_up.Designer.cs rename to TechHelper.Server/Migrations/20250626073834_init.Designer.cs index 48552ae..b0f9267 100644 --- a/TechHelper.Server/Migrations/20250625032845_up.Designer.cs +++ b/TechHelper.Server/Migrations/20250626073834_init.Designer.cs @@ -12,8 +12,8 @@ using TechHelper.Context; namespace TechHelper.Server.Migrations { [DbContext(typeof(ApplicationContext))] - [Migration("20250625032845_up")] - partial class up + [Migration("20250626073834_init")] + partial class init { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -512,7 +512,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("deleted"); b.Property("OverallFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("overall_feedback"); @@ -579,7 +578,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("points_awarded"); b.Property("StudentAnswer") - .IsRequired() .HasColumnType("longtext") .HasColumnName("student_answer"); @@ -592,7 +590,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("submission_id"); b.Property("TeacherFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("teacher_feedback"); @@ -747,19 +744,19 @@ namespace TechHelper.Server.Migrations b.HasData( new { - Id = new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), + Id = new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"), Name = "Student", NormalizedName = "STUDENT" }, new { - Id = new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), + Id = new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"), Name = "Teacher", NormalizedName = "TEACHER" }, new { - Id = new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), + Id = new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"), Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); diff --git a/TechHelper.Server/Migrations/20250624103910_init.cs b/TechHelper.Server/Migrations/20250626073834_init.cs similarity index 98% rename from TechHelper.Server/Migrations/20250624103910_init.cs rename to TechHelper.Server/Migrations/20250626073834_init.cs index e7f0219..eebadc0 100644 --- a/TechHelper.Server/Migrations/20250624103910_init.cs +++ b/TechHelper.Server/Migrations/20250626073834_init.cs @@ -323,7 +323,7 @@ namespace TechHelper.Server.Migrations column: x => x.teacher_id, principalTable: "AspNetUsers", principalColumn: "Id", - onDelete: ReferentialAction.Restrict); + onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_class_teachers_classes_class_id", column: x => x.class_id, @@ -565,7 +565,7 @@ namespace TechHelper.Server.Migrations attempt_number = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), submission_time = table.Column(type: "datetime(6)", nullable: false), overall_grade = table.Column(type: "float", precision: 5, scale: 2, nullable: true), - overall_feedback = table.Column(type: "longtext", nullable: false) + overall_feedback = table.Column(type: "longtext", nullable: true) .Annotation("MySql:CharSet", "utf8mb4"), graded_by = table.Column(type: "char(36)", nullable: true, collation: "ascii_general_ci"), graded_at = table.Column(type: "datetime(6)", nullable: true), @@ -604,11 +604,11 @@ namespace TechHelper.Server.Migrations submission_id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), student_id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), assignment_question_id = table.Column(type: "char(36)", nullable: false, collation: "ascii_general_ci"), - student_answer = table.Column(type: "longtext", nullable: false) + student_answer = table.Column(type: "longtext", nullable: true) .Annotation("MySql:CharSet", "utf8mb4"), is_correct = table.Column(type: "tinyint(1)", nullable: true), points_awarded = table.Column(type: "float", precision: 5, scale: 2, nullable: true), - teacher_feedback = table.Column(type: "longtext", nullable: false) + teacher_feedback = table.Column(type: "longtext", nullable: true) .Annotation("MySql:CharSet", "utf8mb4"), created_at = table.Column(type: "datetime(6)", nullable: false) .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), @@ -644,9 +644,9 @@ namespace TechHelper.Server.Migrations columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, values: new object[,] { - { new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), null, "Administrator", "ADMINISTRATOR" }, - { new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), null, "Student", "STUDENT" }, - { new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), null, "Teacher", "TEACHER" } + { new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"), null, "Student", "STUDENT" }, + { new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"), null, "Teacher", "TEACHER" }, + { new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"), null, "Administrator", "ADMINISTRATOR" } }); migrationBuilder.CreateIndex( diff --git a/TechHelper.Server/Migrations/20250624103910_init.Designer.cs b/TechHelper.Server/Migrations/20250627101025_upd.Designer.cs similarity index 98% rename from TechHelper.Server/Migrations/20250624103910_init.Designer.cs rename to TechHelper.Server/Migrations/20250627101025_upd.Designer.cs index 658ff55..5cab7e3 100644 --- a/TechHelper.Server/Migrations/20250624103910_init.Designer.cs +++ b/TechHelper.Server/Migrations/20250627101025_upd.Designer.cs @@ -12,8 +12,8 @@ using TechHelper.Context; namespace TechHelper.Server.Migrations { [DbContext(typeof(ApplicationContext))] - [Migration("20250624103910_init")] - partial class init + [Migration("20250627101025_upd")] + partial class upd { /// protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -198,6 +198,11 @@ namespace TechHelper.Server.Migrations .HasColumnType("float") .HasColumnName("score"); + b.Property("Sequence") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("sequence"); + b.Property("StructType") .HasColumnType("tinyint unsigned") .HasColumnName("group_state"); @@ -512,7 +517,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("deleted"); b.Property("OverallFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("overall_feedback"); @@ -579,7 +583,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("points_awarded"); b.Property("StudentAnswer") - .IsRequired() .HasColumnType("longtext") .HasColumnName("student_answer"); @@ -592,7 +595,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("submission_id"); b.Property("TeacherFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("teacher_feedback"); @@ -747,19 +749,19 @@ namespace TechHelper.Server.Migrations b.HasData( new { - Id = new Guid("ab2c8f8c-1ade-4ff5-9eb4-2925a89567b1"), + Id = new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"), Name = "Student", NormalizedName = "STUDENT" }, new { - Id = new Guid("c6f92bbf-190f-47a5-b4d6-ed6874a378e8"), + Id = new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"), Name = "Teacher", NormalizedName = "TEACHER" }, new { - Id = new Guid("8b814a50-fd96-4bfa-a666-b247c0f13e22"), + Id = new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"), Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); @@ -925,7 +927,8 @@ namespace TechHelper.Server.Migrations { b.HasOne("Entities.Contracts.AssignmentQuestion", "ParentAssignmentQuestion") .WithMany("ChildrenAssignmentQuestion") - .HasForeignKey("ParentAssignmentQuestionId"); + .HasForeignKey("ParentAssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade); b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") .WithMany("Questions") @@ -985,7 +988,7 @@ namespace TechHelper.Server.Migrations b.HasOne("Entities.Contracts.User", "Teacher") .WithMany("TaughtClassesLink") .HasForeignKey("TeacherId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Class"); @@ -1067,7 +1070,7 @@ namespace TechHelper.Server.Migrations b.HasOne("Entities.Contracts.User", "Student") .WithMany("SubmissionsAsStudent") .HasForeignKey("StudentId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Assignment"); @@ -1088,7 +1091,7 @@ namespace TechHelper.Server.Migrations b.HasOne("Entities.Contracts.User", "Student") .WithMany("SubmissionDetails") .HasForeignKey("StudentId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Entities.Contracts.Submission", "Submission") diff --git a/TechHelper.Server/Migrations/20250627101025_upd.cs b/TechHelper.Server/Migrations/20250627101025_upd.cs new file mode 100644 index 0000000..849e9ec --- /dev/null +++ b/TechHelper.Server/Migrations/20250627101025_upd.cs @@ -0,0 +1,153 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class upd : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_assignment_questions_assignment_questions_parent_question_gr~", + table: "assignment_questions"); + + migrationBuilder.DropForeignKey( + name: "FK_submission_details_AspNetUsers_student_id", + table: "submission_details"); + + migrationBuilder.DropForeignKey( + name: "FK_submissions_AspNetUsers_student_id", + table: "submissions"); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("3cfe35e8-73d5-4170-9856-f1d078554822")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("8546457c-185c-4b79-bece-bc21e41d02e7")); + + migrationBuilder.AddColumn( + name: "sequence", + table: "assignment_questions", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"), null, "Teacher", "TEACHER" }, + { new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"), null, "Student", "STUDENT" } + }); + + migrationBuilder.AddForeignKey( + name: "FK_assignment_questions_assignment_questions_parent_question_gr~", + table: "assignment_questions", + column: "parent_question_group_id", + principalTable: "assignment_questions", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_submission_details_AspNetUsers_student_id", + table: "submission_details", + column: "student_id", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_submissions_AspNetUsers_student_id", + table: "submissions", + column: "student_id", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_assignment_questions_assignment_questions_parent_question_gr~", + table: "assignment_questions"); + + migrationBuilder.DropForeignKey( + name: "FK_submission_details_AspNetUsers_student_id", + table: "submission_details"); + + migrationBuilder.DropForeignKey( + name: "FK_submissions_AspNetUsers_student_id", + table: "submissions"); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9")); + + migrationBuilder.DropColumn( + name: "sequence", + table: "assignment_questions"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("3cfe35e8-73d5-4170-9856-f1d078554822"), null, "Student", "STUDENT" }, + { new Guid("754c4967-6af2-4a81-b970-1e90a3a269b3"), null, "Teacher", "TEACHER" }, + { new Guid("8546457c-185c-4b79-bece-bc21e41d02e7"), null, "Administrator", "ADMINISTRATOR" } + }); + + migrationBuilder.AddForeignKey( + name: "FK_assignment_questions_assignment_questions_parent_question_gr~", + table: "assignment_questions", + column: "parent_question_group_id", + principalTable: "assignment_questions", + principalColumn: "id"); + + migrationBuilder.AddForeignKey( + name: "FK_submission_details_AspNetUsers_student_id", + table: "submission_details", + column: "student_id", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_submissions_AspNetUsers_student_id", + table: "submissions", + column: "student_id", + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/TechHelper.Server/Migrations/20250627101514_upde.Designer.cs b/TechHelper.Server/Migrations/20250627101514_upde.Designer.cs new file mode 100644 index 0000000..b6b8e47 --- /dev/null +++ b/TechHelper.Server/Migrations/20250627101514_upde.Designer.cs @@ -0,0 +1,1241 @@ +// +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("20250627101514_upde")] + partial class upde + { + /// + 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("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("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("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("c0f247ab-b12a-432e-8ce7-d0e28811957e"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("f282e759-deb5-4366-aaf1-51366131cf75"), + 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("Assignment") + .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.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("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("Assignment"); + + 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/20250627101514_upde.cs b/TechHelper.Server/Migrations/20250627101514_upde.cs new file mode 100644 index 0000000..14a72e6 --- /dev/null +++ b/TechHelper.Server/Migrations/20250627101514_upde.cs @@ -0,0 +1,71 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class upde : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9")); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"), null, "Student", "STUDENT" }, + { new Guid("f282e759-deb5-4366-aaf1-51366131cf75"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"), null, "Teacher", "TEACHER" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f282e759-deb5-4366-aaf1-51366131cf75")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a")); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("5f0c1b3c-ad05-4ca9-b9fd-a359cb518236"), null, "Teacher", "TEACHER" }, + { new Guid("a81f5de2-9691-45fa-8d31-ae4ffeb34453"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("c310acf7-9605-4c55-8b9f-9bf9cd2dadb9"), null, "Student", "STUDENT" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/20250627105626_updedd.Designer.cs b/TechHelper.Server/Migrations/20250627105626_updedd.Designer.cs new file mode 100644 index 0000000..e1bfbb3 --- /dev/null +++ b/TechHelper.Server/Migrations/20250627105626_updedd.Designer.cs @@ -0,0 +1,1250 @@ +// +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("20250627105626_updedd")] + partial class updedd + { + /// + 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("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("e3bff43c-36af-497a-971c-ed0a487bdd38"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"), + 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/20250627105626_updedd.cs b/TechHelper.Server/Migrations/20250627105626_updedd.cs new file mode 100644 index 0000000..b85fc51 --- /dev/null +++ b/TechHelper.Server/Migrations/20250627105626_updedd.cs @@ -0,0 +1,102 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class updedd : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f282e759-deb5-4366-aaf1-51366131cf75")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a")); + + migrationBuilder.AddColumn( + name: "AssignmentId", + table: "assignment_questions", + type: "char(36)", + nullable: true, + collation: "ascii_general_ci"); + + 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" } + }); + + migrationBuilder.CreateIndex( + name: "IX_assignment_questions_AssignmentId", + table: "assignment_questions", + column: "AssignmentId"); + + migrationBuilder.AddForeignKey( + name: "FK_assignment_questions_assignments_AssignmentId", + table: "assignment_questions", + column: "AssignmentId", + principalTable: "assignments", + principalColumn: "id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_assignment_questions_assignments_AssignmentId", + table: "assignment_questions"); + + migrationBuilder.DropIndex( + name: "IX_assignment_questions_AssignmentId", + table: "assignment_questions"); + + 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.DropColumn( + name: "AssignmentId", + table: "assignment_questions"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("c0f247ab-b12a-432e-8ce7-d0e28811957e"), null, "Student", "STUDENT" }, + { new Guid("f282e759-deb5-4366-aaf1-51366131cf75"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("f9eeea07-eeda-4bbe-a2e4-6aef2f3c7c9a"), null, "Teacher", "TEACHER" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs index 22b79a9..140ae48 100644 --- a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs +++ b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs @@ -165,6 +165,9 @@ namespace TechHelper.Server.Migrations .HasColumnType("char(36)") .HasColumnName("id"); + b.Property("AssignmentId") + .HasColumnType("char(36)"); + b.Property("CreatedAt") .HasColumnType("datetime(6)") .HasColumnName("created_at"); @@ -195,6 +198,11 @@ namespace TechHelper.Server.Migrations .HasColumnType("float") .HasColumnName("score"); + b.Property("Sequence") + .IsRequired() + .HasColumnType("longtext") + .HasColumnName("sequence"); + b.Property("StructType") .HasColumnType("tinyint unsigned") .HasColumnName("group_state"); @@ -206,6 +214,8 @@ namespace TechHelper.Server.Migrations b.HasKey("Id"); + b.HasIndex("AssignmentId"); + b.HasIndex("ParentAssignmentQuestionId"); b.HasIndex("QuestionContextId"); @@ -509,7 +519,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("deleted"); b.Property("OverallFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("overall_feedback"); @@ -576,7 +585,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("points_awarded"); b.Property("StudentAnswer") - .IsRequired() .HasColumnType("longtext") .HasColumnName("student_answer"); @@ -589,7 +597,6 @@ namespace TechHelper.Server.Migrations .HasColumnName("submission_id"); b.Property("TeacherFeedback") - .IsRequired() .HasColumnType("longtext") .HasColumnName("teacher_feedback"); @@ -744,19 +751,19 @@ namespace TechHelper.Server.Migrations b.HasData( new { - Id = new Guid("a203eb76-97f0-418f-bc06-9549297d2ac3"), + Id = new Guid("e3bff43c-36af-497a-971c-ed0a487bdd38"), Name = "Student", NormalizedName = "STUDENT" }, new { - Id = new Guid("195b19c5-fd30-455c-9f38-9842b44bf5c3"), + Id = new Guid("f05c125e-e70f-40eb-9e19-6e69c3426849"), Name = "Teacher", NormalizedName = "TEACHER" }, new { - Id = new Guid("53cc63db-74bc-47a8-b71a-7e120d4018a9"), + Id = new Guid("cf16c215-63f8-4962-8ad0-058274ecf944"), Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); @@ -874,7 +881,7 @@ namespace TechHelper.Server.Migrations .IsRequired(); b.HasOne("Entities.Contracts.AssignmentQuestion", "ExamStruct") - .WithOne("Assignment") + .WithOne() .HasForeignKey("Entities.Contracts.Assignment", "ExamStructId") .OnDelete(DeleteBehavior.Cascade) .IsRequired(); @@ -920,9 +927,14 @@ namespace TechHelper.Server.Migrations 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"); + .HasForeignKey("ParentAssignmentQuestionId") + .OnDelete(DeleteBehavior.Cascade); b.HasOne("Entities.Contracts.QuestionContext", "QuestionContext") .WithMany("Questions") @@ -934,6 +946,8 @@ namespace TechHelper.Server.Migrations .HasForeignKey("QuestionId") .OnDelete(DeleteBehavior.Cascade); + b.Navigation("Assignment"); + b.Navigation("ParentAssignmentQuestion"); b.Navigation("Question"); @@ -1064,7 +1078,7 @@ namespace TechHelper.Server.Migrations b.HasOne("Entities.Contracts.User", "Student") .WithMany("SubmissionsAsStudent") .HasForeignKey("StudentId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.Navigation("Assignment"); @@ -1085,7 +1099,7 @@ namespace TechHelper.Server.Migrations b.HasOne("Entities.Contracts.User", "Student") .WithMany("SubmissionDetails") .HasForeignKey("StudentId") - .OnDelete(DeleteBehavior.Restrict) + .OnDelete(DeleteBehavior.Cascade) .IsRequired(); b.HasOne("Entities.Contracts.Submission", "Submission") @@ -1163,8 +1177,6 @@ namespace TechHelper.Server.Migrations modelBuilder.Entity("Entities.Contracts.AssignmentQuestion", b => { - b.Navigation("Assignment"); - b.Navigation("ChildrenAssignmentQuestion"); b.Navigation("SubmissionDetails"); diff --git a/TechHelper.Server/Program.cs b/TechHelper.Server/Program.cs index b181450..653fc68 100644 --- a/TechHelper.Server/Program.cs +++ b/TechHelper.Server/Program.cs @@ -12,6 +12,7 @@ using TechHelper.Features; using TechHelper.Services; using TechHelper.Server.Services; using TechHelper.Server.Repositories; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); @@ -90,7 +91,35 @@ builder.Services.AddScoped(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(c => +{ + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Your API Name", Version = "v1" }); + + c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + Name = "Authorization", + Type = SecuritySchemeType.Http, + Scheme = "bearer", + BearerFormat = "JWT", + In = ParameterLocation.Header, + Description = "JWT Authorization header using the Bearer scheme. \r\n\r\n Enter 'Bearer' [space] and then your token in the text input below.\r\n\r\nExample: \"Bearer 12345abcdef\"", + }); + + c.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type = ReferenceType.SecurityScheme, + Id = "Bearer" + } + }, + new string[] {} + } + }); +}); builder.Services.AddCors(options => diff --git a/TechHelper.Server/Repositories/ExamRepository.cs b/TechHelper.Server/Repositories/ExamRepository.cs index eae6f33..5753726 100644 --- a/TechHelper.Server/Repositories/ExamRepository.cs +++ b/TechHelper.Server/Repositories/ExamRepository.cs @@ -17,7 +17,7 @@ namespace TechHelper.Server.Repositories { _unitOfWork = unitOfWork; _assignmentRepo = _unitOfWork.GetRepository(); - _assignQuestionRepo = _unitOfWork.GetRepository(); + _assignQuestionRepo = _unitOfWork.GetRepository(); } public async Task GetFullExamByIdAsync(Guid assignmentId) @@ -29,18 +29,18 @@ namespace TechHelper.Server.Repositories i => i.Include(a => a.ExamStruct) ); - result.ExamStruct = await GetNeed(result.ExamStructId)?? null; + result.ExamStruct = await GetNeed(result.ExamStructId) ?? null; return result; } - public async Task GetNeed(Guid id) + public async Task GetNeed(Guid id) { var result = await _assignQuestionRepo.GetFirstOrDefaultAsync( predicate: aq => aq.Id == id, include: i => i - .Include(aq => aq.ChildrenAssignmentQuestion) + .Include(aq => aq.ChildrenAssignmentQuestion) .Include(aq => aq.Question) .ThenInclude(q => q.Lesson) .Include(aq => aq.Question) @@ -55,13 +55,13 @@ namespace TechHelper.Server.Repositories var loadedChildren = new List(); foreach (var child in result.ChildrenAssignmentQuestion) { - var loadedChild = await GetNeed(child.Id); + var loadedChild = await GetNeed(child.Id); if (loadedChild != null) { loadedChildren.Add(loadedChild); } } - result.ChildrenAssignmentQuestion = loadedChildren; + result.ChildrenAssignmentQuestion = loadedChildren; return result; } @@ -90,5 +90,18 @@ namespace TechHelper.Server.Repositories public async Task AddAsync(AssignmentClass assignment) { } + + public async Task AddAsync(Submission submission) + { + await _unitOfWork.GetRepository().InsertAsync(submission); + } + + public async Task> GetAllSubmissionPreviewsByUserAsync(Guid id) + { + 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(); + } } } diff --git a/TechHelper.Server/Repositories/IExamRepository.cs b/TechHelper.Server/Repositories/IExamRepository.cs index 02e266c..a4f8075 100644 --- a/TechHelper.Server/Repositories/IExamRepository.cs +++ b/TechHelper.Server/Repositories/IExamRepository.cs @@ -1,4 +1,5 @@ using Entities.Contracts; +using Entities.DTO; namespace TechHelper.Server.Repositories { @@ -23,6 +24,7 @@ namespace TechHelper.Server.Repositories /// /// 要添加的试卷实体。 Task AddAsync(Assignment assignment); + Task AddAsync(Submission submission); @@ -32,6 +34,6 @@ namespace TechHelper.Server.Repositories Task AddAsync(Question assignment); Task AddAsync(AssignmentClass assignment); - + Task> GetAllSubmissionPreviewsByUserAsync(Guid id); } } diff --git a/TechHelper.Server/Services/ClassService.cs b/TechHelper.Server/Services/ClassService.cs index c209980..bb3b3e0 100644 --- a/TechHelper.Server/Services/ClassService.cs +++ b/TechHelper.Server/Services/ClassService.cs @@ -67,15 +67,13 @@ namespace TechHelper.Services } } - // 实现 IBaseService.GetAllAsync public async Task GetAllAsync(QueryParameter query) { try { var repository = _work.GetRepository(); - // 构建查询条件 (可根据 QueryParameter.Search 进行筛选) - Func, IOrderedQueryable> orderBy = null; // 默认不排序 + Func, IOrderedQueryable> orderBy = null; if (query.Search != null && !string.IsNullOrWhiteSpace(query.Search)) { // 在 Name 字段中进行模糊搜索 @@ -127,9 +125,22 @@ namespace TechHelper.Services } } - public Task GetClassStudents(ClassDto classDto) + public async Task GetClassStudents(ClassDto classDto) { - throw new NotImplementedException(); + try + { + var result = await _work.GetRepository().GetFirstOrDefaultAsync(predicate: + c => c.Grade == classDto.Grade && c.Number == classDto.Class, + include: i => i + .Include(c => c.ClassStudents) + .ThenInclude(cs => cs.Student)); + + return ApiResponse.Success(result: result.ClassStudents); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取学生列表错误, {ex.Message}, {ex.InnerException}"); + } } public async Task GetUserClass(Guid id) @@ -185,9 +196,6 @@ namespace TechHelper.Services var existingClass = await _work.GetRepository().GetFirstOrDefaultAsync( predicate: (c => c.Number == user.ClassId && c.Grade == user.GradeId)); - // finduser and usrinfo are redundant if they are for the same user. - // Let's just use usrinfo - // var finduser = await _userManager.FindByEmailAsync(user.User); if (existingClass == null || usrinfo == null) // Simplified check { diff --git a/TechHelper.Server/Services/ExamService.cs b/TechHelper.Server/Services/ExamService.cs index 803df0a..88af79c 100644 --- a/TechHelper.Server/Services/ExamService.cs +++ b/TechHelper.Server/Services/ExamService.cs @@ -57,7 +57,7 @@ namespace TechHelper.Server.Services } catch (Exception ex) { - return ApiResponse.Error(ex.Message); + return ApiResponse.Error(ex.Message); } } @@ -78,7 +78,7 @@ namespace TechHelper.Server.Services public async Task GetAllExamPreviewsAsync(Guid userId) { var assignments = await _examRepository.GetExamPreviewsByUserAsync(userId); - var result = _mapper.Map>(assignments); + var result = _mapper.Map>(assignments); return ApiResponse.Success(result: result); } @@ -109,11 +109,72 @@ namespace TechHelper.Server.Services throw new NotImplementedException(); } - public Task DeleteAsync(Guid id) + public async Task DeleteAsync(Guid id) + { + try + { + var assignment = await _unitOfWork.GetRepository().GetFirstOrDefaultAsync(predicate: a => a.Id == id); + + if (assignment == null) return ApiResponse.Error("找不到该试卷"); + _unitOfWork.GetRepository().Delete(id); + _unitOfWork.GetRepository().Delete(assignment.ExamStructId); + + + if (await _unitOfWork.SaveChangesAsync() > 0) + { + return ApiResponse.Success(); + } + return ApiResponse.Error("删除失败"); + } + catch (Exception ex) + { + return ApiResponse.Error("内部问题"); + } + } + + public async Task SubmissionAssignment(SubmissionDto submissionDto) + { + try + { + var submission = _mapper.Map(submissionDto); + + await _examRepository.AddAsync(submission); + + if (await _unitOfWork.SaveChangesAsync() > 0) + { + return ApiResponse.Success("保存成功"); + } + return ApiResponse.Error("保存失败"); + } + catch (Exception ex) + { + return ApiResponse.Error($"出现了错误,{ex.Message} innerEx:{ex.InnerException}"); + } + } + + public Task AssignmentToAllStudentsAsync(Guid id) { throw new NotImplementedException(); } + public Task AssignmentToStudentsAsync(Guid assignementId, Guid studentId) + { + throw new NotImplementedException(); + } + + public async Task GetAllSubmissionAsync(Guid id) + { + try + { + var result = await _examRepository.GetAllSubmissionPreviewsByUserAsync(id); + var allExam = _mapper.Map>(result); + return ApiResponse.Success(result: allExam); + } + catch (Exception ex) + { + return ApiResponse.Error($"Submission 内部错误, {ex.Message}"); + } + } } } diff --git a/TechHelper.Server/Services/IBaseService.cs b/TechHelper.Server/Services/IBaseService.cs index 8be29fe..75c69b1 100644 --- a/TechHelper.Server/Services/IBaseService.cs +++ b/TechHelper.Server/Services/IBaseService.cs @@ -2,10 +2,10 @@ { public interface IBaseService { - Task GetAllAsync(QueryParameter query); - Task GetAsync(TId id); - Task AddAsync(T model); - Task UpdateAsync(T model); - Task DeleteAsync(TId id); + Task GetAllAsync(QueryParameter query); + Task GetAsync(TId id); + Task AddAsync(T model); + Task UpdateAsync(T model); + Task DeleteAsync(TId id); } } diff --git a/TechHelper.Server/Services/IExamService.cs b/TechHelper.Server/Services/IExamService.cs index ec4ba55..ab7bcb7 100644 --- a/TechHelper.Server/Services/IExamService.cs +++ b/TechHelper.Server/Services/IExamService.cs @@ -22,5 +22,17 @@ namespace TechHelper.Server.Services /// 创建成功的试卷ID Task CreateExamAsync(AssignmentDto examDto); + + + Task SubmissionAssignment(SubmissionDto submissionDto); + + + Task AssignmentToAllStudentsAsync(Guid id); + + Task AssignmentToStudentsAsync(Guid assignementId, Guid studentId); + + + Task GetAllSubmissionAsync(Guid id); + } } diff --git a/TechHelper.Server/Services/ISubmissionServices.cs b/TechHelper.Server/Services/ISubmissionServices.cs new file mode 100644 index 0000000..bf4ff01 --- /dev/null +++ b/TechHelper.Server/Services/ISubmissionServices.cs @@ -0,0 +1,16 @@ +using Entities.Contracts; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public interface ISubmissionServices : IBaseService + { + Task GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId); + Task GetAllErrorQuestionsAsync(Guid userId); + Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId); + Task GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId); + + Task GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId); + Task GetQuestionErrorStudents(Guid assignmentId); + } +} diff --git a/TechHelper.Server/Services/IUserSerivces.cs b/TechHelper.Server/Services/IUserSerivces.cs new file mode 100644 index 0000000..51c2e12 --- /dev/null +++ b/TechHelper.Server/Services/IUserSerivces.cs @@ -0,0 +1,10 @@ +using Entities.Contracts; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public interface IUserSerivces : IBaseService + { + Task GetStudentDetailInfo(Guid userId); + } +} diff --git a/TechHelper.Server/Services/SubmissionServices.cs b/TechHelper.Server/Services/SubmissionServices.cs new file mode 100644 index 0000000..f723aa5 --- /dev/null +++ b/TechHelper.Server/Services/SubmissionServices.cs @@ -0,0 +1,91 @@ +using AutoMapper; +using Entities.Contracts; +using Microsoft.EntityFrameworkCore; +using SharedDATA.Api; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public class SubmissionServices : ISubmissionServices + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + private readonly IRepository _submissionRepository; + private readonly IRepository _submissionDetailRepository; + + public SubmissionServices(IMapper mapper, IUnitOfWork unitOfWork) + { + _mapper = mapper; + _unitOfWork = unitOfWork; + _submissionRepository = _unitOfWork.GetRepository(); + _submissionDetailRepository = _unitOfWork.GetRepository(); + } + + public Task AddAsync(Submission model) + { + throw new NotImplementedException(); + } + + public Task DeleteAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task GetAllAsync(QueryParameter query) + { + throw new NotImplementedException(); + } + + public async Task GetAllErrorQuestionsAsync(Guid userId) + { + try + { + var errorSDs = await _submissionDetailRepository.GetPagedListAsync(predicate: sd => sd.StudentId == userId && sd.IsCorrect == false, + include: i => i + .Include(s => s.AssignmentQuestion) + .ThenInclude(aq => aq.Question)); + var errorQuestion = errorSDs.Items.Select(sd => sd.AssignmentQuestion).ToList(); + return ApiResponse.Success(); + } + catch (Exception ex) + { + return ApiResponse.Error(); + } + } + + public Task GetAllErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) + { + throw new NotImplementedException(); + } + + public Task GetAssignmentAllStudentsError(Guid assignmentId, Guid teacherId) + { + throw new NotImplementedException(); + } + + public Task GetAssignmentErrorQuestionsAsync(Guid assignmentId, Guid userId) + { + throw new NotImplementedException(); + } + + public Task GetAssignmentErrorQuestionTypeDisAsync(Guid assignmentId, Guid userId) + { + throw new NotImplementedException(); + } + + public Task GetAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task GetQuestionErrorStudents(Guid assignmentId) + { + throw new NotImplementedException(); + } + + public Task UpdateAsync(Submission model) + { + throw new NotImplementedException(); + } + } +} diff --git a/TechHelper.Server/Services/UserServices.cs b/TechHelper.Server/Services/UserServices.cs new file mode 100644 index 0000000..2065faa --- /dev/null +++ b/TechHelper.Server/Services/UserServices.cs @@ -0,0 +1,38 @@ +using Entities.Contracts; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public class UserServices : IUserSerivces + { + public Task AddAsync(User model) + { + throw new NotImplementedException(); + } + + public Task DeleteAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task GetAllAsync(QueryParameter query) + { + throw new NotImplementedException(); + } + + public Task GetAsync(Guid id) + { + throw new NotImplementedException(); + } + + public Task GetStudentDetailInfo(Guid userId) + { + throw new NotImplementedException(); + } + + public Task UpdateAsync(User model) + { + throw new NotImplementedException(); + } + } +}