diff --git a/src/handler.rs b/src/handler.rs index 7ff9f7c..7a97c8e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -6,6 +6,7 @@ use axum::{ use super::Repository; mod authentication; +mod submissions; mod users; pub fn make_router(app_state: Repository) -> Router { @@ -26,8 +27,12 @@ pub fn make_router(app_state: Repository) -> Router { .route("/me/password", put(users::put_me_password)) .route("/:userId", get(users::get_user)); + let submissions_router = + Router::new().route("/:submissionId", get(submissions::get_submission)); + Router::new() .nest("/", authentication_router) .nest("/users", users_router) + .nest("/submissions", submissions_router) .with_state(app_state) } diff --git a/src/handler/submissions.rs b/src/handler/submissions.rs new file mode 100644 index 0000000..0300f7f --- /dev/null +++ b/src/handler/submissions.rs @@ -0,0 +1,103 @@ +use axum::{ + extract::{Path, State}, + response::IntoResponse, + Json, +}; +use axum_extra::{headers::Cookie, TypedHeader}; +use reqwest::StatusCode; +use serde::Serialize; +use sqlx::types::chrono; + +use super::Repository; + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct SubmissionResponse { + id: String, + user_id: i32, + user_name: String, + problem_id: i32, + submitted_at: chrono::DateTime, + language_id: i32, + total_score: i64, + max_time: i32, + max_memory: i32, + code_length: i32, + overall_judge_status: String, + judge_results: Vec, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct TestcaseResponse { + testcase_id: i32, + testcase_name: String, + judge_status: String, + score: i64, + time: i32, + memory: i32, +} + +pub async fn get_submission( + State(state): State, + TypedHeader(cookie): TypedHeader, + Path(path): Path, +) -> anyhow::Result { + let submission = state + .get_submission_by_id(path) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .ok_or(StatusCode::NOT_FOUND)?; + + let problem = state + .get_normal_problem_by_id(submission.problem_id) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .ok_or(StatusCode::NOT_FOUND)?; + + if !problem.is_public { + let session_id = cookie.get("session_id").ok_or(StatusCode::NOT_FOUND)?; + + let display_id = state + .get_display_id_by_session_id(session_id) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .ok_or(StatusCode::NOT_FOUND)?; + + if display_id != problem.author_id { + return Err(StatusCode::NOT_FOUND); + } + } + + let testcases = state + .get_testcases_by_submission_id(submission.id) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + let response = SubmissionResponse { + id: submission.id.to_string(), + user_id: submission.user_id, + user_name: submission.user_name, + problem_id: submission.problem_id, + submitted_at: submission.submitted_at, + language_id: submission.language_id, + total_score: submission.total_score, + max_time: submission.max_time, + max_memory: submission.max_memory, + code_length: submission.source.len() as i32, + overall_judge_status: submission.judge_status, + judge_results: testcases + .into_iter() + .map(|testcase| TestcaseResponse { + testcase_id: testcase.testcase_id, + testcase_name: testcase.testcase_name, + judge_status: testcase.judge_status, + score: testcase.score, + time: testcase.time, + memory: testcase.memory, + }) + .collect(), + }; + + Ok(Json(response)) +} diff --git a/src/repository.rs b/src/repository.rs index 9f515ed..2de1da8 100644 --- a/src/repository.rs +++ b/src/repository.rs @@ -4,6 +4,8 @@ use sqlx::mysql::{MySqlConnectOptions, MySqlPoolOptions}; use super::Repository; mod jwt; +mod normal_problems; +mod submissions; mod user_password; pub mod users; mod users_session; diff --git a/src/repository/normal_problems.rs b/src/repository/normal_problems.rs new file mode 100644 index 0000000..4c2175d --- /dev/null +++ b/src/repository/normal_problems.rs @@ -0,0 +1,32 @@ +use super::Repository; +use sqlx::types::chrono; + +#[derive(sqlx::FromRow)] +pub struct NormalProblems { + pub id: i32, + pub author_id: i64, + pub title: String, + pub statement: String, + pub time_limit: i32, + pub memory_limit: i32, + pub difficulty: i32, + pub is_public: bool, + pub judgecode_path: String, + pub created_at: chrono::DateTime, + pub updated_at: chrono::DateTime, +} + +impl Repository { + pub async fn get_normal_problem_by_id( + &self, + id: i32, + ) -> anyhow::Result> { + let problem = + sqlx::query_as::<_, NormalProblems>("SELECT * FROM normal_problems WHERE id = ?") + .bind(id) + .fetch_optional(&self.pool) + .await?; + + Ok(problem) + } +} diff --git a/src/repository/submissions.rs b/src/repository/submissions.rs new file mode 100644 index 0000000..d63eafb --- /dev/null +++ b/src/repository/submissions.rs @@ -0,0 +1,53 @@ +use sqlx::{types::chrono, FromRow}; + +use super::Repository; + +#[derive(FromRow)] +pub struct Submission { + pub id: i32, + pub problem_id: i32, + pub user_id: i32, + pub user_name: String, + pub language_id: i32, + pub source: String, + pub judge_status: String, + pub total_score: i64, + pub max_time: i32, + pub max_memory: i32, + pub submitted_at: chrono::DateTime, +} + +#[derive(FromRow)] +pub struct Testcase { + pub submission_id: i32, + pub testcase_id: i32, + pub testcase_name: String, + pub judge_status: String, + pub score: i64, + pub time: i32, + pub memory: i32, +} + +impl Repository { + pub async fn get_submission_by_id(&self, id: i64) -> anyhow::Result> { + let submission = sqlx::query_as::<_, Submission>("SELECT * FROM submissions WHERE id = ?") + .bind(id) + .fetch_optional(&self.pool) + .await?; + + Ok(submission) + } + + pub async fn get_testcases_by_submission_id( + &self, + submission_id: i32, + ) -> anyhow::Result> { + let testcases = + sqlx::query_as::<_, Testcase>("SELECT * FROM testcases WHERE submission_id = ?") + .bind(submission_id) + .fetch_all(&self.pool) + .await?; + + Ok(testcases) + } +}