diff --git a/.env b/.env new file mode 100644 index 0000000..6d1cddd --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MABEL_ADMIN_HOST=admin.localhost \ No newline at end of file diff --git a/Makefile.toml b/Makefile.toml new file mode 100644 index 0000000..209c64c --- /dev/null +++ b/Makefile.toml @@ -0,0 +1,3 @@ +[tasks.test] +command = "cargo" +args = ["nextest", "run", "--features", "server"] diff --git a/src/inbound/admin_ui/mod.rs b/src/inbound/admin_ui/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/src/inbound/mod.rs b/src/inbound/mod.rs index ae20cb4..77eee09 100644 --- a/src/inbound/mod.rs +++ b/src/inbound/mod.rs @@ -1,5 +1,2 @@ #[cfg(any(feature = "web", feature = "server"))] pub mod renderer; - -#[cfg(any(feature = "web", feature = "server"))] -pub mod admin_ui; diff --git a/src/inbound/renderer/admin_ui/mod.rs b/src/inbound/renderer/admin_ui/mod.rs new file mode 100644 index 0000000..e004c7b --- /dev/null +++ b/src/inbound/renderer/admin_ui/mod.rs @@ -0,0 +1,30 @@ +use dioxus::prelude::*; + +#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] +#[rustfmt::skip] +enum Route { + #[layout(AdminLayout)] + #[route("/")] + Home {}, +} + +pub fn App() -> Element { + rsx! { + Router::<Route> {} + } +} + +fn Home() -> Element { + rsx! { + h2 { "Hello!" } + } +} + +fn AdminLayout() -> Element { + rsx! { + h1 { "Admin UI" } + SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "..." }, + main { Outlet::<Route> {} } + } + } +} diff --git a/src/inbound/renderer/meta.rs b/src/inbound/renderer/meta.rs index 6a521d6..82093c8 100644 --- a/src/inbound/renderer/meta.rs +++ b/src/inbound/renderer/meta.rs @@ -1,7 +1,19 @@ use dioxus::prelude::*; +use serde::{Deserialize, Serialize}; use crate::domain::entities::site::{Page, SiteInfo}; +#[derive(Serialize, Deserialize, Clone)] +pub enum SiteType { + UserSite(SiteInfo), + ControlPanel, +} + +#[derive(Clone)] +pub struct UIContext { + pub site_type: SiteType, +} + #[derive(Clone)] pub struct SiteContext { pub info: SiteInfo, diff --git a/src/inbound/renderer/mod.rs b/src/inbound/renderer/mod.rs index b81bd96..ec4f213 100644 --- a/src/inbound/renderer/mod.rs +++ b/src/inbound/renderer/mod.rs @@ -2,11 +2,12 @@ use dioxus::prelude::*; +use meta::SiteType; use page::{Page, PostElement}; -use server::get_site_info; use crate::domain::entities::site::PageContent; +mod admin_ui; mod meta; mod page; mod server; @@ -31,21 +32,28 @@ enum Route { } pub fn App() -> Element { - // Retrieve site info - let site_info = use_server_future(get_site_info)?.suspend()?; - - // Inject site info in context - match &*site_info.read() { - Ok(info) => meta::set_site(info), - Err(err) => { - return rsx! { - h1 { "FATAL ERROR: {err}" } + // Check for special redirects + let site_res = use_server_future(server::get_site_info)?.suspend()?; + let site_type = site_res.read().clone(); + match site_type { + Ok(SiteType::ControlPanel) => { + rsx! { + admin_ui::App {} } } - } + Ok(SiteType::UserSite(site_info)) => { + // Inject site info in context + meta::set_site(&site_info); - rsx! { - Router::<Route> {} + rsx! { + Router::<Route> {} + } + } + _ => { + rsx! { + h1 { "404" } + } + } } } @@ -72,7 +80,7 @@ fn PageLink(page: String, title: String, current: Route) -> Element { } #[component] -pub fn SiteLayout() -> Element { +fn SiteLayout() -> Element { let site = meta::site(); let route = use_route::<Route>(); @@ -100,7 +108,7 @@ pub fn SiteLayout() -> Element { } #[component] -pub fn Home() -> Element { +fn Home() -> Element { let site = meta::site(); let page_ref = use_server_future(move || server::get_page(site.info.domain.clone(), "/".to_string()))? @@ -120,7 +128,7 @@ pub fn Home() -> Element { } #[component] -pub fn Single(page: String) -> Element { +fn Single(page: String) -> Element { let site = meta::site(); let page_ref = use_server_future(move || server::get_page(site.info.domain.clone(), page.clone()))? @@ -140,7 +148,7 @@ pub fn Single(page: String) -> Element { } #[component] -pub fn Post(page: String, id: String) -> Element { +fn Post(page: String, id: String) -> Element { let site = meta::site(); let domain = site.info.domain.clone(); let page_ref = @@ -194,7 +202,10 @@ mod tests { use crate::{ domain::entities, - outbound::services::site::{MockSiteService, SiteServiceProvider}, + outbound::{ + config::AppConfig, + services::site::{MockSiteService, SiteServiceProvider}, + }, }; use super::*; @@ -218,6 +229,9 @@ mod tests { }) }); + server_context().insert(AppConfig { + admin_host: "admin.local".to_string(), + }); server_context().insert(SiteServiceProvider::with(mock_service)); app.rebuild_in_place(); @@ -303,4 +317,26 @@ mod tests { let elem_str = dioxus::ssr::render(&app); assert!(elem_str.contains("Test page name")); } + + #[test] + fn redirects_to_admin_ui() { + let mut app = VirtualDom::new(|| { + rsx! { + App {} + } + }); + + server_context().insert(AppConfig { + admin_host: "admin.local".to_string(), + }); + server_context() + .request_parts_mut() + .headers + .insert("host", "admin.local".parse().unwrap()); + + app.rebuild_in_place(); + let elem_str = dioxus::ssr::render(&app); + println!("elem_str: {elem_str}"); + assert!(elem_str.contains("Admin UI")); + } } diff --git a/src/inbound/renderer/page.rs b/src/inbound/renderer/page.rs index 22ea629..f819a9d 100644 --- a/src/inbound/renderer/page.rs +++ b/src/inbound/renderer/page.rs @@ -27,7 +27,7 @@ pub fn Page() -> Element { } #[component] -pub fn Collection(collection_id: String) -> Element { +fn Collection(collection_id: String) -> Element { let site = meta::site(); let posts = use_server_future(move || get_posts(site.info.domain.clone(), collection_id.clone()))? @@ -59,7 +59,7 @@ pub fn PostElement(post: Post) -> Element { } #[component] -pub fn BlockElement(block: Block) -> Element { +fn BlockElement(block: Block) -> Element { match block { Block::Text { text } => rsx! { p { "{text}" } diff --git a/src/inbound/renderer/server.rs b/src/inbound/renderer/server.rs index 200bf83..071a983 100644 --- a/src/inbound/renderer/server.rs +++ b/src/inbound/renderer/server.rs @@ -2,13 +2,15 @@ use dioxus::prelude::*; use crate::domain::entities::{ cursor::Paginated, - site::{Page, Post, SiteInfo}, + site::{Page, Post}, }; +use super::meta::SiteType; + #[server] -pub async fn get_site_info() -> Result<SiteInfo, ServerFnError> { +pub async fn get_site_info() -> Result<SiteType, ServerFnError> { + use crate::outbound::config::AppConfig; use crate::outbound::services::site::SiteServiceProvider; - let FromContext(SiteServiceProvider { service }) = extract().await?; let headers = server_context().request_parts().headers.clone(); let domain = match headers.get("host").and_then(|h| h.to_str().ok()) { @@ -20,8 +22,14 @@ pub async fn get_site_info() -> Result<SiteInfo, ServerFnError> { } .to_string(); - let site = service.get_site(domain.as_str()).await?; - Ok(site) + let FromContext(AppConfig { admin_host }) = extract().await?; + if domain == admin_host { + return Ok(SiteType::ControlPanel); + } + + let FromContext(SiteServiceProvider { service }) = extract().await?; + let site = service.get_site(&domain).await?; + Ok(SiteType::UserSite(site)) } #[server] diff --git a/src/main.rs b/src/main.rs index 67eab0b..15e0ff4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,10 +16,12 @@ fn main() { let mut builder = dioxus::LaunchBuilder::new(); server_only! { use outbound::services::site::{SiteServiceProvider, SiteServiceImpl}; + use outbound::config::AppConfig; + let store = tokio::runtime::Runtime::new().unwrap().block_on(setup_inmem_store()); builder = builder.with_context_provider(move || { Box::new(SiteServiceProvider::with(SiteServiceImpl::new(store.clone()))) - }) + }).with_context(AppConfig::from_env()); } builder.launch(inbound::renderer::App); diff --git a/src/outbound/config.rs b/src/outbound/config.rs new file mode 100644 index 0000000..fd34f1d --- /dev/null +++ b/src/outbound/config.rs @@ -0,0 +1,12 @@ +#[derive(Clone)] +pub struct AppConfig { + pub admin_host: String, +} + +impl AppConfig { + pub fn from_env() -> Self { + Self { + admin_host: std::env::var("MABEL_ADMIN_HOST").expect("MABEL_ADMIN_HOST must be set"), + } + } +} diff --git a/src/outbound/mod.rs b/src/outbound/mod.rs index ab6fa55..d4a0e5a 100644 --- a/src/outbound/mod.rs +++ b/src/outbound/mod.rs @@ -1,4 +1,7 @@ #[cfg(feature = "server")] pub mod repository; +#[cfg(feature = "server")] +pub mod config; + pub mod services;