From 336b341f9608537ceaf394cba6037bbeffbccfbd Mon Sep 17 00:00:00 2001
From: Hamza Remmal <hamza@remmal.net>
Date: Sun, 23 Mar 2025 17:14:15 +0000
Subject: [PATCH] chore: add support to get the grade from Moodle

---
 .idea/dataSources.xml                         |  2 +-
 .../classes/external/autograde_get_grade.php  | 95 +++++++++++++++++++
 .../src/db/access.php                         |  6 ++
 .../src/db/services.php                       |  7 ++
 .../api/v1/SubmissionController.java          | 35 +++----
 .../response/SubmissionFeedbackResponse.java  |  7 ++
 .../autograde/service/MoodleWebService.java   | 23 +++++
 7 files changed, 150 insertions(+), 25 deletions(-)
 create mode 100644 autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php
 create mode 100644 autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java

diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index 16c9e31e..fd2cd31f 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -5,7 +5,7 @@
       <driver-ref>mysql.8</driver-ref>
       <synchronize>true</synchronize>
       <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
-      <jdbc-url>jdbc:mysql://localhost:3306/moodle</jdbc-url>
+      <jdbc-url>jdbc:mysql://localhost:3305/moodle</jdbc-url>
       <jdbc-additional-properties>
         <property name="com.intellij.clouds.kubernetes.db.host.port" />
         <property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
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 00000000..90ae436a
--- /dev/null
+++ b/autograde-plugins/assignsubmission_autograde/src/classes/external/autograde_get_grade.php
@@ -0,0 +1,95 @@
+<?php
+// This file is part of Moodle - https://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
+/**
+ * This file defines the function to get the grade of a submission
+ *
+ * @see https://moodledev.io/docs/apis/subsystems/external/functions
+ * @author ???
+ * @package ???
+ * @copyright 2023 AUTOGRADE-EPFL <autograde-support@groupes.epfl.ch>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+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(__DIR__.'/../../lib.php');
+require_once(__DIR__.'/../../utils.php');
+require_once($CFG->libdir . '/externallib.php');
+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([
+            'sid' => new external_value(PARAM_INT, 'The ID of the submission'),
+        ]);
+    }
+
+    /**
+     * Retrieve the grade for a submission
+     * @param int $sid
+     * @return stdClass
+     * @throws moodle_exception
+     */
+    public static function get_grade($sid): stdClass {
+        global $DB, $USER;
+
+        self::validate_parameters(self::get_grade_parameters(), ['sid' => $sid]);
+
+        $submission = $DB->get_record('assign_submission', [ 'id' => $sid ]);
+        $assignment = $DB->get_record('assign', [ 'id' => $submission->assignment ]);
+
+        require_capability('assignsubmission/autograde:viewgrades',
+            context_course::instance($assignment->course), $USER->id, false);
+
+        $grade = $DB->get_field('assign_grades', 'grade', ['assignment' => $assignment->id, 'attemptnumber' => $submission->attemptnumber]);
+
+        if (!$grade) {
+            throw new moodle_exception('Grade not found');
+        }
+
+        $result = new stdClass();
+        $result->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([
+            'grade' => new external_value(PARAM_FLOAT, 'The grade'),
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/autograde-plugins/assignsubmission_autograde/src/db/access.php b/autograde-plugins/assignsubmission_autograde/src/db/access.php
index e7f8db95..8cb1dfb5 100644
--- a/autograde-plugins/assignsubmission_autograde/src/db/access.php
+++ b/autograde-plugins/assignsubmission_autograde/src/db/access.php
@@ -36,6 +36,12 @@ $capabilities = array(
         'clonepermissionsfrom' => 'mod/assign/submission/autograde:read_submission'
 
     ),
+    'assignsubmission/autograde:viewgrades' => array(
+        'riskbitmask' => RISK_PERSONAL,
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_COURSE,
+        'archetypes' => array(),
+    ),
     // HR : Capability to write a feedback
     'assignsubmission/autograde:grade' => array(
         'riskbitmask' => RISK_SPAM | RISK_XSS,
diff --git a/autograde-plugins/assignsubmission_autograde/src/db/services.php b/autograde-plugins/assignsubmission_autograde/src/db/services.php
index 103aa3cf..6bffb64a 100644
--- a/autograde-plugins/assignsubmission_autograde/src/db/services.php
+++ b/autograde-plugins/assignsubmission_autograde/src/db/services.php
@@ -52,6 +52,12 @@ $functions = [
         'description'  => 'List all of the information for a given submission',
         'capabilities' => 'assignsubmission/autograde:viewsubmission'
     ],
+    'mod_assignsubmission_autograde_get_grade' => [
+        'classname'    => 'assignsubmission_autograde\external\autograde_get_grade' ,
+        'methodname'   => 'get_grade',
+        'description'  => 'Get the Grade of a Given Submission',
+        'capabilities' => 'assignsubmission/autograde:viewgrades'
+    ],
     'mod_assignsubmission_autograde_download_all_submissions' => [
     'classname'    => 'assignsubmission_autograde\external\autograde_download_all_submissions' ,
     'methodname'   => 'download_all_submissions',
@@ -66,6 +72,7 @@ $services = [
           'mod_assignsubmission_autograde_download_submission',
           'mod_assignsubmission_autograde_list_submissions',
           'mod_assignsubmission_autograde_submission_info',
+          'mod_assignsubmission_autograde_get_grade',
           'mod_assignsubmission_autograde_download_all_submissions',
       ],
       'restrictedusers' => 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 95a81760..423d1d51 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
@@ -2,6 +2,7 @@ package ch.epfl.autograde.controller.api.v1;
 
 import ch.epfl.autograde.model.request.CreateSubmissionRequest;
 import ch.epfl.autograde.model.request.UploadFeedbackRequest;
+import ch.epfl.autograde.model.response.SubmissionFeedbackResponse;
 import ch.epfl.autograde.model.response.SubmissionInfoResponse;
 import ch.epfl.autograde.model.response.SubmissionStatusResponse;
 import ch.epfl.autograde.model.response.CreateSubmissionResponse;
@@ -184,9 +185,13 @@ public final class SubmissionController {
      * </ul>
      */
     @GetMapping(value = "/{id}/feedback", consumes = MediaType.ALL_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<?> getFeedback(final @PathVariable int id) {
-        // TODO: Request the feedback from Moodle
-        return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build();
+    public ResponseEntity<SubmissionFeedbackResponse> getFeedback(final @PathVariable int id) {
+        return ResponseEntity
+                .status(HttpStatus.OK)
+                .body(new SubmissionFeedbackResponse(
+                        moodle.getGradeForSubmission(id),
+                        String.format("%s/api/v1/submission/%d/feedback/files", autograde.getUrl() , id)
+                ));
     }
 
     /**
@@ -207,27 +212,9 @@ public final class SubmissionController {
     @GetMapping(value = "/{id}/feedback/files", consumes = MediaType.ALL_VALUE, produces = "application/zip")
     public ResponseEntity<?> getFeedbackFiles(final @PathVariable int id) {
         // TODO: Request the feedback files from Moodle
-        return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).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) {
-        // TODO: Request the grade from Moodle
-        return ResponseEntity.status(HttpStatus.NOT_IMPLEMENTED).build();
+        return ResponseEntity
+                .status(HttpStatus.NOT_IMPLEMENTED)
+                .build();
     }
 
 }
diff --git a/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java b/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java
new file mode 100644
index 00000000..a8ba8d59
--- /dev/null
+++ b/autograde-service/src/main/java/ch/epfl/autograde/model/response/SubmissionFeedbackResponse.java
@@ -0,0 +1,7 @@
+package ch.epfl.autograde.model.response;
+
+public record SubmissionFeedbackResponse(
+        float grade,
+        String files
+) {
+}
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 6cc96bfe..8d150f3b 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
@@ -222,4 +222,27 @@ public final class MoodleWebService {
     }
 
 
+    public float getGradeForSubmission(int sid) {
+        record Grade(float grade) {}
+        // ----
+        log.info("Fetching grade for submission {}", sid);
+
+        final var FUNCTION_NAME = "mod_assignsubmission_autograde_get_grade";
+        final var params = Map.of("sid", sid);
+
+        try {
+            var response = call(FUNCTION_NAME, params, ofString(UTF_8));
+            if (response.statusCode() != HttpStatus.OK.value()) {
+                log.error("Failed to fetch grade for submission {}", sid);
+                throw new IllegalStateException();
+            }
+
+            System.out.println(response.body());
+            Grade result = mapper.readValue(response.body(), new TypeReference<Grade>() {});
+            return result.grade();
+        } catch (Exception e) {
+            log.error("Error fetching grade for submission {}: {}", sid, e.getMessage());
+            throw new RuntimeException(e);
+        }
+    }
 }
-- 
GitLab