From e13beb9052db565feb0d1eb4ce7874c45e9f28ab Mon Sep 17 00:00:00 2001
From: Hamcha <hamcha@crunchy.rocks>
Date: Thu, 6 Feb 2025 21:11:43 +0100
Subject: [PATCH] site type routing

---
 .env                                 |  1 +
 Makefile.toml                        |  3 ++
 src/inbound/admin_ui/mod.rs          |  0
 src/inbound/mod.rs                   |  3 --
 src/inbound/renderer/admin_ui/mod.rs | 30 ++++++++++++
 src/inbound/renderer/meta.rs         | 12 +++++
 src/inbound/renderer/mod.rs          | 72 +++++++++++++++++++++-------
 src/inbound/renderer/page.rs         |  4 +-
 src/inbound/renderer/server.rs       | 18 +++++--
 src/main.rs                          |  4 +-
 src/outbound/config.rs               | 12 +++++
 src/outbound/mod.rs                  |  3 ++
 12 files changed, 133 insertions(+), 29 deletions(-)
 create mode 100644 .env
 create mode 100644 Makefile.toml
 delete mode 100644 src/inbound/admin_ui/mod.rs
 create mode 100644 src/inbound/renderer/admin_ui/mod.rs
 create mode 100644 src/outbound/config.rs

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;