From eb3cb8692e1a8252f780a6db841df6f43574b647 Mon Sep 17 00:00:00 2001
From: peterrabbit <peterrabbit@msi.home>
Date: Thu, 18 Aug 2022 18:42:41 +0200
Subject: [PATCH] wip static files manager

---
 Cargo.lock               | 59 ++++++++++++++++++++++++++++++++++++++++
 Cargo.toml               |  2 ++
 default_static/script.js |  1 +
 default_static/style.css |  3 ++
 src/main.rs              | 15 +++++++---
 src/static_files.rs      | 52 +++++++++++++++++++++++++++++++++++
 src/website.rs           | 29 ++++++++++++--------
 7 files changed, 146 insertions(+), 15 deletions(-)
 create mode 100644 default_static/script.js
 create mode 100644 default_static/style.css
 create mode 100644 src/static_files.rs

diff --git a/Cargo.lock b/Cargo.lock
index f69647c..00086a3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -375,6 +375,8 @@ name = "cms_rust"
 version = "0.1.0"
 dependencies = [
  "actix-web",
+ "dirs",
+ "fs_extra",
  "regex",
  "rustls",
  "rustls-pemfile",
@@ -467,6 +469,26 @@ dependencies = [
  "subtle",
 ]
 
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "encoding_rs"
 version = "0.8.31"
@@ -508,6 +530,12 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "fs_extra"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+
 [[package]]
 name = "futures-core"
 version = "0.3.21"
@@ -930,6 +958,17 @@ dependencies = [
  "bitflags",
 ]
 
+[[package]]
+name = "redox_users"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+ "thiserror",
+]
+
 [[package]]
 name = "regex"
 version = "1.6.0"
@@ -1142,6 +1181,26 @@ dependencies = [
  "unicode-ident",
 ]
 
+[[package]]
+name = "thiserror"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "time"
 version = "0.3.12"
diff --git a/Cargo.toml b/Cargo.toml
index 63e3fed..f90e2a7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,3 +12,5 @@ rustls-pemfile = "1.0.1"
 serde = { version = "1.0.143", features = ["derive"] }
 serde_json = "1.0.83"
 regex = "1.6"
