Skip to content

Commit

Permalink
feat: support solution
Browse files Browse the repository at this point in the history
  • Loading branch information
K0nnyaku committed Dec 10, 2024
1 parent 05a69e7 commit 78279cc
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .changes/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"algohub-server": patch:feat
---

Support create and delete solutions
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod utils {
pub mod organization;
pub mod problem;
pub mod session;
pub mod solution;
pub mod submission;
}

Expand All @@ -22,6 +23,7 @@ pub mod routes {
pub mod organization;

pub mod problem;
pub mod solution;
pub mod submission;
}

Expand Down
2 changes: 1 addition & 1 deletion src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ pub mod organization;
pub mod problem;
pub mod response;
pub mod shared;
pub mod solution;
pub mod submission;

pub use shared::*;
31 changes: 31 additions & 0 deletions src/models/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

#[derive(Debug, Serialize, Deserialize)]
pub struct Solution {
pub id: Option<Thing>,

pub problem_id: Thing,
pub creator: Thing,
pub title: String,
pub content: String,

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

#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct SolutionData {
pub title: String,
pub content: String,
pub problem_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct CreateSolution<'r> {
pub id: &'r str,
pub token: &'r str,
pub data: SolutionData,
}
2 changes: 2 additions & 0 deletions src/routes/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::category;
use super::contest;
use super::organization;
use super::problem;
use super::solution;
use super::submission;
use crate::{cors::CORS, routes::account};
use anyhow::Result;
Expand Down Expand Up @@ -52,5 +53,6 @@ pub async fn rocket(db: Surreal<Client>) -> rocket::Rocket<rocket::Build> {
.mount("/category", category::routes())
.mount("/contest", contest::routes())
.mount("/code", submission::routes())
.mount("/solution", solution::routes())
.manage(db)
}
63 changes: 63 additions & 0 deletions src/routes/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::{
models::{
error::Error,
response::{Empty, Response},
solution::CreateSolution,
Credentials, OwnedId,
},
utils::{session, solution},
Result,
};
use rocket::{post, serde::json::Json, tokio::fs::remove_dir_all, State};
use std::path::Path;
use surrealdb::{engine::remote::ws::Client, Surreal};

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

let solution = solution::create(db, sol.id, sol.into_inner().data)
.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(),
}),
}))
}

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

solution::delete(db, id).await?;

remove_dir_all(Path::new("content/").join(id)).await?;

Ok(Response {
success: true,
message: "Solution deleted successfully".to_string(),
data: None,
}
.into())
}

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

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

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

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

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

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

pub async fn get_by_id<M>(db: &Surreal<Client>, id: &str) -> Result<Option<M>>
where
for<'de> M: Deserialize<'de>,
{
Ok(db.select(("solution", id)).await?)
}
135 changes: 135 additions & 0 deletions tests/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
mod utils;

use std::path::Path;

use algohub_server::{
models::{
account::Register,
problem::{CreateProblem, ProblemVisibility},
response::{Empty, Response},
solution::{CreateSolution, SolutionData},
Credentials, OwnedCredentials, OwnedId, Token, UserRecordId,
},
routes::problem::ProblemResponse,
};
use anyhow::Result;
use rocket::local::asynchronous::Client;
use utils::rocket;

#[rocket::async_test]
async fn test_solution() -> Result<()> {
let rocket = rocket().await;
let client = Client::tracked(rocket).await?;

println!("Testing solution");

let response = client
.post("/account/create")
.json(&Register {
username: "fu050409".to_string(),
password: "password".to_string(),
email: "[email protected]".to_string(),
})
.dispatch()
.await;

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

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

let id = data.id.clone();
let token = data.token.clone();

assert!(success);

let response = client
.post("/problem/create")
.json(&CreateProblem {
id: &id,
token: &token,
title: "Test Problem",
description: "Test Description".to_string(),
input: Some("Test Input".to_string()),
output: Some("Test Output".to_string()),
samples: vec![],
hint: None,
owner: UserRecordId {
tb: "account".to_string(),
id: id.clone(),
},
time_limit: 1000,
memory_limit: 128,
test_cases: vec![],
categories: vec![],
tags: vec![],
visibility: ProblemVisibility::Public,
})
.dispatch()
.await;

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

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

assert!(success);

let response = client
.post("/solution/create")
.json(&CreateSolution {
id: &id,
token: &token,
data: SolutionData {
title: "test".to_string(),
content: "test".to_string(),
problem_id: problem_data.id,
},
})
.dispatch()
.await;

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

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

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

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

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

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

client
.post(format!("/account/delete/{}", id))
.json(&Token { token: &token })
.dispatch()
.await
.into_json::<Response<Empty>>()
.await
.unwrap();

Ok(())
}

0 comments on commit 78279cc

Please sign in to comment.