diff --git a/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java b/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java index 0eb26487ae..86e3654fa9 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/controller/ExamController.java @@ -14,6 +14,7 @@ import school.hei.haapi.endpoint.rest.mapper.ExamMapper; import school.hei.haapi.endpoint.rest.model.CrupdateExam; import school.hei.haapi.endpoint.rest.model.ExamInfo; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.endpoint.rest.model.StudentExamGrade; import school.hei.haapi.model.BoundedPageSize; import school.hei.haapi.model.PageFromOne; @@ -118,4 +119,11 @@ public List getStudentExamsGrade( .map(exam -> examMapper.toRestStudentExamGrade(studentId, exam)) .toList(); } + + @GetMapping("/courses/{course_id}/student/{student_id}/grade/stats") + public StudentCourseGradeStats getStudentCourseGradeStats( + @PathVariable(value = "course_id") String courseId, + @PathVariable(value = "student_id") String studentId) { + return examService.getStudentCourseGradeStats(courseId, studentId); + } } diff --git a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java index 69b3177130..b1ddc348ee 100644 --- a/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java +++ b/src/main/java/school/hei/haapi/endpoint/rest/security/SecurityConf.java @@ -204,6 +204,7 @@ req, res, null, forbiddenWithRemoteInfo(req)))) antMatcher(GET, "/courses/*/exams/*/details"), antMatcher(GET, "/courses/*/exams/*/participants/*"), antMatcher(GET, "/courses/*/student/*/exams/grades"), + antMatcher(GET, "/courses/*/student/*/grade/stats"), antMatcher(GET, STUDENT_COURSE), antMatcher(GET, "/comments"), antMatcher(GET, "/students/*/comments"), @@ -862,6 +863,11 @@ req, res, null, forbiddenWithRemoteInfo(req)))) .hasAnyRole(STUDENT.getRole()) .requestMatchers(GET, "/courses/*/student/*/exams/grades") .hasAnyRole(TEACHER.getRole(), MANAGER.getRole(), ADMIN.getRole()) + .requestMatchers( + new SelfMatcher(GET, "/courses/*/student/*/grade/stats", "student")) + .hasAnyRole(STUDENT.getRole()) + .requestMatchers(GET, "/courses/*/student/*/grade/stats") + .hasAnyRole(TEACHER.getRole(), MANAGER.getRole(), ADMIN.getRole()) .requestMatchers(new SelfMatcher(GET, STUDENT_COURSE, "students")) .hasAnyRole(STUDENT.getRole(), ADMIN.getRole()) .requestMatchers(GET, STUDENT_COURSE) diff --git a/src/main/java/school/hei/haapi/model/Grade.java b/src/main/java/school/hei/haapi/model/Grade.java index d1dd0ff219..c0e9dfca53 100644 --- a/src/main/java/school/hei/haapi/model/Grade.java +++ b/src/main/java/school/hei/haapi/model/Grade.java @@ -9,6 +9,8 @@ import jakarta.persistence.Table; import java.io.Serializable; import java.time.Instant; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -49,4 +51,13 @@ public Grade(Exam exam, User student) { this.student = student; this.exam = exam; } + + public static double scoreSumWithCoefficient(List grades) { + return grades.stream() + .mapToDouble( + grade -> + Optional.ofNullable(grade.getScore()).orElse(0.) + * Optional.ofNullable(grade.getExam().getCoefficient()).orElse(0)) + .sum(); + } } diff --git a/src/main/java/school/hei/haapi/repository/GradeRepository.java b/src/main/java/school/hei/haapi/repository/GradeRepository.java index 408f0a5b2a..71f0505135 100644 --- a/src/main/java/school/hei/haapi/repository/GradeRepository.java +++ b/src/main/java/school/hei/haapi/repository/GradeRepository.java @@ -1,5 +1,6 @@ package school.hei.haapi.repository; +import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -18,4 +19,10 @@ Optional getGradeByExamIdAndStudentRef( @Param("exam_id") String examId, @Param("student_ref") String studentRef); Optional findByExamIdAndStudentId(String examId, String studentId); + + @Query( + "select g from Grade g where g.exam.awardedCourse.course.id = :course_id" + + " and g.student = :user_id ") + List findGradesByCourseIdAndStudentId( + @Param("course_id") String courseId, @Param("user_id") String studentId); } diff --git a/src/main/java/school/hei/haapi/service/ExamService.java b/src/main/java/school/hei/haapi/service/ExamService.java index 79bdd0016f..1a6606f79e 100644 --- a/src/main/java/school/hei/haapi/service/ExamService.java +++ b/src/main/java/school/hei/haapi/service/ExamService.java @@ -2,6 +2,7 @@ import static java.util.stream.Collectors.toUnmodifiableList; import static org.springframework.data.domain.Sort.Direction.DESC; +import static school.hei.haapi.model.Grade.scoreSumWithCoefficient; import java.time.Instant; import java.util.ArrayList; @@ -11,6 +12,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; import school.hei.haapi.model.BoundedPageSize; import school.hei.haapi.model.Exam; import school.hei.haapi.model.Grade; @@ -93,4 +95,12 @@ public List getAllExams( public List getExamsByCourseId(String courseId) { return examRepository.findExamsByCourseId(courseId); } + + public StudentCourseGradeStats getStudentCourseGradeStats(String courseId, String studentId) { + List exams = getExamsByCourseId(courseId); + double sumScore = + scoreSumWithCoefficient(gradeService.getGradeByCourseIdAndStudentId(courseId, studentId)); + double sumCoefficient = exams.stream().mapToDouble(Exam::getCoefficient).sum(); + return new StudentCourseGradeStats().average(sumScore / sumCoefficient); + } } diff --git a/src/main/java/school/hei/haapi/service/GradeService.java b/src/main/java/school/hei/haapi/service/GradeService.java index 704e0a4fc3..7e26d0383a 100644 --- a/src/main/java/school/hei/haapi/service/GradeService.java +++ b/src/main/java/school/hei/haapi/service/GradeService.java @@ -83,4 +83,17 @@ private double getExamAverageGrade(String examId) { public ExamGradeStats getExamGradeStats(String examId) { return new ExamGradeStats().average(getExamAverageGrade(examId)); } + + public double scoreSumWithCoefficient(List grades) { + return grades.stream() + .mapToDouble( + grade -> + Optional.ofNullable(grade.getScore()).orElse(0.) + * Optional.ofNullable(grade.getExam().getCoefficient()).orElse(0)) + .sum(); + } + + public List getGradeByCourseIdAndStudentId(String courseId, String studentId) { + return gradeRepository.findGradesByCourseIdAndStudentId(courseId, studentId); + } } diff --git a/src/test/java/school/hei/haapi/service/ExamServiceTest.java b/src/test/java/school/hei/haapi/service/ExamServiceTest.java new file mode 100644 index 0000000000..c71a27f418 --- /dev/null +++ b/src/test/java/school/hei/haapi/service/ExamServiceTest.java @@ -0,0 +1,48 @@ +package school.hei.haapi.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static school.hei.haapi.integration.conf.TestUtils.COURSE1_ID; +import static school.hei.haapi.integration.conf.TestUtils.STUDENT1_ID; +import static school.hei.haapi.integration.conf.TestUtils.exam1; +import static school.hei.haapi.integration.conf.TestUtils.exam2; +import static school.hei.haapi.integration.conf.TestUtils.grade1; +import static school.hei.haapi.integration.conf.TestUtils.grade2; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import school.hei.haapi.endpoint.rest.mapper.ExamMapper; +import school.hei.haapi.endpoint.rest.mapper.GradeMapper; +import school.hei.haapi.endpoint.rest.model.StudentCourseGradeStats; +import school.hei.haapi.integration.conf.FacadeITMockedThirdParties; +import school.hei.haapi.repository.ExamRepository; +import school.hei.haapi.repository.GradeRepository; + +class ExamServiceTest extends FacadeITMockedThirdParties { + @Autowired private ExamService subject; + @MockBean private ExamRepository examRepository; + @Autowired private ExamMapper examMapper; + @MockBean private GradeRepository gradeRepository; + @Autowired private GradeMapper gradeMapper; + + @Test + void student_get_grade_for_each_exams_in_cours() { + var exam1 = examMapper.toDomain(exam1(), null); + var exam2 = examMapper.toDomain(exam2(), null); + var grade1 = gradeMapper.toDomain(grade1()); + var grade2 = gradeMapper.toDomain(grade2()); + grade1.setExam(exam1); + grade2.setExam(exam2); + when(examRepository.findExamsByCourseId(anyString())).thenReturn(List.of(exam1, exam2)); + when(gradeRepository.findGradesByCourseIdAndStudentId(anyString(), anyString())) + .thenReturn(List.of(grade1, grade2)); + + StudentCourseGradeStats studentCourseGradeStats = + subject.getStudentCourseGradeStats(COURSE1_ID, STUDENT1_ID); + + assertEquals(6.2, studentCourseGradeStats.getAverage()); + } +}