Skip to content

Commit

Permalink
feat(contest): support create contest
Browse files Browse the repository at this point in the history
  • Loading branch information
fu050409 committed Nov 30, 2024
1 parent 0797749 commit 2261aa6
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 30 deletions.
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@ extern crate rocket;
pub mod models;
pub mod utils {
pub mod account;
pub mod contest;
pub mod organization;
pub mod problem;
pub mod session;
}

pub mod routes {
pub mod account;
pub mod contest;
pub mod index;
pub mod problem;
pub mod organization;
pub mod problem;
}

pub mod cors;
Expand Down
11 changes: 11 additions & 0 deletions src/models/asset.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

#[derive(Serialize, Deserialize)]
pub struct Asset {
pub id: Option<Thing>,
pub name: String,
pub path: PathBuf,
}
73 changes: 73 additions & 0 deletions src/models/contest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

use super::{OwnedCredentials, UserRecordId};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Visibility {
Public,
Internal,
Private,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum Mode {
#[default]
ICPC,
OI,
}

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

pub name: String,
pub mode: Mode,
pub visibility: Visibility,
pub description: String,
pub announcement: Option<String>,

pub start_time: chrono::NaiveDateTime,
pub end_time: chrono::NaiveDateTime,
pub problems: Vec<Thing>,

pub owner: Thing,
pub creator: Thing,
pub updaters: Vec<Thing>,
pub participants: Vec<Thing>,

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

#[derive(Serialize, Deserialize)]
pub struct ContestData {
pub name: String,
pub mode: Mode,
pub visibility: Visibility,
pub description: String,
pub start_time: chrono::NaiveDateTime,
pub end_time: chrono::NaiveDateTime,
pub owner: UserRecordId,
}

#[derive(Serialize, Deserialize)]
pub struct CreateContest {
pub auth: OwnedCredentials,
pub data: ContestData,
}

#[derive(Serialize, Deserialize)]
pub struct AddProblems<'a> {
pub auth: OwnedCredentials,
pub contest_id: &'a str,
pub problem_ids: Vec<&'a str>,
}

