From c1d5fc0c16d2eb34e8090ced197e681e69f4fd8b Mon Sep 17 00:00:00 2001 From: ty-kim7 Date: Sat, 18 Oct 2025 05:52:53 +0000 Subject: [PATCH 1/3] chore: update Go version and dependencies; modify query execution to handle additional return value --- platform/CTFd/plugins/sql_challenges/Dockerfile | 10 +++++----- platform/CTFd/plugins/sql_challenges/go.mod | 2 +- .../CTFd/plugins/sql_challenges/sql_judge_server.go | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/platform/CTFd/plugins/sql_challenges/Dockerfile b/platform/CTFd/plugins/sql_challenges/Dockerfile index 41d62caeff..f3e98b00a9 100644 --- a/platform/CTFd/plugins/sql_challenges/Dockerfile +++ b/platform/CTFd/plugins/sql_challenges/Dockerfile @@ -1,16 +1,16 @@ -FROM golang:1.22-alpine AS builder +FROM golang:1.23-alpine AS builder WORKDIR /app # Copy go mod files COPY go.mod go.sum ./ -# Download dependencies -RUN go mod download - -# Copy source code +# Copy source code (needed for go mod tidy to detect dependencies) COPY sql_judge_server.go . +# Download dependencies and tidy +RUN go mod download && go mod tidy + # Build the application RUN go build -o sql-judge-server sql_judge_server.go diff --git a/platform/CTFd/plugins/sql_challenges/go.mod b/platform/CTFd/plugins/sql_challenges/go.mod index a46cd2ecdc..9be3b1c40f 100644 --- a/platform/CTFd/plugins/sql_challenges/go.mod +++ b/platform/CTFd/plugins/sql_challenges/go.mod @@ -4,7 +4,7 @@ go 1.22 toolchain go1.24.5 -require github.com/dolthub/go-mysql-server v0.18.1 +require github.com/dolthub/go-mysql-server v0.20.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/platform/CTFd/plugins/sql_challenges/sql_judge_server.go b/platform/CTFd/plugins/sql_challenges/sql_judge_server.go index f463ef74b8..c4e56f06a9 100644 --- a/platform/CTFd/plugins/sql_challenges/sql_judge_server.go +++ b/platform/CTFd/plugins/sql_challenges/sql_judge_server.go @@ -291,7 +291,7 @@ func executeQuery(initQueries []string, query string, req *QueryRequest) (*Query // } // } - _, iter, err := engine.Query(ctx, stmt) + _, iter, _, err := engine.Query(ctx, stmt) if err != nil { if len(stmt) > logMaxLength { log.Printf("Failed to execute (truncated): %s...", stmt[:logMaxLength]) @@ -317,7 +317,7 @@ func executeQuery(initQueries []string, query string, req *QueryRequest) (*Query } // Execute the main query - schema, iter, err := engine.Query(ctx, query) + schema, iter, _, err := engine.Query(ctx, query) if err != nil { return nil, fmt.Errorf("query error: %v", err) } From 2ca4d1d8f0c4bee8a9ec19769c90b48f24db4082 Mon Sep 17 00:00:00 2001 From: ty-kim7 Date: Sun, 19 Oct 2025 14:35:39 +0000 Subject: [PATCH 2/3] fix: test mode --- platform/CTFd/api/v1/challenges.py | 14 ++--- .../CTFd/plugins/sql_challenges/__init__.py | 54 ++++++++++------- .../themes/ddps/assets/js/sql_challenge.js | 60 +++++++++---------- ....e008bd25.js => sql_challenge.01922920.js} | 8 +-- .../CTFd/themes/ddps/static/manifest.json | 2 +- .../themes/ddps/templates/sql_challenge.html | 6 +- 6 files changed, 76 insertions(+), 68 deletions(-) rename platform/CTFd/themes/ddps/static/assets/{sql_challenge.e008bd25.js => sql_challenge.01922920.js} (81%) diff --git a/platform/CTFd/api/v1/challenges.py b/platform/CTFd/api/v1/challenges.py index 5dad6744a6..fbc20cb1d5 100644 --- a/platform/CTFd/api/v1/challenges.py +++ b/platform/CTFd/api/v1/challenges.py @@ -631,19 +631,19 @@ def post(self): request_data = request.get_json() challenge_id = request_data.get("challenge_id") - - # Check for preview flag in request data (for both admin and regular users) - preview = request_data.get("preview", False) - - # Allow preview for SQL challenges - if preview: + + # Check for test flag in request data (for testing without recording submission) + test = request_data.get("test", False) + + # Allow test mode for SQL challenges + if test: challenge = Challenges.query.filter_by(id=challenge_id).first_or_404() # Check if challenge is hidden and user is not admin if challenge.state == "hidden" and not is_admin(): abort(403) - # Only allow preview for SQL challenges + # Only allow test mode for SQL challenges if challenge.type == "sql": chal_class = get_chal_class(challenge.type) response = chal_class.attempt(challenge, request) diff --git a/platform/CTFd/plugins/sql_challenges/__init__.py b/platform/CTFd/plugins/sql_challenges/__init__.py index 9b51df350c..4a56490f3f 100644 --- a/platform/CTFd/plugins/sql_challenges/__init__.py +++ b/platform/CTFd/plugins/sql_challenges/__init__.py @@ -211,26 +211,26 @@ def attempt(cls, challenge, request): """ data = request.form or request.get_json() submission = data.get("submission", "").strip() - is_preview = data.get("preview", False) # Check if this is just a preview/test - + is_test = data.get("test", False) # Check if this is test mode (checks correctness but doesn't record) + # Get user information from request user_id = str(data.get("user_id", "")) user_name = data.get("user_name", "") client_ip = get_ip() - + # Debug logging import logging - logging.info(f"SQL Challenge attempt - Preview: {is_preview}, User ID: {user_id}, User Name: {user_name}, IP: {client_ip}") - + logging.info(f"SQL Challenge attempt - Test mode: {is_test}, User ID: {user_id}, User Name: {user_name}, IP: {client_ip}") + if not submission: return ChallengeResponse( status="incorrect", message="Please provide a SQL query" ) - - # Check deadline only for actual submissions, not previews + + # Check deadline only for actual submissions, not test mode # Use deadline_utc (raw datetime) for comparison, not the property (which returns a string) - if not is_preview and challenge.deadline_utc and datetime.utcnow() > challenge.deadline_utc: + if not is_test and challenge.deadline_utc and datetime.utcnow() > challenge.deadline_utc: return ChallengeResponse( status="incorrect", message="Submission deadline has passed" @@ -240,17 +240,17 @@ def attempt(cls, challenge, request): try: import requests import json - + # Use Go MySQL server go_server_url = os.environ.get('SQL_JUDGE_SERVER_URL', 'http://localhost:8080') - - if is_preview: - # For preview, only execute the user query without comparing + + if is_test: + # For test mode, check correctness but format message differently response = requests.post( f"{go_server_url}/judge", json={ 'init_query': challenge.init_query, - 'solution_query': submission, # Use user query as solution to get its result + 'solution_query': challenge.solution_query, 'user_query': submission, 'user_id': user_id, 'user_name': user_name, @@ -259,26 +259,34 @@ def attempt(cls, challenge, request): }, timeout=10 ) - + if response.status_code == 200: result = response.json() - + if not result.get('success'): return ChallengeResponse( status="incorrect", - message=f"[PREVIEW]\nError: {result.get('error', 'Unknown error')}" + message=f"[TEST]\nError: {result.get('error', 'Unknown error')}" ) - - # Just show the query result without grading + + # Format results for display user_result_str = json.dumps(result['user_result']) - return ChallengeResponse( - status="incorrect", - message=f"[PREVIEW]\nQuery executed successfully:\n\n[USER_RESULT]\n{user_result_str}\n[/USER_RESULT]" - ) + + if result['match']: + return ChallengeResponse( + status="correct", + message=f"[TEST]\n✅ Correct! Your query produces the expected result.\n\n[USER_RESULT]\n{user_result_str}\n[/USER_RESULT]" + ) + else: + expected_result_str = json.dumps(result['expected_result']) + return ChallengeResponse( + status="incorrect", + message=f"[TEST]\n❌ Incorrect. Your query does not produce the expected result.\n\n[USER_RESULT]\n{user_result_str}\n[/USER_RESULT]\n\n[EXPECTED_RESULT]\n{expected_result_str}\n[/EXPECTED_RESULT]" + ) else: return ChallengeResponse( status="incorrect", - message=f"[PREVIEW]\nSQL judge server error: HTTP {response.status_code}" + message=f"[TEST]\nSQL judge server error: HTTP {response.status_code}" ) else: # Normal submission - compare with solution diff --git a/platform/CTFd/themes/ddps/assets/js/sql_challenge.js b/platform/CTFd/themes/ddps/assets/js/sql_challenge.js index c8d62c642c..9b5a281dd0 100644 --- a/platform/CTFd/themes/ddps/assets/js/sql_challenge.js +++ b/platform/CTFd/themes/ddps/assets/js/sql_challenge.js @@ -241,29 +241,29 @@ function resetSQLEditor() { showAutoSaveNotification('Editor has been reset'); } -// Execute SQL Query (without submission) +// Execute SQL Query (test mode - checks correctness but doesn't record submission) async function executeSQLQuery() { const challengeId = document.getElementById('challenge-id').value; const submission = sqlEditor ? sqlEditor.getValue() : document.getElementById('challenge-input').value; // Get user information from CTFd object const userId = (CTFd && CTFd.user) ? CTFd.user.id : null; const userName = (CTFd && CTFd.user) ? CTFd.user.name : 'anonymous'; - + if (!submission.trim()) { alert('Please enter a SQL query'); return; } - + try { const requestBody = { challenge_id: parseInt(challengeId), submission: submission, - preview: true, // Flag to indicate this is just a preview/test + test: true, // Flag to indicate this is a test (checks correctness but doesn't record) user_id: userId, user_name: userName }; // Request body prepared - + const response = await fetch('/api/v1/challenges/attempt', { method: 'POST', headers: { @@ -272,16 +272,16 @@ async function executeSQLQuery() { }, body: JSON.stringify(requestBody) }); - + // Response received if (!response.ok) { console.error('Response not OK:', response.statusText); } - + // First get the response as text to debug const responseText = await response.text(); // Response text received - + // Try to parse as JSON let result; try { @@ -293,14 +293,14 @@ async function executeSQLQuery() { return; } // Result parsed - + // Check if result has the expected structure if (!result || typeof result !== 'object') { console.error('Invalid result structure:', result); alert('Invalid response from server'); return; } - + // CTFd API returns {success: bool, data: {...}} structure // We need to wrap our result if it doesn't have this structure if (!result.hasOwnProperty('data')) { @@ -313,7 +313,7 @@ async function executeSQLQuery() { } else { displayResult(result, true); } - + } catch (error) { console.error('Error executing query:', error); console.error('Error details:', error.message, error.stack); @@ -409,28 +409,28 @@ async function submitSQLChallenge() { } // Display Result -function displayResult(result, isPreview = false) { +function displayResult(result, isTest = false) { const container = document.getElementById('query-result-container'); - + // Clear and prepare container container.innerHTML = ''; - - // Parse message first to check if it's a preview + + // Parse message first to check if it's a test let message = result.data.message || ''; - const isActuallyPreview = message.startsWith('[PREVIEW]'); - + const isActuallyTest = message.startsWith('[TEST]'); + // Create status message const statusDiv = document.createElement('div'); const isAlreadySolved = result.data.status === 'already_solved'; - statusDiv.className = result.data.status === 'correct' || isAlreadySolved ? 'alert alert-success' : - (result.data.status === 'incorrect' && !isActuallyPreview) ? 'alert alert-danger' : - isActuallyPreview ? 'alert alert-info' : + statusDiv.className = result.data.status === 'correct' || isAlreadySolved ? 'alert alert-success' : + (result.data.status === 'incorrect' && !isActuallyTest) ? 'alert alert-danger' : + isActuallyTest ? 'alert alert-info' : 'alert alert-warning'; statusDiv.innerHTML = ''; - + // Continue processing message - if (isActuallyPreview) { - message = message.replace('[PREVIEW]\n', ''); + if (isActuallyTest) { + message = message.replace('[TEST]\n', ''); } // Remove "but you already solved this" from message for parsing @@ -517,24 +517,24 @@ function displayResult(result, isPreview = false) { // No user result to render } - // Show success modal if correct (only for actual submissions, not previews) - if (result.data.status === 'correct' && !isPreview) { + // Show success modal if correct (only for actual submissions, not tests) + if (result.data.status === 'correct' && !isTest) { // Challenge solved! Showing success modal - + // Clear saved code since challenge is solved clearSavedCode(); - + // Update earned points if available if (result.data.points) { document.getElementById('earned-points').textContent = result.data.points; } - + // Show modal after a short delay to let the result display first setTimeout(() => { try { const modalElement = document.getElementById('successModal'); // Modal element found - + if (modalElement) { // Manually show modal modalElement.classList.add('show'); @@ -550,7 +550,7 @@ function displayResult(result, isPreview = false) { } }, 1000); } - + } diff --git a/platform/CTFd/themes/ddps/static/assets/sql_challenge.e008bd25.js b/platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js similarity index 81% rename from platform/CTFd/themes/ddps/static/assets/sql_challenge.e008bd25.js rename to platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js index ad81d9f717..3b1dcc791b 100644 --- a/platform/CTFd/themes/ddps/static/assets/sql_challenge.e008bd25.js +++ b/platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js @@ -1,12 +1,12 @@ -let l=null;function y(){const t=document.getElementById("challenge-input"),e=document.getElementById("sql-editor-container");if(!t||!e)return!1;if(l){try{l.toTextArea()}catch{}l=null}if(e.innerHTML="",typeof CodeMirror>"u"){if(!document.querySelector('link[href*="codemirror"]')){const m=document.createElement("link");m.rel="stylesheet",m.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css",document.head.appendChild(m);const f=document.createElement("link");f.rel="stylesheet",f.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css",document.head.appendChild(f)}const d=document.createElement("script");return d.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js",d.onload=function(){const m=document.createElement("script");m.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js",m.onload=function(){y()},document.head.appendChild(m)},document.head.appendChild(d),!1}if(typeof CTFd>"u"||!CTFd.user)return setTimeout(()=>y(),100),!1;const s=document.getElementById("challenge-id").value,o=`sql-challenge-user${CTFd.user.id||"anonymous"}-ch${s}-code`,i=localStorage.getItem(o),n=i||t.value||"";l=CodeMirror(e,{value:"",mode:"text/x-sql",theme:"monokai",lineNumbers:!0,indentUnit:4,lineWrapping:!0,autofocus:!0,scrollbarStyle:"native",viewportMargin:1/0,readOnly:!1,inputStyle:"contenteditable",extraKeys:{"Ctrl-Enter":function(){document.getElementById("challenge-submit").click()},Tab:"indentMore"}}),setTimeout(()=>{l.refresh(),n&&l.setValue(n),l.focus(),l.setCursor(l.lineCount(),0)},200),e.addEventListener("click",function(d){(d.target===e||d.target.classList.contains("CodeMirror-scroll"))&&l.focus()});const r=function(){const d=l.getValue();if(t.value=d,d.trim())try{localStorage.setItem(o,d)}catch(m){console.error("Failed to save:",m)}else localStorage.removeItem(o)};let c=!0;return l.on("beforeChange",function(){if(c){c=!1;return}setTimeout(r,0)}),l.on("inputRead",r),l.on("keyHandled",r),l.on("blur",r),setInterval(()=>{l&&document.querySelector(".CodeMirror-focused")&&r()},1e3),i&&w("Previous code restored from auto-save"),typeof setupBehaviorTracking=="function"&&l&&(setupBehaviorTracking(l),console.log("[SQL Challenge] Behavior tracking initialized")),!0}function w(t){const e=document.createElement("div");e.className="alert alert-info fade show position-fixed",e.style.cssText="top: 70px; right: 20px; z-index: 1050; max-width: 350px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;";const s=document.createElement("div");s.style.cssText="display: flex; align-items: center;",s.innerHTML=`${t}`;const a=document.createElement("button");a.type="button",a.className="btn-close btn-sm",a.style.cssText="position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;",a.onclick=function(){e.remove()},e.appendChild(s),e.appendChild(a),document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.remove()},3e3)}function L(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s)}function x(){if(!confirm("Are you sure you want to reset the editor? This will clear all your current code."))return;if(l)l.setValue(""),l.focus();else{const o=document.getElementById("challenge-input");o&&(o.value="")}const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s);const a=document.getElementById("query-result-container");a&&(a.innerHTML=` +let l=null;function y(){const t=document.getElementById("challenge-input"),e=document.getElementById("sql-editor-container");if(!t||!e)return!1;if(l){try{l.toTextArea()}catch{}l=null}if(e.innerHTML="",typeof CodeMirror>"u"){if(!document.querySelector('link[href*="codemirror"]')){const m=document.createElement("link");m.rel="stylesheet",m.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css",document.head.appendChild(m);const f=document.createElement("link");f.rel="stylesheet",f.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css",document.head.appendChild(f)}const d=document.createElement("script");return d.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js",d.onload=function(){const m=document.createElement("script");m.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js",m.onload=function(){y()},document.head.appendChild(m)},document.head.appendChild(d),!1}if(typeof CTFd>"u"||!CTFd.user)return setTimeout(()=>y(),100),!1;const s=document.getElementById("challenge-id").value,o=`sql-challenge-user${CTFd.user.id||"anonymous"}-ch${s}-code`,i=localStorage.getItem(o),n=i||t.value||"";l=CodeMirror(e,{value:"",mode:"text/x-sql",theme:"monokai",lineNumbers:!0,indentUnit:4,lineWrapping:!0,autofocus:!0,scrollbarStyle:"native",viewportMargin:1/0,readOnly:!1,inputStyle:"contenteditable",extraKeys:{"Ctrl-Enter":function(){document.getElementById("challenge-submit").click()},Tab:"indentMore"}}),setTimeout(()=>{l.refresh(),n&&l.setValue(n),l.focus(),l.setCursor(l.lineCount(),0)},200),e.addEventListener("click",function(d){(d.target===e||d.target.classList.contains("CodeMirror-scroll"))&&l.focus()});const r=function(){const d=l.getValue();if(t.value=d,d.trim())try{localStorage.setItem(o,d)}catch(m){console.error("Failed to save:",m)}else localStorage.removeItem(o)};let c=!0;return l.on("beforeChange",function(){if(c){c=!1;return}setTimeout(r,0)}),l.on("inputRead",r),l.on("keyHandled",r),l.on("blur",r),setInterval(()=>{l&&document.querySelector(".CodeMirror-focused")&&r()},1e3),i&&L("Previous code restored from auto-save"),typeof setupBehaviorTracking=="function"&&l&&(setupBehaviorTracking(l),console.log("[SQL Challenge] Behavior tracking initialized")),!0}function L(t){const e=document.createElement("div");e.className="alert alert-info fade show position-fixed",e.style.cssText="top: 70px; right: 20px; z-index: 1050; max-width: 350px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;";const s=document.createElement("div");s.style.cssText="display: flex; align-items: center;",s.innerHTML=`${t}`;const a=document.createElement("button");a.type="button",a.className="btn-close btn-sm",a.style.cssText="position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;",a.onclick=function(){e.remove()},e.appendChild(s),e.appendChild(a),document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.remove()},3e3)}function w(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s)}function x(){if(!confirm("Are you sure you want to reset the editor? This will clear all your current code."))return;if(l)l.setValue(""),l.focus();else{const o=document.getElementById("challenge-input");o&&(o.value="")}const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s);const a=document.getElementById("query-result-container");a&&(a.innerHTML=`

