From 3df523734d836d9e312fe82d7ea73f41ae90807e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8B=8F=E5=90=91=E5=A4=9C?= <46275354+fu050409@users.noreply.github.com> Date: Sat, 7 Dec 2024 03:46:50 +0800 Subject: [PATCH] feat(contest): support for create and list contests (#54) * feat(contest): support list contest * chore: bump version * feat(contest): support for contest * chore: fix code lint --- .changes/contest.md | 5 ++++ .changes/create-contest.md | 5 ++++ Cargo.lock | 2 +- src/models/contest.rs | 10 +++++++ src/routes/account.rs | 15 +++------- src/routes/contest.rs | 58 ++++++++++++++++++++++++++++++++++++-- src/routes/index.rs | 11 ++------ src/utils/contest.rs | 49 +++++++++++++++++++++++++++++++- 8 files changed, 131 insertions(+), 24 deletions(-) create mode 100644 .changes/contest.md create mode 100644 .changes/create-contest.md diff --git a/.changes/contest.md b/.changes/contest.md new file mode 100644 index 0000000..e18d029 --- /dev/null +++ b/.changes/contest.md @@ -0,0 +1,5 @@ +--- +"algohub-server": patch:feat +--- + +Support create and list all contests. diff --git a/.changes/create-contest.md b/.changes/create-contest.md new file mode 100644 index 0000000..7ae0fc9 --- /dev/null +++ b/.changes/create-contest.md @@ -0,0 +1,5 @@ +--- +"algohub-server": patch:feat +--- + +Support `ContestProblem` model to display submits and acceptations. diff --git a/Cargo.lock b/Cargo.lock index 893d82d..8950848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ [[package]] name = "algohub-server" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "chrono", diff --git a/src/models/contest.rs b/src/models/contest.rs index 2015ef7..c447ced 100644 --- a/src/models/contest.rs +++ b/src/models/contest.rs @@ -117,3 +117,13 @@ impl From for UserContest { } } } + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ContestProblem { + pub id: String, + pub title: String, + pub solved: bool, + pub submitted_count: u32, + pub accepted_count: u32, +} diff --git a/src/routes/account.rs b/src/routes/account.rs index f407f8d..6f9510d 100644 --- a/src/routes/account.rs +++ b/src/routes/account.rs @@ -6,7 +6,7 @@ use surrealdb::{engine::remote::ws::Client, Surreal}; use crate::{ models::{ account::{Login, MergeProfile, Profile, Register}, - error::{Error, ErrorResponse}, + error::Error, response::{Empty, Response}, OwnedCredentials, Record, Token, }, @@ -19,8 +19,8 @@ pub async fn register( db: &State>, register: Json, ) -> Result { - match account::create(db, register.into_inner()).await { - Ok(Some(account)) => { + match account::create(db, register.into_inner()).await? { + Some(account) => { let token = match session::create(db, account.id.clone().unwrap()).await { Ok(session) => session.unwrap().token, Err(e) => return Err(Error::ServerError(Json(e.to_string().into()))), @@ -33,19 +33,12 @@ pub async fn register( } .into()) } - Ok(None) => Ok(Response { + None => Ok(Response { success: false, message: "Specified username or email already exists".to_string(), data: None, } .into()), - Err(e) => Err(Error::ServerError( - ErrorResponse { - success: false, - message: e.to_string(), - } - .into(), - )), } } diff --git a/src/routes/contest.rs b/src/routes/contest.rs index 7e86d7e..143ff0a 100644 --- a/src/routes/contest.rs +++ b/src/routes/contest.rs @@ -3,7 +3,7 @@ use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal}; use crate::{ models::{ - contest::{AddProblems, CreateContest, UserContest}, + contest::{AddProblems, ContestProblem, CreateContest, UserContest}, error::Error, response::{Empty, Response}, Credentials, OwnedId, @@ -42,6 +42,16 @@ pub async fn add_problems( } let problem = data.into_inner(); + contest::add_problems( + db, + problem.contest_id.to_string(), + problem + .problem_ids + .iter() + .map(|&id| Thing::from(("problem", id))) + .collect(), + ) + .await?; Ok(Json(Response { success: true, @@ -55,7 +65,7 @@ pub async fn list_all( db: &State>, auth: Json>, ) -> Result> { - if !session::verify(db, &auth.id, &auth.token).await { + if !session::verify(db, auth.id, auth.token).await { return Err(Error::Unauthorized(Json("Invalid credentials".into()))); } @@ -67,7 +77,49 @@ pub async fn list_all( })) } +#[post("/list//problems", data = "")] +pub async fn list_problems( + db: &State>, + id: &str, + auth: Json>, +) -> Result> { + if !session::verify(db, auth.id, auth.token).await { + return Err(Error::Unauthorized(Json("Invalid credentials".into()))); + } + + let problems = contest::list_problems(db, id, auth.id).await?; + + dbg!(&problems); + + Ok(Json(Response { + success: true, + message: "Problems listed successfully".into(), + data: Some(problems), + })) +} + +#[post("/get/", data = "")] +pub async fn get( + db: &State>, + id: &str, + auth: Json>, +) -> Result { + if !session::verify(db, auth.id, auth.token).await { + return Err(Error::Unauthorized(Json("Invalid credentials".into()))); + } + + let contest = contest::get(db, id) + .await? + .ok_or(Error::NotFound(Json("Contest not found".into())))?; + + Ok(Json(Response { + success: true, + message: "Contest retrieved successfully".into(), + data: Some(contest.into()), + })) +} + pub fn routes() -> Vec { use rocket::routes; - routes![create, add_problems, list_all] + routes![create, get, add_problems, list_problems, list_all] } diff --git a/src/routes/index.rs b/src/routes/index.rs index 1687b9f..ac03cdb 100644 --- a/src/routes/index.rs +++ b/src/routes/index.rs @@ -12,13 +12,8 @@ use rocket::fs::NamedFile; use surrealdb::engine::remote::ws::Client; use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal}; -#[get("/")] -async fn index() -> Result { - NamedFile::open("dist/index.html").await -} - -#[get("/assets/")] -async fn files(file: PathBuf) -> Option { +#[get("/", rank = 1)] +async fn index(file: PathBuf) -> Option { NamedFile::open(Path::new("dist/").join(file)).await.ok() } @@ -44,7 +39,7 @@ pub async fn init_db(db_addr: &str) -> Result> { pub async fn rocket(db: Surreal) -> rocket::Rocket { rocket::build() .attach(CORS) - .mount("/", routes![index, files]) + .mount("/", routes![index]) .mount("/account", account::routes()) .mount("/asset", asset::routes()) .mount("/problem", problem::routes()) diff --git a/src/utils/contest.rs b/src/utils/contest.rs index 3c954b0..ed5897c 100644 --- a/src/utils/contest.rs +++ b/src/utils/contest.rs @@ -1,7 +1,7 @@ use anyhow::Result; use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal}; -use crate::models::contest::{Contest, ContestData}; +use crate::models::contest::{Contest, ContestData, ContestProblem}; pub async fn create( db: &Surreal, @@ -45,6 +45,53 @@ pub async fn list_by_owner(db: &Surreal, id: &str) -> Result, + id: String, + problems: Vec, +) -> Result> { + Ok(db + .query(ADD_PROBLEM) + .bind(("id", id)) + .bind(("problems", problems)) + .await? + .take(0)?) +} + +const LIST_PROBLEMS: &str = r#" +SELECT title, record::id(id) AS id, count( + SELECT VALUE true + FROM submission WHERE record::id(creator) == $account_id AND problem == $parent.id + AND judge_result.status.type == "accepted" +) > 0 AS solved, +count( + SELECT record::id(creator) + FROM submission WHERE record::id(creator) == $account_id AND problem == $parent.id +) AS submittedCount, +count( + SELECT record::id(creator) + FROM submission WHERE record::id(creator) == $account_id AND problem == $parent.id + AND judge_result.status.type == "accepted" +) AS acceptedCount +FROM type::thing("contest", $id).problems; +"#; +pub async fn list_problems( + db: &Surreal, + id: &str, + account_id: &str, +) -> Result> { + Ok(db + .query(LIST_PROBLEMS) + .bind(("id", id.to_string())) + .bind(("account_id", account_id.to_string())) + .await? + .take(0)?) +} + const REMOVE_PROBLEM: &str = "UPDATE contest SET problems -= type::thing(\"problem\", $problem) WHERE record::id(id) = $id"; pub async fn remove_problem(