summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Hader <[email protected]>2026-06-03 22:32:09 -0500
committerDaniel Hader <[email protected]>2026-06-03 22:32:09 -0500
commit328d0cd912dfa5dbda53a09ecdfeaeb93504b560 (patch)
tree5dbe78ebbe4c9df5b8bbcf4530a7f912dbb1b7a6
parent51fac3f3b6f73b649ba8109b37d8ff311b905cd4 (diff)
submission logic and fetching submissions
-rw-r--r--src/database/database.rs51
-rw-r--r--src/database/sql/fetch_display_submissions.sql10
-rw-r--r--src/database/sql/fetch_submissions_by_problem_id.sql1
-rw-r--r--src/database/sql/fetch_user.sql1
-rw-r--r--src/database/submission.rs10
-rw-r--r--src/main.rs3
-rw-r--r--src/routes/submission.rs21
-rw-r--r--static/login.js2
-rw-r--r--static/main.js91
-rw-r--r--static/submit.html5
-rw-r--r--static/submit.js24
11 files changed, 165 insertions, 54 deletions
diff --git a/src/database/database.rs b/src/database/database.rs
index 6ebbe1c..961c39c 100644
--- a/src/database/database.rs
+++ b/src/database/database.rs
@@ -4,7 +4,7 @@ use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use r2d2_sqlite::rusqlite::OptionalExtension;
-use crate::database::submission::Submission;
+use crate::database::submission::{DisplaySubmission, Submission};
use super::problem::Problem;
use super::user::User;
@@ -195,6 +195,55 @@ impl Database {
.map_err(|e| DatabaseError::Query(e.to_string()))?
)
}
+
+ pub fn fetch_submissions_by_problem_id(&self, problem_id: i64) -> Result<Vec<Submission>, DatabaseError> {
+ static QUERY: &str = include_str!("sql/fetch_submissions_by_problem_id.sql");
+ let conn = self.pool
+ .get()
+ .map_err(|e| DatabaseError::Connection(e.to_string()))?;
+ let mut statement = conn.prepare(QUERY)
+ .map_err(|e| DatabaseError::Query(e.to_string()))?;
+
+ Ok(statement
+ .query_map([problem_id], |row| {
+ Ok(Submission::new(
+ row.get("id")?,
+ row.get("user_id")?,
+ row.get("problem_id")?,
+ row.get("language")?,
+ row.get("details")?,
+ row.get("code")?,
+ ))
+ })
+ .map_err(|e| DatabaseError::Query(e.to_string()))?
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|e| DatabaseError::Query(e.to_string()))?
+ )
+ }
+
+ pub fn fetch_display_submissions(&self, problem_id: i64) -> Result<Vec<DisplaySubmission>, DatabaseError> {
+ static QUERY: &str = include_str!("sql/fetch_display_submissions.sql");
+ let conn = self.pool
+ .get()
+ .map_err(|e| DatabaseError::Connection(e.to_string()))?;
+ let mut statement = conn.prepare(QUERY)
+ .map_err(|e| DatabaseError::Query(e.to_string()))?;
+
+ Ok(statement
+ .query_map([problem_id], |row| {
+ Ok(DisplaySubmission {
+ id: row.get("id")?,
+ username: row.get("username")?,
+ language: row.get("language")?,
+ details: row.get("details")?,
+ code: row.get("code")?,
+ })
+ })
+ .map_err(|e| DatabaseError::Query(e.to_string()))?
+ .collect::<Result<Vec<_>, _>>()
+ .map_err(|e| DatabaseError::Query(e.to_string()))?
+ )
+ }
pub fn initialize(&self) -> Result<(), DatabaseError> {
static QUERY: &str = include_str!("sql/initialize.sql");
diff --git a/src/database/sql/fetch_display_submissions.sql b/src/database/sql/fetch_display_submissions.sql
new file mode 100644
index 0000000..5e836b3
--- /dev/null
+++ b/src/database/sql/fetch_display_submissions.sql
@@ -0,0 +1,10 @@
+SELECT
+submission.id AS id,
+user.username AS username,
+submission.language AS language,
+submission.details AS details,
+submission.code AS code
+FROM submission JOIN user
+ON user.id = submission.user_id
+WHERE submission.problem_id = ?1
+ORDER BY length(submission.code);
diff --git a/src/database/sql/fetch_submissions_by_problem_id.sql b/src/database/sql/fetch_submissions_by_problem_id.sql
new file mode 100644
index 0000000..3b5a4ae
--- /dev/null
+++ b/src/database/sql/fetch_submissions_by_problem_id.sql
@@ -0,0 +1 @@
+SELECT * FROM submission WHERE submission.problem_id = ?1;
diff --git a/src/database/sql/fetch_user.sql b/src/database/sql/fetch_user.sql
new file mode 100644
index 0000000..caadee9
--- /dev/null
+++ b/src/database/sql/fetch_user.sql
@@ -0,0 +1 @@
+SELECT * FROM user WHERE user.id = ?1;
diff --git a/src/database/submission.rs b/src/database/submission.rs
index 4a83edd..b117e17 100644
--- a/src/database/submission.rs
+++ b/src/database/submission.rs
@@ -28,5 +28,13 @@ impl Submission {
pub fn language(&self) -> &str { &self.language }
pub fn details(&self) -> &str { &self.details }
pub fn code(&self) -> &str { &self.code }
-
+}
+
+#[derive(Serialize)]
+pub struct DisplaySubmission {
+ pub(crate) id: i64,
+ pub(crate) username: String,
+ pub(crate) language: String,
+ pub(crate) details: String,
+ pub(crate) code: String,
}
diff --git a/src/main.rs b/src/main.rs
index 73dccef..9659af3 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -14,7 +14,7 @@ use routes::user::{create_user, me};
use routes::auth::{login, logout};
use tower_http::services::ServeDir;
-use crate::{database::Database, routes::submission::create_submission, utils::register_admin};
+use crate::{database::Database, routes::submission::{create_submission, get_display_submissions, get_submissions_by_problem_id}, utils::register_admin};
#[derive(Clone)]
struct AppState {
@@ -65,6 +65,7 @@ async fn main() {
.route("/problem", get(get_problems))
.route("/problem", post(create_problem))
.route("/submission", post(create_submission))
+ .route("/submission/{problem_id}", get(get_display_submissions))
.route("/user", post(create_user))
.route("/me", get(me))
.nest_service("/static", static_files)
diff --git a/src/routes/submission.rs b/src/routes/submission.rs
index 50bb53d..4cfad5c 100644
--- a/src/routes/submission.rs
+++ b/src/routes/submission.rs
@@ -1,4 +1,4 @@
-use axum::{Json, extract::State, http::StatusCode, response::IntoResponse};
+use axum::{Json, extract::{Path, State}, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
use crate::{AppState, routes::{auth::AuthUser, errors::RouteError}};
@@ -29,3 +29,22 @@ pub async fn create_submission(
Err(_) => Err(RouteError::Internal(format!("unable to insert submission")))
}
}
+
+pub async fn get_submissions_by_problem_id(
+ State(state): State<AppState>,
+ Path(problem_id): Path<i64>
+) -> Result<impl IntoResponse, RouteError> {
+ let submissions = state.database.fetch_submissions_by_problem_id(problem_id)
+ .map_err(|_| RouteError::Internal("unable to fetch submissions".into()))?;
+ Ok(Json(submissions))
+}
+
+pub async fn get_display_submissions(
+ State(state): State<AppState>,
+ Path(problem_id): Path<i64>
+) -> Result<impl IntoResponse, RouteError> {
+ let submissions = state.database.fetch_display_submissions(problem_id)
+ .map_err(|e| RouteError::Internal(format!("unable to fetch submissions: {e:?}")))?;
+ Ok(Json(submissions))
+}
+
diff --git a/static/login.js b/static/login.js
index 42b3242..1dd61b7 100644
--- a/static/login.js
+++ b/static/login.js
@@ -13,8 +13,6 @@ function init() {
new FormData(form).forEach((value, key) => body[key] = value);
try {
- console.log();
-
const res = await fetch("/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
diff --git a/static/main.js b/static/main.js
index 73a4c60..9132829 100644
--- a/static/main.js
+++ b/static/main.js
@@ -44,54 +44,66 @@ function create_problem_element(problem, submissions) {
const sub_div = document.createElement("div");
sub_div.className = "submission-div";
problem_div.appendChild(sub_div);
+
+ if (submissions.length === 0) {
+ const no_sub_message = document.createElement("span");
+ no_sub_message.innerHTML = "no submissions for this problem yet";
+ sub_div.appendChild(no_sub_message);
+ } else {
+ const sub_table = document.createElement("table");
+ sub_table.className = "submission-table";
+ sub_div.appendChild(sub_table);
- const sub_table = document.createElement("table");
- sub_table.className = "submission-table";
- sub_div.appendChild(sub_table);
-
- const header_row = document.createElement("tr");
- sub_table.appendChild(header_row);
-
- const header_user = document.createElement("th");
- header_user.innerHTML = "User";
- header_row.appendChild(header_user);
- const header_lang = document.createElement("th");
- header_lang.innerHTML = "Language";
- header_row.appendChild(header_lang);
- const header_size = document.createElement("th");
- header_size.innerHTML = "Size";
- header_row.appendChild(header_size);
- const header_subm = document.createElement("th");
- header_subm.innerHTML = "Submission";
- header_row.appendChild(header_subm);
+ const header_row = document.createElement("tr");
+ sub_table.appendChild(header_row);
- for (const submission of submissions) {
- const sub_row = document.createElement("tr");
- sub_table.appendChild(sub_row);
+ const header_user = document.createElement("th");
+ header_user.innerHTML = "User";
+ header_row.appendChild(header_user);
+ const header_lang = document.createElement("th");
+ header_lang.innerHTML = "Language";
+ header_row.appendChild(header_lang);
+ const header_size = document.createElement("th");
+ header_size.innerHTML = "Size";
+ header_row.appendChild(header_size);
+ const header_subm = document.createElement("th");
+ header_subm.innerHTML = "Submission";
+ header_row.appendChild(header_subm);
+
+ for (const submission of submissions) {
+ const sub_row = document.createElement("tr");
+ sub_table.appendChild(sub_row);
- const sub_user = document.createElement("td");
- sub_user.innerHTML = "Username";
- sub_row.appendChild(sub_user);
+ const sub_user = document.createElement("td");
+ sub_user.innerHTML = "Username";
+ sub_row.appendChild(sub_user);
- const sub_lang = document.createElement("td");
- sub_lang.innerHTML = "Language";
- sub_row.appendChild(sub_lang);
+ const sub_lang = document.createElement("td");
+ sub_lang.innerHTML = "Language";
+ sub_row.appendChild(sub_lang);
- const sub_size = document.createElement("td");
- sub_size.innerHTML = "Size";
- sub_row.appendChild(sub_size);
+ const sub_size = document.createElement("td");
+ sub_size.innerHTML = "Size";
+ sub_row.appendChild(sub_size);
- const sub_subm = document.createElement("td");
- const sub_anch = document.createElement("a");
- sub_anch.href = "submission.html?id=5";
- sub_anch.innerText = "view submission";
- sub_subm.appendChild(sub_anch);
- sub_row.appendChild(sub_subm);
+ const sub_subm = document.createElement("td");
+ const sub_anch = document.createElement("a");
+ sub_anch.href = "submission.html?id=5";
+ sub_anch.innerText = "view submission";
+ sub_subm.appendChild(sub_anch);
+ sub_row.appendChild(sub_subm);
+ }
}
return problem_div
}
+async function fetch_submissions(problem_id) {
+ const response = await fetch(`/submission/${problem_id}`)
+ const result = await response.json();
+ return result;
+}
+
async function fetch_problems() {
const response = await fetch("/problem");
if (!response.ok) {
@@ -106,7 +118,10 @@ async function fetch_problems() {
}
for (const problem of result) {
- const problem_div = create_problem_element(problem, [1, 2, 3])
+
+ const submissions = await fetch_submissions(problem.id);
+
+ const problem_div = create_problem_element(problem, submissions)
problems_div.appendChild(problem_div);
}
}
diff --git a/static/submit.html b/static/submit.html
index 9cab87b..151fba5 100644
--- a/static/submit.html
+++ b/static/submit.html
@@ -16,6 +16,10 @@
<a href="index.html">Home</a>
<h1>C&amp;! Code Golf Leaderboard</h1>
<div id="submission-problem"></div>
+ <div id="error" hidden>
+ <span id="error-message" style="color: red"></span>
+ <br><br>
+ </div>
<h2>Submit Solution</h2>
<div id="submission-div">
<label for="submission-language">Language</label><br>
@@ -34,6 +38,7 @@
<option value="ruby">Ruby</option>
<option value="rust">Rust</option>
<option value="typescript">TypeScript</option>
+ <option value="zig">Zig</option>
<option value="other">Other (specify below)</option>
</select>
<br><br>
diff --git a/static/submit.js b/static/submit.js
index 9708e0c..936518b 100644
--- a/static/submit.js
+++ b/static/submit.js
@@ -1,11 +1,16 @@
+function problem_id() {
+ const url_params = new URLSearchParams(window.location.search);
+ return parseInt(url_params.get("problem_id"));
+}
+
async function submit() {
const language = document.getElementById("submission-language").value;
const details = document.getElementById("submission-details").value;
const code = document.getElementById("submission-code").value;
const submission = {
- problem_id: 2, // TODO validate language and problem id in server
+ problem_id: problem_id(), // TODO validate language and problem id in server
language: language,
details: details,
code: code,
@@ -18,18 +23,17 @@ async function submit() {
body: JSON.stringify(submission)
});
- console.log(response);
-
- const result = await response.json();
-
- console.log(result);
+ if (response.ok) {
+ const result = await response.json();
+ window.location.href = "index.html";
+ } else {
+ document.getElementById("error-message").innerHTML = "error submitting solution, try again";
+ document.getElementById("error").hidden = false;
+ }
}
async function init() {
- const url_params = new URLSearchParams(window.location.search);
- const problem_id = url_params.get("problem_id");
-
- const response = await fetch(`/problem/${problem_id}`);
+ const response = await fetch(`/problem/${problem_id()}`);
const problem = await response.json();
const problem_div = document.getElementById("submission-problem");