From 51fac3f3b6f73b649ba8109b37d8ff311b905cd4 Mon Sep 17 00:00:00 2001 From: Daniel Hader Date: Tue, 2 Jun 2026 20:37:40 -0500 Subject: problem statement on submission page and submission logic tweaks --- src/database/database.rs | 53 ++++++++++++++++++++++++++++++++++ src/database/sql/fetch_problem.sql | 1 + src/database/sql/initialize.sql | 5 ++-- src/database/sql/insert_submission.sql | 1 + src/database/submission.rs | 15 +++++----- src/main.rs | 12 +++----- src/routes.rs | 5 ++-- src/routes/auth.rs | 6 ++-- src/routes/errors.rs | 4 +++ src/routes/problem.rs | 13 ++++++++- src/routes/submission.rs | 31 ++++++++++++++++++++ 11 files changed, 124 insertions(+), 22 deletions(-) create mode 100644 src/database/sql/fetch_problem.sql create mode 100644 src/database/sql/insert_submission.sql create mode 100644 src/routes/submission.rs (limited to 'src') 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, 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, 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 { + 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, + Path(problem_id): Path, +) -> Result { + 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, + AuthUser(claims): AuthUser, + Json(request): Json +) -> Result { + 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"))) + } +} -- cgit v1.2.3