#[derive(Serialize, Deserialize)]
pub struct RemoveProblem {
pub auth: OwnedCredentials,
pub contest_id: Thing,
pub problem_id: Thing,
}
2 changes: 2 additions & 0 deletions src/models/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
pub mod account;
pub mod asset;
pub mod contest;
pub mod error;
pub mod organization;
pub mod problem;
Expand Down
14 changes: 1 addition & 13 deletions src/models/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,6 @@ pub struct Sample {
pub output: String,
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub enum Mode {
#[default]
ICPC,
OI,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Problem {
pub id: Option<Thing>,
Expand All @@ -38,7 +31,6 @@ pub struct Problem {
pub categories: Vec<String>,
pub tags: Vec<String>,

pub mode: Mode,
pub private: bool,

pub created_at: chrono::NaiveDateTime,
Expand All @@ -59,11 +51,9 @@ impl From<CreateProblem<'_>> for Problem {
memory_limit: val.memory_limit,
test_cases: val.test_cases,
creator: ("account", val.id).into(),
// owner: val.owner,
owner: ("account", val.id).into(),
owner: val.owner.into(),
categories: val.categories,
tags: val.tags,
mode: val.mode,
private: val.private,
created_at: chrono::Local::now().naive_local(),
updated_at: chrono::Local::now().naive_local(),
Expand Down Expand Up @@ -91,7 +81,6 @@ pub struct ProblemDetail {
pub categories: Vec<String>,
pub tags: Vec<String>,

pub mode: Mode,
pub private: bool,

pub created_at: chrono::NaiveDateTime,
Expand All @@ -115,7 +104,6 @@ impl From<Problem> for ProblemDetail {
owner: value.owner.into(),
categories: value.categories,
tags: value.tags,
mode: value.mode,
private: value.private,
created_at: value.created_at,
updated_at: value.updated_at,
Expand Down
10 changes: 5 additions & 5 deletions src/models/shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pub struct Record {

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserRecordId {
tb: String,
id: String,
pub tb: String,
pub id: String,
}

impl From<Thing> for UserRecordId {
Expand All @@ -21,9 +21,9 @@ impl From<Thing> for UserRecordId {
}
}

impl Into<Thing> for UserRecordId {
fn into(self) -> Thing {
Thing::from((self.tb, self.id))
impl From<UserRecordId> for Thing {
fn from(value: UserRecordId) -> Self {
Thing::from((value.tb, value.id))
}
}

Expand Down
77 changes: 77 additions & 0 deletions src/routes/contest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};
use surrealdb::{engine::remote::ws::Client, sql::Thing, Surreal};

use crate::{
models::{
contest::{AddProblems, CreateContest},
error::Error,
response::{Empty, Response},
},
utils::{contest, session},
Result,
};

#[derive(Serialize, Deserialize)]
pub struct CreateResponse {
pub id: String,
}

#[post("/create", data = "<contest>")]
pub async fn create(
db: &State<Surreal<Client>>,
contest: Json<CreateContest>,
) -> Result<CreateResponse> {
if !session::verify(db, &contest.auth.id, &contest.auth.token).await {
return Err(Error::Unauthorized(Json("Invalid session".into())));
}

let contest = contest.into_inner();
let contest = contest::create(db, &contest.auth.id, contest.data)
.await
.map_err(|e| Error::ServerError(Json(e.into())))?
.ok_or(Error::ServerError(Json("Failed to create contest".into())))?;

Ok(Json(Response {
success: true,
message: "Contest created successfully".into(),
data: Some(CreateResponse {
id: contest.id.unwrap().id.to_string(),
}),
}))
}

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

let problem = data.into_inner();
contest::add_problems(
db,
problem.contest_id,
&problem
.problem_ids
.iter()
.map(|&p| Thing::from(("problem", p)))
.collect::<Vec<Thing>>(),
)
.await
.map_err(|e| Error::ServerError(Json(e.into())))?
.ok_or(Error::NotFound(Json("Contest not found".into())))?;

Ok(Json(Response {
success: true,
message: "Problems added successfully".into(),
data: None,
}))
}

pub fn routes() -> Vec<rocket::Route> {
use rocket::routes;
routes![create, add_problem]
}
7 changes: 4 additions & 3 deletions src/routes/index.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use std::path::{Path, PathBuf};

use super::contest;
use super::organization;
use super::problem;
use crate::{cors::CORS, routes::account};
use anyhow::Result;
use rocket::fs::NamedFile;
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal};
use super::problem;
use super::organization;
#[get("/")]
async fn index() -> Result<NamedFile, std::io::Error> {
NamedFile::open("dist/index.html").await
Expand Down Expand Up @@ -37,6 +38,6 @@ pub async fn rocket() -> rocket::Rocket<rocket::Build> {
.mount("/account", account::routes())
.mount("/problem", problem::routes())
.mount("/org", organization::routes())
.mount("/contest", contest::routes())
.manage(db)

}
7 changes: 3 additions & 4 deletions src/routes/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ use crate::{
models::{
account::Account,
error::Error,
problem::{Mode, Problem, ProblemDetail, Sample},
problem::{Problem, ProblemDetail, Sample},
response::Response,
OwnedCredentials,
OwnedCredentials, UserRecordId,
},
utils::{account, problem, session},
Result,
Expand All @@ -40,15 +40,14 @@ pub struct CreateProblem<'r> {
#[serde(skip_serializing_if = "Option::is_none")]
pub hint: Option<String>,

// pub owner: Thing,
pub owner: UserRecordId,
pub time_limit: u64,
pub memory_limit: u64,
pub test_cases: Vec<Sample>,

pub categories: Vec<String>,
pub tags: Vec<String>,

pub mode: Mode,
pub private: bool,
}

Expand Down
69 changes: 69 additions & 0 deletions src/utils/contest.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use anyhow::Result;
use surrealdb::{engine::remote::ws::Client, opt::PatchOp, sql::Thing, Surreal};

use crate::models::contest::{Contest, ContestData};

pub async fn create(
db: &Surreal<Client>,
creator_id: &str,
contest: ContestData,
) -> Result<Option<Contest>> {
Ok(db
.create("contest")
.content(Contest {
id: None,
name: contest.name.to_string(),
mode: contest.mode,
visibility: contest.visibility,
description: contest.description,
announcement: None,
start_time: contest.start_time,
end_time: contest.end_time,
problems: vec![],
owner: contest.owner.clone().into(),
creator: ("account", creator_id).into(),
updaters: vec![("account", creator_id).into()],
participants: vec![],
created_at: chrono::Utc::now().naive_utc(),
updated_at: chrono::Utc::now().naive_utc(),
})
.await?)
}

pub async fn get(db: &Surreal<Client>, id: &str) -> Result<Option<Contest>> {
Ok(db.select(("contest", id)).await?)
}

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

pub async fn add_problems(
db: &Surreal<Client>,
id: &str,
problems: &[Thing],
) -> Result<Option<Contest>> {
Ok(db
.update(("contest", id))
.patch(PatchOp::add("/problems", problems))
.await?)
}

const REMOVE_PROBLEM: &str =
"UPDATE contest SET problems -= type::thing(\"problem\", $problem) WHERE record::id(id) = $id";
pub async fn remove_problem(
db: &Surreal<Client>,
id: String,
problem: Thing,
) -> Result<Option<Contest>> {
Ok(db
.query(REMOVE_PROBLEM)
.bind(("id", id))
.bind(("problem", problem))
.await?
.take(0)?)
}
Loading

0 comments on commit 2261aa6

Please sign in to comment.