Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support solution #61

Merged
merged 8 commits into from
Dec 17, 2024
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, delete, get, list, update solutions
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::*;
79 changes: 79 additions & 0 deletions src/models/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

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

pub problem: 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<'r> {
pub title: &'r str,
pub content: &'r str,
pub problem: &'r str,
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct CreateSolution<'r> {
pub id: &'r str,
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,
}
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)
}
137 changes: 137 additions & 0 deletions src/routes/solution.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
use crate::{
models::{
error::Error,
response::{Empty, Response},
solution::{CreateSolution, ListSolutions, Solution, UserSolution},
Credentials, OwnedId,
},
utils::{session, solution},
Result,
};
use rocket::{post, serde::json::Json, State};
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.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().id.to_string(),
}),
}))
}

K0nnyaku marked this conversation as resolved.
Show resolved Hide resolved
#[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>>,
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?;

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

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

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

pub async fn create(db: &Surreal<Client>, sol: CreateSolution<'_>) -> Result<Option<Solution>> {
Ok(db
.create("solution")
.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?)
}
Loading
Loading