diff options
| author | Daniel Hader <[email protected]> | 2026-06-03 22:32:09 -0500 |
|---|---|---|
| committer | Daniel Hader <[email protected]> | 2026-06-03 22:32:09 -0500 |
| commit | 328d0cd912dfa5dbda53a09ecdfeaeb93504b560 (patch) | |
| tree | 5dbe78ebbe4c9df5b8bbcf4530a7f912dbb1b7a6 | |
| parent | 51fac3f3b6f73b649ba8109b37d8ff311b905cd4 (diff) | |
submission logic and fetching submissions
| -rw-r--r-- | src/database/database.rs | 51 | ||||
| -rw-r--r-- | src/database/sql/fetch_display_submissions.sql | 10 | ||||
| -rw-r--r-- | src/database/sql/fetch_submissions_by_problem_id.sql | 1 | ||||
| -rw-r--r-- | src/database/sql/fetch_user.sql | 1 | ||||
| -rw-r--r-- | src/database/submission.rs | 10 | ||||
| -rw-r--r-- | src/main.rs | 3 | ||||
| -rw-r--r-- | src/routes/submission.rs | 21 | ||||
| -rw-r--r-- | static/login.js | 2 | ||||
| -rw-r--r-- | static/main.js | 91 | ||||
| -rw-r--r-- | static/submit.html | 5 | ||||
| -rw-r--r-- | static/submit.js | 24 |
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&! 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"); |
