diff --git a/.github/workflows/autograde-service-tests.yml b/.github/workflows/autograde-service-tests.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b861b07ea8a0bce7e71effca0b72295efe504bce
--- /dev/null
+++ b/.github/workflows/autograde-service-tests.yml
@@ -0,0 +1,23 @@
+name: Spring App tests
+
+on:
+  push:
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v3
+      - name: set up java
+        uses: actions/setup-java@v3
+        with:
+          java-version: '17'
+          distribution: 'temurin'
+          cache: maven
+      - name: build and run the tests for the service
+        run: mvn -B clean package --file moodle-grading-service/pom.xml
+      - name: upload junit test report
+        uses: actions/upload-artifact@v3
+        with:
+          name: test-report
+          path: moodle-grading-service/target/surefire-reports
diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml
new file mode 100644
index 0000000000000000000000000000000000000000..85a5b9ac60f4dea00bf2717400833f2f9ab101ce
--- /dev/null
+++ b/.github/workflows/build-push.yml
@@ -0,0 +1,38 @@
+name: Build and Push Docker image for Moodle Grading Service
+
+# Run this workflow every time a new commit on the main branch changes the moodle-grading-service directory
+on:
+  push:
+    branches:
+      - master
+    paths:
+      - "moodle-grading-service/**"
+      - ".github/workflows/build-push.yml"
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v2
+
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Build and push
+        uses: docker/build-push-action@v4
+        with:
+          context: ./moodle-grading-service
+          provenance: false
+          platforms: linux/amd64,linux/arm64
+          push: true
+          tags: |
+            ghcr.io/${{ github.repository }}/moodle-grading-service:latest
+            ghcr.io/${{ github.repository }}/moodle-grading-service:${{ github.sha }}
diff --git a/.github/workflows/test-cluster-setup.yml b/.github/workflows/test-cluster-setup.yml
new file mode 100644
index 0000000000000000000000000000000000000000..c2563791b49cf759eb8f07a175182a52428eaeff
--- /dev/null
+++ b/.github/workflows/test-cluster-setup.yml
@@ -0,0 +1,62 @@
+name: Deploy to Minikube
+
+on:
+  pull_request:
+    types: [review_requested]
+    branches:
+      - master
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+
+      - name: Add entry to /etc/hosts
+        run: |
+          echo "127.0.0.1    moodle" | sudo tee -a /etc/hosts
+
+      - name: Start minikube
+        uses: medyagh/setup-minikube@master
+        id: minikube
+        with:
+          driver: docker
+          mount-path: "${{ github.GITHUB_WORKSPACE }}:/repo"
+
+      - name: Start tunnel
+        run: |
+          minikube tunnel &
+
+      - name: Login to GitHub Container Registry
+        uses: docker/login-action@v2
+        with:
+          registry: ghcr.io
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: Build image
+        run: |
+          eval $(minikube docker-env)
+          docker build -t ghcr.io/hamzaremmal/moodle-autograde/moodle-grading-service:latest moodle-grading-service
+
+      - name: Deploy manifests
+        run: |
+          kubectl config set-context minikube --namespace=cs107
+          kubectl apply -k k8s-overlays/local
+
+      - name: Wait for deployment to complete and grading service to be available
+        run: |
+          kubectl rollout status deployment/grading-service
+          sleep 100
+          kubectl get service grading-service-tcp
+
+      - name: Ping grading service
+        run: |
+          WEB_SERVICE_URL=$(kubectl get service grading-service-tcp -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
+          echo "WEB_SERVICE_URL=$WEB_SERVICE_URL"
+          response=$(curl -s http://$WEB_SERVICE_URL:8082/api/v1/ping/no-auth)
+          if [[ "$response" != *"Hello from Spring boot"* ]]; then
+            echo "Unexpected response: $response"
+            exit 1
+          fi
diff --git a/.gitignore b/.gitignore
index 82f0c3ac6d4d43fca14022bb41875074ff494d99..5c1cc81f0b25f88591b39a953a5754681eadb244 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
-/data/
+#K8S configuration
+kubeconfig
+
+#Local dev variables
+**.prod.env
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..13566b81b018ad684f3a35fee301741b2734c8f4
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000000000000000000000000000000000000..919ce1f1f77253454105acb2aad9997c1047a0e6
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,7 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <ScalaCodeStyleSettings>
+      <option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
+    </ScalaCodeStyleSettings>
+  </code_scheme>
+</component>
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a55e7a179bde3e4e772c29c0c85e53354aa54618
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9ac46c72dc290a1ee007f4ceed44861184eeebb5
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <annotationProcessing>
+      <profile default="true" name="Default" enabled="true" />
+      <profile name="Maven default annotation processors profile" enabled="true">
+        <sourceOutputDir name="target/generated-sources/annotations" />
+        <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
+        <outputRelativeToContentRoot value="true" />
+        <module name="moodle-grading-service" />
+      </profile>
+    </annotationProcessing>
+  </component>
+  <component name="JavacSettings">
+    <option name="ADDITIONAL_OPTIONS_OVERRIDE">
+      <module name="moodle-grading-service" options="-parameters" />
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
new file mode 100644
index 0000000000000000000000000000000000000000..039b9aa01e94241da95f10896cf57f7ef2565d3e
--- /dev/null
+++ b/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="moodle-dev" uuid="55f0af50-0db1-4bef-8422-991545d60c56">
+      <driver-ref>mysql.8</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mysql://localhost:3305/moodle</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0903db4abd4624ef0936771c5c849fdfb4e9d64b
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="Encoding">
+    <file url="file://$PROJECT_DIR$/moodle-grading-service/src/main/java" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/moodle-grading-service/src/main/resources" charset="UTF-8" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ffa314ee27bc8a3fb4a73693df7c3399e75abbba
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,15 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="Project Default" />
+    <inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="IncorrectHttpHeaderInspection" enabled="true" level="WARNING" enabled_by_default="true">
+      <option name="customHeaders">
+        <set>
+          <option value="API-KEY" />
+        </set>
+      </option>
+    </inspection_tool>
+    <inspection_tool class="JSHint" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="Stylelint" enabled="true" level="ERROR" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file
diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a522ac4759d00e88fbb1e4e87143289e0ac24fd2
--- /dev/null
+++ b/.idea/jarRepositories.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="RemoteRepositoriesConfiguration">
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="Maven Central repository" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="jboss.community" />
+      <option name="name" value="JBoss Community repository" />
+      <option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
+    </remote-repository>
+    <remote-repository>
+      <option name="id" value="central" />
+      <option name="name" value="central" />
+      <option name="url" value="https://repo1.maven.org/maven2" />
+    </remote-repository>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/jsLinters/jshint.xml b/.idea/jsLinters/jshint.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3b0fd0171f2671d282fcedf7e4abf376ff4d2944
--- /dev/null
+++ b/.idea/jsLinters/jshint.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="JSHintConfiguration" version="2.13.6" use-config-file="true" use-custom-config-file="true" custom-config-file-path="$PROJECT_DIR$/moodle/.jshintrc">
+    <option bitwise="true" />
+    <option browser="true" />
+    <option curly="true" />
+    <option eqeqeq="true" />
+    <option forin="true" />
+    <option maxerr="50" />
+    <option noarg="true" />
+    <option noempty="true" />
+    <option nonew="true" />
+    <option strict="true" />
+    <option undef="true" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2acfb33066ba1138f2ea4b1e9f0e2ecb3ff55250
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ExternalStorageConfigurationManager" enabled="true" />
+  <component name="MavenProjectsManager">
+    <option name="originalFiles">
+      <list>
+        <option value="$PROJECT_DIR$/moodle-grading-service/pom.xml" />
+      </list>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/out" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1c4ebaf4b7cac6d6c29386ae1c9eceaf9a54bf6c
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/moodle-autograde.iml" filepath="$PROJECT_DIR$/.idea/moodle-autograde.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/moodle-autograde.iml b/.idea/moodle-autograde.iml
new file mode 100644
index 0000000000000000000000000000000000000000..1fb44a343824b2b3a9b726c39b9eb072a41383de
--- /dev/null
+++ b/.idea/moodle-autograde.iml
@@ -0,0 +1,85 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" inherit-compiler-output="true">
+    <exclude-output />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/moodle/admin/presets/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/admin/roles/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/admin/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/analytics/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/auth/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/availability/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/backup/controller/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/backup/converter/moodle1/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/backup/moodle2/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/backup/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/backup/util" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/badges/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/blocks/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/blog/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/cache/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/calendar/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/cohort/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/comment/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/competency/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/completion/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/contentbank/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/course/format/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/course/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/customfield/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/enrol/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/favourites/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/files/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/filter/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/grade/grading/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/grade/import/csv/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/grade/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/group/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/h5p/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/iplookup/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/behat/extension" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/ddl/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/dml/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/editor/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/external/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/filebrowser/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/filestorage/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/form/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/grade/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/src/MaxMind/Db" isTestSource="false" packagePrefix="MaxMind\Db\" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/maxmind/MaxMind/tests/MaxMind/Db/Test/Reader" isTestSource="true" packagePrefix="MaxMind\Db\Test\Reader\" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/php-jwt/src" isTestSource="false" packagePrefix="Firebase\JWT\" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/php-jwt/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/phpunit/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/table/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/testing" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/userkey/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/lib/xapi/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/login/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/message/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/mnet/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/my/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/notes/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/payment/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/plagiarism/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/portfolio/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/privacy/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/question/engine/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/question/engine/upgrade/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/question/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/question/type/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/rating/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/reportbuilder/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/repository/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/rss/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/search/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/tag/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/user/tests" isTestSource="true" />
+      <sourceFolder url="file://$MODULE_DIR$/moodle/webservice/tests" isTestSource="true" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/.idea/php.xml b/.idea/php.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0e09af40726c3560d3f3d145e050377be73a1431
--- /dev/null
+++ b/.idea/php.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PhpProjectSharedConfiguration" php_language_level="7.4">
+    <option name="suggestChangeDefaultLanguageLevel" value="false" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/phpunit.xml b/.idea/phpunit.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d8758d74baa5fdbd3f0bb65234d731f31cbbcc63
--- /dev/null
+++ b/.idea/phpunit.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="PHPUnit">
+    <option name="directories">
+      <list>
+        <option value="$PROJECT_DIR$/moodle/lib/php-jwt/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/maxmind/MaxMind/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/phpunit/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/testing" />
+        <option value="$PROJECT_DIR$/moodle/lib/ddl/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/dml/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/tests" />
+        <option value="$PROJECT_DIR$/moodle/favourites/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/form/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/filestorage/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/filebrowser/tests" />
+        <option value="$PROJECT_DIR$/moodle/files/tests" />
+        <option value="$PROJECT_DIR$/moodle/filter/tests" />
+        <option value="$PROJECT_DIR$/moodle/admin/roles/tests" />
+        <option value="$PROJECT_DIR$/moodle/cohort/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/grade/tests" />
+        <option value="$PROJECT_DIR$/moodle/grade/tests" />
+        <option value="$PROJECT_DIR$/moodle/grade/grading/tests" />
+        <option value="$PROJECT_DIR$/moodle/grade/import/csv/tests" />
+        <option value="$PROJECT_DIR$/moodle/analytics/tests" />
+        <option value="$PROJECT_DIR$/moodle/availability/tests" />
+        <option value="$PROJECT_DIR$/moodle/backup/controller/tests" />
+        <option value="$PROJECT_DIR$/moodle/backup/converter/moodle1/tests" />
+        <option value="$PROJECT_DIR$/moodle/backup/moodle2/tests" />
+        <option value="$PROJECT_DIR$/moodle/backup/tests" />
+        <option value="$PROJECT_DIR$/moodle/backup/util" />
+        <option value="$PROJECT_DIR$/moodle/badges/tests" />
+        <option value="$PROJECT_DIR$/moodle/blog/tests" />
+        <option value="$PROJECT_DIR$/moodle/customfield/tests" />
+        <option value="$PROJECT_DIR$/moodle/iplookup/tests" />
+        <option value="$PROJECT_DIR$/moodle/course/tests" />
+        <option value="$PROJECT_DIR$/moodle/course/format/tests" />
+        <option value="$PROJECT_DIR$/moodle/privacy/tests" />
+        <option value="$PROJECT_DIR$/moodle/question/engine/tests" />
+        <option value="$PROJECT_DIR$/moodle/question/tests" />
+        <option value="$PROJECT_DIR$/moodle/question/type/tests" />
+        <option value="$PROJECT_DIR$/moodle/question/engine/upgrade/tests" />
+        <option value="$PROJECT_DIR$/moodle/cache/tests" />
+        <option value="$PROJECT_DIR$/moodle/calendar/tests" />
+        <option value="$PROJECT_DIR$/moodle/enrol/tests" />
+        <option value="$PROJECT_DIR$/moodle/group/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/external/tests" />
+        <option value="$PROJECT_DIR$/moodle/message/tests" />
+        <option value="$PROJECT_DIR$/moodle/notes/tests" />
+        <option value="$PROJECT_DIR$/moodle/tag/tests" />
+        <option value="$PROJECT_DIR$/moodle/rating/tests" />
+        <option value="$PROJECT_DIR$/moodle/repository/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/userkey/tests" />
+        <option value="$PROJECT_DIR$/moodle/user/tests" />
+        <option value="$PROJECT_DIR$/moodle/webservice/tests" />
+        <option value="$PROJECT_DIR$/moodle/mnet/tests" />
+        <option value="$PROJECT_DIR$/moodle/completion/tests" />
+        <option value="$PROJECT_DIR$/moodle/comment/tests" />
+        <option value="$PROJECT_DIR$/moodle/search/tests" />
+        <option value="$PROJECT_DIR$/moodle/competency/tests" />
+        <option value="$PROJECT_DIR$/moodle/my/tests" />
+        <option value="$PROJECT_DIR$/moodle/auth/tests" />
+        <option value="$PROJECT_DIR$/moodle/blocks/tests" />
+        <option value="$PROJECT_DIR$/moodle/login/tests" />
+        <option value="$PROJECT_DIR$/moodle/plagiarism/tests" />
+        <option value="$PROJECT_DIR$/moodle/portfolio/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/editor/tests" />
+        <option value="$PROJECT_DIR$/moodle/rss/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/table/tests" />
+        <option value="$PROJECT_DIR$/moodle/h5p/tests" />
+        <option value="$PROJECT_DIR$/moodle/lib/xapi/tests" />
+        <option value="$PROJECT_DIR$/moodle/contentbank/tests" />
+        <option value="$PROJECT_DIR$/moodle/payment/tests" />
+        <option value="$PROJECT_DIR$/moodle/reportbuilder/tests" />
+        <option value="$PROJECT_DIR$/moodle/admin/presets/tests" />
+        <option value="$PROJECT_DIR$/moodle/admin/tests" />
+      </list>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ea42099ae68b53475066a9407ab2461d31ac580b
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="" vcs="Git" />
+    <mapping directory="$PROJECT_DIR$/moodle" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/README.md b/README.md
index cd5d17f8db975cc7d2b253a9e0e0eb46d691e7c8..77cf62d1a4d527c6a7f270b7f69813ae62fa3e33 100644
--- a/README.md
+++ b/README.md
@@ -1,32 +1,84 @@
 <div align=center>
-  <h1>Moodle Dev Environment</h1>
+  <h1>Moodle Autograde plugin</h1>
 </div>
 
 > ⚠️ The following readme is yet to be completed. For now, it contains basic information about the repository.
 
 ## Set up locally
 
-- Install *Docker* and *Docker Compose*
+- Install [_minikube_](https://minikube.sigs.k8s.io/docs/start/) and [_kubectl_](https://kubernetes.io/docs/tasks/tools/) on your machine.
+
 - clone the repository using the following command:
   ```console
-  git clone --recursive git@github.com:hamzaremmal/moodle-dev-env.git
+  git clone --recursive git@github.com:hamzaremmal/moodle-autograde.git
   ```
 
 ## Test your environment
 
-- Start everything with one command :
+- Start minikube with the following command:
+
   ```console
-  docker compose up --build
+  minikube start --driver=docker --mount --mount-string="$(pwd):/repo"
+  ```
+
+  _This will also switch your kubectl context to minikube_
+
+- Build the grading service image with the following commands:
+
+  - _Switch to minikube docker environment_
+
+    ```console
+    eval $(minikube docker-env)
+    ```
+
+  - _Build the image_
+
+    ```console
+    docker build -t ghcr.io/hamzaremmal/moodle-autograde/moodle-grading-service:latest moodle-grading-service/
+    ```
+
+- Configure the dotenv file `k8s-base/grading-service-variables.env` with the following content:
+
   ```
+  API_KEY= <Add the autograde service API-KEY>
+  MOODLE_BASE_URL= <Add the moodle base url, `http://moodle:80` if you are using the local environment>
+  MOODLE_AUTOGRADE_TOKEN= <Add the autograde moodle token>
+  GRADING_SERVICE_NAME= <Add the name with which the grading-service is deployed, by default it is `grading-service-tcp`>
+  ```
+
+- Deploy the environment with the following command:
+
+  ```console
+  kubectl apply -k k8s-overlays/local
+  ```
+
 - Kill everything with one command:
+
   ```console
-  docker compose down
+  kubectl delete -k k8s-overlays/local
   ```
-- Access moodle via http://localhost:8080
-- Ping web service with http://localhost:8082/api/v1/ping
 
-> ℹ️ When accessing Moodle for the first time, you will need to install it. This step is pretty simple as all the links with the database are already configured (See config.php ;-) )
+- To access the services, you will need to:
+
+  - Add the following lines to your `/etc/hosts` file:
+
+    ```
+    localhost    moodle
+    ```
 
+  - Start a new terminal session and run :
+
+    ```console
+    minikube tunnel
+    ```
+
+    _This will ask for your password because it configures network routing on your machine._
+
+  - Access Moodle via http://moodle:80
+  - Ping the grading service with http://localhost:8082/api/v1/ping
+
+> ℹ️ When accessing Moodle for the first time, you will need to install it. This step is pretty simple as all the links with the database are already configured (See config.php ;-) )
 
 ## Support
+
 TODO HR : Create an email address in https://groups.epfl.ch
diff --git a/config.php b/config.php
index 198c7c5c7865474f6785a6bcd4f1db4d8afa8565..1068502106803c12f453b1801d3ac0ca7d44b562 100644
--- a/config.php
+++ b/config.php
@@ -18,7 +18,7 @@ $CFG->dboptions = array (
   'dbcollation' => 'utf8mb4_0900_ai_ci',
 );
 
-$CFG->wwwroot   = 'http://localhost:8080';
+$CFG->wwwroot   = 'http://moodle:80';
 $CFG->dataroot  = '/var/www/moodledata';
 $CFG->admin     = 'admin';
 
diff --git a/docker-compose.yml b/docker-compose.yml
index cac8fff2dbf58a1d62486e507ec9705c026e0e9e..e57335367c3804b720db3d587bff03fd48ad3c32 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,4 +1,4 @@
-version: '3'
+version: "3"
 services:
   db:
     container_name: db
@@ -11,18 +11,22 @@ services:
       MYSQL_PASSWORD: moodle
     volumes:
       - db-data:/var/lib/mysql
+    ports:
+      - "3305:3306"
   grading-service:
-    container_name: grading.service
+    container_name: grading-service
     build:
       context: ./moodle-grading-service
       dockerfile: Dockerfile
     ports:
       - "8082:8082"
+    env_file:
+      - grading-service-variables.env
   moodle:
     container_name: moodle
     image: moodlehq/moodle-php-apache:8.0
     ports:
-      - "8080:80"
+      - "80:80"
     depends_on:
       - db
     environment:
@@ -40,7 +44,7 @@ services:
       - ./php-config:/docker-entrypoint.d
       - moodle-data:/var/www/moodledata
       # Mount the plugin
-      - ./moodle-assignsubmission-autograding:/var/www/html/mod/assign/submission/docker_submission
+      - ./moodle-assignsubmission-autograde:/var/www/html/mod/assign/submission/autograde
   selenium:
     image: "selenium/standalone-firefox"
     volumes:
diff --git a/k8s-base/default-network-policy.yaml b/k8s-base/default-network-policy.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..826a627b7b499b34bdee88c38d000c8004cddf57
--- /dev/null
+++ b/k8s-base/default-network-policy.yaml
@@ -0,0 +1,13 @@
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+  name: default
+spec:
+  ingress:
+    - from:
+        - podSelector:
+            matchLabels:
+              network: "default"
+  podSelector:
+    matchLabels:
+      network: "default"
diff --git a/k8s-base/grading-service-variables.env b/k8s-base/grading-service-variables.env
new file mode 100644
index 0000000000000000000000000000000000000000..fbb87747bde1b98ca8a4ccaf50b1c6881f645179
--- /dev/null
+++ b/k8s-base/grading-service-variables.env
@@ -0,0 +1,4 @@
+API_KEY="12345"
+MOODLE_BASEURL="http://moodle:80"
+MOODLE_AUTOGRADE_TOKEN="xxxxxxxxxxxxxx"
+GRADING_SERVICE_NAME="grading-service-tcp"
diff --git a/k8s-base/grading-service.yaml b/k8s-base/grading-service.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c16a79cd26845487701f1336b14bf4fdf509558b
--- /dev/null
+++ b/k8s-base/grading-service.yaml
@@ -0,0 +1,54 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: grading-service
+  name: grading-service-tcp
+spec:
+  ports:
+    - name: "8082"
+      port: 8082
+      targetPort: 8082
+  selector:
+    app: grading-service
+  type: LoadBalancer
+status:
+  loadBalancer: {}
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: grading-service
+  labels:
+    app: grading-service
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: grading-service
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        network: default
+        app: grading-service
+    spec:
+      serviceAccountName: moodle
+      restartPolicy: Always
+      containers:
+        - image: ghcr.io/hamzaremmal/moodle-autograde/moodle-grading-service:latest
+          name: grading-service
+          imagePullPolicy: IfNotPresent
+          ports:
+            - containerPort: 8082
+          envFrom:
+            - secretRef:
+                name: grading-service-variables
+          resources:
+            limits:
+              memory: "512Mi"
+              cpu: "500m"
+status: {}
diff --git a/k8s-base/kustomization.yaml b/k8s-base/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..82c4278b87ecdda64b970f96a615363e0d25b9e9
--- /dev/null
+++ b/k8s-base/kustomization.yaml
@@ -0,0 +1,15 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+  - grading-service.yaml
+  - default-network-policy.yaml
+
+secretGenerator:
+  - name: grading-service-variables
+    envs:
+      - grading-service-variables.env
+    type: Opaque
+
+generatorOptions:
+  disableNameSuffixHash: true
diff --git a/k8s-overlays/local/db.yaml b/k8s-overlays/local/db.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..a7e899612a4c8804e1a1324804990e5459c702c1
--- /dev/null
+++ b/k8s-overlays/local/db.yaml
@@ -0,0 +1,83 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: db
+  name: db
+spec:
+  clusterIP: None
+  ports:
+    - name: "3305"
+      port: 3305
+      targetPort: 3306
+  selector:
+    app: db
+  type: ClusterIP
+status:
+  loadBalancer: {}
+
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: db-data
+  name: db-data
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 100Mi
+status: {}
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: db
+  name: db
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: db
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        network: "default"
+        app: db
+    spec:
+      containers:
+        - args:
+            - --default-authentication-plugin=mysql_native_password
+          env:
+            - name: MYSQL_DATABASE
+              value: moodle
+            - name: MYSQL_PASSWORD
+              value: moodle
+            - name: MYSQL_ROOT_PASSWORD
+              value: moodle
+            - name: MYSQL_USER
+              value: moodle
+          image: mysql:8.0.33
+          name: db
+          ports:
+            - containerPort: 3306
+          resources:
+            limits:
+              cpu: 500m
+              memory: 500Mi
+          volumeMounts:
+            - mountPath: /var/lib/mysql
+              name: db-data
+      restartPolicy: Always
+      volumes:
+        - name: db-data
+          persistentVolumeClaim:
+            claimName: db-data
+status: {}
diff --git a/k8s-overlays/local/grading-service-account.yaml b/k8s-overlays/local/grading-service-account.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..18ca3bf4ff5fff497a4d35e49b365729bcaabfa0
--- /dev/null
+++ b/k8s-overlays/local/grading-service-account.yaml
@@ -0,0 +1,51 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: moodle
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: job-manager-role
+rules:
+  - apiGroups: ["batch"]
+    resources: ["jobs"]
+    verbs: ["get", "list", "watch", "create", "update", "delete"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: secret-manager-role
+rules:
+  - apiGroups: [""]
+    resources: ["secrets"]
+    verbs: ["get", "list", "watch", "create", "update", "delete"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: moodle-job-manager-role-binding
+subjects:
+  - kind: ServiceAccount
+    name: moodle
+roleRef:
+  kind: Role
+  name: job-manager-role
+  apiGroup: rbac.authorization.k8s.io
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: moodle-secret-manager-role-binding
+subjects:
+  - kind: ServiceAccount
+    name: moodle
+roleRef:
+  kind: Role
+  name: secret-manager-role
+  apiGroup: rbac.authorization.k8s.io
diff --git a/k8s-overlays/local/kustomization.yaml b/k8s-overlays/local/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..26d0a2cb25612370728ef14ef5b9d1aee6db72ad
--- /dev/null
+++ b/k8s-overlays/local/kustomization.yaml
@@ -0,0 +1,11 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+namespace: cs107
+
+resources:
+  - ../../k8s-base
+  - db.yaml
+  - moodle.yaml
+  - grading-service-account.yaml
+  - namespace.yaml
diff --git a/k8s-overlays/local/moodle.yaml b/k8s-overlays/local/moodle.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..98de3cc93522f72ab22591d3c5f960dcbef0f8b4
--- /dev/null
+++ b/k8s-overlays/local/moodle.yaml
@@ -0,0 +1,122 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+  labels:
+    app: moodle
+  name: moodle-tcp
+spec:
+  ports:
+    - name: "80"
+      port: 80
+      targetPort: 80
+  selector:
+    app: moodle
+  type: LoadBalancer
+status:
+  loadBalancer: {}
+
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  labels:
+    app: moodle
+  name: moodle
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: moodle
+  strategy:
+    type: Recreate
+  template:
+    metadata:
+      labels:
+        network: "default"
+        app: moodle
+    spec:
+      containers:
+        - env:
+            - name: MOODLE_DB_HOST
+              value: db
+            - name: MOODLE_DB_NAME
+              value: moodle
+            - name: MOODLE_DB_PASSWORD
+              value: moodle
+            - name: MOODLE_DB_PORT
+              value: "3306"
+            - name: MOODLE_DB_TYPE
+              value: mysqli
+            - name: MOODLE_DB_USER
+              value: moodle
+            - name: MOODLE_URL
+              value: http://localhost
+          name: moodle
+          image: moodlehq/moodle-php-apache:8.0
+          ports:
+            - containerPort: 80
+          resources:
+            limits:
+              memory: "1000Mi"
+              cpu: "1000m"
+          volumeMounts:
+            - name: moodle-data
+              mountPath: /var/www/moodledata
+            - name: moodle-html
+              mountPath: /var/www/html
+            - name: moodle-config
+              mountPath: /var/www/html/config.php
+            - name: php-config
+              mountPath: /docker-entrypoint.d
+            - name: moodle-autograde
+              mountPath: /var/www/html/mod/assign/submission/autograde
+      restartPolicy: Always
+      volumes:
+        - name: moodle-data
+          persistentVolumeClaim:
+            claimName: moodle-data-pvc
+        - name: moodle-html
+          hostPath:
+            path: /repo/moodle
+        - name: moodle-config
+          hostPath:
+            path: /repo/config.php
+        - name: php-config
+          hostPath:
+            path: /repo/php-config
+        - name: moodle-autograde
+          hostPath:
+            path: /repo/moodle-assignsubmission-autograde
+status: {}
+
+---
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+  labels:
+    app: moodle-data
+  name: moodle-data-pvc
+spec:
+  accessModes:
+    - ReadWriteOnce
+  resources:
+    requests:
+      storage: 1Gi
+status: {}
+
+---
+apiVersion: v1
+kind: PersistentVolume
+metadata:
+  labels:
+    app: moodle-data
+  name: moodle-data-pv
+spec:
+  storageClassName: manual
+  capacity:
+    storage: 1Gi
+  accessModes:
+    - ReadWriteOnce
+  hostPath:
+    path: /data/moodle-data/
diff --git a/k8s-overlays/local/namespace.yaml b/k8s-overlays/local/namespace.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d0dcc911e9e4f9ddd38539c52d3f8830a25489c0
--- /dev/null
+++ b/k8s-overlays/local/namespace.yaml
@@ -0,0 +1,4 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: cs107
diff --git a/k8s-overlays/prod/increase_memory.yaml b/k8s-overlays/prod/increase_memory.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..10621592f857707c1e4025ae55856ed1a1052ae8
--- /dev/null
+++ b/k8s-overlays/prod/increase_memory.yaml
@@ -0,0 +1,16 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: grading-service
+spec:
+  selector:
+    matchLabels:
+      app: grading-service
+  template:
+    spec:
+      containers:
+        - name: grading-service
+          resources:
+            limits:
+              memory: "2Gi"
+              cpu: "1000m"
diff --git a/k8s-overlays/prod/kustomization.yaml b/k8s-overlays/prod/kustomization.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f465e2d1c5e90abbc2fd0ecc8db9ce2fc5e47880
--- /dev/null
+++ b/k8s-overlays/prod/kustomization.yaml
@@ -0,0 +1,15 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+  - ../../k8s-base
+
+patchesStrategicMerge:
+  - increase_memory.yaml
+
+secretGenerator:
+  - name: grading-service-variables
+    envs:
+      - grading-service-variables.prod.env
+    type: Opaque
+    behavior: replace
diff --git a/moodle-assignsubmission-autograde/.gitignore b/moodle-assignsubmission-autograde/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..cc260178a15d57c37af1b42e18f818e54064cdd4
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.gitignore
@@ -0,0 +1,79 @@
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# AWS User-specific
+.idea/**/aws.xml
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# SonarLint plugin
+.idea/sonarlint/
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
diff --git a/moodle-assignsubmission-autograde/.idea/dataSources.xml b/moodle-assignsubmission-autograde/.idea/dataSources.xml
new file mode 100644
index 0000000000000000000000000000000000000000..379a325f05d9fe0867a6929a3ff2f5d6b7d00725
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.idea/dataSources.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
+    <data-source source="LOCAL" name="moodle" uuid="6470205f-982c-4b3e-91bb-6aed7cd4bd20">
+      <driver-ref>mysql.8</driver-ref>
+      <synchronize>true</synchronize>
+      <jdbc-driver>com.mysql.cj.jdbc.Driver</jdbc-driver>
+      <jdbc-url>jdbc:mysql://localhost:3305/moodle</jdbc-url>
+      <working-dir>$ProjectFileDir$</working-dir>
+    </data-source>
+  </component>
+</project>
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/.idea/modules.xml b/moodle-assignsubmission-autograde/.idea/modules.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c81fe137a1fc903edb7ea7636092bb7585ea978c
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/moodle-assignsubmission-autograde.iml" filepath="$PROJECT_DIR$/.idea/moodle-assignsubmission-autograde.iml" />
+    </modules>
+  </component>
+</project>
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/.idea/moodle-assignsubmission-autograde.iml b/moodle-assignsubmission-autograde/.idea/moodle-assignsubmission-autograde.iml
new file mode 100644
index 0000000000000000000000000000000000000000..239769462a25502a125be21849b063f9ff9c23d6
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.idea/moodle-assignsubmission-autograde.iml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/classes" isTestSource="false" packagePrefix="assignsubmission_autograde" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/.idea/php.xml b/moodle-assignsubmission-autograde/.idea/php.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5fe959c3187d05713710627214df6262ddfd8d37
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.idea/php.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="MessDetectorOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCSFixerOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PHPCodeSnifferOptionsConfiguration">
+    <option name="highlightLevel" value="WARNING" />
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PhpIncludePathManager">
+    <include_path>
+      <path value="$PROJECT_DIR$/../moodle" />
+    </include_path>
+  </component>
+  <component name="PhpProjectSharedConfiguration" php_language_level="7.1">
+    <option name="suggestChangeDefaultLanguageLevel" value="false" />
+  </component>
+  <component name="PhpStanOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+  <component name="PsalmOptionsConfiguration">
+    <option name="transferred" value="true" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/.idea/vcs.xml b/moodle-assignsubmission-autograde/.idea/vcs.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6c0b8635858dc7ad44b93df54b762707ce49eefc
--- /dev/null
+++ b/moodle-assignsubmission-autograde/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$/.." vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograding/README.md b/moodle-assignsubmission-autograde/README.md
similarity index 100%
rename from moodle-assignsubmission-autograding/README.md
rename to moodle-assignsubmission-autograde/README.md
diff --git a/moodle-assignsubmission-autograde/classes/autograde_webservice.php b/moodle-assignsubmission-autograde/classes/autograde_webservice.php
new file mode 100644
index 0000000000000000000000000000000000000000..cc7b905848160faf56c2d8281fceaad19178eef8
--- /dev/null
+++ b/moodle-assignsubmission-autograde/classes/autograde_webservice.php
@@ -0,0 +1,167 @@
+<?php
+
+namespace assignsubmission_autograde;
+
+use dml_exception;
+use stdClass;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+final class autograde_webservice {
+
+    private string $api_key;
+
+    /**
+     * @param string $api_key ???
+     */
+    public function __construct(string $api_key) {
+        $this->api_key = $api_key;
+    }
+
+    /**
+     * ???
+     * @param int $timeout ???
+     * @param bool $auth ???
+     * @return mixed ???
+     * @throws dml_exception ???
+     */
+    public function ping(int $timeout = 2, bool $auth = false) {
+        // HR : Build the URL fot the grade function
+        $url = $auth
+            ? $this->url('/api/v1/ping/auth')
+            : $this->url('/api/v1/ping/no-auth');
+        // HR : Prepare the headers
+        $headers = [
+            $this->api_key_header($this->api_key)
+        ];
+        // HR : Send request using cURL
+        $curl = curl_init();
+        curl_setopt($curl, CURLOPT_URL, $url);
+        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($curl, CURLOPT_TIMEOUT, $timeout);
+        // HR : Only add the key in auth mode
+        if ($auth)
+            curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
+        curl_exec($curl);
+        curl_close($curl);
+        // Return the response
+        return curl_getinfo($curl);
+    }
+
+    /**
+     * ???
+     * @param $userid ???
+     * @param $courseid ???
+     * @param $assignmentid ???
+     * @param $submissionid ???
+     * @return stdClass ???
+     * @throws dml_exception ???
+     */
+    public function grade(
+        $userid, $courseid,
+        $assignmentid, $submissionid,
+        $graderImageName, $dockerRegistryCredential
+    ) : stdClass {
+        // HR : Build the URL for the grade function
+        $url = $this->url('/api/v1/grading/grade');
+        // HR : Prepare the request body (JSON)
+        $body = array(
+            'userid' => $userid,
+            'courseid' => $courseid,
+            'assignmentid' => $assignmentid,
+            'submissionid' => $submissionid,
+            'imageName' => $graderImageName,
+            'registryCredential' => $dockerRegistryCredential
+        );
+        // HR : Prepare the headers
+        $headers = [
+            $this->api_key_header($this->api_key),
+            'Content-Type: application/json'
+        ];
+        // HR : Prepare cURL options
+        $options = array(
+            CURLOPT_URL => $url,
+            CURLOPT_POST => true,
+            CURLOPT_POSTFIELDS => json_encode($body),
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_HTTPHEADER => $headers
+        );
+        // HR : Send request using cURL
+        $curl = curl_init();
+        curl_setopt_array($curl, $options);
+        $response = curl_exec($curl);
+        curl_close($curl);
+        // Return the response
+        $r = new stdClass();
+        $r->response = $response;
+        $r->info = curl_getinfo($curl);
+        return $r;
+    }
+
+    /**
+     * ???
+     * @param $courseid ???
+     * @param $assignmentid
+     * @return stdClass ???
+     * @throws dml_exception
+     */
+    public function upload_credentials($courseid, $assignmentid): stdClass {
+        // HR : Build the URL for the grade function
+        $url = $this->url('/api/v1/registry/credentials/upload');
+        // HR : Prepare the request body (JSON)
+        $body = array(
+            'courseid' => $courseid,
+            'assignmentid' => $assignmentid,
+        );
+        // HR : Prepare the headers
+        $headers = [
+            $this->api_key_header($this->api_key),
+            'Content-Type: application/json'
+        ];
+        // HR : Prepare cURL options
+        $options = array(
+            CURLOPT_URL => $url,
+            CURLOPT_POST => true,
+            CURLOPT_POSTFIELDS => json_encode($body),
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_HTTPHEADER => $headers
+        );
+        // HR : Send request using cURL
+        $curl = curl_init();
+        curl_setopt_array($curl, $options);
+        $response = curl_exec($curl);
+        curl_close($curl);
+        // Return the response
+        $r = new stdClass();
+        $r->response = $response;
+        $r->info = curl_getinfo($curl);
+        return $r;
+    }
+
+    // ============================================================================================
+    // =================================== HELPER METHODS =========================================
+    // ============================================================================================
+
+    /**
+     * ???
+     * @param string $endpoint ???
+     * @return string ???
+     * @throws dml_exception ???
+     */
+    private function url(string $endpoint): string {
+        return get_config('assignsubmission_autograde', 'service_url') . $endpoint;
+    }
+
+    /**
+     * ???
+     * @param string $key ???
+     * @return string ???
+     */
+    private function api_key_header(string $key) : string {
+        return 'API-KEY: ' . $key;
+    }
+
+}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php b/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php
new file mode 100644
index 0000000000000000000000000000000000000000..ccffa273957db8f0221dc7dcdf2f8be78a14a6cd
--- /dev/null
+++ b/moodle-assignsubmission-autograde/classes/external/autograde_download_credentials.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace assignsubmission_autograde\external;
+
+use coding_exception;
+use context_course;
+use external_api;
+use external_function_parameters;
+use external_single_structure;
+use external_value;
+use moodle_exception;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+final class autograde_download_credentials extends external_api {
+
+    /**
+     * ???
+     * @return external_function_parameters ???
+     */
+    public static function download_credentials_parameters(): external_function_parameters {
+        return new external_function_parameters([
+            'courseid' => new external_value(PARAM_INT, 'Course ID'),
+            'assignmentid' => new external_value(PARAM_INT, 'Assignment ID')
+        ]);
+    }
+
+    /**
+     * ???
+     * @param $courseid ???
+     * @param $assignmentid ???
+     * @return object ???
+     * @throws coding_exception ???
+     * @throws moodle_exception ???
+     */
+    public static function download_credentials($courseid, $assignmentid): object {
+        $context = context_course::instance($courseid);
+        $component = 'assignsubmission_autograde';
+        $filearea = 'credentials';
+        $fs = get_file_storage();
+        $files = $fs->get_area_files(
+            $context->id,
+            $component,
+            $filearea,
+            0,
+            'itemid, filepath, filename',
+            false
+        );
+        // HR : Check if files are found
+        if (!empty($files)) {
+            // HR : Iterate over al the available files
+            foreach ($files as $file){
+                // HR : If we find a non-empty file, return it
+                // HR : We allow only one file for now
+                if(!empty($file->get_content())){
+                    // HR : Return the base64 encoding of the file's content
+                    return (object) [
+                        'content' => base64_encode($file->get_content())
+                    ];
+                }
+            }
+        } else {
+            // No files found, return an error
+            throw new moodle_exception('nofilefound', 'autograde');
+        }
+        throw new moodle_exception('all files are empty', 'autograde');
+    }
+
+    /**
+     * ???
+     * @return external_single_structure ???
+     */
+    public static function download_credentials_returns(): external_single_structure{
+        return new external_single_structure([
+            'content' => new external_value(PARAM_TEXT, 'File Content')
+        ]);
+    }
+
+}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/classes/external/autograde_download_submission.php b/moodle-assignsubmission-autograde/classes/external/autograde_download_submission.php
new file mode 100644
index 0000000000000000000000000000000000000000..638b13e68f50fdd6b1c1f856eadc4f84c58acb73
--- /dev/null
+++ b/moodle-assignsubmission-autograde/classes/external/autograde_download_submission.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace assignsubmission_autograde\external;
+
+
+use coding_exception;
+use context_course;
+use external_api;
+use external_function_parameters;
+use external_single_structure;
+use external_value;
+use moodle_exception;
+use stdClass;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+final class autograde_download_submission extends external_api {
+
+    /**
+     * ???
+     * @return external_function_parameters ???
+     */
+    public static function download_submission_parameters() {
+        return new external_function_parameters([
+            'courseid' => new external_value(PARAM_INT, 'Course ID'),
+            'submissionid' => new external_value(PARAM_INT, 'Submission ID')
+        ]);
+    }
+
+    /**
+     * ???
+     * @param $courseid ???
+     * @param $submissionid ???
+     * @return stdClass ???
+     * @throws coding_exception ???
+     * @throws moodle_exception ???
+     */
+    public static function download_submission($courseid, $submissionid): stdClass {
+        $context = context_course::instance($courseid);
+        $component = 'assignsubmission_autograde';
+        $filearea = 'submissions';
+        // HR : Retrieve the files associated with the specified area and item
+        $fs = get_file_storage();
+        $files = $fs->get_area_files(
+            $context->id,
+            $component,
+            $filearea,
+            $submissionid,
+            'itemid, filepath, filename',
+            false
+        );
+        // HR : Check if files are found
+        if (!empty($files)) {
+            // HR : Iterate over al the available files
+            foreach ($files as $file){
+                // HR : If we find a non-empty file, return it
+                // HR : We allow only one file for now
+                if(!empty($file->get_content())){
+                    // HR : Return the base64 encoding of the file's content
+                    return (object) [
+                        'content' => base64_encode($file->get_content())
+                    ];
+                }
+            }
+        } else {
+            // No files found, return an error
+            throw new moodle_exception('nofilefound', 'autograde');
+        }
+        throw new moodle_exception('all files are empty', 'autograde');
+    }
+
+    /**
+     * ???
+     * @return external_single_structure ???
+     */
+    public static function download_submission_returns(): external_single_structure{
+        return new external_single_structure([
+            'content' => new external_value(PARAM_TEXT, 'File Content')
+        ]);
+    }
+
+}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/classes/external/autograde_upload_feedback.php b/moodle-assignsubmission-autograde/classes/external/autograde_upload_feedback.php
new file mode 100644
index 0000000000000000000000000000000000000000..8b435bff53b2162777db1a8cff7014f26da2cfcb
--- /dev/null
+++ b/moodle-assignsubmission-autograde/classes/external/autograde_upload_feedback.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace assignsubmission_autograde\external;
+
+use external_api;
+use external_function_parameters;
+use external_value;
+use stdClass;
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+
+require_once($CFG->libdir . '/externallib.php');
+require_once($CFG->dirroot . '/mod/assign/locallib.php');
+
+
+/**
+ * ???
+ *
+ * See : https://moodledev.io/docs/apis/subsystems/external/functions
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+final class autograde_upload_feedback extends external_api {
+
+    /**
+     * ???
+     * @return external_function_parameters ???
+     */
+   public static function upload_feedback_parameters() {
+       return new external_function_parameters([
+           'userid' => new external_value(PARAM_INT, 'The user ID'),
+           'courseid' => new external_value(PARAM_INT, 'The user ID'),
+           'assignmentid' => new external_value(PARAM_INT, 'The user ID'),
+           'submissionid' => new external_value(PARAM_INT, 'The user ID'),
+           'grade' => new external_value(PARAM_INT, 'The user ID'),
+           'feedback' => new external_value(PARAM_TEXT, 'The feedback message'),
+       ]);
+   }
+
+    /**
+     * ???
+     * @param $userid ???
+     * @param $courseid ???
+     * @param $assignmentid ???
+     * @param $submissionid ???
+     * @param $grade ???
+     * @param $feedback ???
+     * @return true ???
+     */
+   public static function upload_feedback (
+       $userid, $courseid,
+       $assignmentid, $submissionid,
+       $grade, $feedback
+   ) {
+
+       global $DB, $USER;
+
+       //  HR : Fetch metadata about the assignment
+       $assignment = $DB->get_record('assign', array('id' => $assignmentid));
+
+       // HR : Sanity check : Are the actual course id similar to the expected one ?
+       //if ($assignment->course != $courseid) {
+       //    throw new coding_exception('Invalid assignment or course ID. Please verify the assignment ('.$assignmentid.') and course details.');
+       //}
+
+       // HR : Fetch the metadata of the submission
+       $submission = $DB->get_record('assign_submission', array('id' => $submissionid));
+
+       // HR : Sanity check : Is the submission part of the course and is it linked to the user ?
+       //if ($submission->assignment != $assignmentid || $submission->userid != $userid) {
+       //    throw new coding_exception('Invalid submission or user ID');
+       //}
+
+       // ================================ assign_grades table ===================================
+
+       // HR : Create an entry in assign_grades
+       $gradegrade = new stdClass();
+       $gradegrade->assignment = $assignment->id; // HR : Link the entry to the assignment
+       $gradegrade->userid = $submission->userid; // HR : Link the entry to the user
+       $gradegrade->grade = $grade;               // HR : Store the actual grade
+       $gradegrade->feedback = $feedback;         // HR : Store some comment as a feedback
+       $gradegrade->grader = $USER->id;           // HR : Identify the grader (inferred from the context)
+       $gradegrade->timecreated = time();         // HR : Timestamp
+       $gradegrade->timemodified = time();        // HR : Timestamp
+
+       // HR : Save the above entry in the assign_grades table
+       $gradegrade->id = $DB->insert_record('assign_grades', $gradegrade);
+
+       // ==================================== grade_grades table ================================
+
+       $gradeitem = $DB->get_record('grade_items', ['itemmodule' => 'assign', 'iteminstance' => $assignmentid]);
+
+       $gradegradedata = new stdClass();
+       $gradegradedata->itemid = $gradeitem->id;
+       $gradegradedata->userid = $submission->userid;
+       $gradegradedata->rawgrade = $grade;
+       $gradegradedata->finalgrade = $grade;
+       $gradegradedata->usermodified = $userid;
+       $gradegradedata->hidden = 0;
+       $gradegradedata->timecreated = time();
+       $gradegradedata->timemodified = time();
+       $gradegradedata->aggregationstatus = 'used';
+       $gradegradedata->aggregationweight = 1;
+
+       // HR : Save the grade in the GradeBook
+       $gradegradedata->id = $DB->insert_record('grade_grades', $gradegradedata);
+
+       // ============================== feedback comment plugin support ==========================
+
+       //$feedbackcomment = new stdClass();
+       //$feedbackcomment->commenttext = $feedback;
+       //$feedbackcomment->commentformat = FORMAT_HTML;
+       //$feedbackcomment->grade = $gradegrade->id;
+       //$feedbackcomment->assignment = $assignmentid;
+       //$DB->insert_record('assignfeedback_comments', $feedbackcomment);
+
+       //$grade_item = grade_item::fetch(array('id' => $assignmentid));
+       //$grade_item->force_regrading();
+       //$grade_item->update_final_grade($userid, $newgrade);
+       //$grade_item->regrade_final_grades($userid);
+
+       return true;
+   }
+
+    /**
+     * ???
+     * @return external_value ???
+     */
+   public static function upload_feedback_returns() {
+       return new external_value(PARAM_BOOL, 'Feedback added successfully');
+   }
+}
diff --git a/moodle-assignsubmission-autograde/css/autograde.css b/moodle-assignsubmission-autograde/css/autograde.css
new file mode 100644
index 0000000000000000000000000000000000000000..bc5039b00d44fbed778c5f78212793fb97aa072d
--- /dev/null
+++ b/moodle-assignsubmission-autograde/css/autograde.css
@@ -0,0 +1,3 @@
+h2 {
+    color: aqua;
+}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/db/services.php b/moodle-assignsubmission-autograde/db/services.php
new file mode 100644
index 0000000000000000000000000000000000000000..5b5181e592aa0ee122772544b0255359d119c051
--- /dev/null
+++ b/moodle-assignsubmission-autograde/db/services.php
@@ -0,0 +1,42 @@
+<?php
+
+defined('MOODLE_INTERNAL') || die();
+
+
+// https://moodledev.io/docs/apis/subsystems/external/description
+
+$functions = [
+   'mod_assignsubmission_autograde_upload_feedback' => [
+       'classname' => 'assignsubmission_autograde\external\autograde_upload_feedback',
+       'methodname' => 'upload_feedback',
+       'description' => 'Upload an autograde feedback',
+       //'capabilities' => '',
+   ],
+    'mod_assignsubmission_autograde_download_submission' => [
+        'classname' => 'assignsubmission_autograde\external\autograde_download_submission',
+        'methodname' => 'download_submission',
+        'description' => 'Download an autograde submission',
+        //'capabilities' => '',
+    ],
+    'mod_assignsubmission_autograde_download_credentials' => [
+        'classname' => 'assignsubmission_autograde\external\autograde_download_credentials',
+        'methodname' => 'download_credentials',
+        'description' => 'Download an autograde credential',
+        //'capabilities' => '',
+    ],
+];
+
+// https://moodledev.io/docs/apis/subsystems/external/advanced/custom-services
+
+$services = [
+  'AUTOGRADE' => [
+      'functions' => [
+          'mod_assignsubmission_autograde_upload_feedback',
+          'mod_assignsubmission_autograde_download_submission',
+          'mod_assignsubmission_autograde_download_credentials'
+      ],
+      'restrictedusers' => 1,
+      'enabled' => 1,
+      'shortname' => 'autograde-service'
+  ]
+];
diff --git a/moodle-assignsubmission-autograding/lang/en/assignsubmission_docker_submission.php b/moodle-assignsubmission-autograde/lang/en/assignsubmission_autograde.php
similarity index 69%
rename from moodle-assignsubmission-autograding/lang/en/assignsubmission_docker_submission.php
rename to moodle-assignsubmission-autograde/lang/en/assignsubmission_autograde.php
index b22eb6bf2a0d715000e0e7c56007ec5b23eb3509..36b999f5763dc76627df328fe0bab8c1cf5adaf3 100644
--- a/moodle-assignsubmission-autograding/lang/en/assignsubmission_docker_submission.php
+++ b/moodle-assignsubmission-autograde/lang/en/assignsubmission_autograde.php
@@ -1,6 +1,6 @@
 <?php
 
-$string["pluginname"] = "Docker Submission";
+$string["pluginname"] = "Autograde";
 
 // ====================================== FORM ELEMENTS USER ======================================
 
@@ -14,4 +14,10 @@ $string["student_view_summary_header"] = "Feedback";
 $string["setting_docker-image"] = "Docker Image";
 $string["setting_docker-image_help"] = "Help Docker Image";
 
+$string["setting_docker-registry-token"] = "Docker Registry Token";
+$string["setting_docker-registry-token_help"] = "Help Docker Registry Setting";
+
+$string["setting_webservice-key"] = "Web Service Key";
+$string["setting_webservice-key_help"] = "Help Web Service Key Setting";
+
 // ======================================= ADMIN SETTINGS =========================================
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/lib.php b/moodle-assignsubmission-autograde/lib.php
new file mode 100644
index 0000000000000000000000000000000000000000..6cf2904440545be201da82658728b9a9fd6e7b4b
--- /dev/null
+++ b/moodle-assignsubmission-autograde/lib.php
@@ -0,0 +1,3 @@
+<?php
+
+// TODO HR : Add all the generic functions implemented by this plugin
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/locallib.php b/moodle-assignsubmission-autograde/locallib.php
new file mode 100644
index 0000000000000000000000000000000000000000..29d41f201ac6bc7d04ccf2d77c12f12a9626c06b
--- /dev/null
+++ b/moodle-assignsubmission-autograde/locallib.php
@@ -0,0 +1,391 @@
+<?php
+
+use assignsubmission_autograde\autograde_webservice;
+use core\notification;
+
+defined('MOODLE_INTERNAL') || die();
+
+const ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA = 'submissions';
+
+const ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA = 'credentials';
+
+/**
+ * Autograde Submission plugin.
+ *
+ * This plugin serves submissions to a webserver, so they can be graded automatically.
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+final class assign_submission_autograde extends assign_submission_plugin {
+
+    private const COMPONENT_NAME = "assignsubmission_autograde";
+    private const setting_docker_image_id = "docker_image";
+
+    private const setting_docker_registry_token_id = "docker_registry_token";
+
+    private const setting_webservice_key_id = "webservice_key";
+
+
+    /**
+     * Get the name of this plugin
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_name
+     * @return string plugin name
+     * @throws coding_exception
+     */
+    public function get_name(): string {
+        return get_string("pluginname", self::COMPONENT_NAME);
+    }
+
+    // ============================================================================================
+    // ======================================= SETTINGS ===========================================
+    // ============================================================================================
+
+    /**
+     * Configure the settings for the docker_submission plugin
+     *
+     * We only need the docker image of the grader to run
+     * @note See documentation : https://docs.moodle.org/dev/Form_API
+     * @param MoodleQuickForm $mform
+     * @return void
+     * @throws coding_exception
+     */
+    public function get_settings(MoodleQuickForm $mform) {
+        // HR : More details : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text
+        $mform->addElement("text",
+            self::setting_docker_image_id,
+            get_string("setting_docker-image", self::COMPONENT_NAME),
+            null);
+        // HR : More details : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addHelpButton
+        $mform->addHelpButton(
+            self::setting_docker_image_id,
+            "setting_docker-image",
+            self::COMPONENT_NAME);
+        // HR : See : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hideIf
+        $mform->hideIf(
+            self::setting_docker_image_id,
+            'assignsubmission_autograde_enabled',
+            'notchecked');
+
+        // HR : Add filemanager to store the docker registry token
+        $credentials_options = $this->get_credentials_option();
+        $mform->addElement("filemanager", "credentials", "Registry Credentials", null, $credentials_options);
+        $mform->hideIf(
+            'credentials',
+            'assignsubmission_autograde_enabled',
+            'notchecked');
+
+        // HR : Add entry to store the API_KEY
+        $mform->addElement(
+            "password",
+            self::setting_webservice_key_id,
+            get_string("setting_webservice-key", self::COMPONENT_NAME),
+            null);
+        $mform->addHelpButton(
+            self::setting_webservice_key_id,
+            "setting_webservice-key",
+            self::COMPONENT_NAME);
+        $mform->hideIf(
+            self::setting_webservice_key_id,
+            'assignsubmission_autograde_enabled',
+            'notchecked');
+    }
+
+    /**
+     * Save the settings of the docker_submission plugin
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#save_settings
+     * @param stdClass $data ???
+     * @return bool ???
+     * @throws coding_exception
+     * @throws dml_exception
+     */
+    public function save_settings(stdClass $data): bool {
+        // HR : Store the Docker image in the configuration
+        global $COURSE;
+        $this->set_config(self::setting_docker_image_id, $data->docker_image);
+        // HR : Store the WebService API_KEY in the configuration setting
+        $this->set_config(self::setting_webservice_key_id, $data->webservice_key);
+
+        // HR : Save the credentials in the corresponding file area
+        file_save_draft_area_files(
+            $data->credentials,
+            context_course::instance($COURSE->id)->id,
+            self::COMPONENT_NAME,
+            ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA,
+            0
+        );
+
+        // HR : Send request to the autograde service to update the credentials
+        // HR : also test the API_KEY
+        $autograde_webservice = new autograde_webservice($this->get_config(self::setting_webservice_key_id));
+        $response = $autograde_webservice->upload_credentials($COURSE->id, $this->assignment->get_instance()->id);
+        // HR : Analyze the response
+        if ($response->info['http_code'] == 200){
+            notification::info('The credentials were correctly updated in the service');
+        } else if ($response->info['http_code'] == 403) {
+            $this->set_error('The provided API_KEY is not valid !');
+            return false;
+        } else if($response->info['http_code'] == 500){
+            $this->set_error("internal error : " . $response->response);
+            return false;
+        } else if($response->info['http_code'] == 0){
+            $this->set_error("autograde service not found. please contact the support.");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * ???
+     * @param $defaultvalues ???
+     * @return void ???
+     */
+    public function data_preprocessing(&$defaultvalues) {
+        // HR : Call the parent function
+        global $COURSE;
+        parent::data_preprocessing($defaultvalues);
+        // HR : Fill in the docker image setting if it was previously filled
+        $defaultvalues[self::setting_docker_image_id] =
+            $this->get_config(self::setting_docker_image_id);
+        // HR : Fill in the API-KEY setting if it was previously filled
+        $defaultvalues[self::setting_webservice_key_id] =
+            $this->get_config(self::setting_webservice_key_id);
+
+        $draftitemid = file_get_submitted_draft_itemid('credentials');
+        file_prepare_draft_area(
+            $draftitemid,
+            context_course::instance($COURSE->id)->id,
+            self::COMPONENT_NAME,
+            ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA,
+            0,
+            $this->get_credentials_option()
+        );
+        $defaultvalues['credentials'] = $draftitemid;
+    }
+
+    // ============================================================================================
+    // ================================== SUBMISSION FORM =========================================
+    // ============================================================================================
+
+    /**
+     * ???
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_form_elements
+     * @param $submissionorgrade
+     * @param MoodleQuickForm $mform
+     * @param stdClass $data
+     * @param $userid
+     * @return bool
+     * @throws coding_exception
+     */
+    public function get_form_elements_for_user($submissionorgrade,
+                                               MoodleQuickForm $mform,
+                                               stdClass $data,
+                                               $userid): bool {
+        global $COURSE;
+        // HR : Accepted files
+        $fileoptions = $this->get_submission_options();
+
+        // HR : Prepare the filemanager for the submission
+        $data = file_prepare_standard_filemanager(
+            $data,
+            'tasks',
+            $fileoptions,
+            context_course::instance($COURSE->id),
+            self::COMPONENT_NAME,
+            ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA,
+            $submissionorgrade->id);
+
+        $name = get_string("form_filemanager", self::COMPONENT_NAME);
+        $element_id = "tasks_filemanager";
+        $mform->addElement('filemanager', $element_id, $name, null, $fileoptions);
+
+        $mform->addHelpButton(
+            $element_id,
+            "form_filemanager",
+            self::COMPONENT_NAME);
+
+        return true;
+    }
+
+    // ============================================================================================
+    // ================================== INTERACTION WITH USER ===================================
+    // ============================================================================================
+
+
+    /**
+     * ???
+     * @throws dml_exception ???
+     */
+    public function save(stdClass $submissionorgrade, stdClass $data): bool {
+        global $USER, $COURSE;
+
+        // HR : Save the files (before, they were marked as Draft)
+        // HR : Without this step, the download_submission webservice won't work
+        $data = file_postupdate_standard_filemanager($data,
+            'tasks',
+            $this->get_submission_options(),
+            context_course::instance($COURSE->id),
+            self::COMPONENT_NAME,
+            ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA,
+            $submissionorgrade->id);
+
+        // HR : Build the webservice API
+        $autograde_webservice = new autograde_webservice($this->get_config(self::setting_webservice_key_id));
+        // HR : Request from autograde to grade the submission
+        $response = $autograde_webservice->grade(
+            $USER->id,
+            $COURSE->id,
+            $this->assignment->get_instance()->id,
+            $submissionorgrade->id,
+            $this->get_config(self::setting_docker_image_id),
+            $this->get_config(self::setting_docker_registry_token_id)
+        );
+        // HR : Process the autograde webservice response
+        if($response->info === false){
+            $this->set_error("Request failed (Server not responding)!");
+            return false;
+        } else if($response->info['http_code'] == 200){
+            notification::success("You submission was scheduled for grading");
+        } else if($response->info['http_code'] == 403){
+            $this->set_error('You submission was not scheduled for grading. Please contact your teacher or one of the TAs to fix the issue.');
+            return false;
+        } else {
+            $this->set_error('code : ' . $response->info['http_code'] . ' | body : ' . $response->response);
+            return false;
+        }
+        return true;
+    }
+
+    public function delete_instance(): bool {
+        // TODO HR : Implement this function
+        return false;
+    }
+
+    // ============================================================================================
+    // ==================================== FILE MANAGEMENT =======================================
+    // ============================================================================================
+
+    /**
+     * ???
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_files
+     * @param stdClass $submissionorgrade
+     * @param stdClass $user
+     * @return array
+     * @throws coding_exception
+     */
+    public function get_files(stdClass $submissionorgrade, stdClass $user): array {
+            $result = [];
+            $fs = get_file_storage();
+
+            $files = $fs->get_area_files(
+                $this->assignment->get_context()->id,
+                'assignsubmission_file',
+                ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA,
+                $submissionorgrade->id,
+                'timemodified',
+                false
+            );
+
+            foreach ($files as $file) {
+                $result[$file->get_filename()] = $file;
+            }
+            return $result;
+    }
+
+    /**
+     * ???
+     * @return string[] ???
+     */
+    public function get_file_areas(): array {
+        return array(
+            ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA => "store the submissions",
+            ASSIGNSUBMISSION_AUTOGRADE_CREDENTIALS_FILEAREA => "store the credentials"
+        );
+    }
+
+    // ============================================================================================
+    // ========================================= VIEWS ============================================
+    // ============================================================================================
+
+    /**
+     * View of the summary (visible by the student)
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#view_summary
+     * @param $submission ???
+     * @param bool &$showviewlink ???
+     * @return string ???
+     */
+    public function view_summary($submission, &$showviewlink): string {
+        // TODO HR : 1- Fetch the path to the file to render from the database
+        // TODO HR : 2- Render the file in the view
+        // TODO HR : Should also handle the case where the submission is being graded
+        return "
+            <h2 align=center>Feedback for $submission->id</h2>
+            <details>
+                <summary>Test #1</summary>
+                <p>
+                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque tincidunt ligula est. Suspendisse ac aliquam est. Etiam venenatis, ante a. 
+                </p>
+            </details>
+            <details>
+                <summary>Test #2</summary>
+                <p>
+                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque tincidunt ligula est. Suspendisse ac aliquam est. Etiam venenatis, ante a. 
+                </p>
+            </details>
+        ";
+    }
+
+    // ============================================================================================
+    // ====================================== OTHERS ? ============================================
+    // ============================================================================================
+
+    /**
+     * ???
+     *
+     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#is_empty
+     * @param stdClass $submissionorgrade
+     * @return bool
+     * @throws coding_exception
+     */
+    public function is_empty(stdClass $submissionorgrade): bool {
+        // HR : Check if there is a file in the SUBMISSION_FILEAREA
+        global $COURSE;
+        $fs = get_file_storage();
+        $files = $fs->get_area_files(
+            context_course::instance($COURSE->id)->id,
+            self::COMPONENT_NAME,
+            ASSIGNSUBMISSION_AUTOGRADE_SUBMISSION_FILEAREA,
+            $submissionorgrade->id,
+            'id');
+        // HR : The submission is considered empty if no file is stored in the file_area
+        return count($files) == 0;
+    }
+
+    /**
+     * ???
+     *
+     * @return array ???
+     */
+    private function get_submission_options(): array{
+        return array(
+            'subdirs' => 1,
+            "maxfiles" => 1,
+            'accepted_types' => array(".zip"),
+            'return_types' => FILE_INTERNAL
+        );
+    }
+
+    private function get_credentials_option() : array {
+        return array(
+            'subdirs' => 1,
+            "maxfiles" => 1,
+            'accepted_types' => array(".config"),
+            'return_types' => FILE_INTERNAL | FILE_EXTERNAL
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/settings.php b/moodle-assignsubmission-autograde/settings.php
new file mode 100644
index 0000000000000000000000000000000000000000..a815f99fd1a80db90b56d30cf17bf44f954c4824
--- /dev/null
+++ b/moodle-assignsubmission-autograde/settings.php
@@ -0,0 +1,10 @@
+<?php
+
+defined('MOODLE_INTERNAL') || die();
+
+$settings->add(
+    new admin_setting_configtext("assignsubmission_autograde/service_url",
+        "Service URL",
+        "URL to the autograde service",
+        "")
+);
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograde/version.php b/moodle-assignsubmission-autograde/version.php
new file mode 100644
index 0000000000000000000000000000000000000000..24d9bcc29d3fb99b8a110f65e179df5b6b2699d9
--- /dev/null
+++ b/moodle-assignsubmission-autograde/version.php
@@ -0,0 +1,21 @@
+<?php
+
+defined('MOODLE_INTERNAL') || die();
+
+/** ??? */
+$plugin->version   = 2023042402;
+/** ??? */
+$plugin->requires  = 2022111800;
+/** ???? */
+$plugin->component = 'assignsubmission_autograde';
+/** ??? */
+$plugin->dependencies = array(
+    'mod_assign' => ANY_VERSION,
+);
+/** ??? */
+$plugin->stylesheets = array(
+    'autograde' => array(
+        'file' => 'css/autograde.css',
+        'media' => 'all',
+    ),
+);
diff --git a/moodle-assignsubmission-autograding/locallib.php b/moodle-assignsubmission-autograding/locallib.php
deleted file mode 100644
index 81e46a8566b685a7b0d1f6a23bd14e8799ccbe77..0000000000000000000000000000000000000000
--- a/moodle-assignsubmission-autograding/locallib.php
+++ /dev/null
@@ -1,296 +0,0 @@
-<?php
-
-defined('MOODLE_INTERNAL') || die();
-
-const ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA = 'submissions';
-
-/**
- * ???
- */
-final class assign_submission_docker_submission extends assign_submission_plugin {
-
-    private const COMPONENT_NAME = "assignsubmission_docker_submission";
-    private const setting_docker_image_id = "docker_image";
-
-    /**
-     * Get the name of this plugin
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_name
-     * @return string plugin name
-     * @throws coding_exception
-     */
-    public function get_name(): string {
-        return get_string("pluginname", self::COMPONENT_NAME);
-    }
-
-    // ============================================================================================
-    // ======================================= SETTINGS ===========================================
-    // ============================================================================================
-
-    /**
-     * Configure the settings for the docker_submission plugin
-     *
-     * We only need the docker image of the grader to run
-     * @note See documentation : https://docs.moodle.org/dev/Form_API
-     * @param MoodleQuickForm $mform
-     * @return void
-     * @throws coding_exception
-     */
-    public function get_settings(MoodleQuickForm $mform) {
-        $name = get_string("setting_docker-image", self::COMPONENT_NAME);
-        // HR : More details : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text
-        $mform->addElement("text", self::setting_docker_image_id, $name, null);
-        // HR : More details : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addHelpButton
-        $mform->addHelpButton(self::setting_docker_image_id, "setting_docker-image", self::COMPONENT_NAME);
-        // HR : See : https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hideIf
-        $mform->hideIf(self::setting_docker_image_id,
-            'assignsubmission_docker_submission_enabled',
-            'notchecked');
-    }
-
-    /**
-     * Save the settings of the docker_submission plugin
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#save_settings
-     * @param stdClass $data
-     * @return bool
-     */
-    public function save_settings(stdClass $data): bool {
-        $this->set_config(self::setting_docker_image_id, $data->docker_image);
-        return true;
-    }
-
-    public function data_preprocessing(&$defaultvalues) {
-        // TODO HR : Implement this function
-    }
-
-    // ============================================================================================
-    // ================================== SUBMISSION FORM =========================================
-    // ============================================================================================
-
-    /**
-     * ???
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_form_elements
-     * @param $submissionorgrade
-     * @param MoodleQuickForm $mform
-     * @param stdClass $data
-     * @param $userid
-     * @return bool
-     * @throws coding_exception
-     */
-    public function get_form_elements_for_user($submissionorgrade,
-                                               MoodleQuickForm $mform,
-                                               stdClass $data,
-                                               $userid): bool {
-        // TODO HR : Implement this function
-        // HR : Accepted files
-        $fileoptions = array(
-            'subdirs' => 1,
-            "maxfiles" => 1,
-            'accepted_types' => array(".zip"),
-            'return_types' => FILE_INTERNAL
-        );
-
-        $submissionid = $submissionorgrade ? $submissionorgrade->id : 0;
-
-        // HR : What does this code do ?
-        $data = file_prepare_standard_filemanager($data,
-            'tasks',
-            $fileoptions,
-            $this->assignment->get_context(),
-            self::COMPONENT_NAME,
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-            $submissionid);
-
-        $name = get_string("form_filemanager", self::COMPONENT_NAME);
-        $element_id = "tasks_filemanager";
-        $mform->addElement('filemanager', $element_id, $name, null, $fileoptions);
-
-        $mform->addHelpButton(
-            $element_id,
-            "form_filemanager",
-            self::COMPONENT_NAME);
-
-        return true;
-    }
-
-    // ============================================================================================
-    // ================================== INTERACTION WITH USER ===================================
-    // ============================================================================================
-
-
-    /**
-     * @throws coding_exception
-     */
-    public function save(stdClass $submissionorgrade, stdClass $data): bool {
-        // TODO HR : Implement this function
-        // This method will trigger the grading...
-
-        // HR : Accepted files
-        $fileoptions = array(
-            'subdirs' => 1,
-            "maxfiles" => 1,
-            'accepted_types' => array(".zip"),
-            'return_types' => FILE_INTERNAL
-        );
-
-        $submissionid = $submissionorgrade ? $submissionorgrade->id : 0;
-
-        $data = file_prepare_standard_filemanager(
-            $data,
-            'tasks',
-            $fileoptions,
-            $this->assignment->get_context(),
-            self::COMPONENT_NAME,
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-            $submissionid
-        );
-
-        $fs = get_file_storage();
-
-        if($this->is_empty($submissionorgrade)){
-            return true;
-        }
-
-        $files = $fs->get_area_files(
-            $this->assignment->get_context()->id,
-            self::COMPONENT_NAME,
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-            $submissionorgrade->id,
-            'id',
-            false
-        );
-
-        // HR : Prepare curl request
-        $curl = curl_init();
-        curl_setopt($curl, CURLOPT_URL, 'http://grading.service:8082/api/v1/grading/grade');
-        curl_setopt($curl, CURLOPT_POST, true);
-        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
-        curl_setopt($curl, CURLOPT_POSTFIELDS, $files);
-
-        $headers = [
-            'Content-Type: application/octet-stream',
-            'Content-Length: ' . strlen($files)
-        ];
-
-        curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
-
-        $response = curl_exec($curl);
-
-        if($response === false){
-            // TODO HR : Manage error here
-            \core\notification::error("Request failed !");
-            return false;
-        } else {
-            \core\notification::info("Request worked !" . $response);
-        }
-
-        curl_close($curl);
-
-        return true;
-    }
-
-    public function delete_instance(): bool {
-        // TODO HR : Implement this function
-        return false;
-    }
-
-    // ============================================================================================
-    // ==================================== FILE MANAGEMENT =======================================
-    // ============================================================================================
-
-    /**
-     * ???
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#get_files
-     * @param stdClass $submissionorgrade
-     * @param stdClass $user
-     * @return array
-     * @throws coding_exception
-     */
-    public function get_files(stdClass $submissionorgrade, stdClass $user): array {
-            $result = [];
-            $fs = get_file_storage();
-
-            $files = $fs->get_area_files(
-                $this->assignment->get_context()->id,
-                'assignsubmission_file',
-                ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-                $submissionorgrade->id,
-                'timemodified',
-                false
-            );
-
-            foreach ($files as $file) {
-                $result[$file->get_filename()] = $file;
-            }
-            return $result;
-    }
-
-    public function get_file_areas(): array {
-        // TODO HR : Implement this function
-        return array(
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA => "ertzuijokp"
-        );
-    }
-
-    // ============================================================================================
-    // ========================================= VIEWS ============================================
-    // ============================================================================================
-
-    /**
-     * View of the summary (visible by the student)
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#view_summary
-     * @param stdClass $submissionorgrade
-     * @param bool &$showviewlink
-     * @return string
-     * @throws coding_exception
-     */
-    public function view_summary(stdClass $submissionorgrade, &$showviewlink): string {
-        $html = $this->assignment->render_area_files(
-            self::COMPONENT_NAME,
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-            $submissionorgrade->id
-        );
-        $html .= html_writer::tag("h5", get_string("student_view_summary_header", self::COMPONENT_NAME));
-
-        // TODO HR : Implement this function
-        // 1- Fetch the submission from the database
-        // 2- Process the submission
-
-        return html_writer::div($html, "docker_submission_feedback_view");
-    }
-
-    public function view(stdClass $submissionorgrade): string {
-        // TODO HR : Implement this function
-        return "";
-    }
-
-    // ============================================================================================
-    // ====================================== OTHERS ? ============================================
-    // ============================================================================================
-
-    /**
-     * ???
-     *
-     * @note See documentation : https://moodledev.io/docs/apis/plugintypes/assign/submission#is_empty
-     * @param stdClass $submissionorgrade
-     * @return bool
-     * @throws coding_exception
-     */
-    public function is_empty(stdClass $submissionorgrade): bool {
-        // HR : Check if there is a file in the SUBMISSION_FILEAREA
-        /*$fs = get_file_storage();
-        $files = $fs->get_area_files($this->assignment->get_context()->id,
-            self::COMPONENT_NAME,
-            ASSIGNSUBMISSION_DOCKER_SUBMISSION_FILEAREA,
-            $submissionorgrade,
-            'id',
-            false);
-            */
-        return false;//count($files) == 0;
-    }
-
-}
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograding/settings.php b/moodle-assignsubmission-autograding/settings.php
deleted file mode 100644
index e2c9ee589e3c57e860848f53c4e21925e3a094b5..0000000000000000000000000000000000000000
--- a/moodle-assignsubmission-autograding/settings.php
+++ /dev/null
@@ -1,10 +0,0 @@
-<?php
-
-defined('MOODLE_INTERNAL') || die();
-
-$settings->add(
-    new admin_setting_configtext("assignsubmission_docker_submission/service_url",
-        "Service URL",
-        "URL to the kubernetes cluster hosting the service",
-        "")
-);
\ No newline at end of file
diff --git a/moodle-assignsubmission-autograding/version.php b/moodle-assignsubmission-autograding/version.php
deleted file mode 100644
index b72d13ea976e3a1ce2cba5cc3a75235a99b33ff0..0000000000000000000000000000000000000000
--- a/moodle-assignsubmission-autograding/version.php
+++ /dev/null
@@ -1,7 +0,0 @@
-<?php
-
-defined('MOODLE_INTERNAL') || die();
-
-$plugin->version   = 2023042400;
-$plugin->requires  = 2016052300;
-$plugin->component = 'assignsubmission_docker_submission';
diff --git a/moodle-grading-service/pom.xml b/moodle-grading-service/pom.xml
index 10300f4453e09a4094f105d514eff5b9658df60b..86fce6645636d79cd3b2adfa42dc609f8bc9ced4 100644
--- a/moodle-grading-service/pom.xml
+++ b/moodle-grading-service/pom.xml
@@ -21,17 +21,30 @@
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-web</artifactId>
 		</dependency>
-
 		<dependency>
-			<groupId>org.projectlombok</groupId>
-			<artifactId>lombok</artifactId>
-			<optional>true</optional>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-security</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>io.fabric8</groupId>
+			<artifactId>kubernetes-client</artifactId>
+			<version>6.7.2</version>
 		</dependency>
 		<dependency>
 			<groupId>org.springframework.boot</groupId>
 			<artifactId>spring-boot-starter-test</artifactId>
 			<scope>test</scope>
 		</dependency>
+		<dependency>
+			<groupId>org.springframework.security</groupId>
+			<artifactId>spring-security-test</artifactId>
+			<scope>test</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+			<optional>true</optional>
+		</dependency>
         <dependency>
             <groupId>org.springframework.boot</groupId>
             <artifactId>spring-boot-starter-actuator</artifactId>
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/MoodleGradingServiceApplication.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/MoodleGradingServiceApplication.java
index 34d70077e3d76d931956b5941df4c645b8ca8480..7820e620365573c0d94d713d3c158f2790a9b535 100644
--- a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/MoodleGradingServiceApplication.java
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/MoodleGradingServiceApplication.java
@@ -3,9 +3,18 @@ package ch.epfl.cs107.grading.moodle;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
 @SpringBootApplication
 public class MoodleGradingServiceApplication {
 
+	/**
+	 * ???
+	 * @param args ???
+	 */
 	public static void main(String[] args) {
 		SpringApplication.run(MoodleGradingServiceApplication.class, args);
 	}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/ApiKeyAuthentication.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/ApiKeyAuthentication.java
new file mode 100644
index 0000000000000000000000000000000000000000..ce2e927f54b14d5e428eaec68fbcf3dfc62d1b84
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/ApiKeyAuthentication.java
@@ -0,0 +1,69 @@
+package ch.epfl.cs107.grading.moodle.api.v1.auth;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+
+import java.util.Collection;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+public final class ApiKeyAuthentication implements Authentication {
+
+    /** ??? */
+    private boolean authenticated;
+
+    /** ??? */
+    private final String key;
+
+    /**
+     * ???
+     * @param key ???
+     * @param authenticated ???
+     */
+    public ApiKeyAuthentication(String key, boolean authenticated) {
+        this.key           = key;
+        this.authenticated = authenticated;
+    }
+
+    public String getKey(){
+        return key;
+    }
+
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return null;
+    }
+
+    @Override
+    public Object getCredentials() {
+        return null;
+    }
+
+    @Override
+    public Object getDetails() {
+        return null;
+    }
+
+    @Override
+    public Object getPrincipal() {
+        return null;
+    }
+
+    @Override
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    @Override
+    public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
+        this.authenticated = isAuthenticated;
+    }
+
+    @Override
+    public String getName() {
+        return null;
+    }
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/filter/ApiKeyAuthenticationFilter.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/filter/ApiKeyAuthenticationFilter.java
new file mode 100644
index 0000000000000000000000000000000000000000..6299a1e247073b90f2146922535a99ac298de2a4
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/filter/ApiKeyAuthenticationFilter.java
@@ -0,0 +1,58 @@
+package ch.epfl.cs107.grading.moodle.api.v1.auth.filter;
+
+import ch.epfl.cs107.grading.moodle.api.v1.auth.ApiKeyAuthentication;
+import ch.epfl.cs107.grading.moodle.api.v1.auth.manager.ApiKeyAuthenticationManager;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Component
+public class ApiKeyAuthenticationFilter extends OncePerRequestFilter {
+
+    private static final String API_KEY_HEADER_ENTRY = "API-KEY";
+
+    /** ??? */
+    private final ApiKeyAuthenticationManager manager;
+
+    /**
+     * ???
+     * @param manager ???
+     */
+    @Autowired
+    public ApiKeyAuthenticationFilter(ApiKeyAuthenticationManager manager) {
+        this.manager = manager;
+    }
+
+    /**
+     * ???
+     * @param request ???
+     * @param response ???
+     * @param filterChain ???
+     * @throws ServletException ???
+     * @throws IOException ???
+     */
+    @Override
+    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
+        // HR : Fetch the API-KEY from the header
+        var key = request.getHeader(API_KEY_HEADER_ENTRY);
+        // HR : Request the authentication from the authentication manager
+        var auth = manager.authenticate(new ApiKeyAuthentication(key, false));
+        // HR : Set the authentication in the SecurityContext
+        SecurityContextHolder.getContext().setAuthentication(auth);
+        // HR : Complete the chain of filters
+        filterChain.doFilter(request, response);
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/manager/ApiKeyAuthenticationManager.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/manager/ApiKeyAuthenticationManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..9abe18ad5eaf490afbf3a596862dc03610e6451c
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/manager/ApiKeyAuthenticationManager.java
@@ -0,0 +1,41 @@
+package ch.epfl.cs107.grading.moodle.api.v1.auth.manager;
+
+import ch.epfl.cs107.grading.moodle.api.v1.auth.provider.ApiKeyAuthenticationProvider;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Component
+public class ApiKeyAuthenticationManager implements AuthenticationManager {
+
+    /** ??? */
+    private final ApiKeyAuthenticationProvider provider;
+
+    /**
+     * ???
+     * @param provider ???
+     */
+    @Autowired
+    public ApiKeyAuthenticationManager(ApiKeyAuthenticationProvider provider) {
+        this.provider = provider;
+    }
+
+    /**
+     * ???
+     * @param authentication the authentication request object
+     * @return ???
+     * @throws AuthenticationException ???
+     */
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        return provider.authenticate(authentication);
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/provider/ApiKeyAuthenticationProvider.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/provider/ApiKeyAuthenticationProvider.java
new file mode 100644
index 0000000000000000000000000000000000000000..5837f16a3f524be18fbac061a286faccf7370043
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/auth/provider/ApiKeyAuthenticationProvider.java
@@ -0,0 +1,60 @@
+package ch.epfl.cs107.grading.moodle.api.v1.auth.provider;
+
+import ch.epfl.cs107.grading.moodle.api.v1.auth.ApiKeyAuthentication;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.stereotype.Component;
+
+import static java.util.Objects.isNull;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Component
+public class ApiKeyAuthenticationProvider implements AuthenticationProvider {
+
+    /** ??? */
+    private final String apiKey;
+
+    /**
+     * ???
+     * @param apiKey ???
+     */
+    public ApiKeyAuthenticationProvider(@Value("${api.key}") String apiKey) {
+        this.apiKey = apiKey;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     * @param authentication the authentication request object.
+     * @return ???
+     * @throws AuthenticationException ???
+     */
+    @Override
+    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+        var keyAuth = (ApiKeyAuthentication) authentication;
+        if (isNull(keyAuth.getKey()))
+            return authentication;
+        if (keyAuth.getKey().equals(apiKey))
+            return new ApiKeyAuthentication(keyAuth.getKey(), true);
+        else
+            throw new BadCredentialsException("API KEY IS NOT CORRECT");
+    }
+
+    /**
+     * {@inheritDoc}
+     * @param authenticationType ???
+     * @return ???
+     */
+    @Override
+    public boolean supports(Class<?> authenticationType) {
+        return ApiKeyAuthentication.class.equals(authenticationType);
+    }
+}
+
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/KubernetesConfig.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/KubernetesConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..01d92938931f5fefcbc2e36063e7649e5e595b87
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/KubernetesConfig.java
@@ -0,0 +1,15 @@
+package ch.epfl.cs107.grading.moodle.api.v1.config;
+
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientBuilder;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class KubernetesConfig {
+
+    @Bean
+    public KubernetesClient kubernetesClient() {
+        return new KubernetesClientBuilder().build();
+    }
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/SecurityConfig.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/SecurityConfig.java
new file mode 100644
index 0000000000000000000000000000000000000000..8db96b3ddf255815a7497551812ade10cbd4a5cd
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/config/SecurityConfig.java
@@ -0,0 +1,61 @@
+package ch.epfl.cs107.grading.moodle.api.v1.config;
+
+import ch.epfl.cs107.grading.moodle.api.v1.auth.filter.ApiKeyAuthenticationFilter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+    /** ??? */
+    private final ApiKeyAuthenticationFilter apiKeyAuthFilter;
+
+    /**
+     * ???
+     * @param apiKeyAuthFilter ???
+     */
+    @Autowired
+    public SecurityConfig(ApiKeyAuthenticationFilter apiKeyAuthFilter) {
+        this.apiKeyAuthFilter = apiKeyAuthFilter;
+    }
+
+    /**
+     * ???
+     * @param http ???
+     * @return ???
+     * @throws Exception ???
+     */
+    @Bean
+    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+        return http
+                // HR : Add the ApiKeyAuthentication filter
+                .addFilterAfter(apiKeyAuthFilter, BasicAuthenticationFilter.class)
+                // HR : Disable CSRF
+                .csrf(AbstractHttpConfigurer::disable)
+                // HR : Configure the end points authorizations
+                .authorizeHttpRequests(auth -> {
+                    // HR : Allow to ping the no-auth end point
+                    auth.requestMatchers("/api/v1/ping/no-auth").permitAll();
+                    // HR : Request authentication to ping the auth end point
+                    auth.requestMatchers("/api/v1/ping/auth").authenticated();
+                    // HR : By default, request authentication to access any end point
+                    auth.anyRequest().authenticated();
+                })
+                // HR : Build the SecurityFilterChain
+                .build();
+    }
+
+}
+
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/GradingController.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/GradingController.java
index aca83b8e193cefc55873bbcb7462a5ef06ccb370..bdc4c6f14208b0980783284e36872d5ebff1438b 100644
--- a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/GradingController.java
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/GradingController.java
@@ -1,20 +1,108 @@
 package ch.epfl.cs107.grading.moodle.api.v1.controller;
 
+import ch.epfl.cs107.grading.moodle.api.v1.dto.GradeRequestDTO;
+import ch.epfl.cs107.grading.moodle.api.v1.service.KubernetesJobService;
+import ch.epfl.cs107.grading.moodle.api.v1.service.MoodleWebService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
+/**
+ * Handles grading requests from moodle and submit grades and feedback to
+ * moodle.
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
 @RestController
 @RequestMapping("/api/v1/grading")
 public class GradingController {
 
+    private final Logger logger = LoggerFactory.getLogger(GradingController.class);
+
+    /**
+     * Send requests to moodle
+     */
+    private final MoodleWebService moodleWebService;
+
+    /**
+     * Trigger jobs on kubernetes cluster
+     */
+    private final KubernetesJobService kubernetesJobService;
+
+    @Autowired
+    public GradingController(MoodleWebService moodleWebService, KubernetesJobService kubernetesJobService) {
+        this.moodleWebService = moodleWebService;
+        this.kubernetesJobService = kubernetesJobService;
+    }
+
+    /**
+     * Handles grading requests from moodle. Creates a grading job on the kubernetes
+     * cluster.
+     *
+     * @param request ???
+     * @return ???
+     */
     @PostMapping("/grade")
-    public ResponseEntity<String> ping(){
-        try{
-            return ResponseEntity.ok("File received !");
-        } catch (Exception e){
-            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
+    public ResponseEntity<?> grade(@RequestBody GradeRequestDTO request) {
+
+        logger.info("Grading request received: {}", request.toString());
+
+        try {
+            var jobName = kubernetesJobService.createJob(
+                    request.getUserid(), request.getCourseid(),
+                    request.getAssignmentid(), request.getSubmissionid(),
+                    request.getImageName(), request.getRegistryCredential());
+            return ResponseEntity.ok(jobName);
+        } catch (RuntimeException e) {
+            logger.error("Failed to create grading job", e);
+            return ResponseEntity.internalServerError().build();
         }
     }
 
+    /**
+     * Handles requests from grading job to submit grades and feedback. Uploads
+     * grades and feedback to moodle.
+     *
+     * @param userid       ???
+     * @param courseid     ???
+     * @param assignmentid ???
+     * @param submissionid ???
+     * @param feedback     ???
+     */
+    @PostMapping("/result")
+    public ResponseEntity<?> submitGrade(
+            @RequestParam int userid,
+            @RequestParam int courseid,
+            @RequestParam int assignmentid,
+            @RequestParam int submissionid,
+            @RequestBody String feedback
+    ) {
+        logger.info("Grade/feedback submitted by job: " +
+                "userid=" + userid + ", courseid=" + courseid +
+                ", assignmentid=" + assignmentid + ", submissionid=" + submissionid
+        );
+
+        try {
+            // HR : Upload grade and feedback to moodle
+            var res = moodleWebService.uploadAutoGradeFeedback(
+                    userid, courseid, assignmentid, submissionid,
+                    74, feedback);
+
+            if (res.statusCode() != HttpStatus.OK.value()) {
+                throw new Exception("Moodle request returned with status: " + res.statusCode() + " " + res.body());
+            }
+        } catch (Exception e) {
+            logger.error(
+                    "Failed to upload grade and feedback to moodle: " + "userid=" + userid + ", courseid=" + courseid +
+                            ", assignmentid=" + assignmentid + ", submissionid=" + submissionid,
+                    e
+            );
+        }
+
+        return ResponseEntity.ok().build();
+    }
+
 }
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/PingController.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/PingController.java
index 605eedb9dbfec726060d8dc32268f86247330c8e..f9104bb578f28448db7d3a81ff4158f241d5672b 100644
--- a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/PingController.java
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/PingController.java
@@ -1,16 +1,42 @@
 package ch.epfl.cs107.grading.moodle.api.v1.controller;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
 @RestController
-@RequestMapping("/api/v1")
+@RequestMapping("/api/v1/ping")
 public class PingController {
 
-    @GetMapping("/ping")
-    public String ping(){
-        return "Hello from Spring boot";
+    private Logger logger = LoggerFactory.getLogger(PingController.class);
+
+    /**
+     * ???
+     *
+     * @return ???
+     */
+    @GetMapping("/no-auth")
+    public String pingWithoutAuth() {
+        logger.info("Received a ping without auth");
+        return "Hello from Spring boot - No Auth mode";
+    }
+
+    /**
+     * ???
+     *
+     * @return ???
+     */
+    @GetMapping("/auth")
+    public String pingWithAuth() {
+        logger.info("Received a ping");
+        return "Hello from Spring boot - Auth mode";
     }
 
 }
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java
new file mode 100644
index 0000000000000000000000000000000000000000..c02c41a13b73d2c0d1c512a5ec1eea35e752a12e
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/RegistryCredentialsController.java
@@ -0,0 +1,52 @@
+package ch.epfl.cs107.grading.moodle.api.v1.controller;
+
+import ch.epfl.cs107.grading.moodle.api.v1.dto.UploadCredentialsDTO;
+import ch.epfl.cs107.grading.moodle.api.v1.service.KubernetesJobService;
+import ch.epfl.cs107.grading.moodle.api.v1.service.MoodleWebService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@RestController
+@RequestMapping("/api/v1/registry/credentials")
+public final class RegistryCredentialsController {
+
+    /** ??? */
+    private final KubernetesJobService k8s;
+
+    /** ??? */
+    private final MoodleWebService moodle;
+
+    /**
+     * ???
+     * @param k8s ???
+     */
+    @Autowired
+    public RegistryCredentialsController(KubernetesJobService k8s, MoodleWebService moodle) {
+        this.k8s = k8s;
+        this.moodle = moodle;
+    }
+
+    /**
+     * ???
+     * @return ???
+     */
+    @PostMapping("/upload")
+    public ResponseEntity<?> uploadCredentials(@RequestBody UploadCredentialsDTO credentials){
+        try(var cred = moodle.download_credentials(credentials.getCourseid(), credentials.getAssignmentid())){
+            // TODO : Use k8s to store the credentials in the corresponding namespace in the cluster
+            return ResponseEntity.ok().body(null);
+        } catch (Exception e){
+            return ResponseEntity.internalServerError().body(e);
+        }
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/SubmissionController.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/SubmissionController.java
new file mode 100644
index 0000000000000000000000000000000000000000..d314ac31da75a2c1ef0771ccbe6f444cc8a315f0
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/controller/SubmissionController.java
@@ -0,0 +1,77 @@
+package ch.epfl.cs107.grading.moodle.api.v1.controller;
+
+import ch.epfl.cs107.grading.moodle.api.v1.service.MoodleWebService;
+import ch.epfl.cs107.grading.moodle.api.v1.service.SubmissionIntegrityService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+
+import static org.springframework.http.HttpHeaders.CONTENT_TYPE;
+import static org.springframework.http.MediaType.APPLICATION_OCTET_STREAM_VALUE;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@RestController
+@RequestMapping("/api/v1/submission")
+public class SubmissionController {
+
+    /** ??? */
+    private final MoodleWebService moodle;
+
+    private final SubmissionIntegrityService integrity;
+
+    /**
+     * ???
+     * @param moodle ???
+     */
+    @Autowired
+    public SubmissionController(MoodleWebService moodle, SubmissionIntegrityService integrity){
+        this.moodle    = moodle;
+        this.integrity = integrity;
+    }
+
+
+    /**
+     * ???
+     * @return ???
+     */
+    @GetMapping("/download")
+    public ResponseEntity<?> download(
+            @RequestParam int courseid,
+            @RequestParam int submissionid,
+            @RequestParam String signature){
+        // HR : Check the integrity of the signature
+        if (!integrity.check(signature, courseid, submissionid))
+            return ResponseEntity
+                    .status(HttpStatus.UNAUTHORIZED)
+                    .body("The provided signature is not correct !");
+        // HR : Serve the actual file, the integrity of the signature was checked
+        try(var file = moodle.download_submission(courseid, submissionid)) {
+            // HR : Prepare the headers
+            var headers = new HttpHeaders();
+            headers.add(CONTENT_TYPE, APPLICATION_OCTET_STREAM_VALUE);
+            // HR : Prepare the body
+            var body = new InputStreamResource(file);
+            // HR : Build response
+            return ResponseEntity
+                    .ok()
+                    .headers(headers)
+                    .body(body);
+        } catch (URISyntaxException | InterruptedException | IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..ebb9d6f3923d24815460e37de43c398c57b3c6b1
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadCredentialsDTO.java
@@ -0,0 +1,23 @@
+package ch.epfl.cs107.grading.moodle.api.v1.dto;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Data
+public class DownloadCredentialsDTO {
+
+    /** ??? */
+    private final String content;
+
+    @JsonCreator
+    public DownloadCredentialsDTO(@JsonProperty("content") String content) {
+        this.content = content;
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadSubmissionDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadSubmissionDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..2d607f640c78b77f1812e3d9de3f6c9fae574042
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/DownloadSubmissionDTO.java
@@ -0,0 +1,14 @@
+package ch.epfl.cs107.grading.moodle.api.v1.dto;
+
+import lombok.Data;
+
+/**
+ * ???
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Data
+public final class DownloadSubmissionDTO {
+
+    private final String content;
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/GradeRequestDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/GradeRequestDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..8288b825a8b5d4da8d41f6fa0afc66694f7718a5
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/GradeRequestDTO.java
@@ -0,0 +1,29 @@
+package ch.epfl.cs107.grading.moodle.api.v1.dto;
+
+import lombok.Data;
+
+/**
+ * ???
+ */
+@Data
+public class GradeRequestDTO {
+
+    /** ??? */
+    private final int userid;
+
+    /** ??? */
+    private final int courseid;
+
+    /** ??? */
+    private final int assignmentid;
+
+    /** ??? */
+    private final int submissionid;
+
+    /** ??? */
+    private final String imageName;
+
+    /** ??? */
+    private final String registryCredential;
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java
new file mode 100644
index 0000000000000000000000000000000000000000..e1c2bd78fd993d3614bed8bd3200c8f5203cc26d
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/dto/UploadCredentialsDTO.java
@@ -0,0 +1,19 @@
+package ch.epfl.cs107.grading.moodle.api.v1.dto;
+
+import lombok.Data;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Data
+public final class UploadCredentialsDTO {
+
+    /** ???? */
+    private final int courseid;
+
+    private final int assignmentid;
+
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/KubernetesJobService.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/KubernetesJobService.java
new file mode 100644
index 0000000000000000000000000000000000000000..9184592f74f0068cc0651ec6fdf3ea2c57e94c95
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/KubernetesJobService.java
@@ -0,0 +1,343 @@
+package ch.epfl.cs107.grading.moodle.api.v1.service;
+
+import io.fabric8.kubernetes.api.model.*;
+import io.fabric8.kubernetes.api.model.batch.v1.Job;
+import io.fabric8.kubernetes.api.model.batch.v1.JobBuilder;
+import io.fabric8.kubernetes.api.model.batch.v1.JobSpecBuilder;
+import io.fabric8.kubernetes.client.KubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClientException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.Base64;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * ???
+ * @author Dixit Sabharwal (dixit.sabharwal@epfl.ch)
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Service
+public final class KubernetesJobService {
+
+    /** ??? */
+    private final Logger logger = LoggerFactory.getLogger(KubernetesJobService.class);
+
+    /** ??? */
+    private final KubernetesClient kubernetesClient;
+
+    /** ??? */
+    private final SubmissionIntegrityService subIntegrity;
+
+    @Value("${grading.service.name}")
+    private String gradingServiceName;
+    @Value("${server.port}")
+    private int gradingServicePort;
+    @Value("${api.key}")
+    private String key;
+
+    @Autowired
+    public KubernetesJobService(KubernetesClient kubernetesClient, SubmissionIntegrityService subIntegrity) {
+        this.kubernetesClient = kubernetesClient;
+        this.subIntegrity = subIntegrity;
+    }
+
+    public String createJob(
+            int userid, int courseid,
+            int assignmentid, int submissionid,
+            String imageName, String registryCredential
+    ) throws RuntimeException {
+
+        // TODO HR : That is not correct. the id for CS-107
+        // TODO HR: is must likely not 107.
+        // TODO HR : what are the namespaces in k8s ?
+        var namespace = String.format("cs%d", courseid);
+        // Generate the job name
+        var jobName = generate_job_name();
+        // Get secret for pulling image
+        var imagePullSecretName = tryCreateImagePullSecret(namespace, registryCredential);
+        // Create an ephemeral volume for holding the submission
+        var volume = prepare_job_volume(jobName);
+        // Create an InitContainer to download the zip file into the ephemeral volume
+        var initContainer = prepare_init_container(
+                jobName,
+                volume,
+                download_submission_command(courseid, submissionid)
+        );
+        // Create container that will run the grading script
+        var gradingContainer = prepare_grading_container(
+                jobName,
+                imageName,
+                volume
+        );
+        // Create a ReportingContainer to report the grading results
+        var reportingContainer = prepare_feedback_container(
+                courseid,
+                userid,
+                assignmentid,
+                submissionid,
+                volume
+        );
+
+        // Define job
+        var job = prepare_job(
+                jobName, volume, imagePullSecretName,
+                initContainer, gradingContainer, reportingContainer,
+                userid, assignmentid, submissionid
+        );
+
+        // Create job
+        try {
+            kubernetesClient
+                    .batch()
+                    .v1()
+                    .jobs()
+                    .inNamespace(namespace)
+                    .resource(job)
+                    .create();
+        } catch (KubernetesClientException e) {
+            logger.error("Failed to create job {}", jobName, e);
+            throw new RuntimeException("Failed to create job");
+        }
+
+        return jobName;
+    }
+
+    public String tryCreateImagePullSecret(String namespace, String registryCredential)
+            throws RuntimeException {
+        // Hash parameters to generate secret name (to avoid duplicates)
+        String secretName = "regcred-" + registryCredential.hashCode();
+
+        // Check if secret exists
+        Secret secret = null;
+        try {
+            secret = kubernetesClient.secrets().inNamespace(namespace).withName(secretName).get();
+        } catch (KubernetesClientException e) {
+            logger.error("Failed trying to read existing secret {}", secretName, e);
+            throw new RuntimeException("Failed trying to read existing imagePullSecret");
+        }
+
+        if (secret != null) {
+            return secretName;
+        }
+
+        // Create secret if it does not exist
+        Secret regCred = new SecretBuilder()
+                .withApiVersion("v1")
+                .withKind("Secret")
+                .withType("kubernetes.io/dockerconfigjson")
+                .withMetadata(new ObjectMetaBuilder()
+                        .withName(secretName)
+                        .build())
+                .withData(Map.of(
+                        ".dockerconfigjson", Base64.getEncoder().encodeToString(registryCredential.getBytes())))
+                .build();
+
+        try {
+            kubernetesClient.secrets().inNamespace(namespace).resource(regCred).create();
+        } catch (KubernetesClientException e) {
+            logger.error("Failed to create secret {}", secretName, e);
+            throw new RuntimeException("Failed to create imagePullSecret");
+        }
+
+        return secretName;
+    }
+
+    // ============================================================================================
+    // =================================== HELPER FUNCTIONS =======================================
+    // ============================================================================================
+
+    /**
+     * ???
+     * @return ???
+     */
+    private String generate_job_name(){
+        return UUID.randomUUID().toString();
+    }
+
+    /**
+     * ???
+     * @param job_name ???
+     * @return ???
+     */
+    private Volume prepare_job_volume(String job_name){
+        var volumeName = job_name + "-volume";
+        return new VolumeBuilder()
+                .withName(volumeName)
+                .withEmptyDir(
+                        new EmptyDirVolumeSourceBuilder()
+                        .withNewSizeLimit("200Mi")
+                        .build())
+                .build();
+    }
+
+    /**
+     * ???
+     * @param job_name ???
+     * @param volume ???
+     * @param cmd ???
+     * @return ???
+     */
+    private Container prepare_init_container(String job_name, Volume volume, String cmd){
+        return new ContainerBuilder()
+                .withName(job_name + "-init")
+                .withImage("busybox")
+                .withCommand("sh", "-c", cmd)
+                .withVolumeMounts(new VolumeMountBuilder()
+                        .withName(volume.getName())
+                        .withMountPath("/data")
+                        .build())
+                .withResources(new ResourceRequirementsBuilder()
+                        .withRequests(Map.of(
+                                "cpu", new Quantity("100m"),
+                                "memory", new Quantity("200Mi")))
+                        .withLimits(Map.of(
+                                "cpu", new Quantity("100m"),
+                                "memory", new Quantity("500Mi")))
+                        .build())
+                .build();
+    }
+
+    /**
+     * ???
+     * @param job_name ???
+     * @param image_name ???
+     * @param volume ???
+     * @return ???
+     */
+    private Container prepare_grading_container(String job_name, String image_name, Volume volume){
+        return new ContainerBuilder()
+                .withName(job_name + "-grader")
+                .withImage(image_name)
+                .withImagePullPolicy("Always")
+                .withVolumeMounts(new VolumeMountBuilder()
+                        .withName(volume.getName())
+                        .withMountPath("/data")
+                        .build())
+                .withResources(new ResourceRequirementsBuilder()
+                        .withRequests(Map.of(
+                                "cpu", new Quantity("200m"),
+                                "memory", new Quantity("500Mi")))
+                        .withLimits(Map.of(
+                                "cpu", new Quantity("500m"),
+                                "memory", new Quantity("1Gi")))
+                        .build())
+                .build();
+    }
+
+    /**
+     * ???
+     * @param courseid ???
+     * @param userid ???
+     * @param assignmentid ???
+     * @param submissionid ???
+     * @param volume ???
+     * @return ???
+     */
+    private Container prepare_feedback_container(
+            int courseid, int userid,
+            int assignmentid, int submissionid,
+            Volume volume
+    ){
+        return new ContainerBuilder()
+                .withName("reporting-container")
+                .withImage("appropriate/curl")
+                .withCommand(
+                        "sh", "-c",
+                        "curl -X POST " +
+                                "-H 'API-KEY: $API_KEY' " +
+                                "-H 'Content-Type: application/json' " +
+                                "-d @/data/feedback.json " +
+                                String.format("\"http://%s:%d/api/v1/grading/result?", gradingServiceName, gradingServicePort) +
+                                String.format("userid=%d&courseid=%d&assignmentid=%d&submissionid=%d", userid, courseid, assignmentid, submissionid) +
+                                "\""
+                )
+                .withVolumeMounts(new VolumeMountBuilder()
+                        .withName(volume.getName())
+                        .withMountPath("/data")
+                        .build())
+                .withEnv(new EnvVarBuilder()
+                        .withName("API_KEY")
+                        .withValueFrom(new EnvVarSourceBuilder()
+                                .withNewSecretKeyRef()
+                                .withName("grading-service-variables")
+                                .withKey("API_KEY")
+                                .endSecretKeyRef()
+                                .build())
+                        .build())
+                .withResources(new ResourceRequirementsBuilder()
+                        .withRequests(Map.of(
+                                "cpu", new Quantity("100m"),
+                                "memory", new Quantity("200Mi")))
+                        .withLimits(Map.of(
+                                "cpu", new Quantity("100m"),
+                                "memory", new Quantity("200Mi")))
+                        .build())
+                .build();
+    }
+
+    /**
+     * ???
+     * @param job_name ???
+     * @param volume ???
+     * @param credentials ???
+     * @param initContainer ???
+     * @param gradingContainer ???
+     * @param reportingContainer ???
+     * @param userid ???
+     * @param assignmentid ???
+     * @param submissionid ???
+     * @return ???
+     */
+    private Job prepare_job(
+            String job_name, Volume volume, String credentials,
+            Container initContainer, Container gradingContainer, Container reportingContainer,
+            int userid, int assignmentid, int submissionid
+    ){
+        return new JobBuilder()
+                .withApiVersion("batch/v1")
+                .withKind("Job")
+                .withMetadata(new ObjectMetaBuilder()
+                        .withName(job_name)
+                        .withLabels(Map.of(
+                                "userid", String.valueOf(userid),
+                                "assignmentid", String.valueOf(assignmentid),
+                                "submissionid", String.valueOf(submissionid)
+                        ))
+                        .build())
+                .withSpec(new JobSpecBuilder()
+                        .withTemplate(new PodTemplateSpecBuilder()
+                                .withSpec(new PodSpecBuilder()
+                                        .withRestartPolicy("Never")
+                                        .addNewImagePullSecret(credentials)
+                                        .withInitContainers(initContainer, gradingContainer)
+                                        .withContainers(reportingContainer)
+                                        .withVolumes(volume)
+                                        .build())
+                                .build())
+                        .build())
+                .build();
+    }
+
+    /**
+     * ???
+     * @param courseid ???
+     * @param submissionid ???
+     * @return ???
+     */
+    private String download_submission_command(int courseid, int submissionid){
+        var mac = subIntegrity.generate(courseid, submissionid);
+        // HR : The double \\& is important here since we are building a command
+        // HR : and &-only will be interpreted as two different unix commands.
+        return String.format("wget --header=\"API-KEY: %s\" \"http://%s:%d/api/v1/submission/download?courseid=%d&submissionid=%d&signature=%s\" -O /data/submission.zip",
+                key,
+                gradingServiceName, gradingServicePort,
+                courseid, submissionid,
+                mac);
+    }
+
+}
\ No newline at end of file
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java
new file mode 100644
index 0000000000000000000000000000000000000000..8aba00212b68d2fe3b191d6f067161b976449fd8
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/MoodleWebService.java
@@ -0,0 +1,188 @@
+package ch.epfl.cs107.grading.moodle.api.v1.service;
+
+import ch.epfl.cs107.grading.moodle.api.v1.dto.DownloadCredentialsDTO;
+import ch.epfl.cs107.grading.moodle.api.v1.dto.DownloadSubmissionDTO;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.stereotype.Service;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.net.URLEncoder.encode;
+import static java.net.http.HttpResponse.BodyHandlers.ofString;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * ???
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ * @since 1.0.0
+ */
+@Service
+public final class MoodleWebService {
+
+    /** ??? */
+    private final static String MOODLE_WEB_SERVICE_API = "/webservice/rest/server.php";
+
+    /** ??? */
+    private final String MOODLE_BASE_URL;
+
+    /** ??? */
+    private final String MOODLE_ACCESS_TOKEN;
+
+    /**
+     * ???
+     * @param url ???
+     * @param token ???
+     */
+    public MoodleWebService(@Value("${moodle.baseurl}")String url, @Value("${moodle.autograde.token}") String token) {
+        this.MOODLE_BASE_URL     = url;
+        this.MOODLE_ACCESS_TOKEN = token;
+    }
+
+    // ============================================================================================
+    // ===================================== GENERIC FUNCTION =====================================
+    // ============================================================================================
+
+    /**
+     * ???
+     * @param function ???
+     * @param params ???
+     * @return ???
+     * @throws URISyntaxException ???
+     * @throws IOException ???
+     * @throws InterruptedException ???
+     */
+    private <T> HttpResponse<T> call(String function, Map<String, Object> params, HttpResponse.BodyHandler<T> handler)
+            throws URISyntaxException, IOException, InterruptedException {
+
+        var client = HttpClient.newHttpClient();
+
+        // HR : Build the URL
+        var sb = new StringBuilder(MOODLE_BASE_URL);
+        sb.append(MOODLE_WEB_SERVICE_API); // HR : End point of moodle
+        sb.append('?');
+        sb.append("wstoken=").append(MOODLE_ACCESS_TOKEN);
+        sb.append("&wsfunction=").append(function);
+        sb.append("&moodlewsrestformat=json");
+        for (var param : params.entrySet()) {
+            sb.append('&')
+                    .append(encode(param.getKey(), UTF_8))
+                    .append('=')
+                    .append(encode(param.getValue().toString(), UTF_8));
+        }
+
+        // HR : Build the request
+        var request = HttpRequest.newBuilder()
+                .POST(HttpRequest.BodyPublishers.noBody())
+                .header("Content-Type", "application/json")
+                .uri(new URI(sb.toString()))
+                .build();
+        // HR : Send the request to moodle and wait for the response
+        return client.send(request, handler);
+    }
+
+    /**
+     * ???
+     * @param userid ???
+     * @param courseid ???
+     * @param assignmentid ???
+     * @param submissionid ???
+     * @param grade ???
+     * @param feedback ???
+     * @return ???
+     * @throws URISyntaxException ???
+     * @throws IOException ???
+     * @throws InterruptedException ???
+     */
+    public HttpResponse<?> uploadAutoGradeFeedback(
+            int userid,
+            int courseid,
+            int assignmentid,
+            int submissionid,
+            int grade,
+            String feedback
+    ) throws URISyntaxException, IOException, InterruptedException {
+        final var FUNCTION_NAME = "mod_assignsubmission_autograde_upload_feedback";
+
+        final var params = new HashMap<String, Object>();
+        // HR : Build the parameters
+        params.put("userid", userid);
+        params.put("courseid", courseid);
+        params.put("assignmentid", assignmentid);
+        params.put("submissionid", submissionid);
+        params.put("grade", grade);
+        params.put("feedback", feedback);
+        // HR : Call the moodle web service
+        return call(FUNCTION_NAME, params, ofString());
+    }
+
+    /**
+     * ???
+     * @param courseid ???
+     * @param submissionid ???
+     * @return ???
+     * @throws URISyntaxException ???
+     * @throws IOException ???
+     * @throws InterruptedException ???
+     */
+    public InputStream download_submission(int courseid, int submissionid) throws URISyntaxException, IOException, InterruptedException {
+        final var FUNCTION_NAME = "mod_assignsubmission_autograde_download_submission";
+
+        final var params = new HashMap<String, Object>();
+        params.put("courseid", courseid);
+        params.put("submissionid", submissionid);
+
+        var response = call(FUNCTION_NAME, params, ofString(UTF_8));
+
+        var objectMapper = new ObjectMapper();
+        var submission = objectMapper.readValue(response.body(), DownloadSubmissionDTO.class);
+
+        if(response.statusCode() != HttpStatus.OK.value()){
+            throw new IllegalStateException(submission.getContent());
+        } else {
+            return new ByteArrayInputStream(Base64.getDecoder().decode(submission.getContent()));
+        }
+    }
+
+    /**
+     * ???
+     * @param courseid ???
+     * @param assignmentid ???
+     * @return ???
+     * @throws URISyntaxException ???
+     * @throws IOException ???
+     * @throws InterruptedException ???
+     */
+    public InputStream download_credentials(int courseid, int assignmentid) throws URISyntaxException, IOException, InterruptedException {
+        final var FUNCTION_NAME = "mod_assignsubmission_autograde_download_credentials";
+
+        final var params = new HashMap<String, Object>();
+        params.put("courseid", courseid);
+        params.put("assignmentid", assignmentid);
+
+        var response = call(FUNCTION_NAME, params, ofString());
+
+        System.out.println(response.body());
+
+        var objectMapper = new ObjectMapper();
+        var submission = objectMapper.readValue(response.body(), DownloadCredentialsDTO.class);
+
+        if(response.statusCode() != HttpStatus.OK.value()){
+            throw new IllegalStateException(submission.getContent());
+        } else {
+            return new ByteArrayInputStream(Base64.getDecoder().decode(submission.getContent()));
+        }
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/SubmissionIntegrityService.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/SubmissionIntegrityService.java
new file mode 100644
index 0000000000000000000000000000000000000000..1fba9f63150938c06ad7e30def3e9fbfe38609fe
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/api/v1/service/SubmissionIntegrityService.java
@@ -0,0 +1,69 @@
+package ch.epfl.cs107.grading.moodle.api.v1.service;
+
+import org.springframework.stereotype.Service;
+
+import javax.crypto.KeyGenerator;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Base64;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@Service
+public final class SubmissionIntegrityService {
+
+    /** ??? */
+    private static final String HMAC_ALGORITHM = "HmacSHA256";
+
+    /** ??? */
+    private static final SecretKey secret;
+
+    static {
+        try {
+            secret = KeyGenerator.getInstance(HMAC_ALGORITHM).generateKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * ???
+     * @param courseid ???
+     * @param submissionid ???
+     * @return ???
+     */
+    public String generate(int courseid, int submissionid){
+        // HR : Generate a unique string with both ids
+        var data = String.format("%d:%d", courseid, submissionid);
+        try {
+            var mac = Mac.getInstance(HMAC_ALGORITHM);
+            // HR : Initialize the MAC with the secret
+            mac.init(secret);
+            // HR : Append the data to the MAC
+            var macBytes = mac.doFinal(data.getBytes(UTF_8));
+            // HR : Return a base64 encoding of the MAC
+            return Base64.getEncoder().encodeToString(macBytes);
+        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * ???
+     * @param mac ???
+     * @param courseid ???
+     * @param submissionid ???
+     * @return ???
+     */
+    public boolean check(String mac, int courseid, int submissionid){
+        return generate(courseid, submissionid).equals(mac);
+    }
+
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/MdcInterceptor.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/MdcInterceptor.java
new file mode 100644
index 0000000000000000000000000000000000000000..5ffba36b2a8d8ec19f499119985e31376ae01d84
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/MdcInterceptor.java
@@ -0,0 +1,30 @@
+package ch.epfl.cs107.grading.moodle.logging;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.slf4j.MDC;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.HandlerInterceptor;
+
+import java.util.UUID;
+
+/**
+ * Interceptor that adds a unique request id to the MDC,
+ * so that it can be used in the logs to track a request through the system.
+ *
+ * @implNote To work with async requests, you need to create a TaskDecorator that copies the MDC to the new thread.
+ */
+@Component
+public class MdcInterceptor implements HandlerInterceptor {
+
+    @Override
+    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
+        MDC.put("requestId", UUID.randomUUID().toString());
+        return true;
+    }
+
+    @Override
+    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
+        MDC.remove("requestId");
+    }
+}
diff --git a/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/WebMvcConfiguration.java b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/WebMvcConfiguration.java
new file mode 100644
index 0000000000000000000000000000000000000000..607297481d971db9076bea826e2c4bd7aab54385
--- /dev/null
+++ b/moodle-grading-service/src/main/java/ch/epfl/cs107/grading/moodle/logging/WebMvcConfiguration.java
@@ -0,0 +1,22 @@
+package ch.epfl.cs107.grading.moodle.logging;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Component
+public class WebMvcConfiguration implements WebMvcConfigurer {
+
+    private final MdcInterceptor mdcInterceptor;
+
+    @Autowired
+    public WebMvcConfiguration(MdcInterceptor mdcInterceptor) {
+        this.mdcInterceptor = mdcInterceptor;
+    }
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(mdcInterceptor);
+    }
+}
diff --git a/moodle-grading-service/src/main/resources/logback-spring.xml b/moodle-grading-service/src/main/resources/logback-spring.xml
new file mode 100644
index 0000000000000000000000000000000000000000..647364622402c917514e965043a496dce9335440
--- /dev/null
+++ b/moodle-grading-service/src/main/resources/logback-spring.xml
@@ -0,0 +1,11 @@
+<configuration>
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{ISO8601} %X{requestId} [%thread] %-5level %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <root level="info">
+        <appender-ref ref="STDOUT"/>
+    </root>
+</configuration>
diff --git a/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/controller/PingControllerTest.java b/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/controller/PingControllerTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5480d3736cd500929b006d982769e1e52917f547
--- /dev/null
+++ b/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/controller/PingControllerTest.java
@@ -0,0 +1,78 @@
+package ch.epfl.cs107.grading.moodle.controller;
+
+import ch.epfl.cs107.grading.moodle.utils.WithApiKeyAuthentication;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.HttpHeaders;
+import org.springframework.test.web.servlet.MockMvc;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@AutoConfigureMockMvc
+@SpringBootTest
+public final class PingControllerTest {
+
+    @Value("${api.key}")
+    private String api_key;
+
+    /** ??? */
+    private final static String NO_AUTH_END_POINT = "/api/v1/ping/no-auth";
+
+    /** ???? */
+    private final static String AUTH_END_POINT = "/api/v1/ping/auth";
+
+    /** ??? */
+    @Autowired
+    private MockMvc mockMvc;
+
+    /**
+     * ???
+     * @throws Exception ???
+     */
+    @Test
+    void successPingNoAuthWithoutKey() throws Exception {
+        mockMvc.perform(get(NO_AUTH_END_POINT))
+                .andExpect(status().isOk());
+    }
+
+    /**
+     * ???
+     * @throws Exception ???
+     */
+    @Test
+    //@WithApiKeyAuthentication
+    void successPingNoAuthWithKey() throws Exception {
+        var headers = new HttpHeaders();
+        headers.add("API-KEY", api_key);
+        mockMvc.perform(get(NO_AUTH_END_POINT).headers(headers))
+                .andExpect(status().isOk());
+    }
+
+    /**
+     * ???
+     * @throws Exception ???
+     */
+    @Test
+    void failPingAuthWithoutKey() throws Exception {
+        mockMvc.perform(get(AUTH_END_POINT))
+                .andExpect(status().isForbidden());
+    }
+
+    /**
+     * ???
+     * @throws Exception ???
+     */
+    @Test
+    //@WithApiKeyAuthentication
+    void successPingAuthWithKey() throws Exception {
+        var headers = new HttpHeaders();
+        headers.add("API-KEY", api_key);
+        mockMvc.perform(get(AUTH_END_POINT).headers(headers))
+                .andExpect(status().isOk());
+    }
+
+}
diff --git a/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/utils/WithApiKeyAuthentication.java b/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/utils/WithApiKeyAuthentication.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c9503b2aaa1005c9008adf47d4368bbaf832214
--- /dev/null
+++ b/moodle-grading-service/src/test/java/ch/epfl/cs107/grading/moodle/utils/WithApiKeyAuthentication.java
@@ -0,0 +1,39 @@
+package ch.epfl.cs107.grading.moodle.utils;
+
+import ch.epfl.cs107.grading.moodle.api.v1.auth.ApiKeyAuthentication;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.test.context.support.WithSecurityContext;
+import org.springframework.security.test.context.support.WithSecurityContextFactory;
+
+/**
+ * ???
+ *
+ * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+ */
+@WithSecurityContext(factory = WithApiKeyAuthentication.WithApiKeyAuthenticationFactory.class)
+public @interface WithApiKeyAuthentication {
+
+    /**
+     * ???
+     *
+     * @author Hamza REMMAL (hamza.remmal@epfl.ch)
+     */
+    final class WithApiKeyAuthenticationFactory implements WithSecurityContextFactory<WithApiKeyAuthentication> {
+
+        /** ??? */
+        @Value("${api.key}")
+        private String API_KEY;
+
+        @Override
+        public SecurityContext createSecurityContext(WithApiKeyAuthentication annotation) {
+            var authentication = new ApiKeyAuthentication(API_KEY, true);
+            var context = SecurityContextHolder.createEmptyContext();
+            context.setAuthentication(authentication);
+            return context;
+        }
+
+    }
+
+}
diff --git a/moodle-grading-service/src/test/resources/application.properties b/moodle-grading-service/src/test/resources/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..5ba552783ba5081ff9d109ca5af7cd793a66f37f
--- /dev/null
+++ b/moodle-grading-service/src/test/resources/application.properties
@@ -0,0 +1,8 @@
+# Set the port of the server
+server.port=8082
+
+#
+api.key=123456789
+moodle.baseUrl=http://moodle:80
+moodle.autograde.token=123456789
+grading.service.name=grading-service