342 lines
8.7 KiB
Rust
342 lines
8.7 KiB
Rust
#![allow(non_snake_case)]
|
|
|
|
use dioxus::prelude::*;
|
|
|
|
use meta::SiteType;
|
|
use page::{Page, PostElement};
|
|
|
|
use crate::domain::entities::site::PageContent;
|
|
|
|
mod admin_ui;
|
|
mod meta;
|
|
mod page;
|
|
mod server;
|
|
|
|
#[cfg(test)]
|
|
mod testing;
|
|
|
|
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
|
#[rustfmt::skip]
|
|
enum Route {
|
|
#[layout(SiteLayout)]
|
|
#[route("/")]
|
|
Home {},
|
|
#[nest("/p")]
|
|
#[route("/:page")]
|
|
Single { page: String },
|
|
#[route("/:page/:id")]
|
|
Post { page: String, id: String },
|
|
#[end_nest]
|
|
#[route("/:..route")]
|
|
PageNotFound { route: Vec<String> },
|
|
}
|
|
|
|
pub fn App() -> Element {
|
|
// 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! {
|
|
h1 { "404" }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn PageNotFound(route: Vec<String>) -> Element {
|
|
rsx! {
|
|
h1 { "404 - {route:?}" }
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn PageLink(page: String, title: String, current: Route) -> Element {
|
|
let to = if page == "/" {
|
|
Route::Home {}
|
|
} else {
|
|
Route::Single { page }
|
|
};
|
|
|
|
let class = if current == to { "active" } else { "" };
|
|
|
|
rsx! {
|
|
Link { to, class, "{title}" }
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn SiteLayout() -> Element {
|
|
let site = meta::site();
|
|
let route = use_route::<Route>();
|
|
|
|
rsx! {
|
|
header { class: "site-header",
|
|
nav { class: "page-list",
|
|
ul {
|
|
for page in site.info.pages {
|
|
li {
|
|
PageLink {
|
|
page: page.name,
|
|
title: page.title,
|
|
current: route.clone(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
h1 { "{site.info.title}" }
|
|
}
|
|
SuspenseBoundary { fallback: |_context: SuspenseContext| rsx! { "..." },
|
|
main { Outlet::<Route> {} }
|
|
}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn Home() -> Element {
|
|
let site = meta::site();
|
|
let page_ref =
|
|
use_server_future(move || server::get_page(site.info.domain.clone(), "/".to_string()))?
|
|
.suspend()?;
|
|
match &*page_ref.read() {
|
|
Ok(page) => meta::set_page(page.clone()),
|
|
Err(err) => {
|
|
return rsx! {
|
|
h1 { "FATAL ERROR: {err}" }
|
|
}
|
|
}
|
|
}
|
|
|
|
rsx! {
|
|
Page {}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
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()))?
|
|
.suspend()?;
|
|
match &*page_ref.read() {
|
|
Ok(page) => meta::set_page(page.clone()),
|
|
Err(err) => {
|
|
return rsx! {
|
|
h1 { "FATAL ERROR: {err}" }
|
|
}
|
|
}
|
|
}
|
|
|
|
rsx! {
|
|
Page {}
|
|
}
|
|
}
|
|
|
|
#[component]
|
|
fn Post(page: String, id: String) -> Element {
|
|
let site = meta::site();
|
|
let domain = site.info.domain.clone();
|
|
let page_ref =
|
|
use_server_future(move || server::get_page(domain.clone(), page.clone()))?.suspend()?;
|
|
match &*page_ref.read() {
|
|
Ok(page) => {
|
|
meta::set_page(page.clone());
|
|
}
|
|
Err(err) => {
|
|
return rsx! {
|
|
h1 { "FATAL ERROR: {err}" }
|
|
}
|
|
}
|
|
};
|
|
|
|
let page = meta::page();
|
|
|
|
let collection_id = match &page.data.content {
|
|
PageContent::Collection {
|
|
kind: _,
|
|
collection_id,
|
|
} => collection_id.clone(),
|
|
_ => {
|
|
return rsx! {
|
|
h1 { "NOT A COLLECTION" }
|
|
}
|
|
}
|
|
};
|
|
|
|
let post_ref = use_server_future(move || {
|
|
server::get_post(site.info.domain.clone(), collection_id.clone(), id.clone())
|
|
})?
|
|
.suspend()?;
|
|
let post = match &*post_ref.read() {
|
|
Ok(post) => post.clone(),
|
|
Err(err) => {
|
|
return rsx! {
|
|
h1 { "FATAL ERROR: {err}" }
|
|
}
|
|
}
|
|
};
|
|
|
|
rsx! {
|
|
PostElement { post }
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use mockall::predicate;
|
|
|
|
use crate::{
|
|
domain::entities,
|
|
outbound::{
|
|
config::AppConfig,
|
|
services::site::{MockSiteService, SiteServiceProvider},
|
|
},
|
|
};
|
|
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn gets_correct_site_info() {
|
|
let mut app = VirtualDom::new(|| {
|
|
rsx! {
|
|
App {}
|
|
}
|
|
});
|
|
|
|
let mut mock_service = MockSiteService::new();
|
|
mock_service.expect_get_site().times(1).returning(move |_| {
|
|
Box::pin(async {
|
|
Ok(entities::site::SiteInfo {
|
|
title: "My test website".to_string(),
|
|
domain: "test.com".to_string(),
|
|
pages: vec![],
|
|
})
|
|
})
|
|
});
|
|
|
|
server_context().insert(AppConfig {
|
|
admin_host: "admin.local".to_string(),
|
|
});
|
|
server_context().insert(SiteServiceProvider::with(mock_service));
|
|
|
|
app.rebuild_in_place();
|
|
let elem_str = dioxus::ssr::render(&app);
|
|
assert!(elem_str.contains("My test website"));
|
|
}
|
|
|
|
#[test]
|
|
fn single_gets_page_info() {
|
|
let mut app = VirtualDom::new(|| {
|
|
rsx! {
|
|
Single { page: "pagename".to_string() }
|
|
}
|
|
});
|
|
|
|
testing::add_test_site_context(&mut app);
|
|
|
|
let mut mock_service = MockSiteService::new();
|
|
mock_service
|
|
.expect_get_page()
|
|
.times(1)
|
|
.with(predicate::eq("test"), predicate::eq("pagename"))
|
|
.returning(move |_, _| {
|
|
Box::pin(async {
|
|
Ok(entities::site::Page {
|
|
info: entities::site::PageInfo {
|
|
title: "Test page name".to_string(),
|
|
name: "test".to_string(),
|
|
order: 0,
|
|
},
|
|
content: PageContent::Single {
|
|
content: entities::site::Post {
|
|
blocks: vec![entities::site::Block::Text {
|
|
text: "test content".to_string(),
|
|
}],
|
|
},
|
|
},
|
|
})
|
|
})
|
|
});
|
|
|
|
server_context().insert(SiteServiceProvider::with(mock_service));
|
|
|
|
app.rebuild_in_place();
|
|
let elem_str = dioxus::ssr::render(&app);
|
|
assert!(elem_str.contains("Test page name"));
|
|
assert!(elem_str.contains("test content"));
|
|
}
|
|
|
|
#[test]
|
|
fn home_gets_page_info() {
|
|
let mut app = VirtualDom::new(|| {
|
|
rsx! {
|
|
Home {}
|
|
}
|
|
});
|
|
|
|
testing::add_test_site_context(&mut app);
|
|
|
|
let mut mock_service = MockSiteService::new();
|
|
mock_service
|
|
.expect_get_page()
|
|
.times(1)
|
|
.with(predicate::eq("test"), predicate::eq("/"))
|
|
.returning(move |_, _| {
|
|
Box::pin(async {
|
|
Ok(entities::site::Page {
|
|
info: entities::site::PageInfo {
|
|
title: "Test page name".to_string(),
|
|
name: "test".to_string(),
|
|
order: 0,
|
|
},
|
|
content: PageContent::Single {
|
|
content: entities::site::Post { blocks: vec![] },
|
|
},
|
|
})
|
|
})
|
|
});
|
|
|
|
server_context().insert(SiteServiceProvider::with(mock_service));
|
|
|
|
app.rebuild_in_place();
|
|
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"));
|
|
}
|
|
}
|