diff options
| -rw-r--r-- | src/database/database.rs | 53 | ||||
| -rw-r--r-- | src/database/sql/fetch_problem.sql | 1 | ||||
| -rw-r--r-- | src/database/sql/initialize.sql | 5 | ||||
| -rw-r--r-- | src/database/sql/insert_submission.sql | 1 | ||||
| -rw-r--r-- | src/database/submission.rs | 15 | ||||
| -rw-r--r-- | src/main.rs | 12 | ||||
| -rw-r--r-- | src/routes.rs | 5 | ||||
| -rw-r--r-- | src/routes/auth.rs | 6 | ||||
| -rw-r--r-- | src/routes/errors.rs | 4 | ||||
| -rw-r--r-- | src/routes/problem.rs | 13 | ||||
| -rw-r--r-- | src/routes/submission.rs | 31 | ||||
| -rw-r--r-- | static/submit.html | 7 | ||||
| -rw-r--r-- | static/submit.js | 16 |
13 files changed, 143 insertions, 26 deletions
diff --git a/src/database/database.rs b/src/database/database.rs index 7444779..6ebbe1c 100644 --- a/src/database/database.rs +++ b/src/database/database.rs @@ -4,6 +4,8 @@ use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; use r2d2_sqlite::rusqlite::OptionalExtension; +use crate::database::submission::Submission; + use super::problem::Problem; use super::user::User; @@ -120,6 +122,27 @@ impl Database { .map_err(|e| DatabaseError::Query(e.to_string()))? ) } + + pub fn fetch_problem(&self, problem_id: i64) -> Result<Option<Problem>, DatabaseError> { + static QUERY: &str = include_str!("sql/fetch_problem.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_one([problem_id], |row| { + Ok(Problem::new( + row.get("id")?, + row.get("title")?, + row.get("description")?, + )) + }) + .optional() + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) + } pub fn fetch_problems(&self) -> Result<Vec<Problem>, DatabaseError> { static QUERY: &str = include_str!("sql/fetch_problems.sql"); @@ -143,6 +166,36 @@ impl Database { ) } + pub fn insert_submission( + &self, + user_id: i64, + problem_id: i64, + language: &str, + details: &str, + code: &str, + ) -> Result<Submission, DatabaseError> { + static QUERY: &str = include_str!("sql/insert_submission.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_one((user_id, problem_id, language, details, code), |row| { + Ok(Submission::new( + row.get("id")?, + user_id, + problem_id, + language.to_owned(), + details.to_owned(), + code.to_owned() + )) + }) + .map_err(|e| DatabaseError::Query(e.to_string()))? + ) + } + pub fn initialize(&self) -> Result<(), DatabaseError> { static QUERY: &str = include_str!("sql/initialize.sql"); let conn = self.pool diff --git a/src/database/sql/fetch_problem.sql b/src/database/sql/fetch_problem.sql new file mode 100644 index 0000000..f319c2f --- /dev/null +++ b/src/database/sql/fetch_problem.sql @@ -0,0 +1 @@ +SELECT * FROM problem WHERE problem.id == ?1; diff --git a/src/database/sql/initialize.sql b/src/database/sql/initialize.sql index 72b7468..8c0f2ef 100644 --- a/src/database/sql/initialize.sql +++ b/src/database/sql/initialize.sql @@ -16,9 +16,10 @@ CREATE TABLE IF NOT EXISTS submission ( id INTEGER PRIMARY KEY, user_id INTEGER NOT NULL, problem_id INTEGER NOT NULL, - code TEXT NOT NULL, language TEXT NOT NULL, - validated INTEGER NOT NULL + details TEXT NOT NULL, + code TEXT NOT NULL + ); INSERT INTO problem (title, description) VALUES ( diff --git a/src/database/sql/insert_submission.sql b/src/database/sql/insert_submission.sql new file mode 100644 index 0000000..96df322 --- /dev/null +++ b/src/database/sql/insert_submission.sql @@ -0,0 +1 @@ +INSERT INTO submission (user_id, problem_id, language, details, code) VALUES (?1, ?2, ?3, ?4, ?5) RETURNING id; diff --git a/src/database/submission.rs b/src/database/submission.rs index 2ec1287..4a83edd 100644 --- a/src/database/submission.rs +++ b/src/database/submission.rs @@ -5,9 +5,9 @@ pub struct Submission { id: i64, user_id: i64, problem_id: i64, - code: String, language: String, - validated: bool, + details: String, + code: String, } impl Submission { @@ -15,17 +15,18 @@ impl Submission { id: i64, user_id: i64, problem_id: i64, - code: String, language: String, - validated: bool, + details: String, + code: String, ) -> Self { - Self { id, user_id, problem_id, code, language, validated } + Self { id, user_id, problem_id, language, details, code } } pub fn id(&self) -> i64 { self.id } pub fn user_id(&self) -> i64 { self.user_id } pub fn problem_id(&self) -> i64 { self.problem_id } - pub fn code(&self) -> &str { &self.code } pub fn language(&self) -> &str { &self.language } - pub fn validated(&self) -> bool { self.validated } + pub fn details(&self) -> &str { &self.details } + pub fn code(&self) -> &str { &self.code } + } diff --git a/src/main.rs b/src/main.rs index b1979b1..73dccef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,12 +9,12 @@ use axum::{ Router, }; -use routes::problem::{get_problems, create_problem}; +use routes::problem::{get_problem, get_problems, create_problem}; use routes::user::{create_user, me}; use routes::auth::{login, logout}; use tower_http::services::ServeDir; -use crate::{database::Database, utils::register_admin}; +use crate::{database::Database, routes::submission::create_submission, utils::register_admin}; #[derive(Clone)] struct AppState { @@ -61,8 +61,10 @@ async fn main() { let app = Router::new() .route("/login", post(login)) .route("/logout", post(logout)) + .route("/problem/{problem_id}", get(get_problem)) .route("/problem", get(get_problems)) .route("/problem", post(create_problem)) + .route("/submission", post(create_submission)) .route("/user", post(create_user)) .route("/me", get(me)) .nest_service("/static", static_files) @@ -72,9 +74,3 @@ async fn main() { axum::serve(listener, app).await.unwrap(); } -#[cfg(test)] -mod tests { - fn test_app() -> String { - "hello".into() - } -} diff --git a/src/routes.rs b/src/routes.rs index e0adba6..b008273 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,4 +1,5 @@ -pub mod problem; -pub mod user; pub mod auth; +pub mod user; +pub mod problem; +pub mod submission; mod errors; diff --git a/src/routes/auth.rs b/src/routes/auth.rs index ab7a393..8e8d99b 100644 --- a/src/routes/auth.rs +++ b/src/routes/auth.rs @@ -18,9 +18,10 @@ use crate::routes::errors::RouteError; #[derive(Serialize, Deserialize)] pub struct Claims { - pub sub: String, + pub sub: i64, pub exp: usize, pub iat: usize, + pub email: String, pub username: String, pub is_admin: bool, } @@ -90,9 +91,10 @@ pub async fn login( .as_secs() as usize; let claims = Claims { - sub: user.email().to_string(), + sub: user.id(), iat: now, exp: now + 60 * 60 * 24, + email: user.email().to_string(), username: user.username().to_string(), is_admin: false }; diff --git a/src/routes/errors.rs b/src/routes/errors.rs index 78b4e3a..f6a0901 100644 --- a/src/routes/errors.rs +++ b/src/routes/errors.rs @@ -7,6 +7,7 @@ pub enum RouteError { UserCreateUsernameExists(String), UnregisteredEmail(String), AuthorizationFailure(), + NotFound(String), } impl IntoResponse for RouteError { @@ -26,6 +27,9 @@ impl IntoResponse for RouteError { }, RouteError::AuthorizationFailure() => { (StatusCode::UNAUTHORIZED, format!("failed to authorize")) + }, + RouteError::NotFound(resource) => { + (StatusCode::NOT_FOUND, format!("{resource} not found")) } }; diff --git a/src/routes/problem.rs b/src/routes/problem.rs index 9b2eba3..69d1b66 100644 --- a/src/routes/problem.rs +++ b/src/routes/problem.rs @@ -1,4 +1,4 @@ -use axum::{extract::State, http::StatusCode, response::{IntoResponse, Json}}; +use axum::{extract::{Path, State}, http::StatusCode, response::{IntoResponse, Json}}; use serde::Deserialize; use crate::{AppState, routes::errors::RouteError}; @@ -31,3 +31,14 @@ pub async fn get_problems( Ok((StatusCode::CREATED, Json(problems))) } + +pub async fn get_problem( + State(state): State<AppState>, + Path(problem_id): Path<i64>, +) -> Result<impl IntoResponse, RouteError> { + match state.database.fetch_problem(problem_id) { + Err(_) => Err(RouteError::Internal("database action failed".into())), + Ok(None) => Err(RouteError::NotFound("problem".into())), + Ok(Some(problem)) => Ok(Json(problem)) + } +} diff --git a/src/routes/submission.rs b/src/routes/submission.rs new file mode 100644 index 0000000..50bb53d --- /dev/null +++ b/src/routes/submission.rs @@ -0,0 +1,31 @@ +use axum::{Json, extract::State, http::StatusCode, response::IntoResponse}; +use serde::Deserialize; + +use crate::{AppState, routes::{auth::AuthUser, errors::RouteError}}; + +#[derive(Deserialize)] +pub(crate) struct CreateSubmissionRequest { + problem_id: i64, + language: String, + details: String, + code: String, +} + +pub async fn create_submission( + State(state): State<AppState>, + AuthUser(claims): AuthUser, + Json(request): Json<CreateSubmissionRequest> +) -> Result<impl IntoResponse, RouteError> { + let user_id = claims.sub; + + match state.database.insert_submission( + user_id, + request.problem_id, + &request.language, + &request.details, + &request.code + ) { + Ok(submission) => Ok((StatusCode::CREATED, Json(submission))), + Err(_) => Err(RouteError::Internal(format!("unable to insert submission"))) + } +} diff --git a/static/submit.html b/static/submit.html index 393b2e2..9cab87b 100644 --- a/static/submit.html +++ b/static/submit.html @@ -37,12 +37,13 @@ <option value="other">Other (specify below)</option> </select> <br><br> - <p>If your code requires any special instructions or additional details, provide them below. Languages not listed above can be specified here too. If you would like a language added to the list above, reach out to Daniel.</p><br> - <label for="submission-details">Additional Details</label><br> - <textarea id="submission-details" rows="4" cols="87"></textarea><br><br> <label for="submission-code">Code</label><br> <textarea id="submission-code" rows="16" cols="87"></textarea> <br><br> + <p>If your code requires any special instructions or additional details, provide them below. Languages not listed above can be specified here too. If you would like a language added to the list above, reach out to Daniel.</p><br> + <label for="submission-details">Additional Details</label><br> + <textarea id="submission-details" rows="4" cols="87"></textarea> + <br><br> <button id="submission-button" onclick="submit()">Submit</button> <br><br> </div> diff --git a/static/submit.js b/static/submit.js index 03dfd69..9708e0c 100644 --- a/static/submit.js +++ b/static/submit.js @@ -25,6 +25,20 @@ async function submit() { console.log(result); } -function init() { +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 problem = await response.json(); + + const problem_div = document.getElementById("submission-problem"); + + const title = document.createElement("h2"); + title.innerHTML = `Problem: ${problem.title}`; + problem_div.appendChild(title); + + const description = document.createElement("p"); + description.innerHTML = problem.description; + problem_div.appendChild(description); } |
