diff --git a/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_feedback.php b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_feedback.php new file mode 100644 index 0000000000000000000000000000000000000000..eb5f379448966e6ced31177ef5aab06a3e40c232 --- /dev/null +++ b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_feedback.php @@ -0,0 +1,92 @@ +<?php + +namespace assignsubmission_autograde\external; + +defined('MOODLE_INTERNAL') || die(); + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_multiple_structure; +use external_value; +use context_course; +use moodle_exception; +use moodle_url; +use stdClass; +use context_module; +use assign; +use assign_submission; +use function assignsubmission_autograde\utils\assignsubmission_autograde_get_assignment; + +require_once($CFG->dirroot . '/mod/assign/locallib.php'); + +final class autograde_get_feedback extends external_api { + + /** + * Define the function parameters + * @return external_function_parameters + */ + public static function get_feedback_parameters(): external_function_parameters { + return new external_function_parameters([ + 'submissionid' => new external_value(PARAM_INT, 'The ID of the submission'), + ]); + } + + /** + * Retrieve the feedback files for a submission + * @param int $submissionid + * @return array + * @throws moodle_exception + */ + public static function get_feedback($submissionid): array { + global $DB, $USER; + + self::validate_parameters(self::get_feedback_parameters(), ['submissionid' => $submissionid]); + + $assignment_id = $DB->get_field('assign_submission', 'assignment', array('id' => $submissionid)); + $course_id = $DB->get_field('assign', 'course', array('id' => $assignment_id)); + + require_capability('mod/assign:viewgrades', context_course::instance($course_id), $USER->id, false); + + $fs = get_file_storage(); + $context = context_module::instance($assignment_id); + $files = $fs->get_area_files($context->id, 'assignfeedback_comments', 'feedback_files', $submissionid); + + if (empty($files)) { + throw new moodle_exception('No feedback files found'); + } + + $result = []; + foreach ($files as $file) { + if ($file->get_filename() === '.') { + continue; + } + $entry = new stdClass(); + $entry->filename = $file->get_filename(); + $entry->url = moodle_url::make_pluginfile_url( + $file->get_contextid(), + $file->get_component(), + $file->get_filearea(), + $file->get_itemid(), + $file->get_filepath(), + $file->get_filename() + )->out(false); + $result[] = $entry; + } + + return $result; + } + + /** + * Define the return structure + * @return external_multiple_structure + */ + public static function get_feedback_returns(): external_multiple_structure { + return new external_multiple_structure( + new external_single_structure([ + 'filename' => new external_value(PARAM_TEXT, 'The name of the feedback file'), + 'url' => new external_value(PARAM_URL, 'The URL to download the feedback file'), + ]) + ); + } +} diff --git a/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php new file mode 100644 index 0000000000000000000000000000000000000000..ae011bcccd04cd506bb2120abcb360f033cc40a9 --- /dev/null +++ b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php @@ -0,0 +1,74 @@ +<?php + + + +namespace assignsubmission_autograde\external; + +defined('MOODLE_INTERNAL') || die(); + +use external_api; +use external_function_parameters; +use external_single_structure; +use external_value; +use context_course; +use moodle_exception; +use stdClass; +use assign_submission; +use function assignsubmission_autograde\utils\assignsubmission_autograde_get_assignment; + +require_once($CFG->dirroot . '/mod/assign/locallib.php'); + +final class autograde_get_grade extends external_api { + + /** + * Define the function parameters + * @return external_function_parameters + */ + public static function get_grade_parameters(): external_function_parameters { + return new external_function_parameters([ + 'submissionid' => new external_value(PARAM_INT, 'The ID of the submission'), + ]); + } + + /** + * Retrieve the grade for a submission + * @param int $submissionid + * @return stdClass + * @throws moodle_exception + */ + public static function get_grade($submissionid): stdClass { + global $DB, $USER; + + self::validate_parameters(self::get_grade_parameters(), ['submissionid' => $submissionid]); + + $assignment_id = $DB->get_field('assign_submission', 'assignment', array('id' => $submissionid)); + $course_id = $DB->get_field('assign', 'course', array('id' => $assignment_id)); + + require_capability('mod/assign:grade', context_course::instance($course_id), $USER->id, false); + $submission = $DB->get_record('assign_submission', ['id' => $submissionid]); + + $assignment = assignsubmission_autograde_get_assignment($assignment_id); + $grade = $DB->get_record('assign_grades', ['assignment' => $assignment, 'userid' => $submission->userid]); + + if (!$grade) { + throw new moodle_exception('Grade not found'); + } + + $result = new stdClass(); + $result->submissionid = $submissionid; + $result->grade = $grade->grade; + + return $result; + } + + /** + * Define the return structure + * @return external_single_structure + */ + public static function get_grade_returns(): external_single_structure { + return new external_single_structure([ + 'submissionid' => new external_value(PARAM_INT, 'The ID of the submission'), + 'grade' => new external_value(PARAM_FLOAT, 'The grade'), + ]); + } +} diff --git a/autograde-plugins/assignsubmission_autograde/src/db/services.php b/autograde-plugins/assignsubmission_autograde/src/db/services.php index 103aa3cfb28af4dd6cd7107c9d3d9419a7834876..7fee82fba28f08e7dec88c57bd34aacce1a1f2ec 100644 --- a/autograde-plugins/assignsubmission_autograde/src/db/services.php +++ b/autograde-plugins/assignsubmission_autograde/src/db/services.php @@ -56,7 +56,21 @@ $functions = [ 'classname' => 'assignsubmission_autograde\external\autograde_download_all_submissions' , 'methodname' => 'download_all_submissions', 'description' => 'Download all submissions of a given assignment', -] + + ], + 'mod_assignsubmission_autograde_get_grade' => [ + 'classname' => 'assignsubmission_autograde\external\autograde_get_grade', + 'methodname' => 'get_grade', + 'description' => 'Retrieve the grade for a given submission', + 'capabilities' => 'mod/assign:grade', + + ], + 'mod_assignsubmission_autograde_get_feedback' => [ + 'classname' => 'assignsubmission_autograde\external\autograde_get_feedback', + 'methodname' => 'get_feedback', + 'description' => 'Retrieve feedback files for a given submission', + 'capabilities' => 'mod/assign:viewgrades', + ] ]; $services = [ @@ -67,6 +81,8 @@ $services = [ 'mod_assignsubmission_autograde_list_submissions', 'mod_assignsubmission_autograde_submission_info', 'mod_assignsubmission_autograde_download_all_submissions', + 'mod_assignsubmission_autograde_get_grade', + 'mod_assignsubmission_get_feedback', ], 'restrictedusers' => 1, 'enabled' => 1, diff --git a/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java b/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java index ca2d8b433099a260953942741bfc3f883cd0e89a..e4be43406b5f008520de2c04f5e2e2d0426c96e2 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/controller/api/v1/SubmissionController.java @@ -13,10 +13,12 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.io.InputStream; import java.net.URI; /** @@ -163,4 +165,75 @@ public final class SubmissionController { } } + /** + * REST Endpoint to get the feedback files of a given submission + * + * @param id The unique id of the requested submission + * @since 1.3.0 + * @return + * <ul> + * <li>{@link HttpStatus.OK} if the request is successful and the zip file was produced</li> + * <li>{@link HttpStatus.NO_CONTENT} if the request is successful but no feedback files were found</li> + * <li>{@link HttpStatus.UNAUTHORIZED} if the user is not authenticated</li> + * <li>{@link HttpStatus.FORBIDDEN} if the user is authenticated but not allowed to perform this action</li> + * <li>{@link HttpStatus.NOT_FOUND} if the submission is missing</li> + * <li>{@link HttpStatus.INTERNAL_SERVER_ERROR} in case of a server failure</li> + * </ul> + */ + @GetMapping(value = "/{id}/feedback/files", consumes = MediaType.ALL_VALUE, produces = "application/zip") + public ResponseEntity<?> getFeedbackFiles(final @PathVariable int id) { + log.info("Processing request to retrieve feedback files for submission {}", id); + + try { + InputStream feedbackFiles = moodle.get_feedback(id) ; + + if (feedbackFiles == null) { + return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + } + + log.info("Successfully retrieved feedback files for submission {}", id); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=feedback_" + id + ".zip") + .body(new InputStreamResource(feedbackFiles)); + + } catch (Exception e) { + log.error("Failed to retrieve feedback files for submission {}: {}", id, e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + /** + * REST Endpoint to request the grade of a given submission + * + * @param id The unique id of the requested submission + * @since 1.3.0 + * @return + * <ul> + * <li>{@link HttpStatus.OK} if the request is successful</li> + * <li>{@link HttpStatus.UNAUTHORIZED} if the user is not authenticated</li> + * <li>{@link HttpStatus.FORBIDDEN} if the user is authenticated but not allowed to perform this action</li> + * <li>{@link HttpStatus.NOT_FOUND} if the submission is missing</li> + * <li>{@link HttpStatus.INTERNAL_SERVER_ERROR} in case of a server failure</li> + * </ul> + */ + @GetMapping(value = "/{id}/feedback/grade", consumes = MediaType.ALL_VALUE, produces = MediaType.TEXT_PLAIN_VALUE) + public ResponseEntity<?> getGrade(final @PathVariable int id) { + log.info("Processing request to retrieve the grade for submission {}", id); + + try { + String grade = moodle.getGrade(id); + + if (grade == null) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Grade not found for submission " + id); + } + + log.info("Successfully retrieved grade for submission {}: {}", id, grade); + return ResponseEntity.ok(grade); + } catch (Exception e) { + log.error("Failed to retrieve grade for submission {}: {}", id, e.getMessage()); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error retrieving grade"); + } + } + + } diff --git a/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java b/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java index 6cc96bfe5042eee23de59f281e824c7164017755..e8f427abaeaabe22b03e7a94f8fd1f085b7ffff1 100644 --- a/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java +++ b/autograde-service/src/main/java/ch/epfl/autograde/service/MoodleWebService.java @@ -221,5 +221,63 @@ public final class MoodleWebService { } } + public String getGrade(int submissionId) { + log.info("Fetching grade for submission {}", submissionId); + + final var FUNCTION_NAME = "mod_assignsubmission_get_grade"; + + final var params = Map.of( + "submissionid", submissionId + ); + + try { + + var response = call(FUNCTION_NAME, params, ofString(UTF_8)); + + if (response.statusCode() != HttpStatus.OK.value()) { + log.error("Failed to fetch grade for submission {}", submissionId); + throw new IllegalStateException(); + } + + Map<String, Object> result = mapper.readValue(response.body(), new TypeReference<>() {}); + if (result.containsKey("grade")) { + return result.get("grade").toString(); + } + + log.warn("No grade found for submission {}", submissionId); + return null; + } catch (Exception e) { + log.error("Error fetching grade for submission {}: {}", submissionId, e.getMessage()); + throw new RuntimeException(e); + } + } + public InputStream get_feedback(int submissionId) { + log.info("Fetching feedback files for submission {}", submissionId); + + final var FUNCTION_NAME = "mod_assignsubmission_autograde_get_feedback"; + + final var params = Map.of( + "submissionid", submissionId + ); + + try { + // Call Moodle API + var response = call(FUNCTION_NAME, params, ofInputStream()); + + // Check HTTP response status + if (response.statusCode() != HttpStatus.OK.value()) { + log.error("Failed to fetch feedback files for submission {}", submissionId); + return null; + } + + return response.body(); + } catch (Exception e) { + log.error("Error fetching feedback files for submission {}: {}", submissionId, e.getMessage()); + return null; + } + } } + + +