From c7e4983e46cc1a3b162374430d080a7a01cca7bc Mon Sep 17 00:00:00 2001 From: Thomas Gideon Date: Mon, 2 Sep 2024 17:43:49 -0400 Subject: [PATCH] Wire in a database --- .gitignore | 2 + Cargo.lock | 152 +++++++++++++++++++++++ Cargo.toml | 18 ++- docker-compose.yml | 4 + migrations/20240902212954_add-series.sql | 6 + src/app.rs | 67 ++++++---- src/main.rs | 31 ++++- 7 files changed, 252 insertions(+), 28 deletions(-) create mode 100644 migrations/20240902212954_add-series.sql diff --git a/.gitignore b/.gitignore index 8cdaa33..2890e60 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ node_modules/ test-results/ end2end/playwright-report/ playwright/.cache/ + +.envrc diff --git a/Cargo.lock b/Cargo.lock index e606e56..44df4d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,55 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "anstream" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anstyle-parse" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + [[package]] name = "anyhow" version = "1.0.86" @@ -328,6 +377,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" +[[package]] +name = "colorchoice" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -563,6 +618,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_filter" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -937,6 +1015,12 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "hyper" version = "1.4.1" @@ -1018,6 +1102,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "itertools" version = "0.12.1" @@ -1898,6 +1988,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.77", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2198,6 +2322,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2327,6 +2460,7 @@ dependencies = [ "tokio-stream", "tracing", "url", + "uuid", "webpki-roots", ] @@ -2408,6 +2542,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2446,6 +2581,7 @@ dependencies = [ "stringprep", "thiserror", "tracing", + "uuid", "whoami", ] @@ -2470,6 +2606,7 @@ dependencies = [ "sqlx-core", "tracing", "url", + "uuid", ] [[package]] @@ -2600,6 +2737,7 @@ dependencies = [ "libc", "mio", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.52.0", @@ -2625,6 +2763,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] @@ -2864,6 +3003,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.10.0" @@ -2871,6 +3016,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" dependencies = [ "getrandom", + "serde", ] [[package]] @@ -2994,17 +3140,23 @@ dependencies = [ "anyhow", "axum", "console_error_panic_hook", + "env_logger", "http", "leptos", "leptos_axum", "leptos_meta", "leptos_router", + "log", + "rust-embed", + "serde", "sqlx", "thiserror", "tokio", + "tokio-stream", "tower", "tower-http", "tracing", + "uuid", "wasm-bindgen", ] diff --git a/Cargo.toml b/Cargo.toml index 1db3a05..ff1b8df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,14 @@ wasm-bindgen = "=0.2.93" thiserror = "1" tracing = { version = "0.1", optional = true } http = "1" -sqlx = { version = "0.8.1", features = ["postgres", "runtime-tokio", "tls-rustls-ring"], optional = true } +sqlx = { version = "0.8.1", features = ["postgres", "runtime-tokio", "tls-rustls-ring", "uuid"], optional = true } anyhow = "1.0.86" +log = { version = "0.4.22", optional = true } +env_logger = { version = "0.11.5", optional = true } +rust-embed = { version = "8.5.0", optional = true } +uuid = { version = "1.10.0", features = ["serde", "v4"] } +serde = { version = "1.0.209", features = ["derive"] } +tokio-stream = { version = "0.1.15", optional = true, features = ["full"] } [features] hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"] @@ -35,8 +41,16 @@ ssr = [ "leptos_meta/ssr", "leptos_router/ssr", "dep:tracing", - "dep:sqlx" + "dep:sqlx", + "dep:log", + "dep:env_logger", + "dep:rust-embed", + "dep:tokio-stream" ] +log = ["dep:log"] +env_logger = ["dep:env_logger"] +rust-embed = ["dep:rust-embed"] +tokio-stream = ["dep:tokio-stream"] # Defines a size-optimized profile for the WASM bundle in release mode [profile.wasm-release] diff --git a/docker-compose.yml b/docker-compose.yml index ff71fb8..3d5c443 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,11 @@ services: # size: 134217728 # 128*2^20 bytes = 128Mb volumes: - watch_data:/var/lib/postgres/data + ports: + - 5432:5432 environment: + POSTGRES_DB: watch + POSTGRES_USER: watch POSTGRES_PASSWORD: watch adminer: diff --git a/migrations/20240902212954_add-series.sql b/migrations/20240902212954_add-series.sql new file mode 100644 index 0000000..5cae972 --- /dev/null +++ b/migrations/20240902212954_add-series.sql @@ -0,0 +1,6 @@ +create table if not exists "public"."series" ( + "id" uuid default gen_random_uuid() not null primary key, + "name" text not null, + "created" timestamptz default now() not null, + "updated" timestamptz default now() not null +); diff --git a/src/app.rs b/src/app.rs index b76a3ef..fa6d733 100644 --- a/src/app.rs +++ b/src/app.rs @@ -2,6 +2,10 @@ use crate::error_template::{AppError, ErrorTemplate}; use leptos::*; use leptos_meta::*; use leptos_router::*; +use serde::{Deserialize, Serialize}; +#[cfg(feature = "ssr")] +use sqlx::FromRow; +use uuid::Uuid; #[component] pub fn App() -> impl IntoView { @@ -21,7 +25,7 @@ pub fn App() -> impl IntoView { /> // sets the document title - + <Title text="Watch"/> // content for this welcome page <Router fallback=|| { @@ -41,38 +45,55 @@ pub fn App() -> impl IntoView { } } +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "ssr", derive(FromRow))] +struct Series { + id: Uuid, + name: String, +} + #[server] pub async fn get_all() -> Result<Vec<String>, ServerFnError> { - use std::time::Duration; + use sqlx::{Pool, Postgres}; + use tokio_stream::StreamExt; - std::thread::sleep(Duration::from_millis(250)); - println!("Loading"); - Ok(vec!["data1".to_string(), "data2".to_string()]) + let pool: Pool<Postgres> = expect_context(); + + let mut r = sqlx::query_as::<_, Series>("select * from series;").fetch(&pool); + + let mut found = Vec::new(); + while let Some(series) = r.try_next().await? { + found.push(series.name); + } + + Ok(found) } /// Renders the home page of your application. #[component] fn HomePage() -> impl IntoView { // Creates a reactive value to update the button - let load = create_local_resource(|| (), |_| async move { get_all().await }); + let load = create_resource(|| (), |_| async move { get_all().await }); view! { - <h1>"Welcome to Leptos!"</h1> - { - move || match load() { - None => view!{ <div>"Loading..."</div> }, - Some(data) => { - view! { - <div><ul> - { - data.unwrap_or_default().into_iter().map(|d| view!{ - <li>{d}</li> - }).collect::<Vec<_>>() - } - </ul></div> - } - } - } - } + <h1>"What We're Watching"</h1> + <div class="card"> + <Suspense fallback=|| view!{ <div>"Loading..."</div> }> + <ul> + {load().map(format_series)} + </ul> + </Suspense> + </div> } } + +fn format_series(data: Result<Vec<String>, ServerFnError>) -> Vec<impl IntoView> { + data.unwrap_or_default() + .into_iter() + .map(|d| { + view! { + <li>{d}</li> + } + }) + .collect::<Vec<_>>() +} diff --git a/src/main.rs b/src/main.rs index 5fa9f13..1473d86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,13 @@ #[cfg(feature = "ssr")] #[tokio::main] async fn main() { + use std::env; + use axum::Router; use leptos::*; use leptos_axum::{generate_route_list, LeptosRoutes}; + use log::info; + use sqlx::postgres::PgPoolOptions; use watch::app::*; use watch::fileserv::file_and_error_handler; @@ -17,14 +21,35 @@ async fn main() { let addr = leptos_options.site_addr; let routes = generate_route_list(App); + let pool = PgPoolOptions::new() + .test_before_acquire(true) + .max_lifetime(None) + .connect_lazy(&env::var("DATABASE_URL").expect("DATABASE_URL was not set!")) + .expect("Failed to create database connection pool"); + // build our application with a route let app = Router::new() - .leptos_routes(&leptos_options, routes, App) + .leptos_routes_with_context( + &leptos_options, + routes, + { + let pool = pool.clone(); + move || provide_context(pool.clone()) + }, + App, + ) .fallback(file_and_error_handler) - .with_state(leptos_options); + .with_state(leptos_options) + .with_state(pool); + + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", format!("{}=debug", module_path!())); + } + + env_logger::init(); let listener = tokio::net::TcpListener::bind(&addr).await.unwrap(); - logging::log!("listening on http://{}", &addr); + info!("listening on http://{}", &addr); axum::serve(listener, app.into_make_service()) .await .unwrap();