Execute a query to see results

- `),w("Editor has been reset")}async function B(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}try{const o={challenge_id:parseInt(t),submission:e,preview:!0,user_id:s,user_name:a},i=await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify(o)});i.ok||console.error("Response not OK:",i.statusText);const n=await i.text();let r;try{r=JSON.parse(n)}catch(c){console.error("Failed to parse response as JSON:",c),console.error("Response was:",n),alert("Server returned invalid JSON response");return}if(!r||typeof r!="object"){console.error("Invalid result structure:",r),alert("Invalid response from server");return}r.hasOwnProperty("data")?b(r,!0):b({success:!0,data:r},!0)}catch(o){console.error("Error executing query:",o),console.error("Error details:",o.message,o.stack),alert("Error executing query. Please check console for details.")}}async function $(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}const o=document.getElementById("deadline-time");if(o){const i=o.getAttribute("data-deadline");if(i){const n=new Date(i);if(new Date>n){b({success:!0,data:{status:"incorrect",message:"Submission deadline has passed"}},!1);return}}}try{const n=await(await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify({challenge_id:parseInt(t),submission:e,user_id:s,user_name:a})})).json();if(!n||typeof n!="object"){console.error("Invalid result structure:",n),alert("Invalid response from server");return}if(behaviorLogger){const r=n.data.status||"unknown";behaviorLogger.logEvent("submit",{query_text:e,query_length:e.length,submit_status:r})}n.hasOwnProperty("data")?b(n,!1):b({success:!0,data:n},!1)}catch(i){console.error("Error submitting challenge:",i),alert("Error submitting challenge. Please try again.")}}function b(t,e=!1){const s=document.getElementById("query-result-container");s.innerHTML="";let a=t.data.message||"";const o=a.startsWith("[PREVIEW]"),i=document.createElement("div"),n=t.data.status==="already_solved";i.className=t.data.status==="correct"||n?"alert alert-success":t.data.status==="incorrect"&&!o?"alert alert-danger":o?"alert alert-info":"alert alert-warning",i.innerHTML='',o&&(a=a.replace(`[PREVIEW] + `),L("Editor has been reset")}async function B(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}try{const o={challenge_id:parseInt(t),submission:e,test:!0,user_id:s,user_name:a},i=await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify(o)});i.ok||console.error("Response not OK:",i.statusText);const n=await i.text();let r;try{r=JSON.parse(n)}catch(c){console.error("Failed to parse response as JSON:",c),console.error("Response was:",n),alert("Server returned invalid JSON response");return}if(!r||typeof r!="object"){console.error("Invalid result structure:",r),alert("Invalid response from server");return}r.hasOwnProperty("data")?b(r,!0):b({success:!0,data:r},!0)}catch(o){console.error("Error executing query:",o),console.error("Error details:",o.message,o.stack),alert("Error executing query. Please check console for details.")}}async function $(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}const o=document.getElementById("deadline-time");if(o){const i=o.getAttribute("data-deadline");if(i){const n=new Date(i);if(new Date>n){b({success:!0,data:{status:"incorrect",message:"Submission deadline has passed"}},!1);return}}}try{const n=await(await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify({challenge_id:parseInt(t),submission:e,user_id:s,user_name:a})})).json();if(!n||typeof n!="object"){console.error("Invalid result structure:",n),alert("Invalid response from server");return}if(behaviorLogger){const r=n.data.status||"unknown";behaviorLogger.logEvent("submit",{query_text:e,query_length:e.length,submit_status:r})}n.hasOwnProperty("data")?b(n,!1):b({success:!0,data:n},!1)}catch(i){console.error("Error submitting challenge:",i),alert("Error submitting challenge. Please try again.")}}function b(t,e=!1){const s=document.getElementById("query-result-container");s.innerHTML="";let a=t.data.message||"";const o=a.startsWith("[TEST]"),i=document.createElement("div"),n=t.data.status==="already_solved";i.className=t.data.status==="correct"||n?"alert alert-success":t.data.status==="incorrect"&&!o?"alert alert-danger":o?"alert alert-info":"alert alert-warning",i.innerHTML='',o&&(a=a.replace(`[TEST] `,"")),n&&(a=a.replace(" but you already solved this",""));const r=a.split(` `);let c="",d=null,m=null,f=!1,v=!1,h=[];for(const u of r)if(u==="[USER_RESULT]")f=!0,h=[];else if(u==="[/USER_RESULT]"){f=!1;try{const g=h.join("");d=JSON.parse(g)}catch(g){console.error("Failed to parse user result:",g,"JSON string:",h.join(""))}}else if(u==="[EXPECTED_RESULT]")v=!0,h=[];else if(u==="[/EXPECTED_RESULT]"){v=!1;try{const g=h.join("");m=JSON.parse(g)}catch(g){console.error("Failed to parse expected result:",g,"JSON string:",h.join(""))}}else f||v?h.push(u):c+=u+` -`;s.appendChild(i);let p=c.trim();if(n&&(p+=" (Already solved)"),document.getElementById("status-text").innerHTML=p,d){const u=document.createElement("div");u.className="card mb-2";const g=k(d),E=document.createElement("div");E.className="card-header bg-primary text-white",E.innerHTML='
Your Query Result
';const C=document.createElement("div");C.className="card-body p-0";const I=document.createElement("div");I.className="table-responsive",I.innerHTML=g,C.appendChild(I),u.appendChild(E),u.appendChild(C),s.appendChild(u)}t.data.status==="correct"&&!e&&(L(),t.data.points&&(document.getElementById("earned-points").textContent=t.data.points),setTimeout(()=>{try{const u=document.getElementById("successModal");u?(u.classList.add("show"),u.style.display="block",u.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open")):console.error("Modal element not found!")}catch(u){console.error("Error showing modal:",u)}},1e3))}function k(t){if(!t||!t.columns||!t.rows)return'

No data to display

';if(t.rows.length===0)return'

Empty result set

';let e='';e+='';for(const s of t.columns)e+=``;e+="",e+="";for(const s of t.rows){e+="";for(const a of s)e+=``;e+=""}return e+="",e+="
${S(s)}
${S(a)}
",e+=``,e}function S(t){return t==null?"":String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function F(){const t=document.getElementById("resize-handle"),e=document.getElementById("left-panel"),s=document.getElementById("right-panel"),a=document.getElementById("resize-container");let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientX,n=e.offsetWidth,document.body.style.cursor="col-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetWidth,f=(n+(c.clientX-i))/d*100;f>=20&&f<=70&&(e.style.width=f+"%",s.style.width=100-f+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.width;c&&localStorage.setItem("sql-challenge-left-width",c)}});const r=localStorage.getItem("sql-challenge-left-width");r&&(e.style.width=r,s.style.width=100-parseFloat(r)+"%")}function q(){const t=document.getElementById("vertical-resize-handle"),e=document.getElementById("editor-section"),s=document.getElementById("result-section"),a=document.querySelector(".sql-editor-section");if(!t||!e||!s)return;let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientY,n=e.offsetHeight,document.body.style.cursor="row-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetHeight,f=(n+(c.clientY-i))/d*100;f>=20&&f<=80&&(e.style.height=f+"%",s.style.height=100-f-2+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.height;c&&localStorage.setItem("sql-challenge-editor-height",c)}});const r=localStorage.getItem("sql-challenge-editor-height");r&&(e.style.height=r,s.style.height=100-parseFloat(r)-2+"%")}async function M(){const t=document.getElementById("challenge-id").value,e=document.getElementById("historyModal"),s=document.getElementById("submission-history-content");if(!(!e||!s)){e.classList.add("show"),e.style.display="block",e.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open"),s.innerHTML=` +`;s.appendChild(i);let p=c.trim();if(n&&(p+=" (Already solved)"),document.getElementById("status-text").innerHTML=p,d){const u=document.createElement("div");u.className="card mb-2";const g=k(d),E=document.createElement("div");E.className="card-header bg-primary text-white",E.innerHTML='
Your Query Result
';const T=document.createElement("div");T.className="card-body p-0";const C=document.createElement("div");C.className="table-responsive",C.innerHTML=g,T.appendChild(C),u.appendChild(E),u.appendChild(T),s.appendChild(u)}t.data.status==="correct"&&!e&&(w(),t.data.points&&(document.getElementById("earned-points").textContent=t.data.points),setTimeout(()=>{try{const u=document.getElementById("successModal");u?(u.classList.add("show"),u.style.display="block",u.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open")):console.error("Modal element not found!")}catch(u){console.error("Error showing modal:",u)}},1e3))}function k(t){if(!t||!t.columns||!t.rows)return'

No data to display

';if(t.rows.length===0)return'

Empty result set

';let e='';e+='';for(const s of t.columns)e+=``;e+="",e+="";for(const s of t.rows){e+="";for(const a of s)e+=``;e+=""}return e+="",e+="
${S(s)}
${S(a)}
",e+=``,e}function S(t){return t==null?"":String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function F(){const t=document.getElementById("resize-handle"),e=document.getElementById("left-panel"),s=document.getElementById("right-panel"),a=document.getElementById("resize-container");let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientX,n=e.offsetWidth,document.body.style.cursor="col-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetWidth,f=(n+(c.clientX-i))/d*100;f>=20&&f<=70&&(e.style.width=f+"%",s.style.width=100-f+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.width;c&&localStorage.setItem("sql-challenge-left-width",c)}});const r=localStorage.getItem("sql-challenge-left-width");r&&(e.style.width=r,s.style.width=100-parseFloat(r)+"%")}function q(){const t=document.getElementById("vertical-resize-handle"),e=document.getElementById("editor-section"),s=document.getElementById("result-section"),a=document.querySelector(".sql-editor-section");if(!t||!e||!s)return;let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientY,n=e.offsetHeight,document.body.style.cursor="row-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetHeight,f=(n+(c.clientY-i))/d*100;f>=20&&f<=80&&(e.style.height=f+"%",s.style.height=100-f-2+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.height;c&&localStorage.setItem("sql-challenge-editor-height",c)}});const r=localStorage.getItem("sql-challenge-editor-height");r&&(e.style.height=r,s.style.height=100-parseFloat(r)-2+"%")}async function M(){const t=document.getElementById("challenge-id").value,e=document.getElementById("historyModal"),s=document.getElementById("submission-history-content");if(!(!e||!s)){e.classList.add("show"),e.style.display="block",e.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open"),s.innerHTML=`

