http (fs): add file upload
This commit is contained in:
parent
1c1074abaf
commit
3c36122b86
|
@ -622,6 +622,7 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"axum",
|
||||
"axum-auth",
|
||||
"base64",
|
||||
"homedisk-database",
|
||||
"homedisk-types",
|
||||
"hyper",
|
||||
|
|
|
@ -11,3 +11,7 @@ cors = [
|
|||
[jwt]
|
||||
secret = "secret key used to sign tokens" # secret used to sign tokens
|
||||
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 res = db
|
||||
.find_user_by_id(user.id)
|
||||
.await
|
||||
.expect("find user");
|
||||
let res = db.find_user_by_id(user.id).await.expect("find user");
|
||||
|
||||
assert_eq!(res.password, user.password)
|
||||
}
|
||||
|
@ -196,10 +193,7 @@ mod tests {
|
|||
|
||||
let other_user = User::new("other_user", "secretpassphrase123!");
|
||||
|
||||
let err = db
|
||||
.find_user_by_id(other_user.id)
|
||||
.await
|
||||
.unwrap_err();
|
||||
let err = db.find_user_by_id(other_user.id).await.unwrap_err();
|
||||
|
||||
assert_eq!(err.to_string(), "user not found")
|
||||
}
|
||||
|
|
|
@ -15,3 +15,4 @@ homedisk-database = { path = "../database" }
|
|||
homedisk-types = { path = "../types", features = ["axum"] }
|
||||
axum-auth = "0.2.0"
|
||||
jsonwebtoken = "8.1.0"
|
||||
base64 = "0.13.0"
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
pub mod upload;
|
||||
|
||||
pub fn app() -> axum::Router {
|
||||
use axum::routing::post;
|
||||
|
||||
axum::Router::new().route("/upload", post(upload::handle))
|
||||
}
|
|
@ -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 fs;
|
||||
pub mod middleware;
|
||||
|
||||
mod error;
|
||||
|
@ -25,6 +26,7 @@ pub async fn serve(
|
|||
let app = Router::new()
|
||||
.route("/health-check", get(health_check))
|
||||
.nest("/auth", auth::app())
|
||||
.nest("/fs", fs::app())
|
||||
.layer(CorsLayer::new().allow_origin(Origin::list(origins)))
|
||||
.layer(Extension(db))
|
||||
.layer(Extension(config));
|
||||
|
|
|
@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct Config {
|
||||
pub http: ConfigHTTP,
|
||||
pub jwt: ConfigJWT,
|
||||
pub storage: ConfigStorage,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
@ -18,3 +19,8 @@ pub struct ConfigJWT {
|
|||
pub secret: String,
|
||||
pub expires: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigStorage {
|
||||
pub path: String,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
mod user;
|
||||
|
||||
pub use {user::*};
|
||||
pub use user::*;
|
||||
|
|
|
@ -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 fs;
|
||||
|
||||
pub use auth::Error as AuthError;
|
||||
pub use fs::Error as FsError;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
@ -10,6 +12,9 @@ pub enum ServerError {
|
|||
#[error("auth error: {0}")]
|
||||
AuthError(#[from] AuthError),
|
||||
|
||||
#[error("fs error: {0}")]
|
||||
FsError(#[from] FsError),
|
||||
|
||||
#[error("too may requests, please slow down")]
|
||||
TooManyRequests,
|
||||
|
||||
|
@ -35,15 +40,20 @@ impl axum::response::IntoResponse for ServerError {
|
|||
use axum::http::StatusCode;
|
||||
|
||||
let status = match self {
|
||||
Self::TooManyRequests => StatusCode::TOO_MANY_REQUESTS,
|
||||
Self::AuthError(ref err) => match err {
|
||||
AuthError::UserNotFound => StatusCode::BAD_REQUEST,
|
||||
AuthError::UserAlreadyExists => StatusCode::NOT_ACCEPTABLE,
|
||||
AuthError::TokenGenerate => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
AuthError::UnknowError(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
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::JsonDataError => StatusCode::BAD_REQUEST,
|
||||
Self::JsonSyntaxError => StatusCode::BAD_REQUEST,
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
pub mod upload;
|
|
@ -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")]
|
||||
pub mod database;
|
||||
pub mod fs;
|
||||
|
|
Loading…
Reference in New Issue