Skip to content

Commit

Permalink
feat(sol): support list, get, and update
Browse files Browse the repository at this point in the history
  • Loading branch information
K0nnyaku committed Dec 17, 2024
1 parent dfb2e96 commit 5e7754f
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .changes/solution.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"algohub-server": patch:feat
---

Support create and delete solutions
Support create, delete, get, list, update solutions
48 changes: 48 additions & 0 deletions src/models/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,51 @@ pub struct CreateSolution<'r> {
pub token: &'r str,
pub data: SolutionData<'r>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct UserSolution {
pub id: String,

pub problem: String,
pub title: String,
pub content: String,
pub creator: String,

pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}

impl From<Solution> for UserSolution {
fn from(value: Solution) -> Self {
UserSolution {
id: value.id.unwrap().id.to_string(),
problem: value.problem.id.to_string(),
creator: value.creator.id.to_string(),
title: value.title,
content: value.content,

created_at: value.created_at,
updated_at: value.updated_at,
}
}
}

impl From<CreateSolution<'_>> for Solution {
fn from(val: CreateSolution<'_>) -> Self {
Solution {
id: None,
title: val.data.title.to_string(),
content: val.data.content.to_string(),
problem: ("problem", val.data.problem).into(),
creator: ("account", val.id).into(),

created_at: chrono::Local::now().naive_local(),
updated_at: chrono::Local::now().naive_local(),
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ListSolutions {
pub problem: String,
}
85 changes: 81 additions & 4 deletions src/routes/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
models::{
error::Error,
response::{Empty, Response},
solution::CreateSolution,
solution::{CreateSolution, ListSolutions, Solution, UserSolution},
Credentials, OwnedId,
},
utils::{session, solution},
Expand All @@ -19,19 +19,96 @@ pub async fn create(db: &State<Surreal<Client>>, sol: Json<CreateSolution<'_>>)
)));
}

let solution = solution::create(db, sol.id, sol.into_inner().data)
let solution = solution::create(db, sol.into_inner())
.await?
.ok_or(Error::ServerError(Json("Failed to create solution".into())))?;

Ok(Json(Response {
success: true,
message: "Solution created successfully".to_string(),
data: Some(OwnedId {
id: solution.id.unwrap().to_string(),
id: solution.id.unwrap().id.to_string(),
}),
}))
}

#[post("/get/<id>", data = "<auth>")]
pub async fn get(
db: &State<Surreal<Client>>,
id: &str,
auth: Json<Option<Credentials<'_>>>,
) -> Result<UserSolution> {
let solution = solution::get::<Solution>(db, id)
.await?
.ok_or(Error::NotFound(Json(
"Solution with specified id not found".into(),
)))?;

let authed_id = if let Some(auth) = auth.into_inner() {
if !session::verify(db, auth.id, auth.token).await {
return Err(Error::Unauthorized(Json("Invalid credentials".into())));
} else {
Some(auth.id)
}
} else {
None
};

if authed_id.is_none() {
return Err(Error::Unauthorized(Json(
"You have no permission to access this solution".into(),
)));
}

Ok(Json(Response {
success: true,
message: "Solution found".to_string(),
data: Some(solution.into()),
}))
}

#[post("/update/<id>", data = "<sol>")]
pub async fn update(
db: &State<Surreal<Client>>,
id: &str,
sol: Json<CreateSolution<'_>>,
) -> Result<Empty> {
if !session::verify(db, sol.id, sol.token).await {
return Err(Error::Unauthorized(Json("Invalid credentials".into())));
}

solution::update(db, id, sol.into_inner())
.await?
.ok_or(Error::ServerError(Json(
"Failed to update solution, please try again later.".into(),
)))?;

Ok(Json(Response {
success: true,
message: "Solution updated successful".to_string(),
data: None,
}))
}

#[post("/list", data = "<data>")]
pub async fn list(db: &State<Surreal<Client>>, data: Json<ListSolutions>) -> Result<Vec<Solution>> {
let result = solution::list(
db,
("problem".to_string(), data.into_inner().problem).into(),
)
.await?;

if result.is_empty() {
return Err(Error::NotFound(Json("Solution not found".into())));
}

Ok(Json(Response {
success: true,
message: "Solution found successfully".to_string(),
data: Some(result),
}))
}

