diff --git a/v0.5/Cargo.lock b/v0.5/Cargo.lock index 593a4d561..29c8bb394 100644 --- a/v0.5/Cargo.lock +++ b/v0.5/Cargo.lock @@ -1482,6 +1482,7 @@ dependencies = [ "fastn-id52", "fastn-mail", "fastn-router", + "include_dir", "rand 0.8.5", "rusqlite", "serde", @@ -2589,6 +2590,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indenter" version = "0.3.4" diff --git a/v0.5/fastn-account/Cargo.toml b/v0.5/fastn-account/Cargo.toml index bb11099c9..0653dcdd2 100644 --- a/v0.5/fastn-account/Cargo.toml +++ b/v0.5/fastn-account/Cargo.toml @@ -15,6 +15,9 @@ fastn-mail.workspace = true fastn-router.workspace = true fastn-fbr.workspace = true autosurgeon.workspace = true + +# Include directory for embedded fastn-home content +include_dir = "0.7" serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/v0.5/fastn-account/src/account/create.rs b/v0.5/fastn-account/src/account/create.rs index 1807aa593..150a11bc0 100644 --- a/v0.5/fastn-account/src/account/create.rs +++ b/v0.5/fastn-account/src/account/create.rs @@ -126,6 +126,16 @@ impl fastn_account::Account { tracing::info!("Created new account with primary alias: {}", id52); + // Copy default UI content from fastn-home/src to account/public + copy_src_to_public(&account_path) + .await + .map_err(|e| { + tracing::warn!("Failed to copy UI content: {}", e); + // Don't fail account creation if UI copy fails + e + }) + .ok(); + // Create account instance Ok(Self { path: std::sync::Arc::new(account_path), @@ -232,3 +242,99 @@ impl fastn_account::Account { Ok(()) } } + +/// Copy default UI content from fastn-home/src to account/public +async fn copy_src_to_public( + account_path: &std::path::Path, +) -> Result<(), crate::CreateInitialDocumentsError> { + // Find fastn-home directory (account_path is fastn-home/accounts/account-id52) + let fastn_home = account_path + .parent() + .and_then(|p| p.parent()) + .ok_or_else( + || crate::CreateInitialDocumentsError::AliasDocumentCreationFailed { + source: Box::new(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not find fastn-home directory", + )), + }, + )?; + + // Use embedded fastn-home content from build time + static FASTN_HOME_CONTENT: include_dir::Dir = include_dir::include_dir!("../fastn-home/src"); + + let public_dir = account_path.join("public"); + + tracing::info!("Copying embedded UI content to {}", public_dir.display()); + + // Create public directory + tokio::fs::create_dir_all(&public_dir).await.map_err(|e| { + crate::CreateInitialDocumentsError::AliasDocumentCreationFailed { + source: Box::new(e), + } + })?; + + // Copy embedded content to public directory + copy_embedded_dir(&FASTN_HOME_CONTENT, &public_dir).await.map_err(|e| { + crate::CreateInitialDocumentsError::AliasDocumentCreationFailed { + source: Box::new(e), + } + })?; + + tracing::info!("✅ Copied embedded UI content to account public directory"); + + Ok(()) +} + +/// Copy embedded directory contents to filesystem +async fn copy_embedded_dir( + embedded_dir: &include_dir::Dir<'_>, + dst: &std::path::Path, +) -> Result<(), std::io::Error> { + // Copy all files in the embedded directory + for file in embedded_dir.files() { + let file_path = dst.join(file.path()); + + // Create parent directories if needed + if let Some(parent) = file_path.parent() { + tokio::fs::create_dir_all(parent).await?; + } + + // Write file content + tokio::fs::write(&file_path, file.contents()).await?; + } + + // Recursively copy subdirectories + for subdir in embedded_dir.dirs() { + let subdir_path = dst.join(subdir.path()); + tokio::fs::create_dir_all(&subdir_path).await?; + + // Recursive call for subdirectory + Box::pin(copy_embedded_dir(subdir, &subdir_path)).await?; + } + + Ok(()) +} + +/// Recursively copy directory contents +async fn copy_dir_recursive( + src: &std::path::Path, + dst: &std::path::Path, +) -> Result<(), std::io::Error> { + tokio::fs::create_dir_all(dst).await?; + + let mut entries = tokio::fs::read_dir(src).await?; + while let Some(entry) = entries.next_entry().await? { + let entry_path = entry.path(); + let file_name = entry.file_name(); + let dst_path = dst.join(&file_name); + + if entry_path.is_dir() { + Box::pin(copy_dir_recursive(&entry_path, &dst_path)).await?; + } else { + tokio::fs::copy(&entry_path, &dst_path).await?; + } + } + + Ok(()) +} diff --git a/v0.5/fastn-account/src/http_routes.rs b/v0.5/fastn-account/src/http_routes.rs index 487c78d94..6a38f3233 100644 --- a/v0.5/fastn-account/src/http_routes.rs +++ b/v0.5/fastn-account/src/http_routes.rs @@ -35,10 +35,21 @@ impl crate::Account { }; // Try folder-based routing first with account context - let fbr = fastn_fbr::FolderBasedRouter::new(self.path().await); + let account_path = self.path().await; + println!("🔍 Account path: {}", account_path.display()); + + let fbr = fastn_fbr::FolderBasedRouter::new(&account_path); let account_context = self.create_template_context().await; - if let Ok(response) = fbr.route_request(request, Some(&account_context)).await { - return Ok(response); + + match fbr.route_request(request, Some(&account_context)).await { + Ok(response) => { + println!("✅ Folder-based routing succeeded for {}", request.path); + return Ok(response); + } + Err(e) => { + println!("❌ Folder-based routing failed for {}: {}", request.path, e); + // Fall through to default interface + } } // Fallback to default account interface diff --git a/v0.5/fastn-fbr/src/router.rs b/v0.5/fastn-fbr/src/router.rs index 8f73989dd..35d3a9b4c 100644 --- a/v0.5/fastn-fbr/src/router.rs +++ b/v0.5/fastn-fbr/src/router.rs @@ -67,15 +67,24 @@ impl FolderBasedRouter { public_dir.join(&clean_path) }; - tracing::debug!("FBR file path: {}", file_path.display()); + tracing::debug!( + "FBR base_path: {}, public_dir: {}, file_path: {}", + self.base_path.display(), + public_dir.display(), + file_path.display() + ); + println!("🔍 FBR: Looking for file: {}", file_path.display()); // Check if file exists if !file_path.exists() { + println!("❌ FBR: File not found: {}", file_path.display()); return Err(FbrError::FileNotFound { path: file_path.display().to_string(), }); } + println!("✅ FBR: File exists: {}", file_path.display()); + // Handle different file types if file_path.is_dir() { // Try index.html in directory diff --git a/v0.5/fastn-home/src/-/mail/index.html b/v0.5/fastn-home/src/-/mail/index.html new file mode 100644 index 000000000..445a2017c --- /dev/null +++ b/v0.5/fastn-home/src/-/mail/index.html @@ -0,0 +1,39 @@ + + +
+✅ Served via folder-based routing from /-/mail/
+This email interface is served via fastn-fbr folder-based routing.
+Template functions ready for dynamic email data from account!
+ +✅ Served via folder-based routing from embedded content
+This content is embedded at build time from fastn/v0.5/fastn-home/src and copied to public/ directories.
+ + +