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(problem): support problem #1

Merged
merged 5 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/problem.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"algohub-server": patch:feat
---

Support for problems creation and initial submission.
3 changes: 3 additions & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
"algohub",
"chrono",
"covector",
"farmfe",
"ICPC",
"serde",
"dtolnay",
"farmfe",
"jbolda",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@nightly
- name: Setup Surrealdb
run: |
docker run --rm --pull always -d \
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
*.sln
*.sw?

# Datas
# Data Files
/content
/database

# Node.js
/node_modules
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ pub mod utils {
pub mod routes {
pub mod account;
pub mod index;
pub mod problem;
}

pub mod cors;

use models::response::Response;
use rocket::serde::json::Json;
pub type Result<T> = std::result::Result<Json<Response<T>>, models::error::Error>;

pub use crate::routes::index::rocket;
84 changes: 78 additions & 6 deletions src/models/problem.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,97 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

use crate::routes::problem::ProblemData;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Sample {
pub input: String,
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>,
pub sequence: String,

pub title: String,
pub content: String,
pub input: String,
pub output: String,
pub samples: Vec<(String, String)>,
pub description: String,
pub input: Option<String>,
pub output: Option<String>,
pub samples: Vec<Sample>,
pub hint: Option<String>,

pub time_limit: u64,
pub memory_limit: u64,
pub test_cases: Vec<Sample>,

pub creator: Thing,
pub owner: Thing,
pub categories: Vec<String>,
pub tags: Vec<String>,

pub mode: Mode,
pub private: bool,

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

impl From<ProblemData<'_>> for Problem {
fn from(val: ProblemData<'_>) -> Self {
Problem {
id: None,
title: val.title.to_string(),
description: val.description.to_string(),
input: val.input,
output: val.output,
samples: val.samples,
hint: val.hint,
time_limit: val.time_limit,
memory_limit: val.memory_limit,
test_cases: val.test_cases,
creator: ("account", val.id).into(),
// owner: val.owner,
owner: ("account", val.id).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(),
}
}
}

#[derive(Serialize, Deserialize)]
pub struct ProblemDetail {
pub id: Thing,

pub title: String,
pub description: String,
pub input: Option<String>,
pub output: Option<String>,
pub samples: Vec<Sample>,
pub hint: Option<String>,

pub time_limit: i32,
pub memory_limit: i32,
pub test_cases: Vec<(String, String)>,
pub test_cases: Vec<Sample>,

pub creator: Thing,
pub owner: Thing,
pub categories: Vec<String>,
pub tags: Vec<String>,

pub mode: Mode,
pub private: bool,

pub created_at: chrono::NaiveDateTime,
pub updated_at: chrono::NaiveDateTime,
}
23 changes: 10 additions & 13 deletions src/routes/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use crate::{
response::{Empty, Response},
},
utils::{account, session},
Result,
};

#[derive(Serialize, Deserialize)]
Expand All @@ -41,7 +42,7 @@ pub struct RegisterResponse {
pub async fn register(
db: &State<Surreal<Client>>,
register: Json<RegisterData>,
) -> Result<Json<Response<RegisterResponse>>, Error> {
) -> Result<RegisterResponse> {
match account::create(db, register.into_inner()).await {
Ok(Some(account)) => {
let token = match session::create(db, account.id.clone().unwrap()).await {
Expand Down Expand Up @@ -81,10 +82,7 @@ pub struct ProfileData<'r> {
}

#[post("/profile", data = "<profile>")]
pub async fn profile(
db: &State<Surreal<Client>>,
profile: Json<ProfileData<'_>>,
) -> Result<Json<Response<Empty>>, Error> {
pub async fn profile(db: &State<Surreal<Client>>, profile: Json<ProfileData<'_>>) -> Result<Empty> {
account::get_by_id::<Record>(db, profile.id)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
Expand Down Expand Up @@ -116,7 +114,7 @@ pub async fn get_profile(
db: &State<Surreal<Client>>,
id: &str,
auth: Json<Authenticate<'_>>,
) -> Result<Json<Response<Profile>>, Error> {
) -> Result<Profile> {
if !session::verify(db, id, auth.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant access permission".into(),
Expand Down Expand Up @@ -153,13 +151,14 @@ pub struct Upload<'r> {
#[derive(Serialize, Deserialize)]
pub struct UploadResponse {
pub uri: String,
pub path: String,
}

#[put("/content/upload", data = "<data>")]
pub async fn upload_content(
db: &State<Surreal<Client>>,
data: Form<Upload<'_>>,
) -> Result<Json<Response<UploadResponse>>, Error> {
) -> Result<UploadResponse> {
if !session::verify(db, data.id, data.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant access permission".into(),
Expand All @@ -182,7 +181,7 @@ pub async fn upload_content(
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?;
}
let file_name = format!("avatar.{}", file_extension);
let file_name = format!("{}.{}", uuid::Uuid::new_v4(), file_extension);
let file_path = user_path.join(&file_name);

let mut file = data
Expand All @@ -203,6 +202,7 @@ pub async fn upload_content(
message: "Content updated successfully".into(),
data: Some(UploadResponse {
uri: format!("/account/content/{}/{}", data.id, file_name),
path: format!("content/{}/{}", data.id, file_name),
}),
}))
}
Expand All @@ -212,7 +212,7 @@ pub async fn delete(
db: &State<Surreal<Client>>,
id: &str,
auth: Json<Authenticate<'_>>,
) -> Result<Json<Response<Empty>>, Error> {
) -> Result<Empty> {
if !session::verify(db, id, auth.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant access permission".into(),
Expand Down Expand Up @@ -248,10 +248,7 @@ pub struct LoginResponse {
}

#[post("/login", data = "<login>")]
pub async fn login(
db: &State<Surreal<Client>>,
login: Json<Login<'_>>,
) -> Result<Json<Response<LoginResponse>>, Error> {
pub async fn login(db: &State<Surreal<Client>>, login: Json<Login<'_>>) -> Result<LoginResponse> {
let session = session::authenticate(db, login.identity, login.password)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?
Expand Down
3 changes: 3 additions & 0 deletions src/routes/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use anyhow::Result;
use rocket::fs::NamedFile;
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal};

use super::problem;

#[get("/")]
async fn index() -> Result<NamedFile, std::io::Error> {
NamedFile::open("dist/index.html").await
Expand Down Expand Up @@ -34,5 +36,6 @@ pub async fn rocket() -> rocket::Rocket<rocket::Build> {
.attach(CORS)
.mount("/", routes![index, files])
.mount("/account", account::routes())
.mount("/problem", problem::routes())
.manage(db)
}
Loading