From d2859c3e40dcf73b5221a81cae49e11aa8c41bd2 Mon Sep 17 00:00:00 2001 From: Agathe Lenclen Date: Wed, 10 Dec 2025 16:04:23 +0100 Subject: [PATCH 1/5] Connect to postgres and init table --- Cargo.lock | 165 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 9 +++ server/Cargo.toml | 1 + server/src/main.rs | 23 ++++++- 4 files changed, 197 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index c9d87e3..56e4247 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -861,6 +861,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -1712,6 +1713,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + [[package]] name = "fastrand" version = "2.3.0" @@ -2414,6 +2421,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "html5ever" version = "0.29.1" @@ -2896,6 +2912,7 @@ checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ "bitflags 2.10.0", "libc", + "redox_syscall", ] [[package]] @@ -3098,6 +3115,16 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + [[package]] name = "memchr" version = "2.7.6" @@ -3168,6 +3195,7 @@ name = "mindr-server" version = "0.1.0" dependencies = [ "axum", + "postgres", "tokio", "tracing", "tracing-subscriber", @@ -3638,6 +3666,16 @@ dependencies = [ "phf_shared 0.11.3", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", +] + [[package]] name = "phf_codegen" version = "0.8.0" @@ -3729,6 +3767,15 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -3801,6 +3848,49 @@ dependencies = [ "web-time", ] +[[package]] +name = "postgres" +version = "0.19.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c48ece1c6cda0db61b058c1721378da76855140e9214339fa1317decacb176" +dependencies = [ + "bytes", + "fallible-iterator", + "futures-util", + "log", + "tokio", + "tokio-postgres", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" +dependencies = [ + "base64", + "byteorder", + "bytes", + "fallible-iterator", + "hmac", + "md-5", + "memchr", + "rand 0.9.2", + "sha2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -4781,6 +4871,17 @@ dependencies = [ "quote", ] +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + [[package]] name = "subsecond" version = "0.7.2" @@ -5117,6 +5218,32 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-postgres" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures-channel", + "futures-util", + "log", + "parking_lot", + "percent-encoding", + "phf 0.13.1", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "rand 0.9.2", + "socket2", + "tokio", + "tokio-util", + "whoami", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -5472,12 +5599,33 @@ version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + [[package]] name = "unicode-ident" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -5624,6 +5772,12 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" version = "0.2.106" @@ -5912,6 +6066,17 @@ dependencies = [ "windows-core 0.61.2", ] +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/README.md b/README.md index 2a27f9d..bcc9bba 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,15 @@ - Install [rust](https://rust-lang.org/tools/install/) - Install [dioxus](https://dioxuslabs.com/learn/0.7/getting_started/#install-the-dioxus-cli) +- Install PostgreSql + +## Database setup + +Open a `psql` shell and type: + +``` +CREATE DATABASE mindr_dev; +``` ## Run client in web diff --git a/server/Cargo.toml b/server/Cargo.toml index 29aa315..1f3c6d3 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] axum = { version = "0.8.6", features = ["ws"] } tokio = { version = "1.42", features = ["full"] } +postgres = "0.19.12" yrs = { version = "0.24.0" } # Logging (optional, helpful) diff --git a/server/src/main.rs b/server/src/main.rs index 9d14e3f..fbc79d9 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,12 +6,13 @@ use axum::{ response::IntoResponse, routing::get, }; +use postgres::{Client, Error, NoTls}; use tokio::sync::broadcast; use yrs::updates::decoder::Decode; use yrs::{Doc, ReadTxn, StateVector, Transact, Update}; #[tokio::main] -async fn main() { +async fn server() { tracing_subscriber::fmt::init(); let doc = Arc::new(Mutex::new(Doc::new())); @@ -27,6 +28,11 @@ async fn main() { axum::serve(listener, app).await.unwrap(); } +fn main() { + let _ = init_db(); + server(); +} + async fn ws_handler( ws: WebSocketUpgrade, Extension(doc): Extension>>, @@ -77,3 +83,18 @@ async fn handle_socket( tracing::info!("Client disconnected"); } + +fn init_db() -> Result<(), Error> { + let mut client = Client::connect("postgresql://postgres:postgres@localhost/mindr_dev", NoTls)?; + client.batch_execute( + " + CREATE TABLE IF NOT EXISTS documents ( + id SERIAL PRIMARY KEY, + channel VARCHAR NOT NULL, + document BYTEA NOT NULL + ) + ", + )?; + + Ok(()) +} From a0d1c8a2ca0916770cf1c78c46467d07a90d3c0b Mon Sep 17 00:00:00 2001 From: Agathe Lenclen Date: Wed, 10 Dec 2025 16:30:21 +0100 Subject: [PATCH 2/5] Handle db connection errors --- server/src/main.rs | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/server/src/main.rs b/server/src/main.rs index fbc79d9..939f5c8 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -85,16 +85,37 @@ async fn handle_socket( } fn init_db() -> Result<(), Error> { - let mut client = Client::connect("postgresql://postgres:postgres@localhost/mindr_dev", NoTls)?; - client.batch_execute( - " - CREATE TABLE IF NOT EXISTS documents ( - id SERIAL PRIMARY KEY, - channel VARCHAR NOT NULL, - document BYTEA NOT NULL - ) - ", - )?; - - Ok(()) + println!("⛸️ Initializing the Postgres database..."); + + let connection: Result = + Client::connect("postgresql://postgres:postgres@localhost/mindr_dev", NoTls); + match connection { + Ok(client) => { + return init_table(client); + } + Err(e) => { + println!("‼️ Could not connect to the database: {}", e); + std::process::exit(0) + } + } +} + +const INIT_TABLE_QUERY: &str = " + CREATE TABLE IF NOT EXISTS documents ( + id SERIAL PRIMARY KEY, + channel VARCHAR NOT NULL, + document BYTEA NOT NULL +) +"; +fn init_table(mut client: Client) -> Result<(), Error> { + match client.batch_execute(INIT_TABLE_QUERY) { + Ok(_) => { + println!("✅ Database ready!"); + Ok(()) + } + Err(e) => { + println!("‼️ Could not create the table: {}", e); + std::process::exit(0) + } + } } From 4df736bfb537376d5e2988a04a66169cb2b3e5b5 Mon Sep 17 00:00:00 2001 From: Agathe Lenclen Date: Wed, 10 Dec 2025 17:11:33 +0100 Subject: [PATCH 3/5] Add router to web client --- client/src/main.rs | 18 +++-------------- client/src/router.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 client/src/router.rs diff --git a/client/src/main.rs b/client/src/main.rs index 5b8c2f0..b902886 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,12 +1,10 @@ use dioxus::prelude::*; -use components::Mindmap; - mod components; mod data; +mod router; -const FAVICON: Asset = asset!("/assets/favicon.ico"); -const MAIN_CSS: Asset = asset!("/assets/css/main.css"); +use router::Route; fn main() { dioxus::launch(App); @@ -15,16 +13,6 @@ fn main() { #[component] fn App() -> Element { rsx! { - document::Style { - { - format!( - " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", - asset!("/assets/fonts/Roboto-Light.ttf"), - ) - } - } - document::Link { rel: "icon", href: FAVICON } - document::Link { rel: "stylesheet", href: MAIN_CSS } - Mindmap {} + Router:: {} } } diff --git a/client/src/router.rs b/client/src/router.rs new file mode 100644 index 0000000..2c984f0 --- /dev/null +++ b/client/src/router.rs @@ -0,0 +1,47 @@ +use components::Mindmap; +use dioxus::prelude::*; + +use crate::components; + +const FAVICON: Asset = asset!("/assets/favicon.ico"); +const MAIN_CSS: Asset = asset!("/assets/css/main.css"); + +#[derive(Clone, Debug, PartialEq, Routable)] +pub enum Route { + #[route("/")] + Home, + + #[route("/channels/:channel_id")] + Channel { channel_id: String }, + + #[route("/:any")] + NotFound { any: String }, +} + +#[component] +fn Home() -> Element { + rsx! { + document::Style { + { + format!( + " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", + asset!("/assets/fonts/Roboto-Light.ttf"), + ) + } + } + document::Link { rel: "icon", href: FAVICON } + document::Link { rel: "stylesheet", href: MAIN_CSS } + Mindmap {}, + } +} + +#[component] +fn Channel(channel_id: String) -> Element { + rsx! { "Channel page for channel with id: {channel_id}" } +} + +#[component] +fn NotFound(any: String) -> Element { + // TODO: Return 404 status code + rsx! { "Not found :(" } +} From cd3518aead5b938e82ede1c5eb4ac8b894669f2d Mon Sep 17 00:00:00 2001 From: Agathe Lenclen Date: Wed, 10 Dec 2025 17:17:59 +0100 Subject: [PATCH 4/5] Fix formatter --- client/src/router.rs | 22 +++++++++++----------- server/src/main.rs | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/src/router.rs b/client/src/router.rs index 2c984f0..f9afca1 100644 --- a/client/src/router.rs +++ b/client/src/router.rs @@ -21,17 +21,17 @@ pub enum Route { #[component] fn Home() -> Element { rsx! { - document::Style { - { - format!( - " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", - asset!("/assets/fonts/Roboto-Light.ttf"), - ) - } - } - document::Link { rel: "icon", href: FAVICON } - document::Link { rel: "stylesheet", href: MAIN_CSS } - Mindmap {}, + document::Style { + { + format!( + " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", + asset!("/assets/fonts/Roboto-Light.ttf"), + ) + } + } + document::Link { rel: "icon", href: FAVICON } + document::Link { rel: "stylesheet", href: MAIN_CSS } + Mindmap {} } } diff --git a/server/src/main.rs b/server/src/main.rs index 939f5c8..e6040ea 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -87,9 +87,9 @@ async fn handle_socket( fn init_db() -> Result<(), Error> { println!("⛸️ Initializing the Postgres database..."); - let connection: Result = + let db_connection: Result = Client::connect("postgresql://postgres:postgres@localhost/mindr_dev", NoTls); - match connection { + match db_connection { Ok(client) => { return init_table(client); } From 278b442f0edbb44028f00790f20d34a7fd632312 Mon Sep 17 00:00:00 2001 From: Agathe Lenclen Date: Wed, 10 Dec 2025 17:41:04 +0100 Subject: [PATCH 5/5] Pass channel_id --- client/src/components/mindmap.rs | 4 ++-- client/src/data/connection.rs | 2 +- client/src/data/store.rs | 4 ++-- client/src/router.rs | 38 ++++++++++++++++++++++---------- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/client/src/components/mindmap.rs b/client/src/components/mindmap.rs index 514dbad..bd522fd 100644 --- a/client/src/components/mindmap.rs +++ b/client/src/components/mindmap.rs @@ -10,8 +10,8 @@ use dioxus::prelude::*; use std::rc::Rc; #[component] -pub fn Mindmap() -> Element { - let store = Store::new(); +pub fn Mindmap(channel_id: String) -> Element { + let store = Store::new(channel_id); let mut pane = store.pane; let mut graph = store.graph; diff --git a/client/src/data/connection.rs b/client/src/data/connection.rs index 8a66356..4bd50b3 100644 --- a/client/src/data/connection.rs +++ b/client/src/data/connection.rs @@ -19,7 +19,7 @@ pub struct Connection { } impl Connection { - pub fn new(graph: Graph) -> Self { + pub fn new(graph: Graph, channel_id: String) -> Self { let doc = graph.get_doc(); let coroutine = use_coroutine(move |mut rx: UnboundedReceiver| { let mut doc = doc; diff --git a/client/src/data/store.rs b/client/src/data/store.rs index a8c810a..908ac4a 100644 --- a/client/src/data/store.rs +++ b/client/src/data/store.rs @@ -10,12 +10,12 @@ pub struct Store { } impl Store { - pub fn new() -> Self { + pub fn new(channel_id: String) -> Self { let graph = Graph::new(); Self { graph, pane: Pane::new(), - connection: Connection::new(graph), + connection: Connection::new(graph, channel_id), } } } diff --git a/client/src/router.rs b/client/src/router.rs index f9afca1..2a580f1 100644 --- a/client/src/router.rs +++ b/client/src/router.rs @@ -21,27 +21,41 @@ pub enum Route { #[component] fn Home() -> Element { rsx! { - document::Style { - { - format!( - " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", - asset!("/assets/fonts/Roboto-Light.ttf"), - ) - } + default_style {} + h1 { + "Welcome! Create a mindmap by navigating to /channels/YOUR_CHANNEL_NAME" } - document::Link { rel: "icon", href: FAVICON } - document::Link { rel: "stylesheet", href: MAIN_CSS } - Mindmap {} } } #[component] fn Channel(channel_id: String) -> Element { - rsx! { "Channel page for channel with id: {channel_id}" } + rsx! { + default_style {} + Mindmap { channel_id: "root" } + } } #[component] fn NotFound(any: String) -> Element { // TODO: Return 404 status code - rsx! { "Not found :(" } + rsx! { + default_style {} + p { "Not found :(" } + } +} + +fn default_style() -> Element { + rsx! { + document::Style { + { + format!( + " @font-face {{ font-family: 'Roboto Light'; src: url({}) format('truetype');}} ", + asset!("/assets/fonts/Roboto-Light.ttf"), + ) + } + } + document::Link { rel: "icon", href: FAVICON } + document::Link { rel: "stylesheet", href: MAIN_CSS } + } }