#[post("/delete/<id>", data = "<sol>")]
pub async fn delete(
db: &State<Surreal<Client>>,
Expand All @@ -56,5 +133,5 @@ pub async fn delete(

pub fn routes() -> Vec<rocket::Route> {
use rocket::routes;
routes![create, delete]
routes![create, get, update, list, delete]
}
49 changes: 31 additions & 18 deletions src/utils/solution.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
use anyhow::Result;
use surrealdb::{engine::remote::ws::Client, Surreal};
use serde::Deserialize;
use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal};

use crate::models::solution::{Solution, SolutionData};
use crate::models::solution::{CreateSolution, Solution};

pub async fn create(
db: &Surreal<Client>,
id: &str,
data: SolutionData<'_>,
) -> Result<Option<Solution>> {
pub async fn create(db: &Surreal<Client>, sol: CreateSolution<'_>) -> Result<Option<Solution>> {
Ok(db
.create("solution")
.content(Solution {
id: None,

title: data.title.to_string(),
creator: ("account", id).into(),
problem: ("problem", data.problem).into(),
content: data.content.to_string(),

created_at: chrono::Local::now().naive_local(),
updated_at: chrono::Local::now().naive_local(),
})
.content(Into::<Solution>::into(sol))
.await?)
}

pub async fn delete(db: &Surreal<Client>, id: &str) -> Result<Option<Solution>> {
Ok(db.delete(("solution", id)).await?)
}

pub async fn get<M>(db: &Surreal<Client>, id: &str) -> Result<Option<M>>
where
for<'de> M: Deserialize<'de>,
{
Ok(db.select(("solution", id)).await?)
}

pub async fn list(db: &Surreal<Client>, problem: Thing) -> Result<Vec<Solution>> {
Ok(db
.query("SELECT * FROM solution WHERE problem = $problem")
.bind(("problem", problem))
.await?
.take(0)?)
}

pub async fn update(
db: &Surreal<Client>,
id: &str,
solution: CreateSolution<'_>,
) -> Result<Option<Solution>> {
Ok(db
.update(("solution", id))
.content(Into::<Solution>::into(solution))
.await?)
}
77 changes: 72 additions & 5 deletions tests/solution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use algohub_server::{
account::Register,
problem::{CreateProblem, ProblemVisibility},
response::{Empty, Response},
solution::{CreateSolution, SolutionData},
solution::{CreateSolution, ListSolutions, Solution, SolutionData, UserSolution},
Credentials, OwnedCredentials, OwnedId, Token, UserRecordId,
},
routes::problem::ProblemResponse,
Expand Down Expand Up @@ -104,13 +104,80 @@ async fn test_solution() -> Result<()> {
message: _,
data,
} = response.into_json().await.unwrap();
let data: OwnedId = data.unwrap();
let solution_data: OwnedId = data.unwrap();

assert!(success);
println!("Created solution: {}", data.id);
println!("Created solution: {}", solution_data.id);

let response = client
.post(format!("/solution/delete/{}", data.id))
.post(format!("/solution/update/{}", solution_data.id))
.json(&CreateSolution {
id: &id,
token: &token,
data: SolutionData {
title: "test2",
content: "test2",
problem: &problem_data.id,
},
})
.dispatch()
.await;

assert_eq!(response.status().code, 200);

let Response {
success,
message: _,
data: _,
} = response.into_json::<Response<Empty>>().await.unwrap();

assert!(success);

println!("Update solution: {}", solution_data.id);

let response = client
.post(format!("/solution/get/{}", solution_data.id))
.json(&Credentials {
id: &id,
token: &token,
})
.dispatch()
.await;

assert_eq!(response.status().code, 200);

let Response {
success,
message: _,
data,
} = response.into_json().await.unwrap();
let list_solution_data: UserSolution = data.unwrap();

assert!(success);
println!("get solution: {:#?}", list_solution_data);

let response = client
.post("/solution/list")
.json(&ListSolutions {
problem: problem_data.id.to_string(),
})
.dispatch()
.await;

assert_eq!(response.status().code, 200);

let Response {
success,
message: _,
data,
} = response.into_json().await.unwrap();
let list_solution_data: Vec<Solution> = data.unwrap();

assert!(success);
println!("list solution: {:#?}", list_solution_data);

let response = client
.post(format!("/solution/delete/{}", solution_data.id))
.json(&Credentials {
id: &id,
token: &token,
Expand All @@ -120,7 +187,7 @@ async fn test_solution() -> Result<()> {

response.into_json::<Response<Empty>>().await.unwrap();

assert!(!Path::new("content").join(data.id.clone()).exists());
assert!(!Path::new("content").join(solution_data.id.clone()).exists());

client
.post(format!("/account/delete/{}", id))
Expand Down

0 comments on commit 5e7754f

Please sign in to comment.