From a48a64bc60d901d434c30d45a5b5b8109cc4dfe1 Mon Sep 17 00:00:00 2001 From: photino Date: Thu, 7 Sep 2023 16:30:06 +0800 Subject: [PATCH] Add shared_dir method for Application --- examples/actix-app/config/config.dev.toml | 3 + examples/actix-app/config/config.prod.toml | 5 +- examples/actix-app/local/docs/rapidoc.html | 14 ++-- examples/actix-app/src/controller/file.rs | 4 +- examples/actix-app/src/main.rs | 2 +- examples/axum-app/config/config.dev.toml | 3 + examples/axum-app/config/config.prod.toml | 5 +- examples/axum-app/local/docs/rapidoc.html | 14 ++-- examples/axum-app/src/controller/file.rs | 4 +- examples/axum-app/src/main.rs | 2 +- examples/axum-app/src/middleware/access.rs | 4 +- .../dioxus-desktop/config/config.dev.toml | 3 + .../dioxus-desktop/config/config.prod.toml | 3 + examples/dioxus-desktop/src/main.rs | 2 +- zino-core/src/application/mod.rs | 68 +++++++++---------- zino-core/src/extension/json_object.rs | 19 ++++++ zino-model/src/user/mod.rs | 3 + zino/src/application/actix_cluster.rs | 5 +- zino/src/application/axum_cluster.rs | 5 +- 19 files changed, 108 insertions(+), 60 deletions(-) diff --git a/examples/actix-app/config/config.dev.toml b/examples/actix-app/config/config.dev.toml index 2f56ee17..76f84186 100644 --- a/examples/actix-app/config/config.dev.toml +++ b/examples/actix-app/config/config.dev.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.6.1" +[dirs] +uploads = "local/uploads" + [main] host = "127.0.0.1" port = 6080 diff --git a/examples/actix-app/config/config.prod.toml b/examples/actix-app/config/config.prod.toml index ef6fb470..e48d6f58 100644 --- a/examples/actix-app/config/config.prod.toml +++ b/examples/actix-app/config/config.prod.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.6.1" +[dirs] +uploads = "local/uploads" + [main] host = "127.0.0.1" port = 6080 @@ -70,4 +73,4 @@ span = { "http.method" = "string", "http.target" = "string", "http.status_code" app-name = "data-cube" [openapi] -show-docs = false +show-docs = true diff --git a/examples/actix-app/local/docs/rapidoc.html b/examples/actix-app/local/docs/rapidoc.html index 692b22be..4da2e929 100644 --- a/examples/actix-app/local/docs/rapidoc.html +++ b/examples/actix-app/local/docs/rapidoc.html @@ -5,11 +5,15 @@ - - \ No newline at end of file + diff --git a/examples/actix-app/src/controller/file.rs b/examples/actix-app/src/controller/file.rs index b88ae7a3..d28fdf1d 100644 --- a/examples/actix-app/src/controller/file.rs +++ b/examples/actix-app/src/controller/file.rs @@ -4,7 +4,7 @@ use zino::{prelude::*, Cluster, Request, Response, Result}; pub async fn upload(mut req: Request) -> Result { let (mut body, files) = req.parse_form_data::().await?; - let dir = Cluster::relative_path("local/uploads"); + let dir = Cluster::shared_dir("uploads"); let expires = DateTime::now() + Duration::from_secs(600); let mut encryption_duration = Duration::ZERO; let mut uploads = Vec::new(); @@ -54,7 +54,7 @@ pub async fn decrypt(req: Request) -> Result { let Some(file_name) = query.get_str("file_name") else { reject!(req, "file_name", "it should be specified"); }; - let file_path = Cluster::relative_path(format!("local/uploads/{file_name}")); + let file_path = Cluster::shared_dir("uploads").join(file_name); let mut file = NamedFile::try_from_local(file_path).extract(&req)?; let decryption_start_time = Instant::now(); diff --git a/examples/actix-app/src/main.rs b/examples/actix-app/src/main.rs index d16613f2..f618f041 100644 --- a/examples/actix-app/src/main.rs +++ b/examples/actix-app/src/main.rs @@ -14,7 +14,7 @@ mod service; use zino::prelude::*; fn main() { - zino::Cluster::init_dirs(&["local/uploads"]) + zino::Cluster::boot() .register(router::routes()) .spawn(schedule::jobs()) .run(schedule::async_jobs()) diff --git a/examples/axum-app/config/config.dev.toml b/examples/axum-app/config/config.dev.toml index 2b437840..5bc82441 100644 --- a/examples/axum-app/config/config.dev.toml +++ b/examples/axum-app/config/config.dev.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.6.1" +[dirs] +uploads = "local/uploads" + [main] host = "127.0.0.1" port = 6080 diff --git a/examples/axum-app/config/config.prod.toml b/examples/axum-app/config/config.prod.toml index b3e1ade8..91a9736f 100644 --- a/examples/axum-app/config/config.prod.toml +++ b/examples/axum-app/config/config.prod.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.6.1" +[dirs] +uploads = "local/uploads" + [main] host = "127.0.0.1" port = 6080 @@ -70,4 +73,4 @@ span = { "http.method" = "string", "http.target" = "string", "http.status_code" app-name = "data-cube" [openapi] -show-docs = false +show-docs = true diff --git a/examples/axum-app/local/docs/rapidoc.html b/examples/axum-app/local/docs/rapidoc.html index 692b22be..4da2e929 100644 --- a/examples/axum-app/local/docs/rapidoc.html +++ b/examples/axum-app/local/docs/rapidoc.html @@ -5,11 +5,15 @@ - - \ No newline at end of file + diff --git a/examples/axum-app/src/controller/file.rs b/examples/axum-app/src/controller/file.rs index b88ae7a3..d28fdf1d 100644 --- a/examples/axum-app/src/controller/file.rs +++ b/examples/axum-app/src/controller/file.rs @@ -4,7 +4,7 @@ use zino::{prelude::*, Cluster, Request, Response, Result}; pub async fn upload(mut req: Request) -> Result { let (mut body, files) = req.parse_form_data::().await?; - let dir = Cluster::relative_path("local/uploads"); + let dir = Cluster::shared_dir("uploads"); let expires = DateTime::now() + Duration::from_secs(600); let mut encryption_duration = Duration::ZERO; let mut uploads = Vec::new(); @@ -54,7 +54,7 @@ pub async fn decrypt(req: Request) -> Result { let Some(file_name) = query.get_str("file_name") else { reject!(req, "file_name", "it should be specified"); }; - let file_path = Cluster::relative_path(format!("local/uploads/{file_name}")); + let file_path = Cluster::shared_dir("uploads").join(file_name); let mut file = NamedFile::try_from_local(file_path).extract(&req)?; let decryption_start_time = Instant::now(); diff --git a/examples/axum-app/src/main.rs b/examples/axum-app/src/main.rs index d16613f2..f618f041 100644 --- a/examples/axum-app/src/main.rs +++ b/examples/axum-app/src/main.rs @@ -14,7 +14,7 @@ mod service; use zino::prelude::*; fn main() { - zino::Cluster::init_dirs(&["local/uploads"]) + zino::Cluster::boot() .register(router::routes()) .spawn(schedule::jobs()) .run(schedule::async_jobs()) diff --git a/examples/axum-app/src/middleware/access.rs b/examples/axum-app/src/middleware/access.rs index eda21e8e..63044412 100644 --- a/examples/axum-app/src/middleware/access.rs +++ b/examples/axum-app/src/middleware/access.rs @@ -9,8 +9,8 @@ pub async fn init_user_session(mut req: Request, next: Next) -> Result { if verified { - let mut user_session = UserSession::::try_from_jwt_claims(claims) - .extract(&req)?; + let mut user_session = + UserSession::::try_from_jwt_claims(claims).extract(&req)?; if let Ok(session_id) = req.parse_session_id() { user_session.set_session_id(session_id); } diff --git a/examples/dioxus-desktop/config/config.dev.toml b/examples/dioxus-desktop/config/config.dev.toml index 10b1bf0b..696d903d 100644 --- a/examples/dioxus-desktop/config/config.dev.toml +++ b/examples/dioxus-desktop/config/config.dev.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.1.0" +[dirs] +uploads = "local/uploads" + [window] title = "DataCube" theme = "Light" diff --git a/examples/dioxus-desktop/config/config.prod.toml b/examples/dioxus-desktop/config/config.prod.toml index 0a9bc90a..ab2f5424 100644 --- a/examples/dioxus-desktop/config/config.prod.toml +++ b/examples/dioxus-desktop/config/config.prod.toml @@ -3,6 +3,9 @@ name = "data-cube" version = "0.1.0" +[dirs] +uploads = "local/uploads" + [window] title = "DataCube" theme = "Dark" diff --git a/examples/dioxus-desktop/src/main.rs b/examples/dioxus-desktop/src/main.rs index 45e877a4..53f6a2d3 100644 --- a/examples/dioxus-desktop/src/main.rs +++ b/examples/dioxus-desktop/src/main.rs @@ -8,7 +8,7 @@ mod service; use zino::prelude::*; fn main() { - zino::Desktop::init_dirs(&["local/uploads"]) + zino::Desktop::boot() .register(router::Route::default()) .spawn(schedule::jobs()) .run(schedule::async_jobs()) diff --git a/zino-core/src/application/mod.rs b/zino-core/src/application/mod.rs index e0396c78..18e8745d 100644 --- a/zino-core/src/application/mod.rs +++ b/zino-core/src/application/mod.rs @@ -12,12 +12,7 @@ use crate::{ }; use reqwest::Response; use serde::de::DeserializeOwned; -use std::{ - env, fs, - path::{Path, PathBuf}, - sync::LazyLock, - thread, -}; +use std::{env, fs, path::PathBuf, sync::LazyLock, thread}; use toml::value::Table; use utoipa::openapi::{OpenApi, OpenApiBuilder}; @@ -41,12 +36,28 @@ pub trait Application { /// Runs the application. fn run(self, async_jobs: Vec<(&'static str, AsyncCronJob)>); - /// Boots the application. It also setups the default secret key, - /// the tracing subscriber, the metrics exporter and a global HTTP client. + /// Boots the application. It also initializes the required directories + /// and setups the default secret key, the tracing subscriber, + /// the metrics exporter and a global HTTP client. fn boot() -> Self where Self: Default, { + if let Some(dirs) = SHARED_APP_STATE.get_config("dirs") { + let project_dir = Self::project_dir(); + for dir in dirs.values().filter_map(|v| v.as_str()) { + let path = if dir.starts_with('/') { + PathBuf::from(dir) + } else { + project_dir.join(dir) + }; + if !path.exists() && let Err(err) = fs::create_dir_all(&path) { + let path = path.to_string_lossy(); + tracing::error!("fail to create the directory {path}: {err}"); + } + } + } + secret_key::init::(); tracing_subscriber::init::(); metrics_exporter::init::(); @@ -70,27 +81,6 @@ pub trait Application { Self::boot() } - /// Initializes the directories to ensure that they are ready for use, - /// then boots the application. - fn init_dirs(dirs: &[&'static str]) -> Self - where - Self: Default, - { - let project_dir = Self::project_dir(); - for dir in dirs { - let path = if dir.starts_with('/') { - PathBuf::from(dir) - } else { - project_dir.join(dir) - }; - if !path.exists() && let Err(err) = fs::create_dir_all(&path) { - let path = path.to_string_lossy(); - tracing::error!("fail to create the directory {}: {}", path, err); - } - } - Self::boot() - } - /// Gets the system’s information. #[inline] fn sysinfo() -> Map { @@ -159,12 +149,6 @@ pub trait Application { LazyLock::force(&PROJECT_DIR) } - /// Returns the path relative to the project directory. - #[inline] - fn relative_path>(path: P) -> PathBuf { - PROJECT_DIR.join(path) - } - /// Returns the secret key for the application. /// It should have at least 64 bytes. /// @@ -176,6 +160,20 @@ pub trait Application { SECRET_KEY.get().expect("fail to get the secret key") } + /// Returns the shared directory with the specific name, + /// which is defined in the `dirs` table. + #[inline] + fn shared_dir(name: &str) -> PathBuf { + let path = if let Some(dirs) = SHARED_APP_STATE.get_config("dirs") && + let Some(path) = dirs.get_str(name) + { + path + } else { + name + }; + Self::project_dir().join(path) + } + /// Spawns a new thread to run cron jobs. fn spawn(self, jobs: Vec<(&'static str, CronJob)>) -> Self where diff --git a/zino-core/src/extension/json_object.rs b/zino-core/src/extension/json_object.rs index 56284fb3..dc731a7e 100644 --- a/zino-core/src/extension/json_object.rs +++ b/zino-core/src/extension/json_object.rs @@ -486,6 +486,25 @@ mod tests { Map, }; + #[test] + fn it_parses_str_array() { + let mut map = Map::new(); + map.upsert("roles", vec!["admin", "", "worker"]); + + assert_eq!( + map.get_str_array("roles"), + Some(vec!["admin", "", "worker"]) + ); + assert_eq!( + map.parse_str_array("roles"), + Some(vec!["admin", "", "worker"]) + ); + assert_eq!( + map.parse_array::("roles"), + Some(vec!["admin".to_owned(), "worker".to_owned()]) + ); + } + #[test] fn it_lookups_json_value() { let mut map = Map::new(); diff --git a/zino-model/src/user/mod.rs b/zino-model/src/user/mod.rs index a38c7a0e..afe163c4 100644 --- a/zino-model/src/user/mod.rs +++ b/zino-model/src/user/mod.rs @@ -210,6 +210,9 @@ impl User { } else if !USER_ROLE_PATTERN.is_match(role) { let message = format!("the role `{role}` is invalid"); return Err(Error::new(message)); + } else if role.is_empty() { + let message = format!("the `roles` can not contain empty values"); + return Err(Error::new(message)); } } self.roles = roles.into_iter().map(|s| s.to_owned()).collect(); diff --git a/zino/src/application/actix_cluster.rs b/zino/src/application/actix_cluster.rs index 4f51891f..91a681b6 100644 --- a/zino/src/application/actix_cluster.rs +++ b/zino/src/application/actix_cluster.rs @@ -48,9 +48,10 @@ impl Application for ActixCluster { }); // Server config + let project_dir = Self::project_dir(); let mut body_limit = 100 * 1024 * 1024; // 100MB let mut public_dir = PathBuf::new(); - let default_public_dir = Self::relative_path("public"); + let default_public_dir = project_dir.join("public"); if let Some(server_config) = Self::config().get_table("server") { if let Some(limit) = server_config.get_usize("body-limit") { body_limit = limit; @@ -111,7 +112,7 @@ impl Application for ActixCluster { RapiDoc::with_openapi("/api-docs/openapi.json", Self::openapi()) .path("/rapidoc"); if let Some(custom_html) = openapi_config.get_str("custom-html") && - let Ok(html) = fs::read_to_string(Self::relative_path(custom_html)) + let Ok(html) = fs::read_to_string(project_dir.join(custom_html)) { rapidoc = rapidoc.custom_html(html.leak()); } diff --git a/zino/src/application/axum_cluster.rs b/zino/src/application/axum_cluster.rs index 0870760c..65cda07e 100644 --- a/zino/src/application/axum_cluster.rs +++ b/zino/src/application/axum_cluster.rs @@ -68,10 +68,11 @@ impl Application for AxumCluster { }); // Server config + let project_dir = Self::project_dir(); let mut body_limit = 100 * 1024 * 1024; // 100MB let mut request_timeout = Duration::from_secs(10); // 10 seconds let mut public_dir = PathBuf::new(); - let default_public_dir = Self::relative_path("public"); + let default_public_dir = project_dir.join("public"); if let Some(server_config) = Self::config().get_table("server") { if let Some(limit) = server_config.get_usize("body-limit") { body_limit = limit; @@ -119,7 +120,7 @@ impl Application for AxumCluster { RapiDoc::with_openapi("/api-docs/openapi.json", Self::openapi()) .path("/rapidoc"); if let Some(custom_html) = openapi_config.get_str("custom-html") && - let Ok(html) = fs::read_to_string(Self::relative_path(custom_html)) + let Ok(html) = fs::read_to_string(project_dir.join(custom_html)) { app = app.merge(rapidoc.custom_html(html.as_str())); } else {