From 7e215ec523a6d047bb2dedb7665602dced3a56a9 Mon Sep 17 00:00:00 2001
From: LpCote <73721863+LpCote4@users.noreply.github.com>
Date: Sat, 16 Nov 2024 15:14:46 -0500
Subject: [PATCH] feat: sport challenge value modification in admin

---
 CTFd/api/v1/submissions.py                    | 43 +++++++++++++------
 .../admin/assets/js/pages/submissions.js      | 32 ++++++++++++++
 .../assets/pages/submissions.0ab48ae1.js      |  1 +
 .../assets/pages/submissions.a169d18d.js      |  1 -
 CTFd/themes/admin/static/manifest.json        | 28 +++++++-----
 CTFd/themes/admin/templates/submissions.html  |  8 ++++
 6 files changed, 88 insertions(+), 25 deletions(-)
 create mode 100644 CTFd/themes/admin/static/assets/pages/submissions.0ab48ae1.js
 delete mode 100644 CTFd/themes/admin/static/assets/pages/submissions.a169d18d.js

diff --git a/CTFd/api/v1/submissions.py b/CTFd/api/v1/submissions.py
index 6412ebb1..3ef00066 100644
--- a/CTFd/api/v1/submissions.py
+++ b/CTFd/api/v1/submissions.py
@@ -180,21 +180,38 @@ class Submission(Resource):
 
         return {"success": True, "data": response.data}
 
-    @admins_only
-    @submissions_namespace.doc(
-        description="Endpoint to edit a submission object",
-        responses={
-            200: ("Success", "SubmissionDetailedSuccessResponse"),
-            400: (
-                "Une erreur s'est produite en traitant des données fournies ou stockées",
-                "APISimpleErrorResponse",
-            ),
-        },
-    )
+    # @admins_only
+    # @submissions_namespace.doc(
+    #     description="Endpoint to edit a submission object",
+    #     responses={
+    #         200: ("Success", "SubmissionDetailedSuccessResponse"),
+    #         400: (
+    #             "Une erreur s'est produite en traitant des données fournies ou stockées",
+    #             "APISimpleErrorResponse",
+    #         ),
+    #     },
+    # )
     def patch(self, submission_id):
-        submission = Submissions.query.filter_by(id=submission_id).first_or_404()
- 
         req = request.get_json()
+        new_value = req.get("value")
+        submission = Submissions.query.filter_by(id=submission_id).first_or_404()
+        
+        #Patching value of submission
+        if new_value is not None:
+            try:
+                new_value = int(new_value)  # Ensure the value is numeric
+                submission.value = new_value
+                db.session.commit()
+
+                return {
+                    "success": True,
+                    "data": {"id": submission.id, "value": submission.value},
+                }
+            except ValueError:
+                return {"success": False, "error": "Value must be a number"}, 400
+
+        
+        #Changing submission to Solve
         submission_type = req.get("type")
 
         print("\n\nsubmissions/patch...")
diff --git a/CTFd/themes/admin/assets/js/pages/submissions.js b/CTFd/themes/admin/assets/js/pages/submissions.js
index 2655b6ae..fb6764b1 100644
--- a/CTFd/themes/admin/assets/js/pages/submissions.js
+++ b/CTFd/themes/admin/assets/js/pages/submissions.js
@@ -157,6 +157,37 @@ function copyFlag(event) {
   }, 1500);
 }
 
