diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f980ab9 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,7 @@ +{ + // 使用 IntelliSense 了解相关属性。 + // 悬停以查看现有属性的描述。 + // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [] +} \ No newline at end of file diff --git a/Entities/Contracts/AssignmentQuestion.cs b/Entities/Contracts/AssignmentQuestion.cs index 180deda..9c4f81e 100644 --- a/Entities/Contracts/AssignmentQuestion.cs +++ b/Entities/Contracts/AssignmentQuestion.cs @@ -48,6 +48,8 @@ namespace Entities.Contracts [Column("score")] public float? Score { get; set; } + public bool BCorrect { get; set; } + [Column("deleted")] public bool IsDeleted { get; set; } diff --git a/Entities/Contracts/Submission.cs b/Entities/Contracts/Submission.cs index aea74bf..2412f02 100644 --- a/Entities/Contracts/Submission.cs +++ b/Entities/Contracts/Submission.cs @@ -34,7 +34,7 @@ namespace Entities.Contracts public DateTime SubmissionTime { get; set; } [Column("overall_grade")] - public float? OverallGrade { get; set; } + public float OverallGrade { get; set; } = 0; [Column("overall_feedback")] public string? OverallFeedback { get; set; } diff --git a/Entities/DTO/StudentDto.cs b/Entities/DTO/StudentDto.cs index 2bbb3bc..51f38b1 100644 --- a/Entities/DTO/StudentDto.cs +++ b/Entities/DTO/StudentDto.cs @@ -13,8 +13,9 @@ namespace Entities.DTO public string? DisplayName { get; set; } public UInt32 ErrorQuestionNum { get; set; } - public Dictionary ErrorQuestionTypes { get; set; } = new Dictionary(); + public Dictionary ErrorQuestionTypes { get; set; } = new Dictionary(); public Dictionary SubjectAreaErrorQuestionDis { get; set; } = new Dictionary(); public Dictionary LessonErrorDis { get; set; } = new Dictionary(); + public float Score { get; set; } } } diff --git a/Entities/DTO/StudentSubmissionDetailDto.cs b/Entities/DTO/StudentSubmissionDetailDto.cs new file mode 100644 index 0000000..69ba247 --- /dev/null +++ b/Entities/DTO/StudentSubmissionDetailDto.cs @@ -0,0 +1,41 @@ +using Entities.Contracts; +using System; +using System.Collections.Generic; + +namespace Entities.DTO +{ + public class StudentSubmissionDetailDto + { + // 基本信息 + public Guid Id { get; set; } + public Guid AssignmentId { get; set; } + public Guid StudentId { get; set; } + public DateTime SubmissionTime { get; set; } + public float OverallGrade { get; set; } + public string OverallFeedback { get; set; } = string.Empty; + public SubmissionStatus Status { get; set; } + + // Assignment信息 + public AssignmentDto Assignment { get; set; } = new AssignmentDto(); + + // 错误分析 + public Dictionary ErrorTypeDistribution { get; set; } = new Dictionary(); + public Dictionary ErrorTypeScoreDistribution { get; set; } = new Dictionary(); + + // 成绩统计 + public int TotalRank { get; set; } + public List AllScores { get; set; } = new List(); + public float AverageScore { get; set; } + public float ClassAverageScore { get; set; } + + // 课文分布 + public Dictionary LessonErrorDistribution { get; set; } = new Dictionary(); + public Dictionary KeyPointErrorDistribution { get; set; } = new Dictionary(); + + // 基础统计 + public int TotalQuestions { get; set; } + public int CorrectCount { get; set; } + public int ErrorCount { get; set; } + public float AccuracyRate { get; set; } + } +} diff --git a/Entities/DTO/StudentSubmissionSummaryDto.cs b/Entities/DTO/StudentSubmissionSummaryDto.cs new file mode 100644 index 0000000..e100a15 --- /dev/null +++ b/Entities/DTO/StudentSubmissionSummaryDto.cs @@ -0,0 +1,22 @@ +using System; + +namespace Entities.DTO +{ + public class StudentSubmissionSummaryDto + { + public Guid Id { get; set; } + public string AssignmentName { get; set; } + public int ErrorCount { get; set; } + public DateTime CreatedDate { get; set; } + public float Score { get; set; } + public int TotalQuestions { get; set; } + public string StudentName { get; set; } + public string Status { get; set; } + } + + public class StudentSubmissionSummaryResponseDto + { + public List Submissions { get; set; } + public int TotalCount { get; set; } + } +} diff --git a/TechHelper.Client/Layout/MainLayout.razor b/TechHelper.Client/Layout/MainLayout.razor index b48d420..499fb53 100644 --- a/TechHelper.Client/Layout/MainLayout.razor +++ b/TechHelper.Client/Layout/MainLayout.razor @@ -24,13 +24,9 @@ - Dashboard + Home Exam - Billing - - Users - Security - + Students About @@ -38,7 +34,7 @@ - Setting + Setting @@ -46,7 +42,7 @@ - + @Body diff --git a/TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor b/TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor index 763cacd..8112a6b 100644 --- a/TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor +++ b/TechHelper.Client/Pages/Common/Exam/AssignmentInfoCard.razor @@ -2,7 +2,7 @@ - + BETA版本 75 @@ -44,7 +44,7 @@ - + @@ -72,12 +72,6 @@ -@* 成绩趋势 - 分值区间 - Success - Warning - Error - Dark *@ @code { public double[] data = { 25, 77, 28, 5 }; diff --git a/TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor b/TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor new file mode 100644 index 0000000..c0917fe --- /dev/null +++ b/TechHelper.Client/Pages/Common/Exam/SubmissionInfoCard.razor @@ -0,0 +1,191 @@ +@using Entities.DTO +@using TechHelper.Client.Services + + + + + + + BETA版本 + @StudentSubmissionDetail.AverageScore + + + + + + + 总数: + @StudentSubmissionDetail.TotalQuestions + + + + + 总分: + 150 + + + + + + + + 排名: + @StudentSubmissionDetail.TotalRank + + + + + 平均: + @StudentSubmissionDetail.ClassAverageScore + + + + + + + + + + + + + + + + 成绩的整体分布情况 + + + + + + + + 类型分布 + 课时分布 + + + + + +@* 成绩趋势 + 分值区间 + Success + Warning + Error + Dark *@ + +@code { + private AxisChartOptions _axisChartOptions = new AxisChartOptions + { + }; + private ChartOptions options = new ChartOptions + { + InterpolationOption = InterpolationOption.NaturalSpline, + YAxisFormat = "c2", + ShowLegend = false, + YAxisLines = false, + XAxisLines = false, + XAxisLabelPosition = XAxisLabelPosition.None, + YAxisLabelPosition = YAxisLabelPosition.None, + YAxisTicks = 100, + ShowLabels = false, + ShowLegendLabels = false + + }; + public List Series = new List() + { + new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } }, + }; + public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" }; + + Random random = new Random(); + protected override void OnInitialized() + { + options.InterpolationOption = InterpolationOption.NaturalSpline; + options.YAxisFormat = "c2"; + options.ShowLegend = false; + options.YAxisLines = false; + options.XAxisLines = false; + options.XAxisLabelPosition = XAxisLabelPosition.None; + options.YAxisLabelPosition = YAxisLabelPosition.None; + options.ShowLabels = false; + options.ShowLegendLabels = false; + options.LineStrokeWidth = 1; + _axisChartOptions.MatchBoundsToSize = true; + + Series[0].LineDisplayType = LineDisplayType.Area; + + } + + [Parameter] + public Guid SubmissionID { get; set; } = Guid.Empty; + + private StudentSubmissionDetailDto StudentSubmissionDetail { get; set; } = new StudentSubmissionDetailDto(); + private IReadOnlyCollection _selected; +#pragma warning restore 1998 +#nullable restore +#line (82, 8) - (143, 1) "D:\AllWX\AllC\TechHelper\TechHelper.Client\Pages\Common\Exam\SubmissionInfoCard.razor" + + + + [Inject] + public IStudentSubmissionDetailService StudentSubmissionDetailService { get; set; } + [Inject] + public ISnackbar Snackbar { get; set; } + protected override async Task OnInitializedAsync() + { + if (SubmissionID != Guid.Empty) + { + + StudentSubmissionDetailDto result; + try + { + result = await StudentSubmissionDetailService.GetSubmissionDetailAsync(SubmissionID); + + if (result != null) + { + StudentSubmissionDetail = result; + XAxisLabels = result.ErrorTypeDistribution.Keys.ToArray(); + Series.Clear(); + Series.Add(new ChartSeries + { + Name = "类型错误数量分布", + Data = result.ErrorTypeDistribution.Values.Select(d => (double)d).ToArray() + }); + Series.Add(new ChartSeries + { + Name = "类型错误成绩分布", + Data = result.ErrorTypeScoreDistribution.Values.Select(d => (double)d).ToArray() + }); + + } + } + catch (Exception ex) + { + Snackbar.Add($"获取提交错误, 请重试, {ex.Message}", Severity.Warning); + } + } + + } + + private void HandleSelectedValuesChanged(IReadOnlyCollection selected) + { + Series.ForEach(x => x.Visible = false); + + foreach (var item in selected) + { + var sv = Series.FirstOrDefault(predicate: x => x.Name == item); + if (sv != null) + { + sv.Visible = true; + } + } + } +} \ No newline at end of file diff --git a/TechHelper.Client/Pages/Common/Exam/TeacherSubmissionInfoCard.razor b/TechHelper.Client/Pages/Common/Exam/TeacherSubmissionInfoCard.razor new file mode 100644 index 0000000..763cacd --- /dev/null +++ b/TechHelper.Client/Pages/Common/Exam/TeacherSubmissionInfoCard.razor @@ -0,0 +1,143 @@ + + + + + + + BETA版本 + 75 + + + + + + + 总数: + 15 + + + + + 总分: + 15 + + + + + + + + 中位: + 15 + + + + + 方差: + 15 + + + + + + + + + + + + + + + + 成绩的整体分布情况 + + + + + + + + 类型分布 + 课时分布 + + + + + +@* 成绩趋势 + 分值区间 + Success + Warning + Error + Dark *@ + +@code { + public double[] data = { 25, 77, 28, 5 }; + public string[] labels = { "Oil", "Coal", "Gas", "Biomass" }; + private AxisChartOptions _axisChartOptions = new AxisChartOptions + { + }; + private ChartOptions options = new ChartOptions + { + InterpolationOption = InterpolationOption.NaturalSpline, + YAxisFormat = "c2", + ShowLegend = false, + YAxisLines = false, + XAxisLines = false, + XAxisLabelPosition = XAxisLabelPosition.None, + YAxisLabelPosition = YAxisLabelPosition.None, + YAxisTicks = 100, + ShowLabels = false, + ShowLegendLabels = false + + }; + public List Series = new List() + { + new ChartSeries() { Name = "类型错误数量分布", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } }, + new ChartSeries() { Name = "类型错误成绩分布", Data = new double[] { 55, 21, 45, 11, 45, 23, 11, 56, 13 } }, + }; + public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" }; + + Random random = new Random(); + protected override void OnInitialized() + { + options.InterpolationOption = InterpolationOption.NaturalSpline; + options.YAxisFormat = "c2"; + options.ShowLegend = false; + options.YAxisLines = false; + options.XAxisLines = false; + options.XAxisLabelPosition = XAxisLabelPosition.None; + options.YAxisLabelPosition = YAxisLabelPosition.None; + options.ShowLabels = false; + options.ShowLegendLabels = false; + options.LineStrokeWidth = 1; + _axisChartOptions.MatchBoundsToSize = true; + + Series[0].LineDisplayType = LineDisplayType.Area; + + } + + private IReadOnlyCollection _selected; + + private void HandleSelectedValuesChanged(IReadOnlyCollection selected) + { + Series.ForEach(x => x.Visible = false); + + foreach(var item in selected) + { + var sv = Series.FirstOrDefault(predicate: x => x.Name == item); + if(sv != null) + { + sv.Visible = true; + } + } + } +} \ No newline at end of file diff --git a/TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor b/TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor new file mode 100644 index 0000000..820fec5 --- /dev/null +++ b/TechHelper.Client/Pages/Exam/AssignmentManagerCard.razor @@ -0,0 +1,7 @@ + + ExamName + 已经指派人数 + 总人数 + 平均S + 指派 + \ No newline at end of file diff --git a/TechHelper.Client/Pages/Exam/ExamManager.razor b/TechHelper.Client/Pages/Exam/ExamManager.razor index 8bda813..539c1e2 100644 --- a/TechHelper.Client/Pages/Exam/ExamManager.razor +++ b/TechHelper.Client/Pages/Exam/ExamManager.razor @@ -21,8 +21,6 @@ else @foreach (var item in examDtos) { - @* *@ - } diff --git a/TechHelper.Client/Pages/Exam/Index.razor b/TechHelper.Client/Pages/Exam/Index.razor index 8f1f83f..8e5b542 100644 --- a/TechHelper.Client/Pages/Exam/Index.razor +++ b/TechHelper.Client/Pages/Exam/Index.razor @@ -1,7 +1,34 @@ @page "/exam" +@using TechHelper.Client.Pages.Student.BaseInfoCard +@inject NavigationManager NavigationManager + + + + + + + + -HELLO WORLD + + + + + + + @code { + + [CascadingParameter] + private Task authenticationStateTask { get; set; } + + protected override void OnParametersSet() + { + if (authenticationStateTask is null) + { + NavigationManager.Refresh(forceReload: true); + } + } } diff --git a/TechHelper.Client/Pages/Exam/StudentExamView.razor b/TechHelper.Client/Pages/Exam/StudentExamView.razor new file mode 100644 index 0000000..341daae --- /dev/null +++ b/TechHelper.Client/Pages/Exam/StudentExamView.razor @@ -0,0 +1,4 @@ + +@code { + +} diff --git a/TechHelper.Client/Pages/Home.razor b/TechHelper.Client/Pages/Home.razor index 4d6753f..94a006a 100644 --- a/TechHelper.Client/Pages/Home.razor +++ b/TechHelper.Client/Pages/Home.razor @@ -7,7 +7,6 @@ - @code { [CascadingParameter] private Task authenticationStateTask { get; set; } diff --git a/TechHelper.Client/Pages/Student/BaseInfoCard/StudentSubmissionPreviewTableCard.razor b/TechHelper.Client/Pages/Student/BaseInfoCard/StudentSubmissionPreviewTableCard.razor index 62f08da..68cec7a 100644 --- a/TechHelper.Client/Pages/Student/BaseInfoCard/StudentSubmissionPreviewTableCard.razor +++ b/TechHelper.Client/Pages/Student/BaseInfoCard/StudentSubmissionPreviewTableCard.razor @@ -1,39 +1,97 @@ - +@using TechHelper.Client.Services +@inject IStudentSubmissionService StudentSubmissionService + + - @foreach (var submission in _studentSubmissions) + @if (_isLoading) { - +
+ +
+ } + else if (_studentSubmissions == null || _studentSubmissions.Count == 0) + { +
+ 暂无提交记录 +
+ } + else + { + @foreach (var submission in _studentSubmissions) + { + + } } - -
@code { - // 假设的学生提交数据模型 + // 学生提交数据模型 public class StudentSubmission { public string StudentName { get; set; } public int TotalProblems { get; set; } public int ErrorCount { get; set; } + public DateTime CreatedDate { get; set; } + public float Score { get; set; } + public string AssignmentName { get; set; } + public string Status { get; set; } public TimeSpan TimeSpent { get; set; } - public int Score { get; set; } } - // 模拟数据列表 + // 学生提交列表 private List _studentSubmissions = new(); + private bool _isLoading = true; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { - // 模拟获取或初始化数据,实际应用中可能来自数据库或API - _studentSubmissions = new List - { - new() { StudentName = "张三", TotalProblems = 10, ErrorCount = 2, TimeSpent = TimeSpan.FromMinutes(25), Score = 80 }, - new() { StudentName = "李四", TotalProblems = 10, ErrorCount = 1, TimeSpent = TimeSpan.FromMinutes(20), Score = 90 }, - new() { StudentName = "王五", TotalProblems = 10, ErrorCount = 5, TimeSpent = TimeSpan.FromMinutes(30), Score = 50 }, - new() { StudentName = "赵六", TotalProblems = 10, ErrorCount = 3, TimeSpent = TimeSpan.FromMinutes(28), Score = 70 }, - new() { StudentName = "钱七", TotalProblems = 10, ErrorCount = 0, TimeSpent = TimeSpan.FromMinutes(18), Score = 100 } - // ... 可以添加更多模拟数据 - }; + await LoadStudentSubmissions(); } -} \ No newline at end of file + + private async Task LoadStudentSubmissions() + { + try + { + _isLoading = true; + StateHasChanged(); + + var result = await StudentSubmissionService.GetMySubmissionsAsync(); + + if (result.Status && result.Result != null) + { + // 从服务器获取的数据映射到我们的模型 + var submissions = result.Result as List; + + if (submissions != null) + { + _studentSubmissions = submissions.Select(submission => new StudentSubmission + { + AssignmentName = submission.AssignmentName, + CreatedDate = submission.CreatedDate, + ErrorCount = submission.ErrorCount, + Score = submission.Score, + StudentName = submission.StudentName, + Status = submission.Status, + TotalProblems = submission.TotalQuestions, + TimeSpent = TimeSpan.FromMinutes(30) // 默认值,实际应用中可以从服务器获取 + }).ToList(); + } + } + else + { + // 如果API调用失败,使用空列表 + _studentSubmissions = new List(); + } + } + catch (Exception ex) + { + // 处理异常,可以记录日志 + _studentSubmissions = new List(); + } + finally + { + _isLoading = false; + StateHasChanged(); + } + } +} diff --git a/TechHelper.Client/Pages/Student/HomePage.razor b/TechHelper.Client/Pages/Student/HomePage.razor index d7df942..8899a50 100644 --- a/TechHelper.Client/Pages/Student/HomePage.razor +++ b/TechHelper.Client/Pages/Student/HomePage.razor @@ -2,10 +2,10 @@ @using TechHelper.Client.Pages.Student.BaseInfoCard; @using TechHelper.Client.Pages.Common; - - - - + + + + @@ -13,7 +13,7 @@ - + @@ -22,42 +22,4 @@ @code { - public double[] data = { 25, 77, 28, 5 }; - public string[] labels = { "Oil", "Coal", "Gas", "Biomass" }; - private AxisChartOptions _axisChartOptions = new AxisChartOptions(); - private ChartOptions options = new ChartOptions(); - public List Series = new List() - { - new ChartSeries() { Name = "Series 1", Data = new double[] { 90, 79, 72, 69, 62, 62, 55, 65, 70 } }, - new ChartSeries() { Name = "Series 2", Data = new double[] { 35, 41, 35, 51, 49, 62, 69, 91, 148 } }, - }; - public string[] XAxisLabels = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep" }; - - Random random = new Random(); - protected override void OnInitialized() - { - options.InterpolationOption = InterpolationOption.NaturalSpline; - options.YAxisFormat = "c2"; - _axisChartOptions.MatchBoundsToSize = true; - } - - public void RandomizeData() - { - foreach (var series in Series) - { - for (int i = 0; i < series.Data.Length - 1; i++) - { - series.Data[i] = random.NextDouble() * 100 + 10; - } - } - - StateHasChanged(); - } - - void OnClickMenu(InterpolationOption interpolationOption) - { - options.InterpolationOption = interpolationOption; - StateHasChanged(); - } - } \ No newline at end of file diff --git a/TechHelper.Client/Pages/Teacher/ExamDetailView.razor b/TechHelper.Client/Pages/Teacher/ExamDetailView.razor new file mode 100644 index 0000000..93d1796 --- /dev/null +++ b/TechHelper.Client/Pages/Teacher/ExamDetailView.razor @@ -0,0 +1,3 @@ + + EXAM NAME + \ No newline at end of file diff --git a/TechHelper.Client/Pages/Teacher/StudentCard.razor b/TechHelper.Client/Pages/Teacher/StudentCard.razor new file mode 100644 index 0000000..fed0cd1 --- /dev/null +++ b/TechHelper.Client/Pages/Teacher/StudentCard.razor @@ -0,0 +1,7 @@ +@using Entities.DTO +

StudentCard

+ +@code { + [Parameter] + public StudentDto StudentDto{ get; set; } +} diff --git a/TechHelper.Client/Pages/Teacher/StudentsView.razor b/TechHelper.Client/Pages/Teacher/StudentsView.razor index 5c89b17..e16326f 100644 --- a/TechHelper.Client/Pages/Teacher/StudentsView.razor +++ b/TechHelper.Client/Pages/Teacher/StudentsView.razor @@ -6,7 +6,9 @@ @foreach(var cs in ClassStudents) { - @cs.DisplayName + + + } diff --git a/TechHelper.Client/Program.cs b/TechHelper.Client/Program.cs index 0a421e4..9154779 100644 --- a/TechHelper.Client/Program.cs +++ b/TechHelper.Client/Program.cs @@ -39,6 +39,8 @@ builder.Services.AddLocalStorageServices(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/TechHelper.Client/Services/ClasssServices.cs b/TechHelper.Client/Services/ClasssServices.cs index 4801eb0..c8043c3 100644 --- a/TechHelper.Client/Services/ClasssServices.cs +++ b/TechHelper.Client/Services/ClasssServices.cs @@ -54,6 +54,11 @@ namespace TechHelper.Client.Services } } + public StudentDto GetStudents(byte Class) + { + throw new NotImplementedException(); + } + public async Task UserRegister(UserRegistrationToClassDto userRegistrationToClassDto) { try diff --git a/TechHelper.Client/Services/IClassServices.cs b/TechHelper.Client/Services/IClassServices.cs index 2fda8f8..9733f95 100644 --- a/TechHelper.Client/Services/IClassServices.cs +++ b/TechHelper.Client/Services/IClassServices.cs @@ -10,5 +10,6 @@ namespace TechHelper.Client.Services public Task CreateClass(UserRegistrationToClassDto userClass); public Task GetClassStudents(); public Task GetGradeClasses(byte grade); + public StudentDto GetStudents(byte Class); } } diff --git a/TechHelper.Client/Services/IStudentSubmissionDetailService.cs b/TechHelper.Client/Services/IStudentSubmissionDetailService.cs new file mode 100644 index 0000000..3d87b7b --- /dev/null +++ b/TechHelper.Client/Services/IStudentSubmissionDetailService.cs @@ -0,0 +1,14 @@ +using Entities.DTO; + +namespace TechHelper.Client.Services +{ + public interface IStudentSubmissionDetailService + { + /// + /// 获取学生提交的详细信息 + /// + /// 提交ID + /// 学生提交详细信息 + Task GetSubmissionDetailAsync(Guid submissionId); + } +} diff --git a/TechHelper.Client/Services/IStudentSubmissionService.cs b/TechHelper.Client/Services/IStudentSubmissionService.cs new file mode 100644 index 0000000..ca15a6c --- /dev/null +++ b/TechHelper.Client/Services/IStudentSubmissionService.cs @@ -0,0 +1,22 @@ +using Entities.DTO; +using TechHelper.Services; + +namespace TechHelper.Client.Services +{ + public interface IStudentSubmissionService + { + /// + /// 获取当前学生的所有提交摘要 + /// + /// 学生提交摘要列表 + Task GetMySubmissionsAsync(); + + /// + /// 获取当前学生的提交摘要(分页) + /// + /// 页码,默认为1 + /// 每页数量,默认为10 + /// 分页的学生提交摘要列表 + Task GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10); + } +} diff --git a/TechHelper.Client/Services/StudentSubmissionDetailService.cs b/TechHelper.Client/Services/StudentSubmissionDetailService.cs new file mode 100644 index 0000000..6282dce --- /dev/null +++ b/TechHelper.Client/Services/StudentSubmissionDetailService.cs @@ -0,0 +1,26 @@ +using Entities.DTO; +using TechHelper.Client.HttpRepository; +using System.Net.Http.Json; + +namespace TechHelper.Client.Services +{ + public class StudentSubmissionDetailService : IStudentSubmissionDetailService + { + private readonly HttpClient _httpClient; + + public StudentSubmissionDetailService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task GetSubmissionDetailAsync(Guid submissionId) + { + var response = await _httpClient.GetAsync($"api/student-submission-detail/{submissionId}"); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + throw new HttpRequestException($"获取学生提交详细信息失败: {response.StatusCode}"); + } + } +} diff --git a/TechHelper.Client/Services/StudentSubmissionService.cs b/TechHelper.Client/Services/StudentSubmissionService.cs new file mode 100644 index 0000000..620256e --- /dev/null +++ b/TechHelper.Client/Services/StudentSubmissionService.cs @@ -0,0 +1,75 @@ +using Entities.DTO; +using TechHelper.Services; +using System.Net.Http.Json; +using Newtonsoft.Json; + +namespace TechHelper.Client.Services +{ + public class StudentSubmissionService : IStudentSubmissionService + { + private readonly HttpClient _client; + + public StudentSubmissionService(HttpClient client) + { + _client = client; + } + + /// + /// 获取当前学生的所有提交摘要 + /// + /// 学生提交摘要列表 + public async Task GetMySubmissionsAsync() + { + try + { + var response = await _client.GetAsync("student-submission/my-submissions"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var submissions = JsonConvert.DeserializeObject>(content); + return ApiResponse.Success(result: submissions); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}"); + } + } + catch (Exception ex) + { + return ApiResponse.Error(message: $"内部错误: {ex.Message}"); + } + } + + /// + /// 获取当前学生的提交摘要(分页) + /// + /// 页码,默认为1 + /// 每页数量,默认为10 + /// 分页的学生提交摘要列表 + public async Task GetMySubmissionsPagedAsync(int pageNumber = 1, int pageSize = 10) + { + try + { + var response = await _client.GetAsync($"student-submission/my-submissions-paged?pageNumber={pageNumber}&pageSize={pageSize}"); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(content); + return ApiResponse.Success(result: result); + } + else + { + var errorContent = await response.Content.ReadAsStringAsync(); + return ApiResponse.Error(message: $"获取学生提交信息失败: {response.StatusCode} - {errorContent}"); + } + } + catch (Exception ex) + { + return ApiResponse.Error(message: $"内部错误: {ex.Message}"); + } + } + } +} diff --git a/TechHelper.Client/Shared/ExamLayout.razor b/TechHelper.Client/Shared/ExamLayout.razor index d43426e..6accd5a 100644 --- a/TechHelper.Client/Shared/ExamLayout.razor +++ b/TechHelper.Client/Shared/ExamLayout.razor @@ -1,16 +1,44 @@ @inherits LayoutComponentBase @layout AccountLayout +@inject NavigationManager NavigationManager + + + + + + + + + @Body + + + + - + + + + + + + + + @Body + + + + - - - +@code { + [CascadingParameter] + private Task authenticationStateTask { get; set; } - - - - @Body - - + protected override void OnParametersSet() + { + if (authenticationStateTask is null) + { + NavigationManager.Refresh(forceReload: true); + } + } +} diff --git a/TechHelper.Client/Shared/ExamNavMenu.razor b/TechHelper.Client/Shared/ExamNavMenu.razor index 2f42f21..dc9eb82 100644 --- a/TechHelper.Client/Shared/ExamNavMenu.razor +++ b/TechHelper.Client/Shared/ExamNavMenu.razor @@ -1,22 +1,39 @@ @using Microsoft.AspNetCore.Identity - + + + + + + + + + + + + + + @code { - private bool hasExternalLogins; + private bool hasExternalLogins; } diff --git a/TechHelper.Client/Shared/ManageLayout.razor b/TechHelper.Client/Shared/ManageLayout.razor index ccdb53c..c02f8c1 100644 --- a/TechHelper.Client/Shared/ManageLayout.razor +++ b/TechHelper.Client/Shared/ManageLayout.razor @@ -9,3 +9,17 @@ @Body
+ + +@code { + [CascadingParameter] + private Task authenticationStateTask { get; set; } + + protected override void OnParametersSet() + { + if (authenticationStateTask is null) + { + // NavigationManager.Refresh(forceReload: true); + } + } +} diff --git a/TechHelper.Client/Shared/ManageNavMenu.razor b/TechHelper.Client/Shared/ManageNavMenu.razor index 720c299..1640bed 100644 --- a/TechHelper.Client/Shared/ManageNavMenu.razor +++ b/TechHelper.Client/Shared/ManageNavMenu.razor @@ -9,11 +9,8 @@ Class -@* *@ @code { diff --git a/TechHelper.Server/Context/AutoMapperProFile.cs b/TechHelper.Server/Context/AutoMapperProFile.cs index 5a17b0f..20ecde1 100644 --- a/TechHelper.Server/Context/AutoMapperProFile.cs +++ b/TechHelper.Server/Context/AutoMapperProFile.cs @@ -58,6 +58,17 @@ namespace TechHelper.Context CreateMap().ReverseMap(); + // Student Submission Detail + CreateMap() + .ForMember(dest => dest.AssignmentId, opt => opt.MapFrom(src => src.AssignmentId)) + .ForMember(dest => dest.StudentId, opt => opt.MapFrom(src => src.StudentId)) + .ForMember(dest => dest.SubmissionTime, opt => opt.MapFrom(src => src.SubmissionTime)) + .ForMember(dest => dest.OverallGrade, opt => opt.MapFrom(src => src.OverallGrade)) + .ForMember(dest => dest.OverallFeedback, opt => opt.MapFrom(src => src.OverallFeedback)) + .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.Status)); + + CreateMap().ReverseMap(); + CreateMap() .ForMember(dest => dest.Info, opt => opt.MapFrom(src => JsonConvert.SerializeObject(src.Data))); diff --git a/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs b/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs index 800fd1a..ccb3d72 100644 --- a/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs +++ b/TechHelper.Server/Context/Configuration/SubmissionConfiguration.cs @@ -39,6 +39,7 @@ namespace TechHelper.Context.Configuration builder.Property(s => s.OverallGrade) .HasColumnName("overall_grade") + .IsRequired() .HasPrecision(5, 2); // 应用精度设置 // OverallFeedback diff --git a/TechHelper.Server/Controllers/StudentSubmissionController.cs b/TechHelper.Server/Controllers/StudentSubmissionController.cs new file mode 100644 index 0000000..160b350 --- /dev/null +++ b/TechHelper.Server/Controllers/StudentSubmissionController.cs @@ -0,0 +1,127 @@ +using Entities.Contracts; +using Entities.DTO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using TechHelper.Server.Services; +using System.Security.Claims; + +namespace TechHelper.Server.Controllers +{ + [Route("api/student-submission")] + [ApiController] + [Authorize] + public class StudentSubmissionController : ControllerBase + { + private readonly IStudentSubmissionService _studentSubmissionService; + private readonly UserManager _userManager; + + public StudentSubmissionController( + IStudentSubmissionService studentSubmissionService, + UserManager userManager) + { + _studentSubmissionService = studentSubmissionService; + _userManager = userManager; + } + + /// + /// 获取当前学生的所有提交摘要 + /// + /// 学生提交摘要列表 + [HttpGet("my-submissions")] + public async Task GetMySubmissions() + { + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + return NotFound("未找到用户信息"); + + var result = await _studentSubmissionService.GetStudentSubmissionsAsync(user.Id); + + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取当前学生的提交摘要(分页) + /// + /// 页码,默认为1 + /// 每页数量,默认为10 + /// 分页的学生提交摘要列表 + [HttpGet("my-submissions-paged")] + public async Task GetMySubmissionsPaged(int pageNumber = 1, int pageSize = 10) + { + if (pageNumber < 1) pageNumber = 1; + if (pageSize < 1) pageSize = 10; + if (pageSize > 100) pageSize = 100; // 限制最大页面大小 + + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + return NotFound("未找到用户信息"); + + var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(user.Id, pageNumber, pageSize); + + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定学生的提交摘要(仅教师可使用) + /// + /// 学生ID + /// 学生提交摘要列表 + [HttpGet("student/{studentId:guid}")] + [Authorize(Roles = "Teacher")] + public async Task GetStudentSubmissions(Guid studentId) + { + var result = await _studentSubmissionService.GetStudentSubmissionsAsync(studentId); + + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + + /// + /// 获取指定学生的提交摘要(分页,仅教师可使用) + /// + /// 学生ID + /// 页码,默认为1 + /// 每页数量,默认为10 + /// 分页的学生提交摘要列表 + [HttpGet("student/{studentId:guid}/paged")] + [Authorize(Roles = "Teacher")] + public async Task GetStudentSubmissionsPaged(Guid studentId, int pageNumber = 1, int pageSize = 10) + { + if (pageNumber < 1) pageNumber = 1; + if (pageSize < 1) pageSize = 10; + if (pageSize > 100) pageSize = 100; // 限制最大页面大小 + + var result = await _studentSubmissionService.GetStudentSubmissionsPagedAsync(studentId, pageNumber, pageSize); + + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + } +} diff --git a/TechHelper.Server/Controllers/StudentSubmissionDetailController.cs b/TechHelper.Server/Controllers/StudentSubmissionDetailController.cs new file mode 100644 index 0000000..3b0823f --- /dev/null +++ b/TechHelper.Server/Controllers/StudentSubmissionDetailController.cs @@ -0,0 +1,81 @@ +using Entities.Contracts; +using Entities.DTO; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using TechHelper.Server.Services; +using TechHelper.Context; +using TechHelper.Repository; +using SharedDATA.Api; +using System.Security.Claims; + +namespace TechHelper.Server.Controllers +{ + [Route("api/student-submission-detail")] + [ApiController] + [Authorize] + public class StudentSubmissionDetailController : ControllerBase + { + private readonly IStudentSubmissionDetailService _studentSubmissionDetailService; + private readonly UserManager _userManager; + private readonly IUnitOfWork _unitOfWork; + + public StudentSubmissionDetailController( + IStudentSubmissionDetailService studentSubmissionDetailService, + UserManager userManager, + IUnitOfWork unitOfWork) + { + _studentSubmissionDetailService = studentSubmissionDetailService; + _userManager = userManager; + _unitOfWork = unitOfWork; + } + + /// + /// 获取学生提交的详细信息 + /// + /// 提交ID + /// 学生提交详细信息 + [HttpGet("{submissionId:guid}")] + public async Task GetSubmissionDetail(Guid submissionId) + { + try + { + // 验证用户权限 - 只有学生本人或教师可以查看 + var user = await _userManager.FindByEmailAsync(User.Identity.Name); + if (user == null) + { + return NotFound("未找到用户信息"); + } + + var submission = await _unitOfWork.GetRepository() + .GetFirstOrDefaultAsync(predicate: s => s.Id == submissionId); + + if (submission == null) + { + return NotFound("未找到指定的提交记录"); + } + + // 检查权限:学生只能查看自己的提交,教师可以查看所有提交 + if (user.Id != submission.StudentId && !User.IsInRole("Teacher")) + { + return Forbid("您没有权限查看此提交记录"); + } + + var result = await _studentSubmissionDetailService.GetSubmissionDetailAsync(submissionId); + + if (result.Status) + { + return Ok(result.Result); + } + else + { + return BadRequest(result.Message); + } + } + catch (Exception ex) + { + return StatusCode(500, $"获取学生提交详细信息失败: {ex.Message}"); + } + } + } +} diff --git a/TechHelper.Server/Migrations/20250904101811_submission_up_2.Designer.cs b/TechHelper.Server/Migrations/20250904101811_submission_up_2.Designer.cs new file mode 100644 index 0000000..5aa201a --- /dev/null +++ b/TechHelper.Server/Migrations/20250904101811_submission_up_2.Designer.cs @@ -0,0 +1,1299 @@ +// +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("20250904101811_submission_up_2")] + partial class submission_up_2 + { + /// + 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("ExamType") + .HasColumnType("tinyint unsigned"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + 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.Property("Type") + .HasColumnType("tinyint unsigned"); + + 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.Global", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("Area") + .HasColumnType("tinyint unsigned"); + + b.Property("Info") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("global"); + }); + + 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("QType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasMaxLength(100) + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("question_text"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("tinyint unsigned") + .HasColumnName("question_type"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("KeyPointId"); + + b.HasIndex("LessonId"); + + b.HasIndex("Title") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 20 }); + + b.ToTable("questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("QuestionContexts"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("AttemptNumber") + .HasColumnType("tinyint unsigned") + .HasColumnName("attempt_number"); + + b.Property("ErrorQuesNum") + .HasColumnType("tinyint unsigned"); + + 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.Property("TotalQuesNum") + .HasColumnType("tinyint unsigned"); + + b.Property("TotalScore") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("GraderId"); + + b.HasIndex("StudentId"); + + b.ToTable("submissions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("assignment_question_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("IsCorrect") + .HasColumnType("tinyint(1)") + .HasColumnName("is_correct"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("PointsAwarded") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("points_awarded"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentAnswer") + .HasColumnType("longtext") + .HasColumnName("student_answer"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionId") + .HasColumnType("char(36)") + .HasColumnName("submission_id"); + + b.Property("TeacherFeedback") + .HasColumnType("longtext") + .HasColumnName("teacher_feedback"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentQuestionId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubmissionId"); + + b.ToTable("submission_details", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned"); + + b.Property("Publisher") + .HasColumnType("tinyint unsigned"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DisplayName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + 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("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), + 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/20250904101811_submission_up_2.cs b/TechHelper.Server/Migrations/20250904101811_submission_up_2.cs new file mode 100644 index 0000000..8011f05 --- /dev/null +++ b/TechHelper.Server/Migrations/20250904101811_submission_up_2.cs @@ -0,0 +1,97 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class submission_up_2 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("2670f35a-df0c-4071-8879-80eb99d138a1")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db")); + + migrationBuilder.AlterColumn( + name: "overall_grade", + table: "submissions", + type: "float", + precision: 5, + scale: 2, + nullable: false, + defaultValue: 0f, + oldClrType: typeof(float), + oldType: "float", + oldPrecision: 5, + oldScale: 2, + oldNullable: true); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" }, + { new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc")); + + migrationBuilder.AlterColumn( + name: "overall_grade", + table: "submissions", + type: "float", + precision: 5, + scale: 2, + nullable: true, + oldClrType: typeof(float), + oldType: "float", + oldPrecision: 5, + oldScale: 2); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"), null, "Teacher", "TEACHER" }, + { new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"), null, "Student", "STUDENT" }, + { new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"), null, "Administrator", "ADMINISTRATOR" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/20250904102023_submission_up_3.Designer.cs b/TechHelper.Server/Migrations/20250904102023_submission_up_3.Designer.cs new file mode 100644 index 0000000..46c6f6c --- /dev/null +++ b/TechHelper.Server/Migrations/20250904102023_submission_up_3.Designer.cs @@ -0,0 +1,1299 @@ +// +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("20250904102023_submission_up_3")] + partial class submission_up_3 + { + /// + 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("ExamType") + .HasColumnType("tinyint unsigned"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + 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.Property("Type") + .HasColumnType("tinyint unsigned"); + + 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.Global", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("Area") + .HasColumnType("tinyint unsigned"); + + b.Property("Info") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("global"); + }); + + 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("QType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasMaxLength(100) + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("question_text"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("tinyint unsigned") + .HasColumnName("question_type"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("KeyPointId"); + + b.HasIndex("LessonId"); + + b.HasIndex("Title") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 20 }); + + b.ToTable("questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("QuestionContexts"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("AttemptNumber") + .HasColumnType("tinyint unsigned") + .HasColumnName("attempt_number"); + + b.Property("ErrorQuesNum") + .HasColumnType("tinyint unsigned"); + + 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.Property("TotalQuesNum") + .HasColumnType("tinyint unsigned"); + + b.Property("TotalScore") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("GraderId"); + + b.HasIndex("StudentId"); + + b.ToTable("submissions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("assignment_question_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("IsCorrect") + .HasColumnType("tinyint(1)") + .HasColumnName("is_correct"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("PointsAwarded") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("points_awarded"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentAnswer") + .HasColumnType("longtext") + .HasColumnName("student_answer"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionId") + .HasColumnType("char(36)") + .HasColumnName("submission_id"); + + b.Property("TeacherFeedback") + .HasColumnType("longtext") + .HasColumnName("teacher_feedback"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentQuestionId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubmissionId"); + + b.ToTable("submission_details", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned"); + + b.Property("Publisher") + .HasColumnType("tinyint unsigned"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DisplayName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + 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("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), + 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/20250904102023_submission_up_3.cs b/TechHelper.Server/Migrations/20250904102023_submission_up_3.cs new file mode 100644 index 0000000..33a33c3 --- /dev/null +++ b/TechHelper.Server/Migrations/20250904102023_submission_up_3.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 submission_up_3 : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc")); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" }, + { new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9")); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("264e4290-9d15-478d-8c49-8d0935e5a6e1"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("73cafcee-3e99-43ae-86c5-c01a1cbc6124"), null, "Teacher", "TEACHER" }, + { new Guid("f06927ff-4bba-4ab6-8f0a-e45a765c2fcc"), null, "Student", "STUDENT" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/20250905101308_tee.Designer.cs b/TechHelper.Server/Migrations/20250905101308_tee.Designer.cs new file mode 100644 index 0000000..e6518af --- /dev/null +++ b/TechHelper.Server/Migrations/20250905101308_tee.Designer.cs @@ -0,0 +1,1302 @@ +// +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("20250905101308_tee")] + partial class tee + { + /// + 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("ExamType") + .HasColumnType("tinyint unsigned"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + 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("BCorrect") + .HasColumnType("tinyint(1)"); + + 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.Property("Type") + .HasColumnType("tinyint unsigned"); + + 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.Global", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("Area") + .HasColumnType("tinyint unsigned"); + + b.Property("Info") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("global"); + }); + + 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("QType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasMaxLength(100) + .HasColumnType("tinyint unsigned") + .HasColumnName("subject_area"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(65535) + .HasColumnType("longtext") + .HasColumnName("question_text"); + + b.Property("Type") + .HasMaxLength(20) + .HasColumnType("tinyint unsigned") + .HasColumnName("question_type"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("CreatorId"); + + b.HasIndex("KeyPointId"); + + b.HasIndex("LessonId"); + + b.HasIndex("Title") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 20 }); + + b.ToTable("questions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.QuestionContext", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("QuestionContexts"); + }); + + modelBuilder.Entity("Entities.Contracts.Submission", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentId") + .HasColumnType("char(36)") + .HasColumnName("assignment_id"); + + b.Property("AttemptNumber") + .HasColumnType("tinyint unsigned") + .HasColumnName("attempt_number"); + + b.Property("ErrorQuesNum") + .HasColumnType("tinyint unsigned"); + + 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.Property("TotalQuesNum") + .HasColumnType("tinyint unsigned"); + + b.Property("TotalScore") + .HasColumnType("tinyint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentId"); + + b.HasIndex("GraderId"); + + b.HasIndex("StudentId"); + + b.ToTable("submissions", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.SubmissionDetail", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)") + .HasColumnName("id"); + + b.Property("AssignmentQuestionId") + .HasColumnType("char(36)") + .HasColumnName("assignment_question_id"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime(6)") + .HasColumnName("created_at"); + + MySqlPropertyBuilderExtensions.UseMySqlIdentityColumn(b.Property("CreatedAt")); + + b.Property("IsCorrect") + .HasColumnType("tinyint(1)") + .HasColumnName("is_correct"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("tinyint(1)") + .HasDefaultValue(false) + .HasColumnName("deleted"); + + b.Property("PointsAwarded") + .HasPrecision(5, 2) + .HasColumnType("float") + .HasColumnName("points_awarded"); + + b.Property("Status") + .HasColumnType("int") + .HasColumnName("status"); + + b.Property("StudentAnswer") + .HasColumnType("longtext") + .HasColumnName("student_answer"); + + b.Property("StudentId") + .HasColumnType("char(36)") + .HasColumnName("student_id"); + + b.Property("SubmissionId") + .HasColumnType("char(36)") + .HasColumnName("submission_id"); + + b.Property("TeacherFeedback") + .HasColumnType("longtext") + .HasColumnName("teacher_feedback"); + + b.Property("UpdatedAt") + .IsConcurrencyToken() + .ValueGeneratedOnAddOrUpdate() + .HasColumnType("datetime(6)") + .HasColumnName("updated_at"); + + b.HasKey("Id"); + + b.HasIndex("AssignmentQuestionId"); + + b.HasIndex("StudentId"); + + b.HasIndex("SubmissionId"); + + b.ToTable("submission_details", (string)null); + }); + + modelBuilder.Entity("Entities.Contracts.Textbook", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Grade") + .HasColumnType("tinyint unsigned"); + + b.Property("Publisher") + .HasColumnType("tinyint unsigned"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("textbook"); + }); + + modelBuilder.Entity("Entities.Contracts.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DisplayName") + .HasColumnType("longtext"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasColumnName("deleted"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime(6)"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("RefreshToken") + .HasColumnType("longtext"); + + b.Property("RefreshTokenExpiryTime") + .HasColumnType("datetime(6)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("SubjectArea") + .HasColumnType("tinyint unsigned"); + + 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("d480cdca-7de2-4abe-8129-73bbaa6c1b32"), + Name = "Student", + NormalizedName = "STUDENT" + }, + new + { + Id = new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"), + Name = "Teacher", + NormalizedName = "TEACHER" + }, + new + { + Id = new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"), + 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/20250905101308_tee.cs b/TechHelper.Server/Migrations/20250905101308_tee.cs new file mode 100644 index 0000000..760aaa8 --- /dev/null +++ b/TechHelper.Server/Migrations/20250905101308_tee.cs @@ -0,0 +1,82 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace TechHelper.Server.Migrations +{ + /// + public partial class tee : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9")); + + migrationBuilder.AddColumn( + name: "BCorrect", + table: "assignment_questions", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"), null, "Student", "STUDENT" }, + { new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"), null, "Teacher", "TEACHER" }, + { new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"), null, "Administrator", "ADMINISTRATOR" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d")); + + migrationBuilder.DeleteData( + table: "AspNetRoles", + keyColumn: "Id", + keyValue: new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b")); + + migrationBuilder.DropColumn( + name: "BCorrect", + table: "assignment_questions"); + + migrationBuilder.InsertData( + table: "AspNetRoles", + columns: new[] { "Id", "ConcurrencyStamp", "Name", "NormalizedName" }, + values: new object[,] + { + { new Guid("388fdb1d-8cd5-4e8f-b49c-06dbee60527b"), null, "Administrator", "ADMINISTRATOR" }, + { new Guid("ba4054d5-2f8a-4c7f-bd56-0fc864720c7d"), null, "Teacher", "TEACHER" }, + { new Guid("c758a0d2-faea-4cf1-aa14-d162f3d0a1e9"), null, "Student", "STUDENT" } + }); + } + } +} diff --git a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs index 4c2f81e..3d19c48 100644 --- a/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs +++ b/TechHelper.Server/Migrations/ApplicationContextModelSnapshot.cs @@ -175,6 +175,9 @@ namespace TechHelper.Server.Migrations b.Property("AssignmentId") .HasColumnType("char(36)"); + b.Property("BCorrect") + .HasColumnType("tinyint(1)"); + b.Property("CreatedAt") .HasColumnType("datetime(6)") .HasColumnName("created_at"); @@ -558,7 +561,7 @@ namespace TechHelper.Server.Migrations .HasColumnType("longtext") .HasColumnName("overall_feedback"); - b.Property("OverallGrade") + b.Property("OverallGrade") .HasPrecision(5, 2) .HasColumnType("float") .HasColumnName("overall_grade"); @@ -800,19 +803,19 @@ namespace TechHelper.Server.Migrations b.HasData( new { - Id = new Guid("8c6c5e8e-ef00-444c-9c7c-cba5cd6f7043"), + Id = new Guid("d480cdca-7de2-4abe-8129-73bbaa6c1b32"), Name = "Student", NormalizedName = "STUDENT" }, new { - Id = new Guid("2670f35a-df0c-4071-8879-80eb99d138a1"), + Id = new Guid("d7bcfb37-3f1c-467b-a3f0-b2339a8a990d"), Name = "Teacher", NormalizedName = "TEACHER" }, new { - Id = new Guid("9eda9d90-0cd2-4fbe-b07e-f90bd01f32db"), + Id = new Guid("f4a6788a-04d8-499c-9e64-73dfba97ca6b"), Name = "Administrator", NormalizedName = "ADMINISTRATOR" }); diff --git a/TechHelper.Server/Program.cs b/TechHelper.Server/Program.cs index a489afd..b2e64df 100644 --- a/TechHelper.Server/Program.cs +++ b/TechHelper.Server/Program.cs @@ -35,8 +35,10 @@ builder.Services.AddDbContext(options => .AddCustomRepository() .AddCustomRepository() .AddCustomRepository() +.AddCustomRepository() .AddCustomRepository(); + builder.Services.AddAutoMapper(typeof(AutoMapperProFile).Assembly); // 3. 配置服务 (IOptions) @@ -90,6 +92,8 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); diff --git a/TechHelper.Server/Services/IStudentSubmissionDetailService.cs b/TechHelper.Server/Services/IStudentSubmissionDetailService.cs new file mode 100644 index 0000000..718ab32 --- /dev/null +++ b/TechHelper.Server/Services/IStudentSubmissionDetailService.cs @@ -0,0 +1,15 @@ +using Entities.DTO; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public interface IStudentSubmissionDetailService + { + /// + /// 获取学生提交的详细信息 + /// + /// 提交ID + /// 学生提交详细信息 + Task GetSubmissionDetailAsync(Guid submissionId); + } +} diff --git a/TechHelper.Server/Services/IStudentSubmissionService.cs b/TechHelper.Server/Services/IStudentSubmissionService.cs new file mode 100644 index 0000000..711d17b --- /dev/null +++ b/TechHelper.Server/Services/IStudentSubmissionService.cs @@ -0,0 +1,24 @@ +using Entities.DTO; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public interface IStudentSubmissionService : IBaseService + { + /// + /// 获取学生提交的作业摘要列表 + /// + /// 学生ID + /// 学生提交摘要列表 + Task GetStudentSubmissionsAsync(Guid studentId); + + /// + /// 获取学生提交的作业摘要列表(分页) + /// + /// 学生ID + /// 页码 + /// 每页数量 + /// 分页的学生提交摘要列表 + Task GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10); + } +} diff --git a/TechHelper.Server/Services/StudentSubmissionDetailService.cs b/TechHelper.Server/Services/StudentSubmissionDetailService.cs new file mode 100644 index 0000000..d671591 --- /dev/null +++ b/TechHelper.Server/Services/StudentSubmissionDetailService.cs @@ -0,0 +1,144 @@ +using AutoMapper; +using AutoMapper.Internal.Mappers; +using Entities.Contracts; +using Entities.DTO; +using Microsoft.EntityFrameworkCore; +using SharedDATA.Api; +using TechHelper.Context; +using TechHelper.Repository; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public class StudentSubmissionDetailService : IStudentSubmissionDetailService + { + private readonly IUnitOfWork _unitOfWork; + private readonly IExamService examService; + private readonly IMapper _mapper; + + public StudentSubmissionDetailService( + IUnitOfWork unitOfWork, + IExamService examService, + IMapper mapper) + { + _unitOfWork = unitOfWork; + this.examService = examService; + _mapper = mapper; + } + + public async Task GetSubmissionDetailAsync(Guid submissionId) + { + try + { + // 获取submission基本信息 + var submission = await _unitOfWork.GetRepository() + .GetAll(s => s.Id == submissionId) + .Include(s => s.Assignment) + .ThenInclude(a => a.Creator) + .FirstOrDefaultAsync(); + + if (submission == null) + { + return ApiResponse.Error("未找到指定的提交记录"); + } + + var assignment = await examService.GetAsync(submission.AssignmentId); + if (assignment == null) + { + return ApiResponse.Error("未找到指定的作业"); + } + + // 获取所有提交详情 + var submissionDetails = await _unitOfWork.GetRepository() + .GetAll(sd => sd.SubmissionId == submissionId) + .Include(sd => sd.AssignmentQuestion) + .ThenInclude(aq => aq.Question) + .ThenInclude(q => q.Lesson) + .ThenInclude(q => q.KeyPoints) + .ToListAsync(); + + // 获取同作业的所有提交用于排名和成绩分布 + var allSubmissions = await _unitOfWork.GetRepository() + .GetAll(s => s.AssignmentId == submission.AssignmentId) + .ToListAsync(); + + // 映射基本信息 + var result = _mapper.Map(submission); + result.Assignment = assignment.Result as AssignmentDto ?? new AssignmentDto(); + + var errorQuestion = submissionDetails + .Where(sd => sd.IsCorrect == false && sd.AssignmentQuestion?.StructType == AssignmentStructType.Question && sd.AssignmentQuestion?.Question != null) + .ToList(); + + // 计算基础统计 + result.TotalQuestions = submissionDetails.Select(x => x.AssignmentQuestion.StructType == AssignmentStructType.Question && x.AssignmentQuestion?.Question != null).Count(); + result.ErrorCount = errorQuestion.Count; + result.CorrectCount = result.TotalQuestions - result.ErrorCount; + result.AccuracyRate = result.TotalQuestions > 0 ? + (float)result.CorrectCount / result.TotalQuestions : 0; + + // 计算错误类型分布 - 只获取题目类型的错误 + result.ErrorTypeDistribution = errorQuestion + .GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString()) + .ToDictionary(g => g.Key, g => g.Count()); ; + + // 计算错误类型成绩分布 - 只获取题目类型的错误 + result.ErrorTypeScoreDistribution = errorQuestion + .GroupBy(sd => sd.AssignmentQuestion.Question.Type.ToString()) + .ToDictionary(g => g.Key, g => g.Sum(sd => sd.PointsAwarded ?? 0)); + + // 计算成绩排名 + var orderedSubmissions = allSubmissions + .OrderByDescending(s => s.OverallGrade) + .ToList(); + result.TotalRank = orderedSubmissions.FindIndex(s => s.Id == submissionId) + 1; + + SetBCorrect(result.Assignment, errorQuestion); + // 计算成绩分布 + result.AllScores = allSubmissions.Select(s => s.OverallGrade).ToList(); + result.AverageScore = submission.OverallGrade; + result.ClassAverageScore = allSubmissions.Average(s => s.OverallGrade); + + // 计算课文错误分布 + result.LessonErrorDistribution = errorQuestion + .Where(eq => eq.AssignmentQuestion.Question.Lesson != null) + .GroupBy(sd => sd.AssignmentQuestion.Question.Lesson.Title) + .ToDictionary(g => g.Key, g => g.Count()); + + // 计算关键点错误分布 + result.KeyPointErrorDistribution = errorQuestion + .Where(eq => eq.AssignmentQuestion.Question.Lesson != null) + .GroupBy(sd => sd.AssignmentQuestion.Question.KeyPoint.Key) + .ToDictionary(g => g.Key, g => g.Count()); + + return ApiResponse.Success(result: result); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取学生提交详细信息失败: {ex.Message}"); + } + } + + public void SetBCorrect(AssignmentDto assignment, List submissionDetails) + { + SetBCorrect(assignment.ExamStruct, submissionDetails); + } + + public void SetBCorrect(AssignmentQuestionDto assignmentQuestion, List submissionDetails) + { + var sd = submissionDetails.FirstOrDefault(x => x.AssignmentQuestionId == assignmentQuestion.Id); + if (sd != null) + assignmentQuestion.BCorrect = sd.AssignmentQuestion.BCorrect; + else + assignmentQuestion.BCorrect = false; + + assignmentQuestion.ChildrenAssignmentQuestion?.ForEach( + cq => SetBCorrect(cq, submissionDetails)); + } + + //Task IStudentSubmissionDetailService.GetSubmissionDetailAsync(Guid submissionId) + //{ + // throw new NotImplementedException(); + //} + } +} diff --git a/TechHelper.Server/Services/StudentSubmissionService.cs b/TechHelper.Server/Services/StudentSubmissionService.cs new file mode 100644 index 0000000..601ba45 --- /dev/null +++ b/TechHelper.Server/Services/StudentSubmissionService.cs @@ -0,0 +1,142 @@ +using AutoMapper; +using Entities.Contracts; +using Entities.DTO; +using Microsoft.EntityFrameworkCore; +using SharedDATA.Api; +using TechHelper.Context; +using TechHelper.Repository; +using TechHelper.Server.Repositories; +using TechHelper.Services; + +namespace TechHelper.Server.Services +{ + public class StudentSubmissionService : IStudentSubmissionService + { + private readonly IUnitOfWork _unitOfWork; + private readonly IMapper _mapper; + private readonly IRepository _submissionRepository; + private readonly IRepository _assignmentRepository; + private readonly IRepository _userRepository; + + public StudentSubmissionService( + IUnitOfWork unitOfWork, + IMapper mapper, + IRepository submissionRepository, + IRepository assignmentRepository, + IRepository userRepository) + { + _unitOfWork = unitOfWork; + _mapper = mapper; + _submissionRepository = submissionRepository; + _assignmentRepository = assignmentRepository; + _userRepository = userRepository; + } + + public async Task GetStudentSubmissionsAsync(Guid studentId) + { + try + { + var submissions = await _submissionRepository + .GetAll(s => s.StudentId == studentId) + .Include(s => s.Assignment) + .ThenInclude(a => a.Creator) + .OrderByDescending(s => s.SubmissionTime) + .ToListAsync(); + + var result = new List(); + + foreach (var submission in submissions) + { + var summary = new StudentSubmissionSummaryDto + { + Id = submission.Id, + AssignmentName = submission.Assignment?.Title ?? "未知作业", + ErrorCount = await CalculateErrorCountAsync(submission.Id), + CreatedDate = submission.SubmissionTime, + Score = (int)submission.OverallGrade, + TotalQuestions = submission.Assignment?.TotalQuestions ?? 0, + StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师", + Status = submission.Status.ToString() + }; + result.Add(summary); + } + + return ApiResponse.Success(result: result); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}"); + } + } + + public async Task GetStudentSubmissionsPagedAsync(Guid studentId, int pageNumber = 1, int pageSize = 10) + { + try + { + var totalCount = await _submissionRepository + .GetAll(s => s.StudentId == studentId) + .CountAsync(); + + var submissions = await _submissionRepository + .GetAll(s => s.StudentId == studentId) + .Include(s => s.Assignment) + .ThenInclude(a => a.Creator) + .OrderByDescending(s => s.SubmissionTime) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + var result = new List(); + + foreach (var submission in submissions) + { + var summary = new StudentSubmissionSummaryDto + { + Id = submission.Id, + AssignmentName = submission.Assignment?.Title ?? "未知作业", + ErrorCount = await CalculateErrorCountAsync(submission.Id), + CreatedDate = submission.SubmissionTime, + Score = submission.OverallGrade, + TotalQuestions = submission.Assignment?.TotalQuestions ?? 0, + StudentName = submission.Assignment?.Creator?.UserName ?? "未知老师", + Status = submission.Status.ToString() + }; + result.Add(summary); + } + + var response = new StudentSubmissionSummaryResponseDto + { + Submissions = result, + TotalCount = totalCount + }; + + return ApiResponse.Success(result: response); + } + catch (Exception ex) + { + return ApiResponse.Error($"获取学生提交信息失败: {ex.Message}"); + } + } + + private async Task CalculateErrorCountAsync(Guid submissionId) + { + + var submissionDetails = await _unitOfWork.GetRepository() + .GetAll(sd => sd.SubmissionId == submissionId) + .ToListAsync(); + return submissionDetails.Select(x => !x.IsCorrect).Count(); + } + + // 以下方法是IBaseService接口的实现,可以根据需要实现 + public Task GetAllAsync() => throw new NotImplementedException(); + public Task GetAsync(Guid id) => throw new NotImplementedException(); + public Task AddAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException(); + public Task UpdateAsync(StudentSubmissionSummaryDto model) => throw new NotImplementedException(); + public Task DeleteAsync(Guid id) => throw new NotImplementedException(); + + public Task GetAllAsync(QueryParameter query) + { + throw new NotImplementedException(); + } + } +}