mirror of
https://github.com/chenasraf/leptos.git
synced 2026-05-17 17:48:10 +00:00
Mostly working version of axum with server functions
This commit is contained in:
@@ -28,6 +28,7 @@ members = [
|
||||
"examples/router",
|
||||
"examples/todomvc",
|
||||
"examples/todo-app-sqlite",
|
||||
"examples/todo-app-sqlite-axum",
|
||||
"examples/todo-app-cbor",
|
||||
"examples/view-tests",
|
||||
|
||||
|
||||
45
examples/todo-app-sqlite-axum/Cargo.toml
Normal file
45
examples/todo-app-sqlite-axum/Cargo.toml
Normal file
@@ -0,0 +1,45 @@
|
||||
[package]
|
||||
name = "todo-app-sqlite-axum"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
console_log = "0.2"
|
||||
console_error_panic_hook = "0.1"
|
||||
futures = "0.3"
|
||||
cfg-if = "1"
|
||||
leptos = { path = "../../../leptos/leptos", default-features = false, features = [
|
||||
"serde",
|
||||
] }
|
||||
leptos_axum = { path = "../../../leptos/integrations/axum", default-features = false, optional = true }
|
||||
leptos_meta = { path = "../../../leptos/meta", default-features = false }
|
||||
leptos_router = { path = "../../../leptos/router", default-features = false }
|
||||
log = "0.4"
|
||||
simple_logger = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
gloo-net = { version = "0.2", features = ["http"] }
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
axum = { version = "0.5.17", optional=true }
|
||||
tower = { version = "0.4.13", optional=true }
|
||||
tower-http = { version = "0.3.4", features = ["fs"], optional = true }
|
||||
tokio = { version = "1.0", features = ["full"], optional = true }
|
||||
http = {version = "0.2.8", optional = true}
|
||||
sqlx = { version = "0.6", features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlite",
|
||||
], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["csr"]
|
||||
csr = ["leptos/csr", "leptos_meta/csr", "leptos_router/csr"]
|
||||
hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
|
||||
ssr = ["dep:axum", "dep:tower", "dep:tower-http", "dep:tokio", "dep:http", "dep:sqlx", "leptos/ssr", "leptos_meta/ssr", "leptos_router/ssr", "leptos_axum"]
|
||||
|
||||
[package.metadata.cargo-all-features]
|
||||
denylist = ["axum", "tower", "tower-http", "tokio", "http", "sqlx", "leptos_actix"]
|
||||
skip_feature_sets = [["csr", "ssr"], ["csr", "hydrate"], ["ssr", "hydrate"]]
|
||||
21
examples/todo-app-sqlite-axum/LICENSE
Normal file
21
examples/todo-app-sqlite-axum/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Greg Johnston
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
21
examples/todo-app-sqlite-axum/README.md
Normal file
21
examples/todo-app-sqlite-axum/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Leptos Todo App Sqlite with Axum
|
||||
|
||||
This example creates a basic todo app with an Axum backend that uses Leptos' server functions to call sqlx from the client and seamlessly
|
||||
run it on the server
|
||||
|
||||
## Client Side Rendering
|
||||
To run it as a Client Side App, you can issue `trunk serve --open` in the root. This will build the entire
|
||||
app into one CRS bundle
|
||||
|
||||
## Server Side Rendering With Hydration
|
||||
To run it as a server side app with hydration, first you should run
|
||||
```bash
|
||||
wasm-pack build --target=web --no-default-features --features=hydrate
|
||||
```
|
||||
to generate the Webassembly to provide hydration features for the server.
|
||||
Then run the server with `cargo run` to serve the server side rendered HTML and the WASM bundle for hydration.
|
||||
```bash
|
||||
cargo run --no-default-features --features=ssr
|
||||
```
|
||||
> Note that if your hydration code changes, you will have to rerun the wasm-pack command above
|
||||
> This should be temporary, and vastly improve once cargo-leptos becomes ready for prime time!
|
||||
BIN
examples/todo-app-sqlite-axum/Todos.db
Normal file
BIN
examples/todo-app-sqlite-axum/Todos.db
Normal file
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
CREATE TABLE IF NOT EXISTS todos
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY,
|
||||
title VARCHAR,
|
||||
completed BOOLEAN
|
||||
);
|
||||
63
examples/todo-app-sqlite-axum/src/handlers.rs
Normal file
63
examples/todo-app-sqlite-axum/src/handlers.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use cfg_if::cfg_if;
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use axum::{
|
||||
body::{boxed, Body, BoxBody},
|
||||
http::{Request, Response, StatusCode, Uri},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
pub async fn file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let res = get_static_file(uri.clone(), "/pkg").await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
// try with `.html`
|
||||
// TODO: handle if the Uri has query parameters
|
||||
match format!("{}.html", uri).parse() {
|
||||
Ok(uri_html) => get_static_file(uri_html, "/pkg").await,
|
||||
Err(_) => Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string())),
|
||||
}
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_static_file_handler(uri: Uri) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let res = get_static_file(uri.clone(), "/static").await?;
|
||||
|
||||
if res.status() == StatusCode::NOT_FOUND {
|
||||
Err((StatusCode::INTERNAL_SERVER_ERROR, "Invalid URI".to_string()))
|
||||
} else {
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_static_file(uri: Uri, base: &str) -> Result<Response<BoxBody>, (StatusCode, String)> {
|
||||
let req = Request::builder().uri(&uri).body(Body::empty()).unwrap();
|
||||
|
||||
// `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
|
||||
// When run normally, the root should be the crate root
|
||||
if base == "/static" {
|
||||
match ServeDir::new("./static").oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
))
|
||||
}
|
||||
} else if base == "/pkg" {
|
||||
match ServeDir::new("./pkg").oneshot(req).await {
|
||||
Ok(res) => Ok(res.map(boxed)),
|
||||
Err(err) => Err((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", err),
|
||||
)),
|
||||
}
|
||||
} else{
|
||||
Err((StatusCode::NOT_FOUND, "Not Found".to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
examples/todo-app-sqlite-axum/src/lib.rs
Normal file
23
examples/todo-app-sqlite-axum/src/lib.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
pub mod handlers;
|
||||
pub mod todo;
|
||||
|
||||
// Needs to be in lib.rs AFAIK because wasm-bindgen needs us to be compiling a lib. I may be wrong.
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "hydrate")] {
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
use crate::todo::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
console_error_panic_hook::set_once();
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::hydrate(body().unwrap(), |cx| {
|
||||
view! { cx, <TodoApp/> }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
187
examples/todo-app-sqlite-axum/src/main.rs
Normal file
187
examples/todo-app-sqlite-axum/src/main.rs
Normal file
@@ -0,0 +1,187 @@
|
||||
// use cfg_if::cfg_if;
|
||||
// use futures::StreamExt;
|
||||
// use leptos::*;
|
||||
// use leptos_meta::*;
|
||||
// use leptos_router::*;
|
||||
// mod todo;
|
||||
|
||||
// // boilerplate to run in different modes
|
||||
// cfg_if! {
|
||||
// // server-only stuff
|
||||
// if #[cfg(feature = "ssr")] {
|
||||
// use actix_files::{Files};
|
||||
// use actix_web::*;
|
||||
// use crate::todo::*;
|
||||
|
||||
// #[get("{tail:.*}")]
|
||||
// async fn render(req: HttpRequest) -> impl Responder {
|
||||
// let path = req.path();
|
||||
|
||||
// let query = req.query_string();
|
||||
// let path = if query.is_empty() {
|
||||
// "http://leptos".to_string() + path
|
||||
// } else {
|
||||
// "http://leptos".to_string() + path + "?" + query
|
||||
// };
|
||||
|
||||
// let app = move |cx| {
|
||||
// let integration = ServerIntegration { path: path.clone() };
|
||||
// provide_context(cx, RouterIntegrationContext::new(integration));
|
||||
// provide_context(cx, req.clone());
|
||||
|
||||
// view! { cx, <TodoApp/> }
|
||||
// };
|
||||
|
||||
// let head = r#"<!DOCTYPE html>
|
||||
// <html lang="en">
|
||||
// <head>
|
||||
// <meta charset="utf-8"/>
|
||||
// <meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
// <script type="module">import init, { hydrate } from '/pkg/todo_app_sqlite.js'; init().then(hydrate);</script>"#;
|
||||
// let tail = "</body></html>";
|
||||
|
||||
// HttpResponse::Ok().content_type("text/html").streaming(
|
||||
// futures::stream::once(async { head.to_string() })
|
||||
// .chain(render_to_stream(move |cx| {
|
||||
// let app = app(cx);
|
||||
// let head = use_context::<MetaContext>(cx)
|
||||
// .map(|meta| meta.dehydrate())
|
||||
// .unwrap_or_default();
|
||||
// format!("{head}</head><body>{app}")
|
||||
// }))
|
||||
// .chain(futures::stream::once(async { tail.to_string() }))
|
||||
// .map(|html| Ok(web::Bytes::from(html)) as Result<web::Bytes>),
|
||||
// )
|
||||
// }
|
||||
|
||||
// #[post("/api/{tail:.*}")]
|
||||
// async fn handle_server_fns(
|
||||
// req: HttpRequest,
|
||||
// params: web::Path<String>,
|
||||
// body: web::Bytes,
|
||||
// ) -> impl Responder {
|
||||
// let path = params.into_inner();
|
||||
// let accept_header = req
|
||||
// .headers()
|
||||
// .get("Accept")
|
||||
// .and_then(|value| value.to_str().ok());
|
||||
|
||||
// if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
// let body: &[u8] = &body;
|
||||
// let (cx, disposer) = raw_scope_and_disposer();
|
||||
// provide_context(cx, req.clone());
|
||||
// match server_fn(cx, &body).await {
|
||||
// Ok(serialized) => {
|
||||
// // if this is Accept: application/json then send a serialized JSON response
|
||||
// if let Some("application/json") = accept_header {
|
||||
// HttpResponse::Ok().body(serialized)
|
||||
// }
|
||||
// // otherwise, it's probably a <form> submit or something: redirect back to the referrer
|
||||
// else {
|
||||
// HttpResponse::SeeOther()
|
||||
// .insert_header(("Location", "/"))
|
||||
// .content_type("application/json")
|
||||
// .body(serialized)
|
||||
// }
|
||||
// }
|
||||
// Err(e) => {
|
||||
// eprintln!("server function error: {e:#?}");
|
||||
// HttpResponse::InternalServerError().body(e.to_string())
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// HttpResponse::BadRequest().body(format!("Could not find a server function at that route."))
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[actix_web::main]
|
||||
// async fn main() -> std::io::Result<()> {
|
||||
// let mut conn = db().await.expect("couldn't connect to DB");
|
||||
// sqlx::migrate!()
|
||||
// .run(&mut conn)
|
||||
// .await
|
||||
// .expect("could not run SQLx migrations");
|
||||
|
||||
// crate::todo::register_server_functions();
|
||||
|
||||
// HttpServer::new(|| {
|
||||
// App::new()
|
||||
// .service(Files::new("/pkg", "./pkg"))
|
||||
// .service(handle_server_fns)
|
||||
// .service(render)
|
||||
// //.wrap(middleware::Compress::default())
|
||||
// })
|
||||
// .bind(("127.0.0.1", 8081))?
|
||||
// .run()
|
||||
// .await
|
||||
// }
|
||||
// } else {
|
||||
// fn main() {
|
||||
// // no client-side main function
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
|
||||
// boilerplate to run in different modes
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
// use actix_files::{Files, NamedFile};
|
||||
// use actix_web::*;
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
handler::Handler,
|
||||
};
|
||||
use std::net::SocketAddr;
|
||||
use crate::todo::*;
|
||||
use todo_app_sqlite_axum::handlers::{file_handler, get_static_file_handler};
|
||||
use todo_app_sqlite_axum::*;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8082));
|
||||
log::debug!("serving at {addr}");
|
||||
|
||||
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
|
||||
|
||||
let mut conn = db().await.expect("couldn't connect to DB");
|
||||
sqlx::migrate!()
|
||||
.run(&mut conn)
|
||||
.await
|
||||
.expect("could not run SQLx migrations");
|
||||
|
||||
crate::todo::register_server_functions();
|
||||
|
||||
// build our application with a route
|
||||
let app = Router::new()
|
||||
.route("/api/*path", post(leptos_axum::handle_server_fns))
|
||||
.nest("/pkg", get(file_handler))
|
||||
.nest("/static", get(get_static_file_handler))
|
||||
.fallback(leptos_axum::render_app_to_stream("todo_app_sqlite_axum", |cx| view! { cx, <Todos/> }).into_service());
|
||||
|
||||
// run our app with hyper
|
||||
// `axum::Server` is a re-export of `hyper::Server`
|
||||
log!("listening on {}", addr);
|
||||
axum::Server::bind(&addr)
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
// client-only stuff for Trunk
|
||||
else {
|
||||
use leptos_hackernews_axum::*;
|
||||
|
||||
pub fn main() {
|
||||
console_error_panic_hook::set_once();
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(|cx| {
|
||||
view! { cx, <App/> }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
213
examples/todo-app-sqlite-axum/src/todo.rs
Normal file
213
examples/todo-app-sqlite-axum/src/todo.rs
Normal file
@@ -0,0 +1,213 @@
|
||||
use cfg_if::cfg_if;
|
||||
use leptos::*;
|
||||
use leptos_router::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "ssr")] {
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
|
||||
pub async fn db() -> Result<SqliteConnection, ServerFnError> {
|
||||
Ok(SqliteConnection::connect("sqlite:Todos.db").await.map_err(|e| ServerFnError::ServerError(e.to_string()))?)
|
||||
}
|
||||
|
||||
pub fn register_server_functions() {
|
||||
GetTodos::register();
|
||||
AddTodo::register();
|
||||
DeleteTodo::register();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct Todo {
|
||||
id: u16,
|
||||
title: String,
|
||||
completed: bool,
|
||||
}
|
||||
} else {
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Todo {
|
||||
id: u16,
|
||||
title: String,
|
||||
completed: bool,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[server(GetTodos, "/api")]
|
||||
pub async fn get_todos(cx: Scope) -> Result<Vec<Todo>, ServerFnError> {
|
||||
// this is just an example of how to access server context injected in the handlers
|
||||
// http::Request doesn't implement Clone, so more work will be needed to do use_context() on this
|
||||
// let req = use_context::<http::Request<axum::body::BoxBody>>(cx)
|
||||
// .expect("couldn't get HttpRequest from context");
|
||||
// println!("req.path = {:?}", req.uri());
|
||||
|
||||
use futures::TryStreamExt;
|
||||
|
||||
let mut conn = db().await?;
|
||||
|
||||
let mut todos = Vec::new();
|
||||
let mut rows = sqlx::query_as::<_, Todo>("SELECT * FROM todos").fetch(&mut conn);
|
||||
while let Some(row) = rows
|
||||
.try_next()
|
||||
.await
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))?
|
||||
{
|
||||
todos.push(row);
|
||||
}
|
||||
|
||||
Ok(todos)
|
||||
}
|
||||
|
||||
#[server(AddTodo, "/api")]
|
||||
pub async fn add_todo(title: String) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
// fake API delay
|
||||
std::thread::sleep(std::time::Duration::from_millis(1250));
|
||||
|
||||
match sqlx::query("INSERT INTO todos (title, completed) VALUES ($1, false)")
|
||||
.bind(title)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
{
|
||||
Ok(row) => Ok(()),
|
||||
Err(e) => Err(ServerFnError::ServerError(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[server(DeleteTodo, "/api")]
|
||||
pub async fn delete_todo(id: u16) -> Result<(), ServerFnError> {
|
||||
let mut conn = db().await?;
|
||||
|
||||
sqlx::query("DELETE FROM todos WHERE id = $1")
|
||||
.bind(id)
|
||||
.execute(&mut conn)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|e| ServerFnError::ServerError(e.to_string()))
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn TodoApp(cx: Scope) -> Element {
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<Router>
|
||||
<header>
|
||||
<h1>"My Tasks"</h1>
|
||||
</header>
|
||||
<main>
|
||||
<Routes>
|
||||
<Route path="" element=|cx| view! {
|
||||
cx,
|
||||
<Todos/>
|
||||
}/>
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Todos(cx: Scope) -> Element {
|
||||
let add_todo = create_server_multi_action::<AddTodo>(cx);
|
||||
let delete_todo = create_server_action::<DeleteTodo>(cx);
|
||||
let submissions = add_todo.submissions();
|
||||
|
||||
// track mutations that should lead us to refresh the list
|
||||
let add_changed = add_todo.version;
|
||||
let todo_deleted = delete_todo.version;
|
||||
|
||||
// list of todos is loaded from the server in reaction to changes
|
||||
let todos = create_resource(
|
||||
cx,
|
||||
move || (add_changed(), todo_deleted()),
|
||||
move |_| get_todos(cx),
|
||||
);
|
||||
|
||||
view! {
|
||||
cx,
|
||||
<div>
|
||||
<MultiActionForm action=add_todo>
|
||||
<label>
|
||||
"Add a Todo"
|
||||
<input type="text" name="title"/>
|
||||
</label>
|
||||
<input type="submit" value="Add"/>
|
||||
</MultiActionForm>
|
||||
<div>
|
||||
<Suspense fallback=view! {cx, <p>"Loading..."</p> }>
|
||||
{
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
let existing_todos = {
|
||||
let delete_todo = delete_todo.clone();
|
||||
move || {
|
||||
todos
|
||||
.read()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todos| match todos {
|
||||
Err(e) => {
|
||||
vec![view! { cx, <pre class="error">"Server Error: " {e.to_string()}</pre>}]
|
||||
}
|
||||
Ok(todos) => {
|
||||
if todos.is_empty() {
|
||||
vec![view! { cx, <p>"No tasks were found."</p> }]
|
||||
} else {
|
||||
todos
|
||||
.into_iter()
|
||||
.map({
|
||||
let delete_todo = delete_todo.clone();
|
||||
move |todo| {
|
||||
let delete_todo = delete_todo.clone();
|
||||
view! {
|
||||
cx,
|
||||
<li>
|
||||
{todo.title}
|
||||
<ActionForm action=delete_todo.clone()>
|
||||
<input type="hidden" name="id" value={todo.id}/>
|
||||
<input type="submit" value="X"/>
|
||||
</ActionForm>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default()
|
||||
}
|
||||
};
|
||||
|
||||
let pending_todos = move || {
|
||||
submissions
|
||||
.get()
|
||||
.into_iter()
|
||||
.filter(|submission| submission.pending().get())
|
||||
.map(|submission| {
|
||||
view! {
|
||||
cx,
|
||||
<li class="pending">{move || submission.input.get().map(|data| data.title) }</li>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
view! {
|
||||
cx,
|
||||
<ul>
|
||||
<div>{existing_todos}</div>
|
||||
<div>{pending_todos}</div>
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
}
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -33,7 +33,7 @@ cfg_if! {
|
||||
.route("/{tail:.*}", leptos_actix::render_app_to_stream("todo_app_sqlite", |cx| view! { cx, <TodoApp/> }))
|
||||
//.wrap(middleware::Compress::default())
|
||||
})
|
||||
.bind(("127.0.0.1", 8081))?
|
||||
.bind(("127.0.0.1", 8083))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::{io, pin::Pin, sync::Arc};
|
||||
///
|
||||
/// // build our application with a route
|
||||
/// let app = Router::new()
|
||||
/// .route("/api/tail*", post(leptos_axum::handle_server_fns));
|
||||
/// .route("/api/*fn_name", post(leptos_axum::handle_server_fns));
|
||||
///
|
||||
/// // run our app with hyper
|
||||
/// // `axum::Server` is a re-export of `hyper::Server`
|
||||
@@ -40,11 +40,14 @@ use std::{io, pin::Pin, sync::Arc};
|
||||
/// }
|
||||
/// # }
|
||||
pub async fn handle_server_fns(
|
||||
Path(path): Path<String>,
|
||||
Path(fn_name): Path<String>,
|
||||
headers: HeaderMap<HeaderValue>,
|
||||
body: Bytes,
|
||||
req: Request<Body>,
|
||||
// req: Request<Body>,
|
||||
) -> impl IntoResponse {
|
||||
// Axum Path extractor doesn't remove the first slash from the path, while Actix does
|
||||
let fn_name = fn_name.replace("/", "");
|
||||
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
std::thread::spawn({
|
||||
move || {
|
||||
@@ -54,12 +57,12 @@ pub async fn handle_server_fns(
|
||||
async move {
|
||||
let body: &[u8] = &body;
|
||||
|
||||
let res = if let Some(server_fn) = server_fn_by_path(path.as_str()) {
|
||||
let res = if let Some(server_fn) = server_fn_by_path(fn_name.as_str()) {
|
||||
let runtime = create_runtime();
|
||||
let (cx, disposer) = raw_scope_and_disposer(runtime);
|
||||
|
||||
// provide request as context in server scope
|
||||
provide_context(cx, Arc::new(req));
|
||||
// provide_context(cx, Arc::new(req));
|
||||
|
||||
match server_fn(cx, body).await {
|
||||
Ok(serialized) => {
|
||||
|
||||
Reference in New Issue
Block a user