+fs_extra = "1.2"
+dirs = "4.0"
diff --git a/default_static/script.js b/default_static/script.js
new file mode 100644
index 0000000..2aff446
--- /dev/null
+++ b/default_static/script.js
@@ -0,0 +1 @@
+console.log("Hello from default js")
\ No newline at end of file
diff --git a/default_static/style.css b/default_static/style.css
new file mode 100644
index 0000000..45ce6b3
--- /dev/null
+++ b/default_static/style.css
@@ -0,0 +1,3 @@
+body {
+    font-family: monospace;
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index e785b97..72b4826 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,7 @@
+mod static_files;
 mod website;
 use actix_web::{get, App, HttpResponse, HttpServer, Responder};
+use static_files::StaticFilesManager;
 use std::path::PathBuf;
 use website::WebSite;
 
@@ -37,6 +39,9 @@ async fn admin_login() -> impl Responder {
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let website = load_website_template();
+    let site_name = "rust_cms".to_string(); // Get from arg
+    let mut static_files_manager = StaticFilesManager::new(&site_name).unwrap();
+    static_files_manager.build_index().unwrap();
     // GET HOST AND CERTS DIR FROM CLI ARGUMENT
     // Get port from arg, or get context from arg and define default port, or set default port to standard
 
@@ -47,17 +52,15 @@ async fn main() -> std::io::Result<()> {
     // create the static dir in standard location if doesn't exist (like /var/{sitename}/static)
     // create the static files index (like Arc<Mutex<StaticFilesIndex>>)
 
-    // create a Rest service at root with extensive path argument: like #[get(/{pth:.*})]
-    // Then parse the website document and return the corresponding template, or 404 template
-
     let host = "localhost";
-    let certs_dir = std::path::PathBuf::from("/etc/letsencrypt/live").join(host);
+    let certs_dir = PathBuf::from("/etc/letsencrypt/live").join(host);
     let cert_file =
         &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("fullchain.pem")).unwrap());
     let key_file =
         &mut std::io::BufReader::new(std::fs::File::open(certs_dir.join("privkey.pem")).unwrap());
 
     let cert = rustls::Certificate(rustls_pemfile::certs(cert_file).unwrap().remove(0).to_vec());
+
     let key = rustls::PrivateKey(
         rustls_pemfile::pkcs8_private_keys(key_file)
             .unwrap()
@@ -71,11 +74,15 @@ async fn main() -> std::io::Result<()> {
         .with_single_cert(vec![cert], key)
         .expect("bad certificate/key");
 
+    let static_files_manager =
+        actix_web::web::Data::new(std::sync::Mutex::new(static_files_manager));
+
     HttpServer::new(move || {
         App::new()
             .wrap(actix_web::middleware::Logger::default())
             .wrap(actix_web::middleware::Compress::default())
             .app_data(actix_web::web::Data::new(website.clone()))
+            .app_data(actix_web::web::Data::clone(&static_files_manager))
             .service(admin_dashboard)
             .service(admin_login)
             .service(page)
diff --git a/src/static_files.rs b/src/static_files.rs
new file mode 100644
index 0000000..ced5de4
--- /dev/null
+++ b/src/static_files.rs
@@ -0,0 +1,52 @@
+use std::path::PathBuf;
+
+#[derive(Clone)]
+pub struct StaticFilesManager {
+    index: Vec<String>,
+}
+
+impl StaticFilesManager {
+    fn create_dir_if_missing(app_dir: &PathBuf) -> Result<(), String> {
+        let static_path = app_dir.join("static");
+
+        if !static_path.exists() {
+            match std::fs::create_dir_all(static_path) {
+                Ok(_) => return Self::copy_default(&app_dir),
+                Err(err) => return Err(format!("{}", err)),
+            }
+        }
+
+        Ok(())
+    }
+
+    fn copy_default(app_dir: &PathBuf) -> Result<(), String> {
+        let local_default = std::env::current_dir().unwrap().join("default_static");
+        let standard_default = app_dir.join("static").join("default");
+        if let Err(err) = std::fs::create_dir_all(&standard_default) {
+            return Err(format!("{}", err));
+        }
+
+        let mut copy_default_options = fs_extra::dir::CopyOptions::new();
+        copy_default_options.content_only = true;
+        if let Err(err) =
+            fs_extra::dir::copy(local_default, standard_default, &copy_default_options)
+        {
+            return Err(format!("{}", err));
+        };
+        Ok(())
+    }
+
+    pub fn new(website_name: &String) -> Result<Self, String> {
+        if let Err(err) = Self::create_dir_if_missing(&dirs::home_dir().unwrap().join(website_name))
+        {
+            return Err(err);
+        }
+
+        Ok(StaticFilesManager { index: Vec::new() })
+    }
+
+    pub fn build_index(&mut self) -> Result<(), String> {
+        self.index.push("TODO".to_string());
+        Ok(())
+    }
+}
diff --git a/src/website.rs b/src/website.rs
index bfb564c..9e2f6c7 100644
--- a/src/website.rs
+++ b/src/website.rs
@@ -18,26 +18,28 @@ pub struct PageData {
 pub struct HtmlDoc(String);
 
 impl HtmlDoc {
-    pub fn from_page_data(page_data: &PageData) -> Self {
-        let html_doc = std::fs::read_to_string(
+    fn load_template() -> String {
+        std::fs::read_to_string(
             std::env::current_dir()
                 .unwrap()
                 .join("templates")
                 .join("html_doc.html"),
         )
-        .expect("Missing html_doc template");
+        .expect("Missing html_doc template")
+    }
 
+    pub fn from_page_data(page_data: &PageData) -> Self {
         let re = Regex::new(r#"\{[a-z]+\}"#).unwrap();
 
-        let html_doc = re
-            .replace_all(&html_doc, |captures: &Captures| {
+        let html = re
+            .replace_all(&HtmlDoc::load_template(), |captures: &Captures| {
                 let placeholder = captures.iter().next().unwrap().unwrap().as_str();
                 let placeholder = placeholder[1..placeholder.len() - 1].to_owned();
                 page_data.field_from_str_key(placeholder)
             })
             .to_string();
 
-        HtmlDoc(html_doc)
+        HtmlDoc(html)
     }
 
     pub fn to_string(&self) -> String {
@@ -146,11 +148,11 @@ mod test_website {
             \"sub_pages\": [
                 {
                     \"page_data\": {
-                        \"title\": \"Another page\",
+                        \"title\": \"Nested page\",
                         \"lang\": \"en\",
-                        \"slug\": \"otherpage\",
-                        \"description\": \"Another testing page\",
-                        \"html_body\": \"<h1>Another page</h1>\"
+                        \"slug\": \"nested\",
+                        \"description\": \"Nested testing page\",
+                        \"html_body\": \"<h1>Nested page</h1>\"
                     } 
                 }
             ]
@@ -167,9 +169,14 @@ mod test_website {
         let root_page = root_page.unwrap();
         assert_eq!(root_page.page_data.html_body, "<h1>Test Website</h1>");
 
-        let sub_page = website.get_page_by_url(&PathBuf::from("/subpage"));
+        let sub_page = website.get_page_by_url(&PathBuf::from("subpage"));
         assert!(sub_page.is_some());
         let sub_page = sub_page.unwrap();
         assert_eq!(sub_page.page_data.html_body, "<h1>A sub page</h1>");
+
+        let nested_page = website.get_page_by_url(&PathBuf::from("subpage/nested"));
+        assert!(nested_page.is_some());
+        let nested_page = nested_page.unwrap();
+        assert_eq!(nested_page.page_data.html_body, "<h1>Nested page</h1>");
     }
 }
-- 
GitLab