Loading submission history...

@@ -55,4 +55,4 @@ let l=null;function y(){const t=document.getElementById("challenge-input"),e=doc An error occurred while loading submission history.
- `}}}function T(){const t=document.getElementById("deadline-time");if(!t)return;const e=t.getAttribute("data-deadline");if(!e)return;const s=new Date(e),a=new Date,o=s-a;if(o<=0){t.textContent="Expired",t.closest(".deadline-display").classList.add("deadline-danger");return}const i=Math.floor(o/(1e3*60*60*24)),n=Math.floor(o%(1e3*60*60*24)/(1e3*60*60)),r=Math.floor(o%(1e3*60*60)/(1e3*60)),c=Math.floor(o%(1e3*60)/1e3);let d="";i>0?d=`${i}d ${n}h ${r}m`:n>0?d=`${n}h ${r}m ${c}s`:r>0?d=`${r}m ${c}s`:d=`${c}s`,t.textContent=d+" remaining";const m=t.closest(".deadline-display");m.classList.remove("deadline-warning","deadline-danger"),i===0&&n<1?m.classList.add("deadline-danger"):i===0&&n<6&&m.classList.add("deadline-warning")}function R(){console.log("=== LocalStorage Debug ==="),console.log("All localStorage keys:",Object.keys(localStorage)),console.log("SQL related keys:");for(let t in localStorage)t.includes("sql-challenge")&&console.log(` ${t}: ${localStorage[t].substring(0,50)}...`);console.log("========================")}window.testSaveCode=function(t){const e=document.getElementById("challenge-id").value,a=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${e}-code`;localStorage.setItem(a,t||"SELECT * FROM test;"),console.log(`Saved to ${a}`),R()};window.testLoadCode=function(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${t}-code`,a=localStorage.getItem(s);return console.log(`Loading from ${s}:`,a),a&&l&&(l.setValue(a),console.log("Code loaded into editor")),a};document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".challenge-panel");t&&(t.addEventListener("contextmenu",function(n){return n.preventDefault(),!1}),t.addEventListener("selectstart",function(n){return n.preventDefault(),!1}),t.addEventListener("keydown",function(n){if((n.ctrlKey||n.metaKey)&&(n.keyCode===67||n.keyCode===65||n.keyCode===88))return n.preventDefault(),!1}),t.addEventListener("dragstart",function(n){return n.preventDefault(),!1}),t.addEventListener("mousedown",function(n){if(n.detail>1)return n.preventDefault(),!1})),y(),setTimeout(()=>{l||y()},500),setTimeout(()=>{l||y()},1e3),F(),q(),T(),setInterval(T,1e3);const e=document.getElementById("challenge-reset"),s=document.getElementById("challenge-execute"),a=document.getElementById("challenge-submit"),o=document.getElementById("challenge-history");e&&e.addEventListener("click",x),s&&s.addEventListener("click",B),a&&a.addEventListener("click",$),o&&o.addEventListener("click",M),document.querySelectorAll('[data-bs-dismiss="modal"]').forEach(n=>{n.addEventListener("click",function(){const r=this.closest(".modal");r&&(r.classList.remove("show"),r.style.display="none",r.setAttribute("aria-hidden","true"),document.body.classList.remove("modal-open"))})})});window.addEventListener("load",function(){l||y()}); + `}}}function I(){const t=document.getElementById("deadline-time");if(!t)return;const e=t.getAttribute("data-deadline");if(!e)return;const s=new Date(e),a=new Date,o=s-a;if(o<=0){t.textContent="Expired",t.closest(".deadline-display").classList.add("deadline-danger");return}const i=Math.floor(o/(1e3*60*60*24)),n=Math.floor(o%(1e3*60*60*24)/(1e3*60*60)),r=Math.floor(o%(1e3*60*60)/(1e3*60)),c=Math.floor(o%(1e3*60)/1e3);let d="";i>0?d=`${i}d ${n}h ${r}m`:n>0?d=`${n}h ${r}m ${c}s`:r>0?d=`${r}m ${c}s`:d=`${c}s`,t.textContent=d+" remaining";const m=t.closest(".deadline-display");m.classList.remove("deadline-warning","deadline-danger"),i===0&&n<1?m.classList.add("deadline-danger"):i===0&&n<6&&m.classList.add("deadline-warning")}function H(){console.log("=== LocalStorage Debug ==="),console.log("All localStorage keys:",Object.keys(localStorage)),console.log("SQL related keys:");for(let t in localStorage)t.includes("sql-challenge")&&console.log(` ${t}: ${localStorage[t].substring(0,50)}...`);console.log("========================")}window.testSaveCode=function(t){const e=document.getElementById("challenge-id").value,a=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${e}-code`;localStorage.setItem(a,t||"SELECT * FROM test;"),console.log(`Saved to ${a}`),H()};window.testLoadCode=function(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${t}-code`,a=localStorage.getItem(s);return console.log(`Loading from ${s}:`,a),a&&l&&(l.setValue(a),console.log("Code loaded into editor")),a};document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".challenge-panel");t&&(t.addEventListener("contextmenu",function(n){return n.preventDefault(),!1}),t.addEventListener("selectstart",function(n){return n.preventDefault(),!1}),t.addEventListener("keydown",function(n){if((n.ctrlKey||n.metaKey)&&(n.keyCode===67||n.keyCode===65||n.keyCode===88))return n.preventDefault(),!1}),t.addEventListener("dragstart",function(n){return n.preventDefault(),!1}),t.addEventListener("mousedown",function(n){if(n.detail>1)return n.preventDefault(),!1})),y(),setTimeout(()=>{l||y()},500),setTimeout(()=>{l||y()},1e3),F(),q(),I(),setInterval(I,1e3);const e=document.getElementById("challenge-reset"),s=document.getElementById("challenge-execute"),a=document.getElementById("challenge-submit"),o=document.getElementById("challenge-history");e&&e.addEventListener("click",x),s&&s.addEventListener("click",B),a&&a.addEventListener("click",$),o&&o.addEventListener("click",M),document.querySelectorAll('[data-bs-dismiss="modal"]').forEach(n=>{n.addEventListener("click",function(){const r=this.closest(".modal");r&&(r.classList.remove("show"),r.style.display="none",r.setAttribute("aria-hidden","true"),document.body.classList.remove("modal-open"))})})});window.addEventListener("load",function(){l||y()}); diff --git a/platform/CTFd/themes/ddps/static/manifest.json b/platform/CTFd/themes/ddps/static/manifest.json index 44b9f34b2e..992740e367 100644 --- a/platform/CTFd/themes/ddps/static/manifest.json +++ b/platform/CTFd/themes/ddps/static/manifest.json @@ -127,7 +127,7 @@ "isEntry": true }, "assets/js/sql_challenge.js": { - "file": "assets/sql_challenge.e008bd25.js", + "file": "assets/sql_challenge.01922920.js", "src": "assets/js/sql_challenge.js", "isEntry": true }, diff --git a/platform/CTFd/themes/ddps/templates/sql_challenge.html b/platform/CTFd/themes/ddps/templates/sql_challenge.html index 34fecaa0c7..8fee00e719 100644 --- a/platform/CTFd/themes/ddps/templates/sql_challenge.html +++ b/platform/CTFd/themes/ddps/templates/sql_challenge.html @@ -103,9 +103,9 @@
Execution Results
id="challenge-execute" class="btn btn-secondary" type="button" - title="Run query without submitting" + title="Test your query and check if it's correct" > - Execute + Test From be9df05df892de139bea97c5524331dc12099ea7 Mon Sep 17 00:00:00 2001 From: ty-kim7 Date: Sun, 19 Oct 2025 14:53:33 +0000 Subject: [PATCH 3/3] feat: enhance SQL challenge interface with improved button layout and error handling notifications --- .../plugins/sql_challenges/assets/update.html | 11 ++- .../themes/ddps/assets/js/sql_challenge.js | 94 +++++++++++++++---- .../static/assets/sql_challenge.01922920.js | 58 ------------ .../static/assets/sql_challenge.d0862715.js | 58 ++++++++++++ .../CTFd/themes/ddps/static/manifest.json | 2 +- .../themes/ddps/templates/sql_challenge.html | 4 +- 6 files changed, 143 insertions(+), 84 deletions(-) delete mode 100644 platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js create mode 100644 platform/CTFd/themes/ddps/static/assets/sql_challenge.d0862715.js diff --git a/platform/CTFd/plugins/sql_challenges/assets/update.html b/platform/CTFd/plugins/sql_challenges/assets/update.html index 6011a66d21..bb507bb321 100644 --- a/platform/CTFd/plugins/sql_challenges/assets/update.html +++ b/platform/CTFd/plugins/sql_challenges/assets/update.html @@ -83,7 +83,7 @@
Test Results:
The state of the challenge (visible, hidden) - @@ -97,6 +97,11 @@
Test Results:
- - + + \ No newline at end of file diff --git a/platform/CTFd/themes/ddps/assets/js/sql_challenge.js b/platform/CTFd/themes/ddps/assets/js/sql_challenge.js index 9b5a281dd0..a84a477d03 100644 --- a/platform/CTFd/themes/ddps/assets/js/sql_challenge.js +++ b/platform/CTFd/themes/ddps/assets/js/sql_challenge.js @@ -169,23 +169,23 @@ function showAutoSaveNotification(message) { const notification = document.createElement('div'); notification.className = 'alert alert-info fade show position-fixed'; notification.style.cssText = 'top: 70px; right: 20px; z-index: 1050; max-width: 350px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;'; - + // Create message content const messageContent = document.createElement('div'); messageContent.style.cssText = 'display: flex; align-items: center;'; messageContent.innerHTML = `${message}`; - + // Create close button const closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.className = 'btn-close btn-sm'; closeButton.style.cssText = 'position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;'; closeButton.onclick = function() { notification.remove(); }; - + notification.appendChild(messageContent); notification.appendChild(closeButton); document.body.appendChild(notification); - + // Auto-hide after 3 seconds setTimeout(() => { if (notification.parentNode) { @@ -194,6 +194,36 @@ function showAutoSaveNotification(message) { }, 3000); } +// Show error toast notification +function showErrorToast(message) { + const notification = document.createElement('div'); + notification.className = 'alert alert-danger fade show position-fixed'; + notification.style.cssText = 'top: 70px; right: 20px; z-index: 1050; max-width: 400px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;'; + + // Create message content + const messageContent = document.createElement('div'); + messageContent.style.cssText = 'display: flex; align-items: center;'; + messageContent.innerHTML = `${escapeHtml(message)}`; + + // Create close button + const closeButton = document.createElement('button'); + closeButton.type = 'button'; + closeButton.className = 'btn-close btn-sm'; + closeButton.style.cssText = 'position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;'; + closeButton.onclick = function() { notification.remove(); }; + + notification.appendChild(messageContent); + notification.appendChild(closeButton); + document.body.appendChild(notification); + + // Auto-hide after 5 seconds + setTimeout(() => { + if (notification.parentNode) { + notification.remove(); + } + }, 5000); +} + // Clear saved code when challenge is solved function clearSavedCode() { const challengeId = document.getElementById('challenge-id').value; @@ -264,15 +294,22 @@ async function executeSQLQuery() { }; // Request body prepared + // Create abort controller for timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 second timeout + const response = await fetch('/api/v1/challenges/attempt', { method: 'POST', headers: { 'Content-Type': 'application/json', 'CSRF-Token': init.csrfNonce }, - body: JSON.stringify(requestBody) + body: JSON.stringify(requestBody), + signal: controller.signal }); + clearTimeout(timeoutId); + // Response received if (!response.ok) { console.error('Response not OK:', response.statusText); @@ -289,7 +326,7 @@ async function executeSQLQuery() { } catch (e) { console.error('Failed to parse response as JSON:', e); console.error('Response was:', responseText); - alert('Server returned invalid JSON response'); + showErrorToast('Server returned invalid JSON response'); return; } // Result parsed @@ -297,7 +334,7 @@ async function executeSQLQuery() { // Check if result has the expected structure if (!result || typeof result !== 'object') { console.error('Invalid result structure:', result); - alert('Invalid response from server'); + showErrorToast('Invalid response from server'); return; } @@ -315,9 +352,14 @@ async function executeSQLQuery() { } } catch (error) { - console.error('Error executing query:', error); - console.error('Error details:', error.message, error.stack); - alert('Error executing query. Please check console for details.'); + if (error.name === 'AbortError') { + console.error('Request timed out after 20 seconds'); + showErrorToast('Request timed out. The server took too long to respond. Please try again.'); + } else { + console.error('Error executing query:', error); + console.error('Error details:', error.message, error.stack); + showErrorToast('Error executing query. Please try again.'); + } } } @@ -333,7 +375,7 @@ async function submitSQLChallenge() { alert('Please enter a SQL query'); return; } - + // Check deadline before submitting const deadlineElement = document.getElementById('deadline-time'); if (deadlineElement) { @@ -355,8 +397,12 @@ async function submitSQLChallenge() { } } } - + try { + // Create abort controller for timeout + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 20000); // 20 second timeout + const response = await fetch('/api/v1/challenges/attempt', { method: 'POST', headers: { @@ -368,16 +414,19 @@ async function submitSQLChallenge() { submission: submission, user_id: userId, user_name: userName - }) + }), + signal: controller.signal }); - + + clearTimeout(timeoutId); + const result = await response.json(); // Submit result received - + // Check if result has the expected structure if (!result || typeof result !== 'object') { console.error('Invalid result structure:', result); - alert('Invalid response from server'); + showErrorToast('Invalid response from server'); return; } @@ -389,7 +438,7 @@ async function submitSQLChallenge() { submit_status: submitStatus }) } - + // CTFd API returns {success: bool, data: {...}} structure if (!result.hasOwnProperty('data')) { // Wrapping result in CTFd format @@ -401,10 +450,15 @@ async function submitSQLChallenge() { } else { displayResult(result, false); } - + } catch (error) { - console.error('Error submitting challenge:', error); - alert('Error submitting challenge. Please try again.'); + if (error.name === 'AbortError') { + console.error('Request timed out after 20 seconds'); + showErrorToast('Request timed out. The server took too long to respond. Please try again.'); + } else { + console.error('Error submitting challenge:', error); + showErrorToast('Error submitting challenge. Please try again.'); + } } } diff --git a/platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js b/platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js deleted file mode 100644 index 3b1dcc791b..0000000000 --- a/platform/CTFd/themes/ddps/static/assets/sql_challenge.01922920.js +++ /dev/null @@ -1,58 +0,0 @@ -let l=null;function y(){const t=document.getElementById("challenge-input"),e=document.getElementById("sql-editor-container");if(!t||!e)return!1;if(l){try{l.toTextArea()}catch{}l=null}if(e.innerHTML="",typeof CodeMirror>"u"){if(!document.querySelector('link[href*="codemirror"]')){const m=document.createElement("link");m.rel="stylesheet",m.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css",document.head.appendChild(m);const f=document.createElement("link");f.rel="stylesheet",f.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css",document.head.appendChild(f)}const d=document.createElement("script");return d.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js",d.onload=function(){const m=document.createElement("script");m.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js",m.onload=function(){y()},document.head.appendChild(m)},document.head.appendChild(d),!1}if(typeof CTFd>"u"||!CTFd.user)return setTimeout(()=>y(),100),!1;const s=document.getElementById("challenge-id").value,o=`sql-challenge-user${CTFd.user.id||"anonymous"}-ch${s}-code`,i=localStorage.getItem(o),n=i||t.value||"";l=CodeMirror(e,{value:"",mode:"text/x-sql",theme:"monokai",lineNumbers:!0,indentUnit:4,lineWrapping:!0,autofocus:!0,scrollbarStyle:"native",viewportMargin:1/0,readOnly:!1,inputStyle:"contenteditable",extraKeys:{"Ctrl-Enter":function(){document.getElementById("challenge-submit").click()},Tab:"indentMore"}}),setTimeout(()=>{l.refresh(),n&&l.setValue(n),l.focus(),l.setCursor(l.lineCount(),0)},200),e.addEventListener("click",function(d){(d.target===e||d.target.classList.contains("CodeMirror-scroll"))&&l.focus()});const r=function(){const d=l.getValue();if(t.value=d,d.trim())try{localStorage.setItem(o,d)}catch(m){console.error("Failed to save:",m)}else localStorage.removeItem(o)};let c=!0;return l.on("beforeChange",function(){if(c){c=!1;return}setTimeout(r,0)}),l.on("inputRead",r),l.on("keyHandled",r),l.on("blur",r),setInterval(()=>{l&&document.querySelector(".CodeMirror-focused")&&r()},1e3),i&&L("Previous code restored from auto-save"),typeof setupBehaviorTracking=="function"&&l&&(setupBehaviorTracking(l),console.log("[SQL Challenge] Behavior tracking initialized")),!0}function L(t){const e=document.createElement("div");e.className="alert alert-info fade show position-fixed",e.style.cssText="top: 70px; right: 20px; z-index: 1050; max-width: 350px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;";const s=document.createElement("div");s.style.cssText="display: flex; align-items: center;",s.innerHTML=`${t}`;const a=document.createElement("button");a.type="button",a.className="btn-close btn-sm",a.style.cssText="position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;",a.onclick=function(){e.remove()},e.appendChild(s),e.appendChild(a),document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.remove()},3e3)}function w(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s)}function x(){if(!confirm("Are you sure you want to reset the editor? This will clear all your current code."))return;if(l)l.setValue(""),l.focus();else{const o=document.getElementById("challenge-input");o&&(o.value="")}const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s);const a=document.getElementById("query-result-container");a&&(a.innerHTML=` -
- -

Execute a query to see results

-
- `),L("Editor has been reset")}async function B(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}try{const o={challenge_id:parseInt(t),submission:e,test:!0,user_id:s,user_name:a},i=await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify(o)});i.ok||console.error("Response not OK:",i.statusText);const n=await i.text();let r;try{r=JSON.parse(n)}catch(c){console.error("Failed to parse response as JSON:",c),console.error("Response was:",n),alert("Server returned invalid JSON response");return}if(!r||typeof r!="object"){console.error("Invalid result structure:",r),alert("Invalid response from server");return}r.hasOwnProperty("data")?b(r,!0):b({success:!0,data:r},!0)}catch(o){console.error("Error executing query:",o),console.error("Error details:",o.message,o.stack),alert("Error executing query. Please check console for details.")}}async function $(){const t=document.getElementById("challenge-id").value,e=l?l.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,a=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}const o=document.getElementById("deadline-time");if(o){const i=o.getAttribute("data-deadline");if(i){const n=new Date(i);if(new Date>n){b({success:!0,data:{status:"incorrect",message:"Submission deadline has passed"}},!1);return}}}try{const n=await(await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify({challenge_id:parseInt(t),submission:e,user_id:s,user_name:a})})).json();if(!n||typeof n!="object"){console.error("Invalid result structure:",n),alert("Invalid response from server");return}if(behaviorLogger){const r=n.data.status||"unknown";behaviorLogger.logEvent("submit",{query_text:e,query_length:e.length,submit_status:r})}n.hasOwnProperty("data")?b(n,!1):b({success:!0,data:n},!1)}catch(i){console.error("Error submitting challenge:",i),alert("Error submitting challenge. Please try again.")}}function b(t,e=!1){const s=document.getElementById("query-result-container");s.innerHTML="";let a=t.data.message||"";const o=a.startsWith("[TEST]"),i=document.createElement("div"),n=t.data.status==="already_solved";i.className=t.data.status==="correct"||n?"alert alert-success":t.data.status==="incorrect"&&!o?"alert alert-danger":o?"alert alert-info":"alert alert-warning",i.innerHTML='',o&&(a=a.replace(`[TEST] -`,"")),n&&(a=a.replace(" but you already solved this",""));const r=a.split(` -`);let c="",d=null,m=null,f=!1,v=!1,h=[];for(const u of r)if(u==="[USER_RESULT]")f=!0,h=[];else if(u==="[/USER_RESULT]"){f=!1;try{const g=h.join("");d=JSON.parse(g)}catch(g){console.error("Failed to parse user result:",g,"JSON string:",h.join(""))}}else if(u==="[EXPECTED_RESULT]")v=!0,h=[];else if(u==="[/EXPECTED_RESULT]"){v=!1;try{const g=h.join("");m=JSON.parse(g)}catch(g){console.error("Failed to parse expected result:",g,"JSON string:",h.join(""))}}else f||v?h.push(u):c+=u+` -`;s.appendChild(i);let p=c.trim();if(n&&(p+=" (Already solved)"),document.getElementById("status-text").innerHTML=p,d){const u=document.createElement("div");u.className="card mb-2";const g=k(d),E=document.createElement("div");E.className="card-header bg-primary text-white",E.innerHTML='
Your Query Result
';const T=document.createElement("div");T.className="card-body p-0";const C=document.createElement("div");C.className="table-responsive",C.innerHTML=g,T.appendChild(C),u.appendChild(E),u.appendChild(T),s.appendChild(u)}t.data.status==="correct"&&!e&&(w(),t.data.points&&(document.getElementById("earned-points").textContent=t.data.points),setTimeout(()=>{try{const u=document.getElementById("successModal");u?(u.classList.add("show"),u.style.display="block",u.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open")):console.error("Modal element not found!")}catch(u){console.error("Error showing modal:",u)}},1e3))}function k(t){if(!t||!t.columns||!t.rows)return'

No data to display

';if(t.rows.length===0)return'

Empty result set

';let e='';e+='';for(const s of t.columns)e+=``;e+="",e+="";for(const s of t.rows){e+="";for(const a of s)e+=``;e+=""}return e+="",e+="
${S(s)}
${S(a)}
",e+=``,e}function S(t){return t==null?"":String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function F(){const t=document.getElementById("resize-handle"),e=document.getElementById("left-panel"),s=document.getElementById("right-panel"),a=document.getElementById("resize-container");let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientX,n=e.offsetWidth,document.body.style.cursor="col-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetWidth,f=(n+(c.clientX-i))/d*100;f>=20&&f<=70&&(e.style.width=f+"%",s.style.width=100-f+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.width;c&&localStorage.setItem("sql-challenge-left-width",c)}});const r=localStorage.getItem("sql-challenge-left-width");r&&(e.style.width=r,s.style.width=100-parseFloat(r)+"%")}function q(){const t=document.getElementById("vertical-resize-handle"),e=document.getElementById("editor-section"),s=document.getElementById("result-section"),a=document.querySelector(".sql-editor-section");if(!t||!e||!s)return;let o=!1,i=0,n=0;t.addEventListener("mousedown",function(c){o=!0,i=c.clientY,n=e.offsetHeight,document.body.style.cursor="row-resize",document.body.style.userSelect="none",c.preventDefault()}),document.addEventListener("mousemove",function(c){if(!o)return;const d=a.offsetHeight,f=(n+(c.clientY-i))/d*100;f>=20&&f<=80&&(e.style.height=f+"%",s.style.height=100-f-2+"%",l&&setTimeout(()=>{l.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&l.focus()},10))}),document.addEventListener("mouseup",function(){if(o){o=!1,document.body.style.cursor="",document.body.style.userSelect="";const c=e.style.height;c&&localStorage.setItem("sql-challenge-editor-height",c)}});const r=localStorage.getItem("sql-challenge-editor-height");r&&(e.style.height=r,s.style.height=100-parseFloat(r)-2+"%")}async function M(){const t=document.getElementById("challenge-id").value,e=document.getElementById("historyModal"),s=document.getElementById("submission-history-content");if(!(!e||!s)){e.classList.add("show"),e.style.display="block",e.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open"),s.innerHTML=` -
- -

Loading submission history...

-
- `;try{const o=await(await fetch(`/api/v1/challenges/${t}/sql-submissions`)).json();if(o.success&&o.data)if(o.data.length===0)s.innerHTML=` -
- -
No Submissions Yet
-

You haven't submitted any solutions for this challenge yet.

-
- `;else{let i='
';o.data.forEach((n,r)=>{const d=new Date(n.date).toLocaleString("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZone:"Asia/Seoul",hour12:!1}),m=n.type==="correct"?'':'',f=n.type==="correct"?"correct":"incorrect",v=n.type==="correct"?"Correct":"Incorrect",h=o.data.length-r,p=`collapse${n.id}`,u=`heading${n.id}`,g=r===0,E=g?"show":"";i+=` -
-

- -

-
-
-
- ${S(n.submission||"")} -
-
-
-
- `}),i+="
",s.innerHTML=i}else s.innerHTML=` -
- - Failed to load submission history. Please try again. -
- `}catch(a){console.error("Error loading submission history:",a),s.innerHTML=` -
- - An error occurred while loading submission history. -
- `}}}function I(){const t=document.getElementById("deadline-time");if(!t)return;const e=t.getAttribute("data-deadline");if(!e)return;const s=new Date(e),a=new Date,o=s-a;if(o<=0){t.textContent="Expired",t.closest(".deadline-display").classList.add("deadline-danger");return}const i=Math.floor(o/(1e3*60*60*24)),n=Math.floor(o%(1e3*60*60*24)/(1e3*60*60)),r=Math.floor(o%(1e3*60*60)/(1e3*60)),c=Math.floor(o%(1e3*60)/1e3);let d="";i>0?d=`${i}d ${n}h ${r}m`:n>0?d=`${n}h ${r}m ${c}s`:r>0?d=`${r}m ${c}s`:d=`${c}s`,t.textContent=d+" remaining";const m=t.closest(".deadline-display");m.classList.remove("deadline-warning","deadline-danger"),i===0&&n<1?m.classList.add("deadline-danger"):i===0&&n<6&&m.classList.add("deadline-warning")}function H(){console.log("=== LocalStorage Debug ==="),console.log("All localStorage keys:",Object.keys(localStorage)),console.log("SQL related keys:");for(let t in localStorage)t.includes("sql-challenge")&&console.log(` ${t}: ${localStorage[t].substring(0,50)}...`);console.log("========================")}window.testSaveCode=function(t){const e=document.getElementById("challenge-id").value,a=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${e}-code`;localStorage.setItem(a,t||"SELECT * FROM test;"),console.log(`Saved to ${a}`),H()};window.testLoadCode=function(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${t}-code`,a=localStorage.getItem(s);return console.log(`Loading from ${s}:`,a),a&&l&&(l.setValue(a),console.log("Code loaded into editor")),a};document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".challenge-panel");t&&(t.addEventListener("contextmenu",function(n){return n.preventDefault(),!1}),t.addEventListener("selectstart",function(n){return n.preventDefault(),!1}),t.addEventListener("keydown",function(n){if((n.ctrlKey||n.metaKey)&&(n.keyCode===67||n.keyCode===65||n.keyCode===88))return n.preventDefault(),!1}),t.addEventListener("dragstart",function(n){return n.preventDefault(),!1}),t.addEventListener("mousedown",function(n){if(n.detail>1)return n.preventDefault(),!1})),y(),setTimeout(()=>{l||y()},500),setTimeout(()=>{l||y()},1e3),F(),q(),I(),setInterval(I,1e3);const e=document.getElementById("challenge-reset"),s=document.getElementById("challenge-execute"),a=document.getElementById("challenge-submit"),o=document.getElementById("challenge-history");e&&e.addEventListener("click",x),s&&s.addEventListener("click",B),a&&a.addEventListener("click",$),o&&o.addEventListener("click",M),document.querySelectorAll('[data-bs-dismiss="modal"]').forEach(n=>{n.addEventListener("click",function(){const r=this.closest(".modal");r&&(r.classList.remove("show"),r.style.display="none",r.setAttribute("aria-hidden","true"),document.body.classList.remove("modal-open"))})})});window.addEventListener("load",function(){l||y()}); diff --git a/platform/CTFd/themes/ddps/static/assets/sql_challenge.d0862715.js b/platform/CTFd/themes/ddps/static/assets/sql_challenge.d0862715.js new file mode 100644 index 0000000000..d080f5f572 --- /dev/null +++ b/platform/CTFd/themes/ddps/static/assets/sql_challenge.d0862715.js @@ -0,0 +1,58 @@ +let i=null;function v(){const t=document.getElementById("challenge-input"),e=document.getElementById("sql-editor-container");if(!t||!e)return!1;if(i){try{i.toTextArea()}catch{}i=null}if(e.innerHTML="",typeof CodeMirror>"u"){if(!document.querySelector('link[href*="codemirror"]')){const u=document.createElement("link");u.rel="stylesheet",u.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.css",document.head.appendChild(u);const f=document.createElement("link");f.rel="stylesheet",f.href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/theme/monokai.min.css",document.head.appendChild(f)}const l=document.createElement("script");return l.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/codemirror.min.js",l.onload=function(){const u=document.createElement("script");u.src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.2/mode/sql/sql.min.js",u.onload=function(){v()},document.head.appendChild(u)},document.head.appendChild(l),!1}if(typeof CTFd>"u"||!CTFd.user)return setTimeout(()=>v(),100),!1;const s=document.getElementById("challenge-id").value,a=`sql-challenge-user${CTFd.user.id||"anonymous"}-ch${s}-code`,d=localStorage.getItem(a),n=d||t.value||"";i=CodeMirror(e,{value:"",mode:"text/x-sql",theme:"monokai",lineNumbers:!0,indentUnit:4,lineWrapping:!0,autofocus:!0,scrollbarStyle:"native",viewportMargin:1/0,readOnly:!1,inputStyle:"contenteditable",extraKeys:{"Ctrl-Enter":function(){document.getElementById("challenge-submit").click()},Tab:"indentMore"}}),setTimeout(()=>{i.refresh(),n&&i.setValue(n),i.focus(),i.setCursor(i.lineCount(),0)},200),e.addEventListener("click",function(l){(l.target===e||l.target.classList.contains("CodeMirror-scroll"))&&i.focus()});const c=function(){const l=i.getValue();if(t.value=l,l.trim())try{localStorage.setItem(a,l)}catch(u){console.error("Failed to save:",u)}else localStorage.removeItem(a)};let r=!0;return i.on("beforeChange",function(){if(r){r=!1;return}setTimeout(c,0)}),i.on("inputRead",c),i.on("keyHandled",c),i.on("blur",c),setInterval(()=>{i&&document.querySelector(".CodeMirror-focused")&&c()},1e3),d&&w("Previous code restored from auto-save"),typeof setupBehaviorTracking=="function"&&i&&(setupBehaviorTracking(i),console.log("[SQL Challenge] Behavior tracking initialized")),!0}function w(t){const e=document.createElement("div");e.className="alert alert-info fade show position-fixed",e.style.cssText="top: 70px; right: 20px; z-index: 1050; max-width: 350px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;";const s=document.createElement("div");s.style.cssText="display: flex; align-items: center;",s.innerHTML=`${t}`;const o=document.createElement("button");o.type="button",o.className="btn-close btn-sm",o.style.cssText="position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;",o.onclick=function(){e.remove()},e.appendChild(s),e.appendChild(o),document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.remove()},3e3)}function p(t){const e=document.createElement("div");e.className="alert alert-danger fade show position-fixed",e.style.cssText="top: 70px; right: 20px; z-index: 1050; max-width: 400px; padding: 0.75rem 2.5rem 0.75rem 1rem; position: relative;";const s=document.createElement("div");s.style.cssText="display: flex; align-items: center;",s.innerHTML=`${I(t)}`;const o=document.createElement("button");o.type="button",o.className="btn-close btn-sm",o.style.cssText="position: absolute; top: 50%; right: 0.5rem; transform: translateY(-50%); padding: 0.25rem; font-size: 0.875rem;",o.onclick=function(){e.remove()},e.appendChild(s),e.appendChild(o),document.body.appendChild(e),setTimeout(()=>{e.parentNode&&e.remove()},5e3)}function L(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s)}function B(){if(!confirm("Are you sure you want to reset the editor? This will clear all your current code."))return;if(i)i.setValue(""),i.focus();else{const a=document.getElementById("challenge-input");a&&(a.value="")}const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"anonymous"}-ch${t}-code`;localStorage.removeItem(s);const o=document.getElementById("query-result-container");o&&(o.innerHTML=` +
+ +

Execute a query to see results

+
+ `),w("Editor has been reset")}async function $(){const t=document.getElementById("challenge-id").value,e=i?i.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,o=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}try{const a={challenge_id:parseInt(t),submission:e,test:!0,user_id:s,user_name:o},d=new AbortController,n=setTimeout(()=>d.abort(),2e4),c=await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify(a),signal:d.signal});clearTimeout(n),c.ok||console.error("Response not OK:",c.statusText);const r=await c.text();let l;try{l=JSON.parse(r)}catch(u){console.error("Failed to parse response as JSON:",u),console.error("Response was:",r),p("Server returned invalid JSON response");return}if(!l||typeof l!="object"){console.error("Invalid result structure:",l),p("Invalid response from server");return}l.hasOwnProperty("data")?T(l,!0):T({success:!0,data:l},!0)}catch(a){a.name==="AbortError"?(console.error("Request timed out after 20 seconds"),p("Request timed out. The server took too long to respond. Please try again.")):(console.error("Error executing query:",a),console.error("Error details:",a.message,a.stack),p("Error executing query. Please try again."))}}async function k(){const t=document.getElementById("challenge-id").value,e=i?i.getValue():document.getElementById("challenge-input").value,s=CTFd&&CTFd.user?CTFd.user.id:null,o=CTFd&&CTFd.user?CTFd.user.name:"anonymous";if(!e.trim()){alert("Please enter a SQL query");return}const a=document.getElementById("deadline-time");if(a){const d=a.getAttribute("data-deadline");if(d){const n=new Date(d);if(new Date>n){T({success:!0,data:{status:"incorrect",message:"Submission deadline has passed"}},!1);return}}}try{const d=new AbortController,n=setTimeout(()=>d.abort(),2e4),c=await fetch("/api/v1/challenges/attempt",{method:"POST",headers:{"Content-Type":"application/json","CSRF-Token":init.csrfNonce},body:JSON.stringify({challenge_id:parseInt(t),submission:e,user_id:s,user_name:o}),signal:d.signal});clearTimeout(n);const r=await c.json();if(!r||typeof r!="object"){console.error("Invalid result structure:",r),p("Invalid response from server");return}if(behaviorLogger){const l=r.data.status||"unknown";behaviorLogger.logEvent("submit",{query_text:e,query_length:e.length,submit_status:l})}r.hasOwnProperty("data")?T(r,!1):T({success:!0,data:r},!1)}catch(d){d.name==="AbortError"?(console.error("Request timed out after 20 seconds"),p("Request timed out. The server took too long to respond. Please try again.")):(console.error("Error submitting challenge:",d),p("Error submitting challenge. Please try again."))}}function T(t,e=!1){const s=document.getElementById("query-result-container");s.innerHTML="";let o=t.data.message||"";const a=o.startsWith("[TEST]"),d=document.createElement("div"),n=t.data.status==="already_solved";d.className=t.data.status==="correct"||n?"alert alert-success":t.data.status==="incorrect"&&!a?"alert alert-danger":a?"alert alert-info":"alert alert-warning",d.innerHTML='',a&&(o=o.replace(`[TEST] +`,"")),n&&(o=o.replace(" but you already solved this",""));const c=o.split(` +`);let r="",l=null,u=null,f=!1,b=!1,h=[];for(const m of c)if(m==="[USER_RESULT]")f=!0,h=[];else if(m==="[/USER_RESULT]"){f=!1;try{const g=h.join("");l=JSON.parse(g)}catch(g){console.error("Failed to parse user result:",g,"JSON string:",h.join(""))}}else if(m==="[EXPECTED_RESULT]")b=!0,h=[];else if(m==="[/EXPECTED_RESULT]"){b=!1;try{const g=h.join("");u=JSON.parse(g)}catch(g){console.error("Failed to parse expected result:",g,"JSON string:",h.join(""))}}else f||b?h.push(m):r+=m+` +`;s.appendChild(d);let y=r.trim();if(n&&(y+=" (Already solved)"),document.getElementById("status-text").innerHTML=y,l){const m=document.createElement("div");m.className="card mb-2";const g=q(l),E=document.createElement("div");E.className="card-header bg-primary text-white",E.innerHTML='
Your Query Result
';const C=document.createElement("div");C.className="card-body p-0";const S=document.createElement("div");S.className="table-responsive",S.innerHTML=g,C.appendChild(S),m.appendChild(E),m.appendChild(C),s.appendChild(m)}t.data.status==="correct"&&!e&&(L(),t.data.points&&(document.getElementById("earned-points").textContent=t.data.points),setTimeout(()=>{try{const m=document.getElementById("successModal");m?(m.classList.add("show"),m.style.display="block",m.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open")):console.error("Modal element not found!")}catch(m){console.error("Error showing modal:",m)}},1e3))}function q(t){if(!t||!t.columns||!t.rows)return'

No data to display

';if(t.rows.length===0)return'

Empty result set

';let e='';e+='';for(const s of t.columns)e+=``;e+="",e+="";for(const s of t.rows){e+="";for(const o of s)e+=``;e+=""}return e+="",e+="
${I(s)}
${I(o)}
",e+=``,e}function I(t){return t==null?"":String(t).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function F(){const t=document.getElementById("resize-handle"),e=document.getElementById("left-panel"),s=document.getElementById("right-panel"),o=document.getElementById("resize-container");let a=!1,d=0,n=0;t.addEventListener("mousedown",function(r){a=!0,d=r.clientX,n=e.offsetWidth,document.body.style.cursor="col-resize",document.body.style.userSelect="none",r.preventDefault()}),document.addEventListener("mousemove",function(r){if(!a)return;const l=o.offsetWidth,f=(n+(r.clientX-d))/l*100;f>=20&&f<=70&&(e.style.width=f+"%",s.style.width=100-f+"%",i&&setTimeout(()=>{i.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&i.focus()},10))}),document.addEventListener("mouseup",function(){if(a){a=!1,document.body.style.cursor="",document.body.style.userSelect="";const r=e.style.width;r&&localStorage.setItem("sql-challenge-left-width",r)}});const c=localStorage.getItem("sql-challenge-left-width");c&&(e.style.width=c,s.style.width=100-parseFloat(c)+"%")}function M(){const t=document.getElementById("vertical-resize-handle"),e=document.getElementById("editor-section"),s=document.getElementById("result-section"),o=document.querySelector(".sql-editor-section");if(!t||!e||!s)return;let a=!1,d=0,n=0;t.addEventListener("mousedown",function(r){a=!0,d=r.clientY,n=e.offsetHeight,document.body.style.cursor="row-resize",document.body.style.userSelect="none",r.preventDefault()}),document.addEventListener("mousemove",function(r){if(!a)return;const l=o.offsetHeight,f=(n+(r.clientY-d))/l*100;f>=20&&f<=80&&(e.style.height=f+"%",s.style.height=100-f-2+"%",i&&setTimeout(()=>{i.refresh(),document.activeElement&&document.activeElement.classList.contains("CodeMirror")&&i.focus()},10))}),document.addEventListener("mouseup",function(){if(a){a=!1,document.body.style.cursor="",document.body.style.userSelect="";const r=e.style.height;r&&localStorage.setItem("sql-challenge-editor-height",r)}});const c=localStorage.getItem("sql-challenge-editor-height");c&&(e.style.height=c,s.style.height=100-parseFloat(c)-2+"%")}async function R(){const t=document.getElementById("challenge-id").value,e=document.getElementById("historyModal"),s=document.getElementById("submission-history-content");if(!(!e||!s)){e.classList.add("show"),e.style.display="block",e.setAttribute("aria-hidden","false"),document.body.classList.add("modal-open"),s.innerHTML=` +
+ +

Loading submission history...

+
+ `;try{const a=await(await fetch(`/api/v1/challenges/${t}/sql-submissions`)).json();if(a.success&&a.data)if(a.data.length===0)s.innerHTML=` +
+ +
No Submissions Yet
+

You haven't submitted any solutions for this challenge yet.

+
+ `;else{let d='
';a.data.forEach((n,c)=>{const l=new Date(n.date).toLocaleString("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZone:"Asia/Seoul",hour12:!1}),u=n.type==="correct"?'':'',f=n.type==="correct"?"correct":"incorrect",b=n.type==="correct"?"Correct":"Incorrect",h=a.data.length-c,y=`collapse${n.id}`,m=`heading${n.id}`,g=c===0,E=g?"show":"";d+=` +
+

+ +

+
+
+
+ ${I(n.submission||"")} +
+
+
+
+ `}),d+="
",s.innerHTML=d}else s.innerHTML=` +
+ + Failed to load submission history. Please try again. +
+ `}catch(o){console.error("Error loading submission history:",o),s.innerHTML=` +
+ + An error occurred while loading submission history. +
+ `}}}function x(){const t=document.getElementById("deadline-time");if(!t)return;const e=t.getAttribute("data-deadline");if(!e)return;const s=new Date(e),o=new Date,a=s-o;if(a<=0){t.textContent="Expired",t.closest(".deadline-display").classList.add("deadline-danger");return}const d=Math.floor(a/(1e3*60*60*24)),n=Math.floor(a%(1e3*60*60*24)/(1e3*60*60)),c=Math.floor(a%(1e3*60*60)/(1e3*60)),r=Math.floor(a%(1e3*60)/1e3);let l="";d>0?l=`${d}d ${n}h ${c}m`:n>0?l=`${n}h ${c}m ${r}s`:c>0?l=`${c}m ${r}s`:l=`${r}s`,t.textContent=l+" remaining";const u=t.closest(".deadline-display");u.classList.remove("deadline-warning","deadline-danger"),d===0&&n<1?u.classList.add("deadline-danger"):d===0&&n<6&&u.classList.add("deadline-warning")}function H(){console.log("=== LocalStorage Debug ==="),console.log("All localStorage keys:",Object.keys(localStorage)),console.log("SQL related keys:");for(let t in localStorage)t.includes("sql-challenge")&&console.log(` ${t}: ${localStorage[t].substring(0,50)}...`);console.log("========================")}window.testSaveCode=function(t){const e=document.getElementById("challenge-id").value,o=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${e}-code`;localStorage.setItem(o,t||"SELECT * FROM test;"),console.log(`Saved to ${o}`),H()};window.testLoadCode=function(){const t=document.getElementById("challenge-id").value,s=`sql-challenge-user${CTFd&&CTFd.user?CTFd.user.id:"test"}-ch${t}-code`,o=localStorage.getItem(s);return console.log(`Loading from ${s}:`,o),o&&i&&(i.setValue(o),console.log("Code loaded into editor")),o};document.addEventListener("DOMContentLoaded",function(){const t=document.querySelector(".challenge-panel");t&&(t.addEventListener("contextmenu",function(n){return n.preventDefault(),!1}),t.addEventListener("selectstart",function(n){return n.preventDefault(),!1}),t.addEventListener("keydown",function(n){if((n.ctrlKey||n.metaKey)&&(n.keyCode===67||n.keyCode===65||n.keyCode===88))return n.preventDefault(),!1}),t.addEventListener("dragstart",function(n){return n.preventDefault(),!1}),t.addEventListener("mousedown",function(n){if(n.detail>1)return n.preventDefault(),!1})),v(),setTimeout(()=>{i||v()},500),setTimeout(()=>{i||v()},1e3),F(),M(),x(),setInterval(x,1e3);const e=document.getElementById("challenge-reset"),s=document.getElementById("challenge-execute"),o=document.getElementById("challenge-submit"),a=document.getElementById("challenge-history");e&&e.addEventListener("click",B),s&&s.addEventListener("click",$),o&&o.addEventListener("click",k),a&&a.addEventListener("click",R),document.querySelectorAll('[data-bs-dismiss="modal"]').forEach(n=>{n.addEventListener("click",function(){const c=this.closest(".modal");c&&(c.classList.remove("show"),c.style.display="none",c.setAttribute("aria-hidden","true"),document.body.classList.remove("modal-open"))})})});window.addEventListener("load",function(){i||v()}); diff --git a/platform/CTFd/themes/ddps/static/manifest.json b/platform/CTFd/themes/ddps/static/manifest.json index 992740e367..14d26e9556 100644 --- a/platform/CTFd/themes/ddps/static/manifest.json +++ b/platform/CTFd/themes/ddps/static/manifest.json @@ -127,7 +127,7 @@ "isEntry": true }, "assets/js/sql_challenge.js": { - "file": "assets/sql_challenge.01922920.js", + "file": "assets/sql_challenge.d0862715.js", "src": "assets/js/sql_challenge.js", "isEntry": true }, diff --git a/platform/CTFd/themes/ddps/templates/sql_challenge.html b/platform/CTFd/themes/ddps/templates/sql_challenge.html index 8fee00e719..3139004a78 100644 --- a/platform/CTFd/themes/ddps/templates/sql_challenge.html +++ b/platform/CTFd/themes/ddps/templates/sql_challenge.html @@ -75,12 +75,12 @@
Execution Results
{% if challenge.deadline %} - Deadline: + Deadline: {{ challenge.deadline }} {% endif %}
- +