Skip to content

Commit

Permalink
refactor(assets): full rewrite all api endpoints (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
fu050409 authored Nov 30, 2024
1 parent c2d07a2 commit 46477a6
Show file tree
Hide file tree
Showing 14 changed files with 291 additions and 154 deletions.
5 changes: 5 additions & 0 deletions .changes/refactor-asset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"algohub-server": patch:refactor
---

Full rewrite all api endpoints of assets.
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate rocket;
pub mod models;
pub mod utils {
pub mod account;
pub mod asset;
pub mod contest;
pub mod organization;
pub mod problem;
Expand All @@ -13,11 +14,12 @@ pub mod utils {

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

pub mod cors;
Expand Down
16 changes: 15 additions & 1 deletion src/models/account.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[derive(Default, Serialize, Deserialize)]
pub struct Account {
pub id: Option<Thing>,
pub username: String,
Expand All @@ -28,6 +28,20 @@ pub struct Account {
pub updated_at: chrono::NaiveDateTime,
}

#[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct Register {
pub username: String,
pub email: String,
pub password: String,
}

#[derive(Deserialize)]
pub struct Login<'r> {
pub identity: &'r str,
pub password: &'r str,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Profile {
#[serde(skip_serializing_if = "Option::is_none")]
Expand Down
16 changes: 16 additions & 0 deletions src/models/asset.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,27 @@
use std::path::PathBuf;

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

use super::{Credentials, UserRecordId};

#[derive(Serialize, Deserialize)]
pub struct Asset {
pub id: Option<Thing>,
pub name: String,
pub owner: Thing,
pub path: PathBuf,
}

#[derive(FromForm)]
pub struct CreateAsset<'a> {
pub auth: Credentials<'a>,
pub owner: UserRecordId,
pub file: TempFile<'a>,
}

#[derive(Serialize, Deserialize)]
pub struct UserContent {
pub id: String,
}
6 changes: 3 additions & 3 deletions src/models/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ pub enum Error {
Forbidden(Json<ErrorResponse>),
}

impl From<String> for Error {
fn from(message: String) -> Self {
Error::ServerError(Json(ErrorResponse::from(message)))
impl<T: ToString> From<T> for Error {
fn from(e: T) -> Self {
Error::ServerError(Json(e.into()))
}
}
25 changes: 24 additions & 1 deletion src/models/shared.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use rocket::form::{FromForm, FromFormField};
use serde::{Deserialize, Serialize};
use surrealdb::sql::Thing;

Expand All @@ -12,6 +13,28 @@ pub struct UserRecordId {
pub id: String,
}

#[rocket::async_trait]
impl<'v> FromFormField<'v> for UserRecordId {
fn from_value(field: rocket::form::ValueField<'v>) -> rocket::form::Result<'v, Self> {
if let Some((tb, id)) = field.value.split_once(':') {
Ok(UserRecordId {
tb: tb.to_string(),
id: id.to_string(),
})
} else {
Err(field.unexpected())?
}
}

async fn from_data(field: rocket::form::DataField<'v, '_>) -> rocket::form::Result<'v, Self> {
Err(field.unexpected())?
}

fn default() -> Option<Self> {
None
}
}

impl From<Thing> for UserRecordId {
fn from(thing: Thing) -> Self {
UserRecordId {
Expand All @@ -32,7 +55,7 @@ pub struct UpdateAt {
pub updated_at: chrono::NaiveDateTime,
}

#[derive(Serialize, Deserialize)]
#[derive(FromForm, Serialize, Deserialize)]
#[serde(crate = "rocket::serde")]
pub struct Credentials<'c> {
pub id: &'c str,
Expand Down
130 changes: 12 additions & 118 deletions src/routes/account.rs
Original file line number Diff line number Diff line change
@@ -1,49 +1,25 @@
use std::path::{Path, PathBuf};
use std::path::Path;

use rocket::{
form::Form,
fs::{NamedFile, TempFile},
get, post, put,
serde::json::Json,
tokio::{
self,
fs::{create_dir_all, remove_dir_all, File},
},
State,
};
use rocket::{get, post, serde::json::Json, tokio::fs::remove_dir_all, State};
use serde::{Deserialize, Serialize};
use surrealdb::{engine::remote::ws::Client, Surreal};

use crate::{
models::{
account::Profile,
account::{Login, Profile, Register},
error::{Error, ErrorResponse},
response::{Empty, Response},
Record, Token,
OwnedCredentials, Record, Token,
},
utils::{account, session},
Result,
};

#[derive(Serialize, Deserialize)]
pub struct RegisterData {
pub username: String,
pub email: String,
pub password: String,
}

#[derive(Serialize, Deserialize, Debug)]
#[serde(crate = "rocket::serde")]
pub struct RegisterResponse {
pub id: String,
pub token: String,
}

#[post("/create", data = "<register>")]
pub async fn register(
db: &State<Surreal<Client>>,
register: Json<RegisterData>,
) -> Result<RegisterResponse> {
register: Json<Register>,
) -> Result<OwnedCredentials> {
match account::create(db, register.into_inner()).await {
Ok(Some(account)) => {
let token = match session::create(db, account.id.clone().unwrap()).await {
Expand All @@ -54,7 +30,7 @@ pub async fn register(
Ok(Response {
success: true,
message: format!("Account with id {} created successfully", &id),
data: Some(RegisterResponse { id, token }),
data: Some(OwnedCredentials { id, token }),
}
.into())
}
Expand Down Expand Up @@ -83,7 +59,10 @@ pub struct MergeProfile<'r> {
}

#[post("/profile", data = "<profile>")]
pub async fn profile(db: &State<Surreal<Client>>, profile: Json<MergeProfile<'_>>) -> Result<Empty> {
pub async fn profile(
db: &State<Surreal<Client>>,
profile: Json<MergeProfile<'_>>,
) -> 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 @@ -119,77 +98,6 @@ pub async fn get_profile(db: &State<Surreal<Client>>, id: &str) -> Result<Profil
.into())
}

#[get("/content/<file..>")]
pub async fn content(file: PathBuf) -> Option<NamedFile> {
NamedFile::open(Path::new("content/").join(file)).await.ok()
}

#[derive(FromForm)]
pub struct Upload<'r> {
pub id: &'r str,
pub token: &'r str,
pub file: TempFile<'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<UploadResponse> {
if !session::verify(db, data.id, data.token).await {
return Err(Error::Unauthorized(Json(
"Failed to grant access permission".into(),
)));
}

let file_extension = data
.file
.content_type()
.and_then(|ext| ext.extension().map(ToString::to_string))
.ok_or_else(|| Error::BadRequest(Json("Invalid file type".into())))?;

let user_path = std::env::current_dir()
.unwrap()
.join("content")
.join(data.id);

if !user_path.exists() {
create_dir_all(&user_path)
.await
.map_err(|e| Error::ServerError(Json(e.to_string().into())))?;
}
let file_name = format!("{}.{}", uuid::Uuid::new_v4(), file_extension);
let file_path = user_path.join(&file_name);

let mut file = data
.file
.open()
.await
.map_err(|e| Error::ServerError(Json(format!("Failed to open file: {}", e).into())))?;
let mut output_file = File::create(&file_path)
.await
.map_err(|e| Error::ServerError(Json(format!("Failed to create file: {}", e).into())))?;

tokio::io::copy(&mut file, &mut output_file)
.await
.map_err(|e| Error::ServerError(Json(format!("Failed to save file: {}", e).into())))?;

Ok(Json(Response {
success: true,
message: "Content updated successfully".into(),
data: Some(UploadResponse {
uri: format!("/account/content/{}/{}", data.id, file_name),
path: format!("content/{}/{}", data.id, file_name),
}),
}))
}

#[post("/delete/<id>", data = "<auth>")]
pub async fn delete(db: &State<Surreal<Client>>, id: &str, auth: Json<Token<'_>>) -> Result<Empty> {
if !session::verify(db, id, auth.token).await {
Expand All @@ -214,12 +122,6 @@ pub async fn delete(db: &State<Surreal<Client>>, id: &str, auth: Json<Token<'_>>
.into())
}

#[derive(Deserialize)]
pub struct Login<'r> {
pub identity: &'r str,
pub password: &'r str,
}

#[derive(Serialize, Deserialize)]
pub struct LoginResponse {
pub id: String,
Expand All @@ -245,13 +147,5 @@ pub async fn login(db: &State<Surreal<Client>>, login: Json<Login<'_>>) -> Resul

pub fn routes() -> Vec<rocket::Route> {
use rocket::routes;
routes![
register,
profile,
get_profile,
content,
upload_content,
delete,
login
]
routes![register, profile, get_profile, delete, login]
}
Loading

0 comments on commit 46477a6

Please sign in to comment.