mirror of
https://github.com/MedzikUser/HomeDisk.git
synced 2024-08-14 21:46:53 +00:00
http (fs): add file upload
This commit is contained in:
parent
1c1074abaf
commit
3c36122b86
14 changed files with 129 additions and 12 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -622,6 +622,7 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"axum-auth",
|
"axum-auth",
|
||||||
|
"base64",
|
||||||
"homedisk-database",
|
"homedisk-database",
|
||||||
"homedisk-types",
|
"homedisk-types",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
|
|
@ -11,3 +11,7 @@ cors = [
|
||||||
[jwt]
|
[jwt]
|
||||||
secret = "secret key used to sign tokens" # secret used to sign tokens
|
secret = "secret key used to sign tokens" # secret used to sign tokens
|
||||||
expires = 24 # validity of token in hours
|
expires = 24 # validity of token in hours
|
||||||
|
|
||||||
|
# Storage
|
||||||
|
[storage]
|
||||||
|
path = "/home/homedisk" # the folder where the user files will be stored
|
||||||
|
|
|
@ -180,10 +180,7 @@ mod tests {
|
||||||
|
|
||||||
let user = User::new("medzik", "Qwerty1234!");
|
let user = User::new("medzik", "Qwerty1234!");
|
||||||
|
|
||||||
let res = db
|
let res = db.find_user_by_id(user.id).await.expect("find user");
|
||||||
.find_user_by_id(user.id)
|
|
||||||
.await
|
|
||||||
.expect("find user");
|
|
||||||
|
|
||||||
assert_eq!(res.password, user.password)
|
assert_eq!(res.password, user.password)
|
||||||
}
|
}
|
||||||
|
@ -196,10 +193,7 @@ mod tests {
|
||||||
|
|
||||||
let other_user = User::new("other_user", "secretpassphrase123!");
|
let other_user = User::new("other_user", "secretpassphrase123!");
|
||||||
|
|
||||||
let err = db
|
let err = db.find_user_by_id(other_user.id).await.unwrap_err();
|
||||||
.find_user_by_id(other_user.id)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert_eq!(err.to_string(), "user not found")
|
assert_eq!(err.to_string(), "user not found")
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,3 +15,4 @@ homedisk-database = { path = "../database" }
|
||||||
homedisk-types = { path = "../types", features = ["axum"] }
|
homedisk-types = { path = "../types", features = ["axum"] }
|
||||||
axum-auth = "0.2.0"
|
axum-auth = "0.2.0"
|
||||||
jsonwebtoken = "8.1.0"
|
jsonwebtoken = "8.1.0"
|
||||||
|
base64 = "0.13.0"
|
||||||
|
|
7
server/src/fs/mod.rs
Normal file
7
server/src/fs/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod upload;
|
||||||
|
|
||||||
|
pub fn app() -> axum::Router {
|
||||||
|
use axum::routing::post;
|
||||||
|
|
||||||
|
axum::Router::new().route("/upload", post(upload::handle))
|
||||||
|
}
|
62
server/src/fs/upload.rs
Normal file
62
server/src/fs/upload.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
use axum::{extract::rejection::JsonRejection, Extension, Json};
|
||||||
|
use axum_auth::AuthBearer;
|
||||||
|
use homedisk_database::{Database, Error};
|
||||||
|
use homedisk_types::{
|
||||||
|
config::types::Config,
|
||||||
|
errors::{AuthError, FsError, ServerError},
|
||||||
|
fs::upload::{Request, Response},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::middleware::{validate_json, validate_jwt};
|
||||||
|
|
||||||
|
pub async fn handle(
|
||||||
|
db: Extension<Database>,
|
||||||
|
config: Extension<Config>,
|
||||||
|
AuthBearer(token): AuthBearer,
|
||||||
|
request: Result<Json<Request>, JsonRejection>,
|
||||||
|
) -> Result<Json<Response>, ServerError> {
|
||||||
|
let Json(request) = validate_json::<Request>(request)?;
|
||||||
|
let token = validate_jwt(config.jwt.secret.as_bytes(), &token)?;
|
||||||
|
|
||||||
|
let response = match db.find_user_by_id(token.claims.sub).await {
|
||||||
|
Ok(res) => {
|
||||||
|
// get file content
|
||||||
|
let content = base64::decode(request.content)
|
||||||
|
.map_err(|err| ServerError::FsError(FsError::Base64(err.to_string())))?;
|
||||||
|
|
||||||
|
// check if file already exists
|
||||||
|
if Path::new(&request.path).exists() {
|
||||||
|
return Err(ServerError::FsError(FsError::FileAlreadyExists));
|
||||||
|
}
|
||||||
|
|
||||||
|
let folder_path = format!(
|
||||||
|
"{path}/{username}",
|
||||||
|
path = config.storage.path,
|
||||||
|
username = res.username,
|
||||||
|
);
|
||||||
|
let path = format!("{folder_path}/{filepath}", filepath = request.path);
|
||||||
|
|
||||||
|
// create user directory
|
||||||
|
fs::create_dir_all(&folder_path).unwrap();
|
||||||
|
|
||||||
|
// write file
|
||||||
|
fs::write(&path, content)
|
||||||
|
.map_err(|err| ServerError::FsError(FsError::WriteFile(err.to_string())))?;
|
||||||
|
|
||||||
|
Response { uploaded: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(err) => match err {
|
||||||
|
Error::UserNotFound => return Err(ServerError::AuthError(AuthError::UserNotFound)),
|
||||||
|
_ => {
|
||||||
|
return Err(ServerError::AuthError(AuthError::UnknowError(
|
||||||
|
err.to_string(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod fs;
|
||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
|
|
||||||
mod error;
|
mod error;
|
||||||
|
@ -25,6 +26,7 @@ pub async fn serve(
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/health-check", get(health_check))
|
.route("/health-check", get(health_check))
|
||||||
.nest("/auth", auth::app())
|
.nest("/auth", auth::app())
|
||||||
|
.nest("/fs", fs::app())
|
||||||
.layer(CorsLayer::new().allow_origin(Origin::list(origins)))
|
.layer(CorsLayer::new().allow_origin(Origin::list(origins)))
|
||||||
.layer(Extension(db))
|
.layer(Extension(db))
|
||||||
.layer(Extension(config));
|
.layer(Extension(config));
|
||||||
|
|
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub http: ConfigHTTP,
|
pub http: ConfigHTTP,
|
||||||
pub jwt: ConfigJWT,
|
pub jwt: ConfigJWT,
|
||||||
|
pub storage: ConfigStorage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
@ -18,3 +19,8 @@ pub struct ConfigJWT {
|
||||||
pub secret: String,
|
pub secret: String,
|
||||||
pub expires: i64,
|
pub expires: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ConfigStorage {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
pub use {user::*};
|
pub use user::*;
|
||||||
|
|
16
types/src/errors/fs.rs
Normal file
16
types/src/errors/fs.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("file already exists")]
|
||||||
|
FileAlreadyExists,
|
||||||
|
|
||||||
|
#[error("file already exists")]
|
||||||
|
WriteFile(String),
|
||||||
|
|
||||||
|
#[error("base64 - {0}")]
|
||||||
|
Base64(String),
|
||||||
|
|
||||||
|
#[error("unknow error")]
|
||||||
|
UnknowError(String),
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod fs;
|
||||||
|
|
||||||
pub use auth::Error as AuthError;
|
pub use auth::Error as AuthError;
|
||||||
|
pub use fs::Error as FsError;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
@ -10,6 +12,9 @@ pub enum ServerError {
|
||||||
#[error("auth error: {0}")]
|
#[error("auth error: {0}")]
|
||||||
AuthError(#[from] AuthError),
|
AuthError(#[from] AuthError),
|
||||||
|
|
||||||
|
#[error("fs error: {0}")]
|
||||||
|
FsError(#[from] FsError),
|
||||||
|
|
||||||
#[error("too may requests, please slow down")]
|
#[error("too may requests, please slow down")]
|
||||||
TooManyRequests,
|
TooManyRequests,
|
||||||
|
|
||||||
|
@ -35,15 +40,20 @@ impl axum::response::IntoResponse for ServerError {
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
|
|
||||||
let status = match self {
|
let status = match self {
|
||||||
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
|
|
||||||
Self::AuthError(ref err) => match err {
|
Self::AuthError(ref err) => match err {
|
||||||
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
|
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
|
||||||
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
|
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
|
||||||
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
|
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
|
||||||
AuthError::InvalidToken => StatusCode::BAD_REQUEST,
|
AuthError::InvalidToken => StatusCode::BAD_REQUEST,
|
||||||
|
AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
},
|
},
|
||||||
|
Self::FsError(ref err) => match err {
|
||||||
|
FsError::FileAlreadyExists => StatusCode::BAD_REQUEST,
|
||||||
|
FsError::WriteFile(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
FsError::Base64(_) => StatusCode::BAD_REQUEST,
|
||||||
|
FsError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
},
|
||||||
|
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
|
||||||
Self::MissingJsonContentType => StatusCode::BAD_REQUEST,
|
Self::MissingJsonContentType => StatusCode::BAD_REQUEST,
|
||||||
Self::JsonDataError => StatusCode::BAD_REQUEST,
|
Self::JsonDataError => StatusCode::BAD_REQUEST,
|
||||||
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,
|
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,
|
||||||
|
|
1
types/src/fs/mod.rs
Normal file
1
types/src/fs/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod upload;
|
12
types/src/fs/upload.rs
Normal file
12
types/src/fs/upload.rs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Request {
|
||||||
|
pub content: String,
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
pub struct Response {
|
||||||
|
pub uploaded: bool,
|
||||||
|
}
|
|
@ -5,3 +5,4 @@ pub mod macros;
|
||||||
|
|
||||||
#[cfg(feature = "database")]
|
#[cfg(feature = "database")]
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
pub mod fs;
|
||||||
|
|
Loading…
Reference in a new issue