+function updateValue(event) {
+  const target = $(event.target).closest("[data-submission-id]"); // Look for the closest parent with data attribute
+  const submissionId = target.data("submission-id");
+
+  if (!submissionId) {
+    alert("Submission ID is not defined.");
+    return;
+  }
+  const currentValue = $(event.target).text(); // Get the current text value
+  const newValue = prompt("Enter the new value for the submission:", currentValue); // Prompt for a new value
+
+  if (newValue !== null && newValue.trim() !== "") {
+      // Send a PATCH request to update the submission value
+      CTFd.fetch(`/api/v1/submissions/${submissionId}`, {
+        method: "PATCH",
+        credentials: "same-origin",
+        headers: {
+        Accept: "application/json",
+        "Content-Type": "application/json",
+        },
+        body: JSON.stringify({ value: newValue.trim() }),
+      }).then(function (response) {
+        if (response.success) {
+          window.location.reload();
+        }
+      });
+  } else {
+      alert("Invalid input. Update canceled.");
+  }
+}
+
 $(() => {
   $("#show-full-flags-button").click(showFlagsToggle);
   $("#show-short-flags-button").click(showFlagsToggle);
@@ -165,6 +196,7 @@ $(() => {
   $("#correct-flags-button").click(correctSubmissions);
   $(".delete-correct-submission").click(deleteCorrectSubmission);
   $("#submission-delete-button").click(deleteSelectedSubmissions);
+  $(".submission-value").click(updateValue);
 });
 let elements = document.getElementsByClassName("imageContainer");
 for (let i = 0; i < elements.length; i++) {
diff --git a/CTFd/themes/admin/static/assets/pages/submissions.0ab48ae1.js b/CTFd/themes/admin/static/assets/pages/submissions.0ab48ae1.js
new file mode 100644
index 00000000..aeff45ba
--- /dev/null
+++ b/CTFd/themes/admin/static/assets/pages/submissions.0ab48ae1.js
@@ -0,0 +1 @@
+import{$ as e,u as c,z as u,C as l,B as f}from"./main.a9ca099a.js";import{s as p}from"../visual.09506247.js";window.carouselPosition=0;window.carouselMax=0;function h(i){const t=e(this).data("submission-id"),n=e(this).parent().parent(),s=n.find(".chal").text().trim(),a=n.find(".team").text().trim(),o=e(this).parent().parent();c({title:"Supprimer soumission",body:"\xCAtes-vous certain de vouloir supprimer la soumission de l'\xE9quipe {0} pour le d\xE9fi {1}".format("<strong>"+u(a)+"</strong>","<strong>"+u(s)+"</strong>"),success:function(){alert({submissionId:t}),l.api.delete_submission({submissionId:t}).then(function(r){r.success&&o.remove()})}})}function g(i){let t=e("input[data-submission-id]:checked").map(function(){return e(this).data("submission-id")}),n=t.length===1?"submission":"submissions";c({title:"Supprimer une soumission",body:`\xCAtes-vous certain de vouloir supprimer ${t.length} ${n}?`,success:function(){const s=[];for(var a of t)s.push(l.api.delete_submission({submissionId:a}));Promise.all(s).then(o=>{window.location.reload()})}})}function b(i){let t=e("input[data-submission-id]:checked").map(function(){return e(this).data("submission-id")}).get(),n=t.length===1?"submission":"submissions",s=[];c({title:"Valider une soumission",body:`\xCAtes-vous s\xFBr de vouloir marquer ${t.length} ${n} correct?`,success:function(){const a=t.map(o=>l.fetch(`/api/v1/submissions/${o}`,{method:"GET",credentials:"same-origin",headers:{Accept:"application/json"}}).then(r=>r.json()).then(r=>{r.data.provided!=="M\xE9dia en traitement"?s.push(o):f({title:"Erreur",body:`Soumission ID ${o} ne peut \xEAtre marqu\xE9e comme correcte car le media est encore en traitement.`,button:"OK"})}));Promise.all(a).then(()=>{if(s.length>0){const o=s.map(r=>l.fetch(`/api/v1/submissions/${r}`,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({type:"correct"})}));Promise.all(o).then(()=>{window.location.reload()})}})}})}function m(i){const t=new URLSearchParams(window.location.search);t.has("full")?t.delete("full"):t.set("full","true"),window.location.href=`${window.location.pathname}?${t.toString()}`}function w(i){let t=e(i.currentTarget),n=t.find("i"),s=t.parent().find("pre");s.hasClass("full-flag")?(s.text(s.attr("title").substring(0,42)+"..."),s.removeClass("full-flag"),n.addClass("fa-eye"),n.removeClass("fa-eye-slash")):(s.text(s.attr("title")),s.addClass("full-flag"),n.addClass("fa-eye-slash"),n.removeClass("fa-eye"))}function v(i){let s=e(i.currentTarget).parent().find("pre").attr("title");navigator.clipboard.writeText(s),e(i.currentTarget).tooltip({title:"Copi\xE9!",trigger:"manual"}),e(i.currentTarget).tooltip("show"),setTimeout(function(){e(i.currentTarget).tooltip("hide")},1500)}function y(i){const n=e(i.target).closest("[data-submission-id]").data("submission-id");if(!n){alert("Submission ID is not defined.");return}const s=e(i.target).text(),a=prompt("Enter the new value for the submission:",s);a!==null&&a.trim()!==""?l.fetch(`/api/v1/submissions/${n}`,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({value:a.trim()})}).then(function(o){o.success&&window.location.reload()}):alert("Invalid input. Update canceled.")}e(()=>{e("#show-full-flags-button").click(m),e("#show-short-flags-button").click(m),e(".show-flag").click(w),e(".copy-flag").click(v),e("#correct-flags-button").click(b),e(".delete-correct-submission").click(h),e("#submission-delete-button").click(g),e(".submission-value").click(y)});let d=document.getElementsByClassName("imageContainer");for(let i=0;i<d.length;i++)p(d[i]);
diff --git a/CTFd/themes/admin/static/assets/pages/submissions.a169d18d.js b/CTFd/themes/admin/static/assets/pages/submissions.a169d18d.js
deleted file mode 100644
index c89b3d3c..00000000
--- a/CTFd/themes/admin/static/assets/pages/submissions.a169d18d.js
+++ /dev/null
@@ -1 +0,0 @@
-import{$ as e,u as c,z as u,C as l,B as f}from"./main.a9ca099a.js";import{s as p}from"../visual.09506247.js";window.carouselPosition=0;window.carouselMax=0;function h(i){const t=e(this).data("submission-id"),o=e(this).parent().parent(),s=o.find(".chal").text().trim(),r=o.find(".team").text().trim(),n=e(this).parent().parent();c({title:"Supprimer soumission",body:"\xCAtes-vous certain de vouloir supprimer la soumission de l'\xE9quipe {0} pour le d\xE9fi {1}".format("<strong>"+u(r)+"</strong>","<strong>"+u(s)+"</strong>"),success:function(){alert({submissionId:t}),l.api.delete_submission({submissionId:t}).then(function(a){a.success&&n.remove()})}})}function g(i){let t=e("input[data-submission-id]:checked").map(function(){return e(this).data("submission-id")}),o=t.length===1?"submission":"submissions";c({title:"Supprimer une soumission",body:`\xCAtes-vous certain de vouloir supprimer ${t.length} ${o}?`,success:function(){const s=[];for(var r of t)s.push(l.api.delete_submission({submissionId:r}));Promise.all(s).then(n=>{window.location.reload()})}})}function b(i){let t=e("input[data-submission-id]:checked").map(function(){return e(this).data("submission-id")}).get(),o=t.length===1?"submission":"submissions",s=[];c({title:"Valider une soumission",body:`\xCAtes-vous s\xFBr de vouloir marquer ${t.length} ${o} correct?`,success:function(){const r=t.map(n=>l.fetch(`/api/v1/submissions/${n}`,{method:"GET",credentials:"same-origin",headers:{Accept:"application/json"}}).then(a=>a.json()).then(a=>{a.data.provided!=="M\xE9dia en traitement"?s.push(n):f({title:"Erreur",body:`Soumission ID ${n} ne peut \xEAtre marqu\xE9e comme correcte car le media est encore en traitement.`,button:"OK"})}));Promise.all(r).then(()=>{if(s.length>0){const n=s.map(a=>l.fetch(`/api/v1/submissions/${a}`,{method:"PATCH",credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"},body:JSON.stringify({type:"correct"})}));Promise.all(n).then(()=>{window.location.reload()})}})}})}function m(i){const t=new URLSearchParams(window.location.search);t.has("full")?t.delete("full"):t.set("full","true"),window.location.href=`${window.location.pathname}?${t.toString()}`}function w(i){let t=e(i.currentTarget),o=t.find("i"),s=t.parent().find("pre");s.hasClass("full-flag")?(s.text(s.attr("title").substring(0,42)+"..."),s.removeClass("full-flag"),o.addClass("fa-eye"),o.removeClass("fa-eye-slash")):(s.text(s.attr("title")),s.addClass("full-flag"),o.addClass("fa-eye-slash"),o.removeClass("fa-eye"))}function v(i){let s=e(i.currentTarget).parent().find("pre").attr("title");navigator.clipboard.writeText(s),e(i.currentTarget).tooltip({title:"Copi\xE9!",trigger:"manual"}),e(i.currentTarget).tooltip("show"),setTimeout(function(){e(i.currentTarget).tooltip("hide")},1500)}e(()=>{e("#show-full-flags-button").click(m),e("#show-short-flags-button").click(m),e(".show-flag").click(w),e(".copy-flag").click(v),e("#correct-flags-button").click(b),e(".delete-correct-submission").click(h),e("#submission-delete-button").click(g)});let d=document.getElementsByClassName("imageContainer");for(let i=0;i<d.length;i++)p(d[i]);
diff --git a/CTFd/themes/admin/static/manifest.json b/CTFd/themes/admin/static/manifest.json
index c79f9b3a..c4c48089 100644
--- a/CTFd/themes/admin/static/manifest.json
+++ b/CTFd/themes/admin/static/manifest.json
@@ -87,7 +87,7 @@
     ]
   },
   "assets/js/pages/submissions.js": {
-    "file": "assets/pages/submissions.a169d18d.js",
+    "file": "assets/pages/submissions.0ab48ae1.js",
     "src": "assets/js/pages/submissions.js",
     "isEntry": true,
     "imports": [
@@ -153,8 +153,8 @@
       "assets/CommentBox.23213b39.css"
     ]
   },
-  "_htmlmixed.3b26375a.js": {
-    "file": "assets/htmlmixed.3b26375a.js",
+  "_htmlmixed.5f06a96a.js": {
+    "file": "assets/htmlmixed.5f06a96a.js",
     "imports": [
       "assets/js/pages/main.js"
     ]
@@ -178,24 +178,24 @@
       "_echarts.common.27787dd0.js"
     ]
   },
-  "assets/css/challenge-board.scss": {
-    "file": "assets/challenge-board.44e07e05.css",
-    "src": "assets/css/challenge-board.scss",
-    "isEntry": true
+  "assets/js/pages/challenge.css": {
+    "file": "assets/challenge.66ec3ebe.css",
+    "src": "assets/js/pages/challenge.css"
   },
   "CommentBox.css": {
     "file": "assets/CommentBox.23213b39.css",
     "src": "CommentBox.css"
   },
+  "assets/css/challenge-board.scss": {
+    "file": "assets/challenge-board.44e07e05.css",
+    "src": "assets/css/challenge-board.scss",
+    "isEntry": true
+  },
   "assets/css/admin.scss": {
     "file": "assets/admin.3594ea3f.css",
     "src": "assets/css/admin.scss",
     "isEntry": true
   },
-  "assets/js/pages/challenge.css": {
-    "file": "assets/challenge.66ec3ebe.css",
-    "src": "assets/js/pages/challenge.css"
-  },
   "assets/css/codemirror.scss": {
     "file": "assets/codemirror.d74a88bc.css",
     "src": "assets/css/codemirror.scss",
@@ -210,5 +210,11 @@
     "file": "assets/main.088f55c6.css",
     "src": "assets/css/main.scss",
     "isEntry": true
+  },
+  "_htmlmixed.3b26375a.js": {
+    "file": "assets/htmlmixed.3b26375a.js",
+    "imports": [
+      "assets/js/pages/main.js"
+    ]
   }
 }
\ No newline at end of file
diff --git a/CTFd/themes/admin/templates/submissions.html b/CTFd/themes/admin/templates/submissions.html
index 972c96fe..30405fd4 100644
--- a/CTFd/themes/admin/templates/submissions.html
+++ b/CTFd/themes/admin/templates/submissions.html
@@ -88,6 +88,7 @@
 						<th class="sort-col"><b>Défi</b></th>
 						<th class="sort-col"><b>Type</b></th>
 						<th class="sort-col"><b>Fourni</b></th>
+						<th class="sort-col"><b>Valeur</b></th>
 						<th class="text-center sort-col"><b>Date</b></th>
 					</tr>
 				</thead>
@@ -130,6 +131,13 @@
 								<pre class="mb-0 pl-2 float-left imageContainer" id="{{sub.provided}}" title=""></pre>
 							{% endif %}
 						</td>
+						<td {% if sub.value %} style="color:blue;" class="submission-value" data-submission-id="{{ sub.id }}"{% endif %}>
+							{% if sub.value %}
+							{{ sub.value }}
+							{% else %}
+							{{ sub.challenge.value }}
+							{% endif %}
+						</td>
 						<td class="text-center solve-time">
 							<span data-time="{{ sub.date | isoformat }}"></span>
 						</td>
-- 
GitLab