From 5160929958eccf367456990db10da0a94187e732 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Mon, 7 Oct 2024 21:12:49 +0200 Subject: [PATCH] get this to work a slight bit more --- Cargo.lock | 302 ++++++++++++++++++++------ crates/backend/Cargo.toml | 8 +- crates/backend/src/admin.rs | 10 +- crates/backend/src/chat.rs | 78 +++++-- crates/backend/src/main.rs | 22 +- crates/backend/src/markup_response.rs | 47 ++++ crates/backend/src/stat.rs | 10 +- crates/backend/src/state.rs | 74 +++++++ crates/backend/src/ws.rs | 15 ++ static/chat.js | 26 +++ 10 files changed, 484 insertions(+), 108 deletions(-) create mode 100644 crates/backend/src/markup_response.rs create mode 100644 crates/backend/src/state.rs create mode 100644 crates/backend/src/ws.rs create mode 100644 static/chat.js diff --git a/Cargo.lock b/Cargo.lock index 43849e4..0369422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -145,19 +145,20 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", "axum-core", "axum-macros", + "base64", "bytes", "futures-util", "http", @@ -176,8 +177,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper 1.0.1", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", @@ -186,9 +189,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -216,6 +219,16 @@ dependencies = [ "syn", ] +[[package]] +name = "axum_static" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d134d2aeabd596bc82e44748f999c9b0e535a7b756cb36c62df0de6d2445d6e" +dependencies = [ + "axum", + "tower-http", +] + [[package]] name = "backend" version = "0.1.0" @@ -223,15 +236,19 @@ dependencies = [ "anyhow", "axum", "axum-macros", + "axum_static", "chrono", "chrono-tz", + "dashmap", "http", "maud", "rand", "serde", "sqlx", + "thiserror", "tokio", - "tokio-tungstenite", + "tracing", + "tracing-subscriber", "uuid", ] @@ -315,9 +332,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "677207f6eaec43fcfd092a718c847fc38aa261d0e19b8ef6797e0ccbe789e738" dependencies = [ "shlex", ] @@ -366,9 +383,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -376,9 +393,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -519,6 +536,20 @@ dependencies = [ "typenum", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -635,9 +666,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -645,15 +676,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -673,27 +704,27 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", "futures-io", @@ -728,9 +759,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "hashbrown" @@ -742,13 +773,19 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -831,10 +868,16 @@ dependencies = [ ] [[package]] -name = "httparse" -version = "1.9.4" +name = "http-range-header" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" + +[[package]] +name = "httparse" +version = "1.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -912,12 +955,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] [[package]] @@ -1020,7 +1063,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" dependencies = [ - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1073,6 +1116,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1111,6 +1164,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1160,18 +1223,24 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" @@ -1412,18 +1481,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "355ae415ccd3a04315d3f8246e86d67689ea74d88d915576e1589a351062a13b" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -1433,9 +1502,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1444,9 +1513,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "ring" @@ -1504,9 +1573,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "ring", @@ -1518,19 +1587,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" [[package]] name = "rustls-webpki" @@ -1637,6 +1705,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1778,7 +1855,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown", + "hashbrown 0.14.5", "hashlink", "hex", "indexmap", @@ -2004,9 +2081,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2027,9 +2104,9 @@ checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -2058,6 +2135,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2125,6 +2212,19 @@ dependencies = [ "tungstenite", ] +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.1" @@ -2141,6 +2241,31 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2183,6 +2308,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", ] [[package]] @@ -2210,10 +2361,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] -name = "unicode-bidi" -version = "0.3.15" +name = "unicase" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -2232,9 +2392,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" @@ -2303,6 +2463,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index ea51139..10ef08e 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -5,15 +5,19 @@ edition = "2021" [dependencies] http = "1" -axum = { version = "0.7.5", features = [ "json", "macros" ] } +axum = { version = "0.7.5", features = [ "json", "macros", "ws" ] } axum-macros = "0.4.1" chrono = { version = "0.4", features = [ "serde" ] } chrono-tz = "0.10.0" maud = "0.26.0" sqlx = { version = "0.8.2", features = [ "postgres", "runtime-tokio", "tls-rustls-ring", "uuid", "chrono" ] } tokio = { version = "1.40.0", features = [ "full" ] } -tokio-tungstenite = "0.24.0" +dashmap = "6" serde = { version = "1", features = [ "derive" ] } +thiserror = "1" anyhow = "1" uuid = { version = "1.10.0", features = [ "serde" ] } rand = "0.8.5" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +axum_static = "1.7.1" diff --git a/crates/backend/src/admin.rs b/crates/backend/src/admin.rs index 6a9e410..7c6d4b0 100644 --- a/crates/backend/src/admin.rs +++ b/crates/backend/src/admin.rs @@ -6,15 +6,15 @@ use axum::{ use rand::distributions::{Alphanumeric, DistString}; use sqlx::{Pool, Postgres, QueryBuilder}; -use crate::model::Chat; +use crate::{model::Chat, state::AppState}; -pub fn router(pool: Pool) -> Router { +pub fn router(state: AppState) -> Router { Router::new() .route("/new/:amount", get(create)) - .with_state(pool) + .with_state(state) } -async fn create(Path(amount): Path, State(pool): State>) -> Json> { +async fn create(Path(amount): Path, State(state): State) -> Json> { let paths: Vec = (0..amount) .map(|_| Alphanumeric.sample_string(&mut rand::thread_rng(), 6)) .collect(); @@ -25,7 +25,7 @@ async fn create(Path(amount): Path, State(pool): State>) -> J }) .push("returning *") .build_query_as() - .fetch_all(&pool) + .fetch_all(state.pool()) .await .unwrap(); diff --git a/crates/backend/src/chat.rs b/crates/backend/src/chat.rs index 825a8c6..37456b5 100644 --- a/crates/backend/src/chat.rs +++ b/crates/backend/src/chat.rs @@ -10,42 +10,55 @@ use axum::{ use maud::{html, Render}; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Postgres}; +use tracing::error; use uuid::Uuid; use crate::{ + markup_response::{simple_error_page, MarkupResponse}, model::{Chat, Message}, + state::AppState, ADMIN_TOK, }; pub async fn get( Path(url_path): Path, headers: HeaderMap, - State(pool): State>, + State(state): State, ) -> impl IntoResponse { println!("headers: {headers:#?}"); - let chat = sqlx::query_as!(Chat, r#"select * from chats where url_path = $1"#, url_path) - .fetch_one(&pool) - .await - .unwrap(); - let messages = sqlx::query_as!( - Message, - r#"select * from messages where chat_id = $1"#, - chat.id - ) - .fetch_all(&pool) - .await - .unwrap(); + // TODO: Error handling + let chat = match state.fetch_chat_by_url_path(&url_path).await { + Ok(v) => v, + Err(err) => { + error!("Error fetching chat: {err:?}"); + return simple_error_page(err.into()).into_response(); + } + }; + let messages = match state.fetch_messages(&chat).await { + Ok(v) => v, + Err(err) => { + error!("Error fetching messages for chat {}: {err:?}", chat.id); + return simple_error_page(err.into()).into_response(); + } + }; if Some(&HeaderValue::from_static("application/json")) == headers.get(ACCEPT) { Json(messages).into_response() } else { - Html( + MarkupResponse::new( html! { + template #chatmessage { + div.message { + p { } + span.timestamp { } + } + } main { div #history { @for msg in &messages { - div.message.(if msg.from_admin { "from_admin" } else { "from_user" }) { - p { (msg.content) "(" (msg.timestamp) ")" } + div.message.(if msg.from_admin { "from_admin" } else { "from_user" }) #(msg.id) { + p { (msg.content) } + span.timestamp { (msg.timestamp) } } } } @@ -54,24 +67,45 @@ pub async fn get( button type="submit" { "Send!" } } } - } - .into_string(), + script src="/static/chat.js" {}; + + }, + "Cursed Messenger from hell", ) .into_response() } } +pub async fn poll(Path(message): Path, State(state): State) -> impl IntoResponse { + let message = sqlx::query_as!(Message, r#"select * from messages where id = $1"#, message) + .fetch_one(state.pool()) + .await + .unwrap(); + + let new_messages = sqlx::query_as!( + Message, + r#"select * from messages where chat_id = $1 and timestamp > $2;"#, + message.chat_id, + message.timestamp + ) + .fetch_all(state.pool()) + .await + .unwrap(); + + Json(new_messages) +} + // TODO: // - validation of msg length // - fix terrible returns lmao pub async fn post( Path(url_path): Path, headers: HeaderMap, - State(pool): State>, + State(state): State, Form(FormMessageBody { msgcontent: body }): Form, ) -> impl IntoResponse { let chat = sqlx::query_as!(Chat, r#"select * from chats where url_path = $1"#, url_path) - .fetch_one(&pool) + .fetch_one(state.pool()) .await .unwrap(); @@ -85,7 +119,7 @@ pub async fn post( chat.id, body ) - .execute(&pool) + .execute(state.pool()) .await .unwrap(); StatusCode::OK.into_response() @@ -95,7 +129,7 @@ pub async fn post( chat.id, body ) - .execute(&pool) + .execute(state.pool()) .await .unwrap(); Redirect::to(&format!("/{url_path}")).into_response() diff --git a/crates/backend/src/main.rs b/crates/backend/src/main.rs index 7c6cacf..9da292e 100644 --- a/crates/backend/src/main.rs +++ b/crates/backend/src/main.rs @@ -1,26 +1,36 @@ use axum::{routing::get, Router}; use sqlx::{Pool, Postgres}; +use state::AppState; +use tracing::Level; +use tracing_subscriber::FmtSubscriber; -const DB_URL: &str = "postgres://localhost/chatdings"; const ADMIN_TOK: &str = "meow"; mod admin; mod chat; +mod markup_response; mod model; mod stat; +mod state; +mod ws; #[tokio::main] async fn main() -> anyhow::Result<()> { - let pool = Pool::::connect(DB_URL).await?; + let subscriber = FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); - sqlx::migrate!().run(&pool).await?; + let state = AppState::init().await?; let app = Router::new() .route("/", get(|| async { "

gay

" })) .route("/:path", get(chat::get).post(chat::post)) - .with_state(pool.clone()) - .nest("/stat", stat::router(pool.clone())) - .nest("/admin", admin::router(pool.clone())); + .route("/poll/:msg", get(chat::poll)) + .with_state(state.clone()) + .nest("/stat", stat::router(state.clone())) + .nest("/admin", admin::router(state.clone())) + .nest("/static", axum_static::static_router("static")); let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap(); axum::serve(listener, app).await?; diff --git a/crates/backend/src/markup_response.rs b/crates/backend/src/markup_response.rs new file mode 100644 index 0000000..9edbd79 --- /dev/null +++ b/crates/backend/src/markup_response.rs @@ -0,0 +1,47 @@ +use axum::response::{Html, IntoResponse}; +use http::StatusCode; +use maud::{html, Markup, DOCTYPE}; + +pub struct MarkupResponse { + title: String, + body: Markup, +} + +impl MarkupResponse { + pub fn new(body: Markup, title: &str) -> Self { + Self { + title: title.to_owned(), + body, + } + } +} + +impl IntoResponse for MarkupResponse { + fn into_response(self) -> axum::response::Response { + Html::from(base_page(&self.title, &self.body).into_string()).into_response() + } +} + +fn base_page(title: &str, markup: &Markup) -> Markup { + html! { + (DOCTYPE) + html lang="en" { + head { + meta charset="utf-8"; + meta name="viewport" content="width=device-width, initial-scale=1"; + title { (title) } + // link rel="stylesheet" href="/style"; + } + body { (markup) } + } + } +} + +pub fn simple_error_page(status: StatusCode) -> MarkupResponse { + MarkupResponse::new( + html! { + img src=(format!("https://http.cat/{}", status.as_u16())); + }, + "Error", + ) +} diff --git a/crates/backend/src/stat.rs b/crates/backend/src/stat.rs index f551d1a..57480aa 100644 --- a/crates/backend/src/stat.rs +++ b/crates/backend/src/stat.rs @@ -3,16 +3,16 @@ use std::sync::Arc; use axum::{extract::State, routing::get, Json, Router}; use sqlx::{types::Uuid, Pool, Postgres}; -use crate::model::Chat; +use crate::{model::Chat, state::AppState}; // TODO: /stat/* should require authentication -pub fn router(pool: Pool) -> Router { - Router::new().route("/chats", get(chats)).with_state(pool) +pub fn router(state: AppState) -> Router { + Router::new().route("/chats", get(chats)).with_state(state) } -async fn chats(State(pool): State>) -> Json> { +async fn chats(State(state): State) -> Json> { let r = sqlx::query_as!(Chat, "select * from chats;") - .fetch_all(&pool) + .fetch_all(state.pool()) .await .unwrap(); diff --git a/crates/backend/src/state.rs b/crates/backend/src/state.rs new file mode 100644 index 0000000..31a7a3e --- /dev/null +++ b/crates/backend/src/state.rs @@ -0,0 +1,74 @@ +use axum::response::IntoResponse; +use http::StatusCode; +use sqlx::{Pool, Postgres}; +use thiserror::Error; + +use crate::model::{Chat, Message}; + +type Result = std::result::Result; +const DB_URL: &str = "postgres://localhost/chatdings"; + +#[derive(Debug, Clone)] +pub struct AppState { + pool: Pool, +} + +impl AppState { + pub async fn init() -> Result { + let pool = Pool::::connect(DB_URL).await?; + + sqlx::migrate!() + .run(&pool) + .await + .expect("migration should not fail"); + + Ok(Self { pool }) + } + + pub async fn fetch_chat_by_url_path(&self, url_path: &str) -> Result { + Ok( + sqlx::query_as!(Chat, r#"select * from chats where url_path = $1"#, url_path) + .fetch_one(&self.pool) + .await?, + ) + } + + pub async fn fetch_messages(&self, chat: &Chat) -> Result> { + Ok(sqlx::query_as!( + Message, + r#"select * from messages where chat_id = $1"#, + chat.id + ) + .fetch_all(&self.pool) + .await?) + } + + pub async fn send_message(&self, chat: &Chat, content: String, from_admin: bool) -> Result<()> { + todo!() + } + + pub fn pool(&self) -> &Pool { + &self.pool + } +} + +#[derive(Error, Debug)] +pub enum AppStateError { + #[error("database error")] + Sqlx(#[from] sqlx::Error), +} + +impl IntoResponse for AppStateError { + fn into_response(self) -> axum::response::Response { + todo!() + } +} + +impl From for StatusCode { + fn from(value: AppStateError) -> Self { + match value { + AppStateError::Sqlx(sqlx::Error::RowNotFound) => Self::NOT_FOUND, + AppStateError::Sqlx(_) => Self::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/crates/backend/src/ws.rs b/crates/backend/src/ws.rs new file mode 100644 index 0000000..6153432 --- /dev/null +++ b/crates/backend/src/ws.rs @@ -0,0 +1,15 @@ +use axum::{ + extract::{ws::WebSocket, Path, State, WebSocketUpgrade}, + response::Response, +}; +use sqlx::{Pool, Postgres}; + +fn get( + Path(url_path): Path, + ws: WebSocketUpgrade, + State(pool): State>, +) -> Response { + todo!() +} + +// fn handle_socket(socket: WebSocket, pool: Pool) diff --git a/static/chat.js b/static/chat.js new file mode 100644 index 0000000..e39157c --- /dev/null +++ b/static/chat.js @@ -0,0 +1,26 @@ +init_polling(); + +// terrible hack. uses polling. +function init_polling() { + let history_el = document.getElementById("history"); + let msg_template = document.querySelector("#chatmessage"); + setInterval(() => { + let messages = document.getElementsByClassName("message"); + let last_msg_uuid = messages[messages.length - 1].id; + fetch(`/poll/${last_msg_uuid}`) + .then(data => data.text().then(bleh => { + let new_messages = JSON.parse(bleh); + for (const message of new_messages) { + const clone = msg_template.content.cloneNode(true); + let msgclone = clone.querySelector("div"); + msgclone.id = message.id; + msgclone.classList.add(message.from_admin ? "from_admin" : "from_user"); + let content = msgclone.querySelector("p"); + content.textContent = message.content; + let ts = msgclone.querySelector(".timestamp"); + ts.textContent = message.timestamp; + history_el.appendChild(msgclone); + } + })); + }, 5000) +}