basic
This commit is contained in:
parent
4299ea3d9a
commit
ea43e71c30
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -98,6 +98,7 @@ name = "ascend"
|
|||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"camino",
|
||||||
"clap",
|
"clap",
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
@ -105,12 +106,15 @@ dependencies = [
|
|||||||
"leptos",
|
"leptos",
|
||||||
"leptos_axum",
|
"leptos_axum",
|
||||||
"leptos_meta",
|
"leptos_meta",
|
||||||
|
"leptos_router",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"type-toppings",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -514,6 +518,12 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "error_reporter"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "event-listener"
|
name = "event-listener"
|
||||||
version = "5.4.0"
|
version = "5.4.0"
|
||||||
@ -647,8 +657,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1036,15 +1048,16 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos"
|
name = "leptos"
|
||||||
version = "0.7.0"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ba5046c590aea121f6ad5e71fcb75453a933425d39527b9a3b1b295235afc8df"
|
checksum = "a8a90c679094979aa12927e8e925fe8eead1420d69420b2d8c6540863937ca75"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"base64",
|
"base64",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"either_of",
|
"either_of",
|
||||||
"futures",
|
"futures",
|
||||||
|
"getrandom",
|
||||||
"hydration_context",
|
"hydration_context",
|
||||||
"leptos_config",
|
"leptos_config",
|
||||||
"leptos_dom",
|
"leptos_dom",
|
||||||
@ -1065,6 +1078,7 @@ dependencies = [
|
|||||||
"tachys",
|
"tachys",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"throw_error",
|
"throw_error",
|
||||||
|
"tracing",
|
||||||
"typed-builder",
|
"typed-builder",
|
||||||
"typed-builder-macro",
|
"typed-builder-macro",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@ -1110,15 +1124,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_dom"
|
name = "leptos_dom"
|
||||||
version = "0.7.0"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c15aca81dc2edd040b51c46734f65c6f36e6ba8a31347c1354c94b958044ae0"
|
checksum = "99803be421344a2184fd5796e1a7645c2090738b2ab5d1a856084816853ec322"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"or_poisoned",
|
"or_poisoned",
|
||||||
"reactive_graph",
|
"reactive_graph",
|
||||||
"send_wrapper",
|
"send_wrapper",
|
||||||
"tachys",
|
"tachys",
|
||||||
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
@ -1175,6 +1190,7 @@ dependencies = [
|
|||||||
"rstml",
|
"rstml",
|
||||||
"server_fn_macro",
|
"server_fn_macro",
|
||||||
"syn",
|
"syn",
|
||||||
|
"tracing",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1232,9 +1248,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "leptos_server"
|
name = "leptos_server"
|
||||||
version = "0.7.0"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93450589df3b3e398c7f5ea64d8f1c8369b1ba9b90e1f70f6cb996b8d443ca3e"
|
checksum = "0fb23bd110ac04c7276aae3d8ba523f94cf06989d00b4e76eaee89451b06b494"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"any_spawner",
|
"any_spawner",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1248,6 +1264,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"server_fn",
|
"server_fn",
|
||||||
"tachys",
|
"tachys",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1715,6 +1732,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
|
"tracing",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1938,9 +1956,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn"
|
name = "server_fn"
|
||||||
version = "0.7.0"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "033cb8014aa86a7ce0c6ee58d23dce1a078b2e320dc6c53bb439663993199b1f"
|
checksum = "d0b9f0d2eecb2bf4f909661acc731009e3657574dec93a0ec9f114e250f74bc4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -2125,6 +2143,7 @@ dependencies = [
|
|||||||
"send_wrapper",
|
"send_wrapper",
|
||||||
"slotmap",
|
"slotmap",
|
||||||
"throw_error",
|
"throw_error",
|
||||||
|
"tracing",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
@ -2423,6 +2442,15 @@ dependencies = [
|
|||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "type-toppings"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2efc827a3c37071ebc9812b9bee5d6c0154fdf9ba6a7ec78b38515dbd4fde8e5"
|
||||||
|
dependencies = [
|
||||||
|
"error_reporter",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typed-builder"
|
name = "typed-builder"
|
||||||
version = "0.20.0"
|
version = "0.20.0"
|
||||||
|
24
Cargo.toml
24
Cargo.toml
@ -9,20 +9,28 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
axum = { version = "0.7", optional = true }
|
axum = { version = "0.7", optional = true }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
leptos = { version = "=0.7.0" }
|
leptos = { version = "0.7.3" }
|
||||||
leptos_axum = { version = "0.7", optional = true }
|
leptos_axum = { version = "0.7", optional = true }
|
||||||
leptos_meta = { version = "0.7" }
|
leptos_meta = { version = "0.7" }
|
||||||
# leptos_router = { version = "0.7.0" }
|
leptos_router = { version = "0.7.0" }
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
||||||
tower = { version = "0.4", optional = true }
|
tower = { version = "0.4", optional = true }
|
||||||
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||||
wasm-bindgen = "=0.2.99"
|
wasm-bindgen = "=0.2.99"
|
||||||
tracing = { version = "0.1", optional = true }
|
|
||||||
http = "1"
|
http = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
|
||||||
derive_more = { version = "1", features = ["display", "error", "from"] }
|
derive_more = { version = "1", features = ["display", "error", "from"] }
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
clap = { version = "4.5.7", features = ["derive"] }
|
||||||
|
serde_json = { version = "1", optional = true }
|
||||||
|
camino = { version = "1.1", optional = true }
|
||||||
|
type-toppings = { version = "0.2.1", features = ["result"] }
|
||||||
|
|
||||||
|
# Tracing
|
||||||
|
tracing = { version = "0.1", optional = true }
|
||||||
|
tracing-subscriber = { version = "0.3.18", features = [
|
||||||
|
"env-filter",
|
||||||
|
], optional = true }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
hydrate = ["leptos/hydrate"]
|
hydrate = ["leptos/hydrate"]
|
||||||
@ -32,11 +40,15 @@ ssr = [
|
|||||||
"dep:tower",
|
"dep:tower",
|
||||||
"dep:tower-http",
|
"dep:tower-http",
|
||||||
"dep:leptos_axum",
|
"dep:leptos_axum",
|
||||||
|
"dep:camino",
|
||||||
|
"dep:serde_json",
|
||||||
"leptos/ssr",
|
"leptos/ssr",
|
||||||
"leptos_meta/ssr",
|
"leptos_meta/ssr",
|
||||||
# "leptos_router/ssr",
|
"leptos_router/ssr",
|
||||||
"dep:tracing",
|
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
tracing = ["leptos/tracing", "dep:tracing", "dep:tracing-subscriber"]
|
||||||
|
|
||||||
# Defines a size-optimized profile for the WASM bundle in release mode
|
# Defines a size-optimized profile for the WASM bundle in release mode
|
||||||
[profile.wasm-release]
|
[profile.wasm-release]
|
||||||
|
4
justfile
4
justfile
@ -22,3 +22,7 @@ run-release:
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
cd dist
|
cd dist
|
||||||
LEPTOS_SITE_ROOT="site" LEPTOS_SITE_ADDR="127.0.0.1:1337" ./ascend serve
|
LEPTOS_SITE_ROOT="site" LEPTOS_SITE_ADDR="127.0.0.1:1337" ./ascend serve
|
||||||
|
|
||||||
|
reset-state:
|
||||||
|
cargo leptos serve -- reset-state
|
||||||
|
|
||||||
|
12
src/app.rs
12
src/app.rs
@ -1,4 +1,3 @@
|
|||||||
use leptos::logging;
|
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
|
|
||||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||||
@ -26,6 +25,9 @@ pub fn App() -> impl leptos::IntoView {
|
|||||||
use leptos_meta::Stylesheet;
|
use leptos_meta::Stylesheet;
|
||||||
use leptos_meta::Title;
|
use leptos_meta::Title;
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
tracing::debug!("Rendering root component");
|
||||||
|
|
||||||
// Provides context that manages stylesheets, titles, meta tags, etc.
|
// Provides context that manages stylesheets, titles, meta tags, etc.
|
||||||
leptos_meta::provide_meta_context();
|
leptos_meta::provide_meta_context();
|
||||||
|
|
||||||
@ -35,17 +37,15 @@ pub fn App() -> impl leptos::IntoView {
|
|||||||
// sets the document title
|
// sets the document title
|
||||||
<Title text="Ascend" />
|
<Title text="Ascend" />
|
||||||
|
|
||||||
|
<main>
|
||||||
<Ascend />
|
<Ascend />
|
||||||
|
</main>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[leptos::component]
|
#[leptos::component]
|
||||||
fn Ascend() -> impl leptos::IntoView {
|
fn Ascend() -> impl leptos::IntoView {
|
||||||
logging::log!("Rendering root component");
|
|
||||||
|
|
||||||
leptos::view! {
|
leptos::view! {
|
||||||
<div>
|
<crate::pages::wall::Wall />
|
||||||
{ "hello world" }
|
|
||||||
</div>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/lib.rs
14
src/lib.rs
@ -1,5 +1,11 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod pages {}
|
pub mod pages {
|
||||||
|
pub mod wall;
|
||||||
|
}
|
||||||
|
pub mod components {}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub mod server;
|
||||||
|
|
||||||
#[cfg(feature = "hydrate")]
|
#[cfg(feature = "hydrate")]
|
||||||
#[wasm_bindgen::prelude::wasm_bindgen]
|
#[wasm_bindgen::prelude::wasm_bindgen]
|
||||||
@ -8,9 +14,3 @@ pub fn hydrate() {
|
|||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
leptos::mount::hydrate_body(App);
|
leptos::mount::hydrate_body(App);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
|
||||||
pub mod server {
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct AppState {}
|
|
||||||
}
|
|
||||||
|
66
src/main.rs
66
src/main.rs
@ -1,73 +1,15 @@
|
|||||||
#[cfg(feature = "ssr")]
|
/// Server-side main function
|
||||||
mod cli {
|
|
||||||
#[derive(clap::Parser)]
|
|
||||||
#[command(version, about, long_about = None)]
|
|
||||||
pub struct Cli {
|
|
||||||
#[command(subcommand)]
|
|
||||||
pub command: Command,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::Subcommand, Default)]
|
|
||||||
pub enum Command {
|
|
||||||
#[default]
|
|
||||||
Serve,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
use ascend::app::App;
|
ascend::server::main().await
|
||||||
use ascend::app::shell;
|
|
||||||
use ascend::server::AppState;
|
|
||||||
use axum::Router;
|
|
||||||
use clap::Parser as _;
|
|
||||||
use leptos::logging;
|
|
||||||
use leptos::prelude::*;
|
|
||||||
use leptos_axum::LeptosRoutes;
|
|
||||||
use leptos_axum::generate_route_list;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
|
||||||
.without_time()
|
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
|
||||||
.pretty()
|
|
||||||
.init();
|
|
||||||
|
|
||||||
let cli = cli::Cli::parse();
|
|
||||||
match cli.command {
|
|
||||||
cli::Command::Serve => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
|
||||||
// For deployment these variables are:
|
|
||||||
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
|
||||||
// Alternately a file can be specified such as Some("Cargo.toml")
|
|
||||||
// The file would need to be included with the executable when moved to deployment
|
|
||||||
let conf = get_configuration(None).unwrap();
|
|
||||||
let leptos_options = conf.leptos_options;
|
|
||||||
let addr = leptos_options.site_addr;
|
|
||||||
let routes = generate_route_list(App);
|
|
||||||
|
|
||||||
let app_state = AppState {};
|
|
||||||
|
|
||||||
// build our application with a route
|
|
||||||
let app = Router::new()
|
|
||||||
.leptos_routes_with_context(&leptos_options, routes, move || provide_context(app_state.clone()), {
|
|
||||||
let leptos_options = leptos_options.clone();
|
|
||||||
move || shell(leptos_options.clone())
|
|
||||||
})
|
|
||||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
|
||||||
.with_state(leptos_options);
|
|
||||||
|
|
||||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
|
||||||
logging::log!("listening on http://{addr}");
|
|
||||||
axum::serve(listener, app.into_make_service()).await.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Client-side main function
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
// no client-side main function
|
// no client-side main function
|
||||||
// unless we want this to work with e.g., Trunk for a purely client-side app
|
// unless we want this to work with e.g., Trunk for a purely client-side app
|
||||||
// see lib.rs for hydration function instead
|
// see lib.rs for hydration function instead
|
||||||
|
eprintln!("Main function is empty. Run with the \"ssr\" feature.");
|
||||||
}
|
}
|
||||||
|
22
src/pages/wall.rs
Normal file
22
src/pages/wall.rs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
use leptos::prelude::*;
|
||||||
|
|
||||||
|
#[leptos::component]
|
||||||
|
pub fn Wall() -> impl leptos::IntoView {
|
||||||
|
let cells = (1..=(12 * 12))
|
||||||
|
.into_iter()
|
||||||
|
.map(|i| {
|
||||||
|
let i = i.to_string();
|
||||||
|
view! {
|
||||||
|
<div class="aspect-square rounded border-2 border-dashed border-sky-500 bg-indigo-100"> { i } </div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
|
||||||
|
leptos::view! {
|
||||||
|
<div class="container mx-auto border">
|
||||||
|
<div class="grid grid-rows-4 grid-cols-12 gap-4">
|
||||||
|
{cells}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
178
src/server.rs
Normal file
178
src/server.rs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
//! Server-only features
|
||||||
|
|
||||||
|
use persistence::Persistent;
|
||||||
|
use state::PersistentState;
|
||||||
|
use tracing::level_filters::LevelFilter;
|
||||||
|
use type_toppings::ResultExt;
|
||||||
|
|
||||||
|
pub mod cli {
|
||||||
|
//! Server CLI interface
|
||||||
|
|
||||||
|
#[derive(clap::Parser)]
|
||||||
|
#[command(version, about, long_about = None)]
|
||||||
|
pub struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(clap::Subcommand, Default)]
|
||||||
|
pub enum Command {
|
||||||
|
#[default]
|
||||||
|
Serve,
|
||||||
|
|
||||||
|
/// Resets state, replacing it with defaults
|
||||||
|
ResetState,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod state {
|
||||||
|
//! Server state
|
||||||
|
|
||||||
|
use super::persistence::Persistent;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct State {
|
||||||
|
pub persistent: Persistent<PersistentState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct PersistentState {
|
||||||
|
pub wall: Wall,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Wall {
|
||||||
|
pub rows: u32,
|
||||||
|
pub cols: u32,
|
||||||
|
pub holds: BTreeMap<HoldPosition, Hold>,
|
||||||
|
pub routes: BTreeSet<route::Route>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct HoldPosition {
|
||||||
|
pub row: u32,
|
||||||
|
pub col: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Hold {
|
||||||
|
pub position: HoldPosition,
|
||||||
|
pub image: Option<Image>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
|
pub struct Image {}
|
||||||
|
|
||||||
|
mod route {
|
||||||
|
use super::HoldPosition;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct Route {
|
||||||
|
pub holds: BTreeMap<HoldPosition, HoldRole>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The role of a hold on a route
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub enum HoldRole {
|
||||||
|
/// Start hold
|
||||||
|
Start,
|
||||||
|
|
||||||
|
/// Any hold on the route without a specific role
|
||||||
|
Normal,
|
||||||
|
|
||||||
|
/// Zone hold
|
||||||
|
Zone,
|
||||||
|
|
||||||
|
/// End hold
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod persistence;
|
||||||
|
|
||||||
|
pub const STATE_FILE: &str = "state.json";
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn main() {
|
||||||
|
use crate::server::cli::Cli;
|
||||||
|
use crate::server::cli::Command;
|
||||||
|
use clap::Parser as _;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.without_time()
|
||||||
|
.with_file(true)
|
||||||
|
.with_line_number(true)
|
||||||
|
.with_target(false)
|
||||||
|
.with_ansi(true)
|
||||||
|
.with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()))
|
||||||
|
.pretty()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let cli = Cli::parse();
|
||||||
|
match cli.command {
|
||||||
|
Command::Serve => serve().await.unwrap_or_report(),
|
||||||
|
Command::ResetState => {
|
||||||
|
let s = PersistentState::default();
|
||||||
|
let p = camino::Utf8Path::new(STATE_FILE);
|
||||||
|
tracing::info!("Resetting state to default: {p}");
|
||||||
|
Persistent::persist(p, &s).await.unwrap_or_report();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument(err)]
|
||||||
|
async fn serve() -> Result<(), Error> {
|
||||||
|
use crate::app::App;
|
||||||
|
use crate::app::shell;
|
||||||
|
use crate::server::state::State;
|
||||||
|
use axum::Router;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
use leptos_axum::LeptosRoutes;
|
||||||
|
use leptos_axum::generate_route_list;
|
||||||
|
|
||||||
|
// Setting get_configuration(None) means we'll be using cargo-leptos's env values
|
||||||
|
// For deployment these variables are:
|
||||||
|
// <https://github.com/leptos-rs/start-axum#executing-a-server-on-a-remote-machine-without-the-toolchain>
|
||||||
|
// Alternately a file can be specified such as Some("Cargo.toml")
|
||||||
|
// The file would need to be included with the executable when moved to deployment
|
||||||
|
let conf = get_configuration(None).unwrap_or_report();
|
||||||
|
let leptos_options = conf.leptos_options;
|
||||||
|
let addr = leptos_options.site_addr;
|
||||||
|
let routes = generate_route_list(App);
|
||||||
|
|
||||||
|
tracing::info!("Loading state");
|
||||||
|
let server_state = State {
|
||||||
|
persistent: Persistent::<PersistentState>::load(STATE_FILE.into()).await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
// build our application with a route
|
||||||
|
let app = Router::new()
|
||||||
|
.leptos_routes_with_context(&leptos_options, routes, move || provide_context(server_state.clone()), {
|
||||||
|
let leptos_options = leptos_options.clone();
|
||||||
|
move || shell(leptos_options.clone())
|
||||||
|
})
|
||||||
|
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||||
|
.with_state(leptos_options);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr).await?;
|
||||||
|
tracing::info!("Listening on http://{addr}");
|
||||||
|
axum::serve(listener, app.into_make_service()).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||||
|
#[display("Server crash")]
|
||||||
|
pub enum Error {
|
||||||
|
Io(std::io::Error),
|
||||||
|
Persistence(persistence::Error),
|
||||||
|
}
|
122
src/server/persistence.rs
Normal file
122
src/server/persistence.rs
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
use camino::Utf8Path;
|
||||||
|
use camino::Utf8PathBuf;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Persistent<T> {
|
||||||
|
state: Arc<Mutex<T>>,
|
||||||
|
file_path: Utf8PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Persistent<T> {
|
||||||
|
#[tracing::instrument(skip(state))]
|
||||||
|
pub fn new(state: T, file_path: Utf8PathBuf) -> Self {
|
||||||
|
Self {
|
||||||
|
state: Arc::new(Mutex::new(state)),
|
||||||
|
file_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Instantiates state from file system
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub async fn load(file_path: Utf8PathBuf) -> Result<Self, Error>
|
||||||
|
where
|
||||||
|
T: DeserializeOwned,
|
||||||
|
{
|
||||||
|
let content = tokio::fs::read_to_string(&file_path).await.map_err(|source| Error::Read {
|
||||||
|
file_path: file_path.to_owned(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let t = serde_json::from_str(&content).map_err(|source| Error::Deserialize {
|
||||||
|
file_path: file_path.to_owned(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let persistent = Self {
|
||||||
|
state: Arc::new(Mutex::new(t)),
|
||||||
|
file_path,
|
||||||
|
};
|
||||||
|
Ok(persistent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns state
|
||||||
|
#[tracing::instrument(skip(self))]
|
||||||
|
pub async fn get(&self) -> T
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
let state = self.state.lock().await;
|
||||||
|
state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns state
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn with<F, R>(&self, f: F) -> R
|
||||||
|
where
|
||||||
|
F: FnOnce(&T) -> R,
|
||||||
|
{
|
||||||
|
let state = self.state.lock().await;
|
||||||
|
f(&state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates and persists state
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn update<F>(&self, f: F) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T),
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let mut state = self.state.lock().await;
|
||||||
|
f(&mut state);
|
||||||
|
Self::persist(&self.file_path, &state).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets and persists state
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub async fn set(&self, new_state: T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
self.update(move |state| {
|
||||||
|
*state = new_state;
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist.
|
||||||
|
///
|
||||||
|
/// Implicitly called by `set` and `update`.
|
||||||
|
#[tracing::instrument(skip_all, err)]
|
||||||
|
pub async fn persist(file_path: &Utf8Path, state: &T) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
T: Serialize,
|
||||||
|
{
|
||||||
|
let serialized = serde_json::to_string(state).map_err(|source| Error::Serialize { source })?;
|
||||||
|
tokio::fs::write(file_path, serialized).await.map_err(|source| Error::Write {
|
||||||
|
file_path: file_path.to_owned(),
|
||||||
|
source,
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||||
|
#[display("Persistent state error: {_variant}")]
|
||||||
|
pub enum Error {
|
||||||
|
#[display("Failed to read file: {file_path}")]
|
||||||
|
Read { file_path: Utf8PathBuf, source: std::io::Error },
|
||||||
|
|
||||||
|
#[display("Failed to deserialize state from file: {file_path}")]
|
||||||
|
Deserialize { file_path: Utf8PathBuf, source: serde_json::Error },
|
||||||
|
|
||||||
|
#[display("Failed to serialize state")]
|
||||||
|
Serialize { source: serde_json::Error },
|
||||||
|
|
||||||
|
#[display("Failed to write file: {file_path}")]
|
||||||
|
Write { file_path: Utf8PathBuf, source: std::io::Error },
|
||||||
|
}
|
1
state.json
Normal file
1
state.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"wall":{"rows":0,"cols":0,"holds":{},"routes":[]}}
|
Loading…
x
Reference in New Issue
Block a user