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/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/__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/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)
-
+
Visible
Hidden
@@ -97,6 +97,11 @@ Test Results:
-
- Update
+
+
\ No newline at end of file
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)
}
diff --git a/platform/CTFd/themes/ddps/assets/js/sql_challenge.js b/platform/CTFd/themes/ddps/assets/js/sql_challenge.js
index c8d62c642c..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;
@@ -241,47 +271,54 @@ 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
-
+
+ // 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);
}
-
+
// First get the response as text to debug
const responseText = await response.text();
// Response text received
-
+
// Try to parse as JSON
let result;
try {
@@ -289,18 +326,18 @@ 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
-
+
// 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;
}
-
+
// 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,11 +350,16 @@ async function executeSQLQuery() {
} else {
displayResult(result, true);
}
-
+
} 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,36 +450,41 @@ 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.');
+ }
}
}
// 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 +571,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 +604,7 @@ function displayResult(result, isPreview = false) {
}
}, 1000);
}
-
+
}
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+=`${I(s)} `;e+=" ",e+="";for(const s of t.rows){e+="";for(const o of s)e+=`${I(o)} `;e+=" "}return e+=" ",e+="
",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/assets/sql_challenge.e008bd25.js b/platform/CTFd/themes/ddps/static/assets/sql_challenge.e008bd25.js
deleted file mode 100644
index ad81d9f717..0000000000
--- a/platform/CTFd/themes/ddps/static/assets/sql_challenge.e008bd25.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&&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=`
-
-
-
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]
-`,"")),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+=`${S(s)} `;e+=" ",e+="";for(const s of t.rows){e+="";for(const a of s)e+=`${S(a)} `;e+=" "}return e+=" ",e+="
",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 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()});
diff --git a/platform/CTFd/themes/ddps/static/manifest.json b/platform/CTFd/themes/ddps/static/manifest.json
index 44b9f34b2e..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.e008bd25.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 34fecaa0c7..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 %}
-
+
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
Execution Results
type="button"
title="Submit query for grading"
>
- Execute & Submit
+ Submit