From 6ee0d1b27919aec47a81c8eb15773fa297468a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= Date: Wed, 27 Nov 2024 20:19:40 +0800 Subject: [PATCH] feat(problem): list all problems from specific user --- .changes/list-problem.md | 5 ++ Cargo.lock | 2 +- src/models/account.rs | 5 -- src/models/mod.rs | 8 +-- src/models/problem.rs | 2 +- src/models/shared.rs | 19 +++++++ src/routes/account.rs | 3 +- src/routes/problem.rs | 26 ++++++++- src/utils/problem.rs | 11 ++++ tests/problem.rs | 115 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 180 insertions(+), 16 deletions(-) create mode 100644 .changes/list-problem.md create mode 100644 src/models/shared.rs create mode 100644 tests/problem.rs diff --git a/.changes/list-problem.md b/.changes/list-problem.md new file mode 100644 index 0000000..d2c4d2f --- /dev/null +++ b/.changes/list-problem.md @@ -0,0 +1,5 @@ +--- +"algohub-server": patch:feat +--- + +Add method for listing all problems from specific user. diff --git a/Cargo.lock b/Cargo.lock index 3cc5fff..8d5c165 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ [[package]] name = "algohub-server" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "chrono", diff --git a/src/models/account.rs b/src/models/account.rs index c50fc3d..c8a1a04 100644 --- a/src/models/account.rs +++ b/src/models/account.rs @@ -114,8 +114,3 @@ pub struct Session { pub account_id: Thing, pub token: String, } - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Record { - id: Option, -} diff --git a/src/models/mod.rs b/src/models/mod.rs index a26a622..132bdb0 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,11 +1,7 @@ -use serde::Serialize; - pub mod account; pub mod error; pub mod problem; pub mod response; +pub mod shared; -#[derive(Serialize)] -pub struct UpdateAt { - pub updated_at: chrono::NaiveDateTime, -} +pub use shared::*; diff --git a/src/models/problem.rs b/src/models/problem.rs index 5d9fa2d..f5d16c0 100644 --- a/src/models/problem.rs +++ b/src/models/problem.rs @@ -69,7 +69,7 @@ impl From> for Problem { } } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct ProblemDetail { pub id: Thing, diff --git a/src/models/shared.rs b/src/models/shared.rs new file mode 100644 index 0000000..6975fcf --- /dev/null +++ b/src/models/shared.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; +use surrealdb::sql::Thing; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Record { + id: Option, +} + +#[derive(Serialize)] +pub struct UpdateAt { + pub updated_at: chrono::NaiveDateTime, +} + +#[derive(Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Credential<'c> { + pub id: &'c str, + pub token: &'c str, +} diff --git a/src/routes/account.rs b/src/routes/account.rs index dc5ecc0..23f0a00 100644 --- a/src/routes/account.rs +++ b/src/routes/account.rs @@ -16,9 +16,10 @@ use surrealdb::{engine::remote::ws::Client, Surreal}; use crate::{ models::{ - account::{Profile, Record}, + account::Profile, error::{Error, ErrorResponse}, response::{Empty, Response}, + Record, }, utils::{account, session}, Result, diff --git a/src/routes/problem.rs b/src/routes/problem.rs index 735f4bb..69834fe 100644 --- a/src/routes/problem.rs +++ b/src/routes/problem.rs @@ -17,6 +17,7 @@ use crate::{ error::Error, problem::{Mode, Problem, ProblemDetail, Sample}, response::Response, + Credential, }, utils::{problem, session}, Result, @@ -50,7 +51,7 @@ pub struct ProblemData<'r> { pub private: bool, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] #[serde(crate = "rocket::serde")] pub struct ProblemResponse { pub id: String, @@ -128,6 +129,27 @@ pub async fn get( })) } +#[post("/list", data = "")] +pub async fn list( + db: &State>, + auth: Json>, +) -> Result> { + if !session::verify(db, auth.id, auth.token).await { + return Err(Error::Unauthorized(Json("Invalid token".into()))); + } + + let problems = problem::list(db, auth.id) + .await + .map_err(|e| Error::ServerError(Json(e.to_string().into())))?; + println!("{:?}", problems); + + Ok(Json(Response { + success: true, + message: "Problems found".to_string(), + data: Some(problems), + })) +} + #[derive(Serialize, Deserialize)] pub struct SubmitData<'r> { pub id: &'r str, @@ -234,5 +256,5 @@ pub async fn submit( pub fn routes() -> Vec { use rocket::routes; - routes![create, get, submit] + routes![create, get, submit, list] } diff --git a/src/utils/problem.rs b/src/utils/problem.rs index 9a0ac0d..b720fdd 100644 --- a/src/utils/problem.rs +++ b/src/utils/problem.rs @@ -31,3 +31,14 @@ where { Ok(db.select(("problem", id)).await?) } + +pub async fn list(db: &Surreal, id: &str) -> Result> +where + for<'de> M: Deserialize<'de>, +{ + Ok(db + .query("SELECT * FROM problem WHERE owner = type::thing(\"account\", $id)") + .bind(("id", id.to_string())) + .await? + .take(0)?) +} diff --git a/tests/problem.rs b/tests/problem.rs new file mode 100644 index 0000000..3e797cd --- /dev/null +++ b/tests/problem.rs @@ -0,0 +1,115 @@ +use algohub_server::{ + models::{ + problem::{Mode, ProblemDetail}, + response::{Empty, Response}, + Credential, + }, + routes::{ + account::{Authenticate, RegisterData, RegisterResponse}, + problem::{ProblemData, ProblemResponse}, + }, +}; +use anyhow::Result; +use rocket::local::asynchronous::Client; + +#[rocket::async_test] +async fn test_problem() -> Result<()> { + let rocket = algohub_server::rocket().await; + + let client = Client::tracked(rocket).await?; + + println!("Testing register..."); + let response = client + .post("/account/create") + .json(&RegisterData { + username: "fu050409".to_string(), + password: "password".to_string(), + email: "email@example.com".to_string(), + }) + .dispatch() + .await; + + assert_eq!(response.status().code, 200); + + let Response { + success, + message: _, + data, + } = response.into_json().await.unwrap(); + let data: RegisterResponse = data.unwrap(); + + let id = data.id.clone(); + let token = data.token.clone(); + + assert!(success); + println!("Registered account: {:?}", &data); + + for i in 0..10 { + let response = client + .post("/problem/create") + .json(&ProblemData { + id: &id, + token: &token, + title: &format!("Test Problem #{}", i), + description: "Test Description".to_string(), + input: Some("Test Input".to_string()), + output: Some("Test Output".to_string()), + samples: vec![], + hint: None, + + time_limit: 1000, + memory_limit: 128, + test_cases: vec![], + categories: vec![], + tags: vec![], + mode: Mode::ICPC, + private: true, + }) + .dispatch() + .await; + + assert_eq!(response.status().code, 200); + + let Response { + success, + message: _, + data, + } = response.into_json().await.unwrap(); + let data: ProblemResponse = data.unwrap(); + + assert!(success); + println!("Created problem: {:?}", &data); + } + + let response = client + .post("/problem/list") + .json(&Credential { + id: &id, + token: &token, + }) + .dispatch() + .await; + + let Response { + success, + message: _, + data, + } = response + .into_json::>>() + .await + .unwrap(); + let data = data.unwrap(); + assert!(success); + assert_eq!(data.len(), 10); + + client + .post(format!("/account/delete/{}", id)) + .json(&Authenticate { token: &token }) + .dispatch() + .await + .into_json::>() + .await + .unwrap(); + + Ok(()) +}