diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2024-09-14 00:16:51 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2024-09-14 00:18:08 -0400 |
| commit | 5249aad35741f6f029c442a04d679937fb91d2bb (patch) | |
| tree | c75085a93538367ae3f5da8f64f90b33a3c8feef | |
| parent | 407ca8df6284ce1a4c649b018c7326fd195bbd26 (diff) | |
Placeholder UX, probably
| -rw-r--r-- | .sqlx/query-79e6e76bc3f974248bf8a7f4f89221feb030ac8f42cf594c875ecd3bfeca3eb7.json | 26 | ||||
| -rw-r--r-- | Cargo.lock | 83 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | js/channel.js | 34 | ||||
| -rw-r--r-- | src/channel/repo/channels.rs | 16 | ||||
| -rw-r--r-- | src/channel/routes.rs | 3 | ||||
| -rw-r--r-- | src/index/app.rs | 10 | ||||
| -rw-r--r-- | src/index/routes.rs | 45 | ||||
| -rw-r--r-- | src/index/templates.rs | 39 |
9 files changed, 251 insertions, 7 deletions
diff --git a/.sqlx/query-79e6e76bc3f974248bf8a7f4f89221feb030ac8f42cf594c875ecd3bfeca3eb7.json b/.sqlx/query-79e6e76bc3f974248bf8a7f4f89221feb030ac8f42cf594c875ecd3bfeca3eb7.json new file mode 100644 index 0000000..b46d940 --- /dev/null +++ b/.sqlx/query-79e6e76bc3f974248bf8a7f4f89221feb030ac8f42cf594c875ecd3bfeca3eb7.json @@ -0,0 +1,26 @@ +{ + "db_name": "SQLite", + "query": "\n select id as \"id: Id\", name\n from channel\n where id = $1\n ", + "describe": { + "columns": [ + { + "name": "id: Id", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "name", + "ordinal": 1, + "type_info": "Text" + } + ], + "parameters": { + "Right": 1 + }, + "nullable": [ + false, + false + ] + }, + "hash": "79e6e76bc3f974248bf8a7f4f89221feb030ac8f42cf594c875ecd3bfeca3eb7" +} @@ -775,9 +775,11 @@ dependencies = [ "futures", "headers", "maud", + "mime_guess", "password-hash", "rand", "rand_core", + "rust-embed", "serde", "serde_json", "sqlx", @@ -1064,6 +1066,16 @@ 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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1413,6 +1425,40 @@ dependencies = [ ] [[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", + "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" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1444,6 +1490,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2040,6 +2095,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + +[[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2111,6 +2175,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2188,6 +2262,15 @@ dependencies = [ ] [[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -13,9 +13,11 @@ clap = { version = "4.5.16", features = ["derive", "env"] } futures = "0.3.30" headers = "0.4.0" maud = { version = "0.26.0", features = ["axum"] } +mime_guess = "2.0.5" password-hash = { version = "0.5.0", features = ["std"] } rand = "0.8.5" rand_core = { version = "0.6.4", features = ["getrandom"] } +rust-embed = "8.5.0" serde = { version = "1.0.209", features = ["derive"] } serde_json = "1.0.128" sqlx = { version = "0.8.1", features = ["chrono", "runtime-tokio", "sqlite"] } diff --git a/js/channel.js b/js/channel.js new file mode 100644 index 0000000..96d1061 --- /dev/null +++ b/js/channel.js @@ -0,0 +1,34 @@ +"use strict"; + +function ready(callback) { + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', callback); + } else { + callback(); + } +} + +ready(() => { + let template = document.querySelector('#message').content; + + document.querySelectorAll('link[rel=events]').forEach(elem => { + let url = elem.getAttribute("href"); + let source = new EventSource(url); + source.addEventListener('message', message => { + let body = JSON.parse(message.data); + + document.querySelectorAll('.messages').forEach(elem => { + let message = template.cloneNode(true); + + message.querySelectorAll('.sender') + .forEach(elem => elem.textContent = body.sender.name); + message.querySelectorAll('.message') + .forEach(elem => elem.textContent = body.body); + message.querySelectorAll('.sent_at') + .forEach(elem => elem.textContent = body.sent_at); + + message.childNodes.forEach(node => elem.appendChild(node)); + }); + }); + }); +}) diff --git a/src/channel/repo/channels.rs b/src/channel/repo/channels.rs index 6fb0c23..fc52aa3 100644 --- a/src/channel/repo/channels.rs +++ b/src/channel/repo/channels.rs @@ -44,6 +44,22 @@ impl<'c> Channels<'c> { Ok(channel) } + pub async fn by_id(&mut self, channel: Id) -> Result<Channel, BoxedError> { + let channel = sqlx::query_as!( + Channel, + r#" + select id as "id: Id", name + from channel + where id = $1 + "#, + channel, + ) + .fetch_one(&mut *self.0) + .await?; + + Ok(channel) + } + pub async fn all(&mut self) -> Result<Vec<Channel>, BoxedError> { let channels = sqlx::query_as!( Channel, diff --git a/src/channel/routes.rs b/src/channel/routes.rs index 4f83a8b..eae68a2 100644 --- a/src/channel/routes.rs +++ b/src/channel/routes.rs @@ -1,6 +1,5 @@ use axum::{ extract::{Form, Path, State}, - http::StatusCode, response::{ sse::{self, Sse}, IntoResponse, Redirect, @@ -59,7 +58,7 @@ async fn on_send( .send(&login, &channel, &form.message, &sent_at) .await?; - Ok(StatusCode::ACCEPTED) + Ok(Redirect::to(&format!("/{}", channel))) } async fn on_events( diff --git a/src/index/app.rs b/src/index/app.rs index 6075c6f..b315b45 100644 --- a/src/index/app.rs +++ b/src/index/app.rs @@ -1,7 +1,7 @@ use sqlx::sqlite::SqlitePool; use crate::{ - channel::repo::channels::{Channel, Provider as _}, + channel::repo::channels::{Channel, Id as ChannelId, Provider as _}, error::BoxedError, }; @@ -21,4 +21,12 @@ impl<'a> Index<'a> { Ok(channels) } + + pub async fn channel(&self, channel: ChannelId) -> Result<Channel, BoxedError> { + let mut tx = self.db.begin().await?; + let channel = tx.channels().by_id(channel).await?; + tx.commit().await?; + + Ok(channel) + } } diff --git a/src/index/routes.rs b/src/index/routes.rs index c57278f..07b6001 100644 --- a/src/index/routes.rs +++ b/src/index/routes.rs @@ -1,8 +1,17 @@ -use axum::{extract::State, routing::get, Router}; +use axum::{ + extract::{Path, State}, + http::{header, StatusCode}, + response::IntoResponse, + routing::get, + Router, +}; use maud::Markup; use super::templates; -use crate::{app::App, error::InternalError, login::repo::logins::Login}; +use crate::{ + app::App, channel::repo::channels::Id as ChannelId, error::InternalError, + login::repo::logins::Login, +}; async fn index(State(app): State<App>, login: Option<Login>) -> Result<Markup, InternalError> { match login { @@ -17,6 +26,36 @@ async fn index_authenticated(app: App, login: Login) -> Result<Markup, InternalE Ok(templates::authenticated(login, &channels)) } +#[derive(rust_embed::Embed)] +#[folder = "js"] +struct Js; + +async fn js(Path(path): Path<String>) -> impl IntoResponse { + let mime = mime_guess::from_path(&path).first_or_octet_stream(); + + match Js::get(&path) { + Some(file) => ( + StatusCode::OK, + [(header::CONTENT_TYPE, mime.as_ref())], + file.data, + ) + .into_response(), + None => (StatusCode::NOT_FOUND, "").into_response(), + } +} + +async fn channel( + State(app): State<App>, + _: Login, + Path(channel): Path<ChannelId>, +) -> Result<Markup, InternalError> { + let channel = app.index().channel(channel).await?; + Ok(templates::channel(&channel)) +} + pub fn router() -> Router<App> { - Router::new().route("/", get(index)) + Router::new() + .route("/", get(index)) + .route("/js/*path", get(js)) + .route("/:channel", get(channel)) } diff --git a/src/index/templates.rs b/src/index/templates.rs index 38cd93f..7472fd0 100644 --- a/src/index/templates.rs +++ b/src/index/templates.rs @@ -33,7 +33,9 @@ fn channel_list<'c>(channels: impl IntoIterator<Item = &'c Channel>) -> Markup { fn channel_list_entry(channel: &Channel) -> Markup { html! { li { - (channel.name) " (" (channel.id) ")" + a href=(format!("/{}", channel.id)) { + (channel.name) " (" (channel.id) ")" + } } } } @@ -87,3 +89,38 @@ fn login_form() -> Markup { } } } + +pub fn channel(channel: &Channel) -> Markup { + html! { + (DOCTYPE) + head { + title { "hi - " (channel.name) } + script src="/js/channel.js" {} + template id="message" { + p { + span.sender { "(sender)" } + ": " + span.message { "(message)" } + " (at " + span.sent_at { "(sent_at)" } + ")" } + } + link rel="events" href=(format!("/{}/events", channel.id)) {} + } + body { + section class="messages" {} + section { + form action=(format!("/{}/send", channel.id)) method="post" { + label { + "message" + input name="message" type="text" autofocus {} + } + button { "send" } + } + } + section { + a href="/" { "back" } + } + } + } +} |
