From 74f80d5c11d0a212a545f053bfd4ca160bcedcd8 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 19 Feb 2025 21:22:03 -0500 Subject: Upgrade Axum to 0.8.1. --- Cargo.lock | 49 ++++++++++--------------------------------- Cargo.toml | 4 ++-- src/channel/routes/mod.rs | 4 ++-- src/clock.rs | 1 - src/event/extract.rs | 41 ++++++++++++++++++++++++++++++------ src/invite/routes/mod.rs | 4 ++-- src/message/routes/mod.rs | 2 +- src/token/extract/cookie.rs | 1 - src/token/extract/identity.rs | 18 ++++++++++++++-- src/ui/routes/mod.rs | 6 +++--- 10 files changed, 72 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a056285..dcf39b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,13 +140,13 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.7.9" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" dependencies = [ - "async-trait", "axum-core", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -174,11 +174,10 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" dependencies = [ - "async-trait", "bytes", "futures-util", "http", @@ -195,25 +194,25 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.6" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b" dependencies = [ "axum", "axum-core", "bytes", "cookie", - "fastrand", + "form_urlencoded", "futures-util", "headers", "http", "http-body", "http-body-util", "mime", - "multer", "pin-project-lite", "serde", "serde_html_form", + "serde_path_to_error", "tower", "tower-layer", "tower-service", @@ -518,15 +517,6 @@ dependencies = [ "serde", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1218,9 +1208,9 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" @@ -1264,23 +1254,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "memchr", - "mime", - "spin", - "version_check", -] - [[package]] name = "num-bigint-dig" version = "0.8.4" diff --git a/Cargo.toml b/Cargo.toml index 86549b9..4f3615b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,8 +24,8 @@ assets = [ [dependencies] argon2 = "0.5.3" async-trait = "0.1.86" -axum = { version = "0.7.9", features = ["form"] } -axum-extra = { version = "0.9.6", features = ["cookie", "query", "typed-header"] } +axum = { version = "0.8.1", features = ["form"] } +axum-extra = { version = "0.10.0", features = ["cookie", "query", "typed-header"] } chrono = { version = "0.4.39", features = ["serde"] } clap = { version = "4.5.30", features = ["derive", "env"] } futures = "0.3.31" diff --git a/src/channel/routes/mod.rs b/src/channel/routes/mod.rs index 696bd72..c1ef5cd 100644 --- a/src/channel/routes/mod.rs +++ b/src/channel/routes/mod.rs @@ -14,6 +14,6 @@ mod test; pub fn router() -> Router { Router::new() .route("/api/channels", post(post::handler)) - .route("/api/channels/:channel", post(channel::post::handler)) - .route("/api/channels/:channel", delete(channel::delete::handler)) + .route("/api/channels/{channel}", post(channel::post::handler)) + .route("/api/channels/{channel}", delete(channel::delete::handler)) } diff --git a/src/clock.rs b/src/clock.rs index 242bcdf..acd0df0 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -21,7 +21,6 @@ impl RequestedAt { } } -#[async_trait::async_trait] impl FromRequestParts for RequestedAt where S: Send + Sync, diff --git a/src/event/extract.rs b/src/event/extract.rs index e3021e2..4a35937 100644 --- a/src/event/extract.rs +++ b/src/event/extract.rs @@ -1,7 +1,7 @@ use std::ops::Deref; use axum::{ - extract::FromRequestParts, + extract::{FromRequestParts, OptionalFromRequestParts}, http::{request::Parts, HeaderName, HeaderValue}, }; use axum_extra::typed_header::TypedHeader; @@ -44,7 +44,6 @@ where } } -#[async_trait::async_trait] impl FromRequestParts for LastEventId where S: Send + Sync, @@ -53,12 +52,35 @@ where type Rejection = as FromRequestParts>::Rejection; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { - // This is purely for ergonomics: it allows `RequestedAt` to be extracted - // without having to wrap it in `Extension<>`. Callers _can_ still do that, + // This is purely for ergonomics: it allows `LastEventId` to be extracted + // without having to wrap it in `TypedHeader<>`. Callers _can_ still do that, // but they aren't forced to. - let TypedHeader(requested_at) = TypedHeader::from_request_parts(parts, state).await?; + let header = + as FromRequestParts>::from_request_parts(parts, state).await?; - Ok(requested_at) + Ok(header.into()) + } +} + +impl OptionalFromRequestParts for LastEventId +where + S: Send + Sync, + T: Serialize + DeserializeOwned, +{ + type Rejection = as FromRequestParts>::Rejection; + + async fn from_request_parts( + parts: &mut Parts, + state: &S, + ) -> Result, Self::Rejection> { + // This is purely for ergonomics: it allows `Option` to be extracted + // without having to wrap it in `TypedHeader<>`. Callers _can_ still do that, + // but they aren't forced to. + let header = + as OptionalFromRequestParts>::from_request_parts(parts, state) + .await?; + + Ok(header.map(Self::from)) } } @@ -71,6 +93,13 @@ impl Deref for LastEventId { } } +impl From>> for LastEventId { + fn from(header: TypedHeader) -> Self { + let TypedHeader(value) = header; + value + } +} + impl From for LastEventId { fn from(value: T) -> Self { Self(value) diff --git a/src/invite/routes/mod.rs b/src/invite/routes/mod.rs index 2f7375c..a25dcd9 100644 --- a/src/invite/routes/mod.rs +++ b/src/invite/routes/mod.rs @@ -13,6 +13,6 @@ mod test; pub fn router() -> Router { Router::new() .route("/api/invite", post(post::handler)) - .route("/api/invite/:invite", get(invite::get::handler)) - .route("/api/invite/:invite", post(invite::post::handler)) + .route("/api/invite/{invite}", get(invite::get::handler)) + .route("/api/invite/{invite}", post(invite::post::handler)) } diff --git a/src/message/routes/mod.rs b/src/message/routes/mod.rs index dfe8628..85c192f 100644 --- a/src/message/routes/mod.rs +++ b/src/message/routes/mod.rs @@ -5,5 +5,5 @@ use crate::app::App; mod message; pub fn router() -> Router { - Router::new().route("/api/messages/:message", delete(message::delete::handler)) + Router::new().route("/api/messages/{message}", delete(message::delete::handler)) } diff --git a/src/token/extract/cookie.rs b/src/token/extract/cookie.rs index af5787d..df03aea 100644 --- a/src/token/extract/cookie.rs +++ b/src/token/extract/cookie.rs @@ -71,7 +71,6 @@ impl Identity { } } -#[async_trait::async_trait] impl FromRequestParts for Identity where S: Send + Sync, diff --git a/src/token/extract/identity.rs b/src/token/extract/identity.rs index a69f509..acfd7ae 100644 --- a/src/token/extract/identity.rs +++ b/src/token/extract/identity.rs @@ -1,5 +1,5 @@ use axum::{ - extract::{FromRequestParts, State}, + extract::{FromRequestParts, OptionalFromRequestParts, State}, http::request::Parts, response::{IntoResponse, Response}, }; @@ -20,7 +20,6 @@ pub struct Identity { pub login: Login, } -#[async_trait::async_trait] impl FromRequestParts for Identity { type Rejection = LoginError; @@ -39,6 +38,21 @@ impl FromRequestParts for Identity { } } +impl OptionalFromRequestParts for Identity { + type Rejection = LoginError; + + async fn from_request_parts( + parts: &mut Parts, + state: &App, + ) -> Result, Self::Rejection> { + match >::from_request_parts(parts, state).await { + Ok(identity) => Ok(Some(identity)), + Err(LoginError::Unauthorized) => Ok(None), + Err(other) => Err(other), + } + } +} + pub enum LoginError { Failure(E), Unauthorized, diff --git a/src/ui/routes/mod.rs b/src/ui/routes/mod.rs index 48b3f90..7877dba 100644 --- a/src/ui/routes/mod.rs +++ b/src/ui/routes/mod.rs @@ -13,14 +13,14 @@ mod setup; pub fn router(app: &App) -> Router { [ Router::new() - .route("/*path", get(path::get::handler)) + .route("/{*path}", get(path::get::handler)) .route("/setup", get(setup::get::handler)), Router::new() .route("/", get(get::handler)) .route("/me", get(me::get::handler)) .route("/login", get(login::get::handler)) - .route("/ch/:channel", get(ch::channel::get::handler)) - .route("/invite/:invite", get(invite::invite::get::handler)) + .route("/ch/{channel}", get(ch::channel::get::handler)) + .route("/invite/{invite}", get(invite::invite::get::handler)) .route_layer(middleware::from_fn_with_state(app.clone(), setup_required)), ] .into_iter() -- cgit v1.2.3 From 615ec470e26492c8c73c356e147ddabba6aa15ce Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Wed, 19 Feb 2025 21:32:01 -0500 Subject: Upgrade to latest thiserror --- Cargo.lock | 30 +++++------------------------- Cargo.toml | 2 +- src/name.rs | 2 +- 3 files changed, 7 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcf39b0..e061bfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1402,7 +1402,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 1.0.69", + "thiserror", "tokio", "tokio-stream", "unicode-casefold", @@ -1849,7 +1849,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.11", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -1933,7 +1933,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.11", + "thiserror", "tracing", "whoami", ] @@ -1971,7 +1971,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.11", + "thiserror", "tracing", "whoami", ] @@ -2071,33 +2071,13 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ - "thiserror-impl 2.0.11", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4f3615b..15c0aa7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ serde_json = "1.0.138" # Pinned to maintain libsqlite3 version match between this and rusqlite. See # also: sqlx = { version = "=0.8.3", features = ["chrono", "runtime-tokio", "sqlite"] } -thiserror = "1.0.69" +thiserror = "2.0.11" tokio = { version = "1.43.0", features = ["rt", "macros", "rt-multi-thread"] } tokio-stream = { version = "0.1.17", features = ["sync"] } unicode-casefold = "0.2.0" diff --git a/src/name.rs b/src/name.rs index 9187d33..9861bc1 100644 --- a/src/name.rs +++ b/src/name.rs @@ -50,7 +50,7 @@ impl Name { #[derive(Debug, thiserror::Error)] pub enum Error { - #[error("stored canonical form {0:#?} does not match computed canonical form {:#?} for name {:#?}", .1.as_str(), .2.as_str())] + #[error("stored canonical form {stored:#?} does not match computed canonical form {computed:#?} for name {expected:#?}", stored=.0, computed=.1.as_str(), expected=.2.as_str())] CanonicalMismatch(String, ident::String, nfc::String), } -- cgit v1.2.3 From d57d68116ee8e5af583ef0bb1f87cf3f40d2b845 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 20 Feb 2025 17:21:18 -0500 Subject: Let Svelte's `$derived` handling figure out update ordering for the channels list. This fixes a bug. To reproduce: 1. Open the client and log in. 2. Create a new channel using the `create channel` UI. The expected result - and the behaviour after this commit - is that the newly-created channel will be shown in the sidebar immediately. The buggy behaviour is that it was not, but would appear in the sidebar once the client is reloaded. The channel would also not appear for other clients until they reloaded. I'm not actually completely sure of _why_ this fixes the bug, but it does. --- ui/routes/(app)/+layout.svelte | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index cbfef54..3752bef 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -19,14 +19,8 @@ let loading = $state(true); let channel = $derived($page.params.channel); - let rawChannels; - channelsList.subscribe((val) => { - rawChannels = val.channels; - }); - let rawMessages; - messages.subscribe((val) => { - rawMessages = val; - }); + let rawChannels = $derived($channelsList.channels); + let rawMessages = $derived($messages); let enrichedChannels = $derived.by(() => { const channels = rawChannels; -- cgit v1.2.3 From 60f6b298329728fc8a91cd8c688c9b38fa302f0d Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 20 Feb 2025 18:41:18 -0500 Subject: Remove the last lingering Svelte4-style event bindings. --- ui/routes/(app)/+layout.svelte | 8 ++++---- ui/routes/(app)/ch/[channel]/+page.svelte | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index 3752bef..5ec84d9 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -99,18 +99,18 @@ } }); - function beforeUnload(evt) { - evt.preventDefault(); + function onbeforeunload(event) { + event.preventDefault(); if (events !== null) { events.close(); } // For some compat reasons? - evt.returnValue = ''; + event.returnValue = ''; return ''; } - + diff --git a/ui/routes/(app)/ch/[channel]/+page.svelte b/ui/routes/(app)/ch/[channel]/+page.svelte index dbdb507..9335198 100644 --- a/ui/routes/(app)/ch/[channel]/+page.svelte +++ b/ui/routes/(app)/ch/[channel]/+page.svelte @@ -58,7 +58,7 @@ } let lastReadCallback = null; - function handleScroll() { + function onscroll() { clearTimeout(lastReadCallback); // Fine if lastReadCallback is null still. lastReadCallback = setTimeout(setLastRead, 2 * 1000); } @@ -66,7 +66,7 @@ -
+
-- cgit v1.2.3 From d9beb27e0e1bdaff8207382b664c8bc93af2468c Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 20 Feb 2025 19:32:49 -0500 Subject: We no longer need async-trait. It was here to support axum 0.7.x. --- Cargo.lock | 12 ------------ Cargo.toml | 1 - 2 files changed, 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e061bfb..b6ea4d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -112,17 +112,6 @@ dependencies = [ "password-hash", ] -[[package]] -name = "async-trait" -version = "0.1.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atoi" version = "2.0.0" @@ -1382,7 +1371,6 @@ name = "pilcrow" version = "0.1.0" dependencies = [ "argon2", - "async-trait", "axum", "axum-extra", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 15c0aa7..b3ac755 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,6 @@ assets = [ [dependencies] argon2 = "0.5.3" -async-trait = "0.1.86" axum = { version = "0.8.1", features = ["form"] } axum-extra = { version = "0.10.0", features = ["cookie", "query", "typed-header"] } chrono = { version = "0.4.39", features = ["serde"] } -- cgit v1.2.3 From d0cc34800243807dc1e4d352375566fde3b0f950 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 20 Feb 2025 21:15:44 -0500 Subject: Use axios to compute event stream URL, why not. It's amazing what you can learn by skimming the docs. --- ui/lib/apiServer.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index e52daff..e541d43 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -55,9 +55,13 @@ export async function acceptInvite(inviteId, username, password) { } export function subscribeToEvents(resumePoint) { - const eventsUrl = new URL('/api/events', window.location); - eventsUrl.searchParams.append('resume_point', resumePoint); - const evtSource = new EventSource(eventsUrl.toString()); + const eventsUrl = apiServer.getUri({ + url: '/events', + params: { + resume_point: resumePoint + } + }); + const evtSource = new EventSource(eventsUrl); // TODO: this should process all incoming events and store them. // TODO: eventually we'll need to handle expiring old info, so as not to use // infinite browser memory. -- cgit v1.2.3 From 70525c018dfd31a27805bb1fb666501b73d43e21 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Thu, 20 Feb 2025 21:37:33 -0500 Subject: Upgrade to Rust 1.85 and Rust 2024 edition. There are a couple of migration suggestions from `cargo fix --edition` that I have deliberately skipped, which are intended to make sure that the changes to `if let` scoping don't bite us. They don't, I'm pretty sure, and if I turn out to be wrong, I'd rather fix the scoping issues (as they arise) than use `match` (`cargo fix --edition`'s suggestion). This change also includes a bulk reformat and a clippy cleanup. NOTA BENE: As this requires a new Rust toolchain, you'll need to update Rust (`rustup update`, normally) or the server won't build. This also applies to the Debian builder Docker image; it'll need to be rebuilt (from scratch, pulling its base image again) as well. --- Cargo.toml | 4 ++-- src/boot/routes/mod.rs | 2 +- src/broadcast.rs | 8 ++++---- src/channel/app.rs | 5 +++-- src/channel/history.rs | 4 ++-- src/channel/repo.rs | 6 +++--- src/channel/routes/channel/post.rs | 2 +- src/channel/routes/mod.rs | 2 +- src/channel/routes/post.rs | 2 +- src/channel/snapshot.rs | 2 +- src/cli.rs | 2 +- src/clock.rs | 2 +- src/db/backup.rs | 4 ++-- src/event/app.rs | 9 ++++----- src/event/extract.rs | 4 ++-- src/event/repo.rs | 6 +++--- src/event/routes/get.rs | 5 ++--- src/event/routes/mod.rs | 2 +- src/event/routes/test/resume.rs | 26 ++++++++++++++++---------- src/invite/app.rs | 6 +++--- src/invite/repo.rs | 6 +++--- src/invite/routes/mod.rs | 2 +- src/login/app.rs | 2 +- src/login/create.rs | 10 +++++----- src/login/history.rs | 4 ++-- src/login/repo.rs | 8 ++++---- src/login/routes/mod.rs | 2 +- src/login/snapshot.rs | 2 +- src/message/app.rs | 4 ++-- src/message/event.rs | 2 +- src/message/history.rs | 4 ++-- src/message/repo.rs | 8 ++++---- src/message/routes/mod.rs | 2 +- src/message/snapshot.rs | 2 +- src/normalize/string.rs | 2 +- src/setup/app.rs | 4 ++-- src/setup/repo.rs | 6 +++--- src/setup/routes/mod.rs | 2 +- src/test/fixtures/cookie.rs | 2 +- src/test/fixtures/event.rs | 6 +++--- src/test/fixtures/future.rs | 10 +++++----- src/token/app.rs | 14 +++++++------- src/token/repo/auth.rs | 8 ++++---- src/token/repo/token.rs | 6 +++--- src/ui/assets.rs | 2 +- src/ui/routes/mod.rs | 2 +- 46 files changed, 115 insertions(+), 110 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b3ac755..10e6adb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "pilcrow" version = "0.1.0" -edition = "2021" -rust-version = "1.82" +edition = "2024" +rust-version = "1.85" authors = [ "Owen Jacobson ", "Kit La Touche ", diff --git a/src/boot/routes/mod.rs b/src/boot/routes/mod.rs index e4d5ac8..8fd99d3 100644 --- a/src/boot/routes/mod.rs +++ b/src/boot/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::get, Router}; +use axum::{Router, routing::get}; use crate::app::App; diff --git a/src/broadcast.rs b/src/broadcast.rs index bedc263..2792a18 100644 --- a/src/broadcast.rs +++ b/src/broadcast.rs @@ -1,8 +1,8 @@ use std::sync::{Arc, Mutex}; -use futures::{future, stream::StreamExt as _, Stream}; -use tokio::sync::broadcast::{channel, Sender}; -use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream}; +use futures::{Stream, future, stream::StreamExt as _}; +use tokio::sync::broadcast::{Sender, channel}; +use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError}; // Clones will share the same sender. #[derive(Clone)] @@ -47,7 +47,7 @@ where // panic: if ``channel`` has not been previously registered, and was not // part of the initial set of channels. - pub fn subscribe(&self) -> impl Stream + std::fmt::Debug { + pub fn subscribe(&self) -> impl Stream + std::fmt::Debug + use { let rx = self.sender().subscribe(); BroadcastStream::from(rx).scan((), |(), r| { diff --git a/src/channel/app.rs b/src/channel/app.rs index 1e341e3..dc9e584 100644 --- a/src/channel/app.rs +++ b/src/channel/app.rs @@ -3,13 +3,14 @@ use itertools::Itertools; use sqlx::sqlite::SqlitePool; use super::{ + Channel, Id, repo::{LoadError, Provider as _}, - validate, Channel, Id, + validate, }; use crate::{ clock::DateTime, db::{Duplicate as _, NotFound as _}, - event::{repo::Provider as _, Broadcaster, Event, Sequence}, + event::{Broadcaster, Event, Sequence, repo::Provider as _}, message::{self, repo::Provider as _}, name::{self, Name}, }; diff --git a/src/channel/history.rs b/src/channel/history.rs index ef2120d..4af46ce 100644 --- a/src/channel/history.rs +++ b/src/channel/history.rs @@ -1,8 +1,8 @@ use itertools::Itertools as _; use super::{ - event::{Created, Deleted, Event}, Channel, Id, + event::{Created, Deleted, Event}, }; use crate::event::{Instant, Sequence}; @@ -42,7 +42,7 @@ impl History { // Event factories impl History { - pub fn events(&self) -> impl Iterator { + pub fn events(&self) -> impl Iterator + use<> { [self.created()] .into_iter() .merge_by(self.deleted(), Sequence::merge) diff --git a/src/channel/repo.rs b/src/channel/repo.rs index 6612151..91f245b 100644 --- a/src/channel/repo.rs +++ b/src/channel/repo.rs @@ -1,5 +1,5 @@ use futures::stream::{StreamExt as _, TryStreamExt as _}; -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ channel::{Channel, History, Id}, @@ -13,7 +13,7 @@ pub trait Provider { fn channels(&mut self) -> Channels; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn channels(&mut self) -> Channels { Channels(self) } @@ -21,7 +21,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Channels<'t>(&'t mut SqliteConnection); -impl<'c> Channels<'c> { +impl Channels<'_> { pub async fn create(&mut self, name: &Name, created: &Instant) -> Result { let id = Id::generate(); let name = name.clone(); diff --git a/src/channel/routes/channel/post.rs b/src/channel/routes/channel/post.rs index b51e691..3f14d64 100644 --- a/src/channel/routes/channel/post.rs +++ b/src/channel/routes/channel/post.rs @@ -8,7 +8,7 @@ use crate::{ app::App, clock::RequestedAt, error::{Internal, NotFound}, - message::{app::SendError, Body, Message}, + message::{Body, Message, app::SendError}, token::extract::Identity, }; diff --git a/src/channel/routes/mod.rs b/src/channel/routes/mod.rs index c1ef5cd..c917348 100644 --- a/src/channel/routes/mod.rs +++ b/src/channel/routes/mod.rs @@ -1,6 +1,6 @@ use axum::{ - routing::{delete, post}, Router, + routing::{delete, post}, }; use crate::app::App; diff --git a/src/channel/routes/post.rs b/src/channel/routes/post.rs index 2cf1cc0..72eaad6 100644 --- a/src/channel/routes/post.rs +++ b/src/channel/routes/post.rs @@ -6,7 +6,7 @@ use axum::{ use crate::{ app::App, - channel::{app, Channel}, + channel::{Channel, app}, clock::RequestedAt, error::Internal, name::Name, diff --git a/src/channel/snapshot.rs b/src/channel/snapshot.rs index 129c0d6..046ac38 100644 --- a/src/channel/snapshot.rs +++ b/src/channel/snapshot.rs @@ -1,6 +1,6 @@ use super::{ - event::{Created, Event}, Id, + event::{Created, Event}, }; use crate::{clock::DateTime, name::Name}; diff --git a/src/cli.rs b/src/cli.rs index 0d448d2..775df7f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -6,10 +6,10 @@ use std::{future, io}; use axum::{ + Router, http::header, middleware, response::{IntoResponse, Response}, - Router, }; use clap::{CommandFactory, Parser}; use sqlx::sqlite::SqlitePool; diff --git a/src/clock.rs b/src/clock.rs index acd0df0..4341266 100644 --- a/src/clock.rs +++ b/src/clock.rs @@ -1,6 +1,6 @@ use axum::{ extract::{Extension, FromRequestParts, Request}, - http::{request::Parts, StatusCode}, + http::{StatusCode, request::Parts}, middleware::Next, response::Response, }; diff --git a/src/db/backup.rs b/src/db/backup.rs index bb36aea..e3fa871 100644 --- a/src/db/backup.rs +++ b/src/db/backup.rs @@ -1,6 +1,6 @@ use rusqlite::{ - backup::{self}, Connection, + backup::{self}, }; use sqlx::sqlite::{LockedSqliteHandle, SqlitePool}; @@ -28,7 +28,7 @@ impl<'p> Backup<'p> { } } -impl<'p> Backup<'p> { +impl Backup<'_> { pub async fn backup(&mut self) -> Result<(), Error> { let mut to = self.to.acquire().await?; let mut to = Self::connection(&mut to.lock_handle().await?)?; diff --git a/src/event/app.rs b/src/event/app.rs index b309245..8661c90 100644 --- a/src/event/app.rs +++ b/src/event/app.rs @@ -1,12 +1,11 @@ use futures::{ - future, + Stream, future, stream::{self, StreamExt as _}, - Stream, }; use itertools::Itertools as _; use sqlx::sqlite::SqlitePool; -use super::{broadcaster::Broadcaster, Event, Sequence, Sequenced}; +use super::{Event, Sequence, Sequenced, broadcaster::Broadcaster}; use crate::{ channel::{self, repo::Provider as _}, login::{self, repo::Provider as _}, @@ -27,7 +26,7 @@ impl<'a> Events<'a> { pub async fn subscribe( &self, resume_at: Sequence, - ) -> Result + std::fmt::Debug, Error> { + ) -> Result + std::fmt::Debug + use<>, Error> { // Subscribe before retrieving, to catch messages broadcast while we're // querying the DB. We'll prune out duplicates later. let live_messages = self.events.subscribe(); @@ -76,7 +75,7 @@ impl<'a> Events<'a> { Ok(replay.chain(live_messages)) } - fn resume(resume_at: Sequence) -> impl for<'m> FnMut(&'m Event) -> future::Ready { + fn resume(resume_at: Sequence) -> impl for<'m> FnMut(&'m Event) -> future::Ready + use<> { let filter = Sequence::after(resume_at); move |event| future::ready(filter(event)) } diff --git a/src/event/extract.rs b/src/event/extract.rs index 4a35937..8fde1d5 100644 --- a/src/event/extract.rs +++ b/src/event/extract.rs @@ -2,10 +2,10 @@ use std::ops::Deref; use axum::{ extract::{FromRequestParts, OptionalFromRequestParts}, - http::{request::Parts, HeaderName, HeaderValue}, + http::{HeaderName, HeaderValue, request::Parts}, }; use axum_extra::typed_header::TypedHeader; -use serde::{de::DeserializeOwned, Serialize}; +use serde::{Serialize, de::DeserializeOwned}; // A typed header. When used as a bare extractor, reads from the // `Last-Event-Id` HTTP header. diff --git a/src/event/repo.rs b/src/event/repo.rs index 56beeea..ab3c449 100644 --- a/src/event/repo.rs +++ b/src/event/repo.rs @@ -1,4 +1,4 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ clock::DateTime, @@ -9,7 +9,7 @@ pub trait Provider { fn sequence(&mut self) -> Sequences; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn sequence(&mut self) -> Sequences { Sequences(self) } @@ -17,7 +17,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Sequences<'t>(&'t mut SqliteConnection); -impl<'c> Sequences<'c> { +impl Sequences<'_> { pub async fn next(&mut self, at: &DateTime) -> Result { let next = sqlx::query_scalar!( r#" diff --git a/src/event/routes/get.rs b/src/event/routes/get.rs index ceebcc9..2ca8991 100644 --- a/src/event/routes/get.rs +++ b/src/event/routes/get.rs @@ -1,9 +1,8 @@ use axum::{ extract::State, response::{ - self, + self, IntoResponse, sse::{self, Sse}, - IntoResponse, }, }; use axum_extra::extract::Query; @@ -12,7 +11,7 @@ use futures::stream::{Stream, StreamExt as _}; use crate::{ app::App, error::{Internal, Unauthorized}, - event::{app, extract::LastEventId, Event, Sequence, Sequenced as _}, + event::{Event, Sequence, Sequenced as _, app, extract::LastEventId}, token::{app::ValidateError, extract::Identity}, }; diff --git a/src/event/routes/mod.rs b/src/event/routes/mod.rs index 57ab9db..742d397 100644 --- a/src/event/routes/mod.rs +++ b/src/event/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::get, Router}; +use axum::{Router, routing::get}; use crate::app::App; diff --git a/src/event/routes/test/resume.rs b/src/event/routes/test/resume.rs index fabda0c..dc27691 100644 --- a/src/event/routes/test/resume.rs +++ b/src/event/routes/test/resume.rs @@ -5,7 +5,7 @@ use axum_extra::extract::Query; use futures::stream::{self, StreamExt as _}; use crate::{ - event::{routes::get, Sequenced as _}, + event::{Sequenced as _, routes::get}, test::fixtures::{self, future::Expect as _}, }; @@ -132,9 +132,11 @@ async fn serial_resume() { .expect_ready("zipping a finite list of events is ready immediately") .await; - assert!(events - .iter() - .all(|(event, message)| message == &event.message)); + assert!( + events + .iter() + .all(|(event, message)| message == &event.message) + ); let (event, _) = events.last().expect("this vec is non-empty"); @@ -173,9 +175,11 @@ async fn serial_resume() { .expect_ready("zipping a finite list of events is ready immediately") .await; - assert!(events - .iter() - .all(|(event, message)| message == &event.message)); + assert!( + events + .iter() + .all(|(event, message)| message == &event.message) + ); let (event, _) = events.last().expect("this vec is non-empty"); @@ -214,8 +218,10 @@ async fn serial_resume() { .expect_ready("zipping a finite list of events is ready immediately") .await; - assert!(events - .iter() - .all(|(event, message)| message == &event.message)); + assert!( + events + .iter() + .all(|(event, message)| message == &event.message) + ); }; } diff --git a/src/invite/app.rs b/src/invite/app.rs index d4e877a..c56c9b3 100644 --- a/src/invite/app.rs +++ b/src/invite/app.rs @@ -1,17 +1,17 @@ use chrono::TimeDelta; use sqlx::sqlite::SqlitePool; -use super::{repo::Provider as _, Id, Invite, Summary}; +use super::{Id, Invite, Summary, repo::Provider as _}; use crate::{ clock::DateTime, db::{Duplicate as _, NotFound as _}, event::Broadcaster, login::{ - create::{self, Create}, Login, Password, + create::{self, Create}, }, name::Name, - token::{repo::Provider as _, Secret}, + token::{Secret, repo::Provider as _}, }; pub struct Invites<'a> { diff --git a/src/invite/repo.rs b/src/invite/repo.rs index 5f86e49..c1dc701 100644 --- a/src/invite/repo.rs +++ b/src/invite/repo.rs @@ -1,4 +1,4 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use super::{Id, Invite, Summary}; use crate::{ @@ -11,7 +11,7 @@ pub trait Provider { fn invites(&mut self) -> Invites; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn invites(&mut self) -> Invites { Invites(self) } @@ -19,7 +19,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Invites<'t>(&'t mut SqliteConnection); -impl<'c> Invites<'c> { +impl Invites<'_> { pub async fn create( &mut self, issuer: &Login, diff --git a/src/invite/routes/mod.rs b/src/invite/routes/mod.rs index a25dcd9..d83efc6 100644 --- a/src/invite/routes/mod.rs +++ b/src/invite/routes/mod.rs @@ -1,6 +1,6 @@ use axum::{ - routing::{get, post}, Router, + routing::{get, post}, }; use crate::app::App; diff --git a/src/login/app.rs b/src/login/app.rs index f458561..2da4d6a 100644 --- a/src/login/app.rs +++ b/src/login/app.rs @@ -1,8 +1,8 @@ use sqlx::sqlite::SqlitePool; use super::{ - create::{self, Create}, Login, Password, + create::{self, Create}, }; use crate::{clock::DateTime, event::Broadcaster, name::Name}; diff --git a/src/login/create.rs b/src/login/create.rs index 693daaf..c4cb2bb 100644 --- a/src/login/create.rs +++ b/src/login/create.rs @@ -1,9 +1,9 @@ -use sqlx::{sqlite::Sqlite, Transaction}; +use sqlx::{Transaction, sqlite::Sqlite}; -use super::{password::StoredHash, repo::Provider as _, validate, History, Password}; +use super::{History, Password, password::StoredHash, repo::Provider as _, validate}; use crate::{ clock::DateTime, - event::{repo::Provider as _, Broadcaster, Event}, + event::{Broadcaster, Event, repo::Provider as _}, name::Name, }; @@ -51,9 +51,9 @@ pub struct Validated<'a> { created_at: &'a DateTime, } -impl<'a> Validated<'a> { +impl Validated<'_> { #[must_use = "dropping a login creation attempt is likely a mistake"] - pub async fn store<'c>(self, tx: &mut Transaction<'c, Sqlite>) -> Result { + pub async fn store(self, tx: &mut Transaction<'_, Sqlite>) -> Result { let Self { name, password_hash, diff --git a/src/login/history.rs b/src/login/history.rs index 8161b0b..d67bcce 100644 --- a/src/login/history.rs +++ b/src/login/history.rs @@ -1,6 +1,6 @@ use super::{ - event::{Created, Event}, Id, Login, + event::{Created, Event}, }; use crate::event::{Instant, Sequence}; @@ -46,7 +46,7 @@ impl History { .into() } - pub fn events(&self) -> impl Iterator { + pub fn events(&self) -> impl Iterator + use<> { [self.created()].into_iter() } } diff --git a/src/login/repo.rs b/src/login/repo.rs index 1c63a4b..03f2c17 100644 --- a/src/login/repo.rs +++ b/src/login/repo.rs @@ -1,10 +1,10 @@ use futures::stream::{StreamExt as _, TryStreamExt as _}; -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ clock::DateTime, event::{Instant, Sequence}, - login::{password::StoredHash, History, Id, Login}, + login::{History, Id, Login, password::StoredHash}, name::{self, Name}, }; @@ -12,7 +12,7 @@ pub trait Provider { fn logins(&mut self) -> Logins; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn logins(&mut self) -> Logins { Logins(self) } @@ -20,7 +20,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Logins<'t>(&'t mut SqliteConnection); -impl<'c> Logins<'c> { +impl Logins<'_> { pub async fn create( &mut self, name: &Name, diff --git a/src/login/routes/mod.rs b/src/login/routes/mod.rs index bbd0c3f..ade96cb 100644 --- a/src/login/routes/mod.rs +++ b/src/login/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::post, Router}; +use axum::{Router, routing::post}; use crate::app::App; diff --git a/src/login/snapshot.rs b/src/login/snapshot.rs index e1eb96c..5c5dce0 100644 --- a/src/login/snapshot.rs +++ b/src/login/snapshot.rs @@ -1,6 +1,6 @@ use super::{ - event::{Created, Event}, Id, + event::{Created, Event}, }; use crate::name::Name; diff --git a/src/message/app.rs b/src/message/app.rs index 7bf68d1..60206f1 100644 --- a/src/message/app.rs +++ b/src/message/app.rs @@ -2,12 +2,12 @@ use chrono::TimeDelta; use itertools::Itertools; use sqlx::sqlite::SqlitePool; -use super::{repo::Provider as _, Body, Id, Message}; +use super::{Body, Id, Message, repo::Provider as _}; use crate::{ channel::{self, repo::Provider as _}, clock::DateTime, db::NotFound as _, - event::{repo::Provider as _, Broadcaster, Event, Sequence}, + event::{Broadcaster, Event, Sequence, repo::Provider as _}, login::Login, name, }; diff --git a/src/message/event.rs b/src/message/event.rs index 1cd5847..bd116b1 100644 --- a/src/message/event.rs +++ b/src/message/event.rs @@ -1,4 +1,4 @@ -use super::{snapshot::Message, Id}; +use super::{Id, snapshot::Message}; use crate::event::{Instant, Sequenced}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] diff --git a/src/message/history.rs b/src/message/history.rs index ed8f5df..1a72c08 100644 --- a/src/message/history.rs +++ b/src/message/history.rs @@ -1,8 +1,8 @@ use itertools::Itertools as _; use super::{ - event::{Deleted, Event, Sent}, Id, Message, + event::{Deleted, Event, Sent}, }; use crate::event::{Instant, Sequence}; @@ -58,7 +58,7 @@ impl History { }) } - pub fn events(&self) -> impl Iterator { + pub fn events(&self) -> impl Iterator + use<> { [self.sent()] .into_iter() .merge_by(self.deleted(), Sequence::merge) diff --git a/src/message/repo.rs b/src/message/repo.rs index 14f8eaf..8a0a72c 100644 --- a/src/message/repo.rs +++ b/src/message/repo.rs @@ -1,6 +1,6 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; -use super::{snapshot::Message, Body, History, Id}; +use super::{Body, History, Id, snapshot::Message}; use crate::{ channel, clock::DateTime, @@ -12,7 +12,7 @@ pub trait Provider { fn messages(&mut self) -> Messages; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn messages(&mut self) -> Messages { Messages(self) } @@ -20,7 +20,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Messages<'t>(&'t mut SqliteConnection); -impl<'c> Messages<'c> { +impl Messages<'_> { pub async fn create( &mut self, channel: &channel::History, diff --git a/src/message/routes/mod.rs b/src/message/routes/mod.rs index 85c192f..00b2b1a 100644 --- a/src/message/routes/mod.rs +++ b/src/message/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::delete, Router}; +use axum::{Router, routing::delete}; use crate::app::App; diff --git a/src/message/snapshot.rs b/src/message/snapshot.rs index 53b7176..d924ea1 100644 --- a/src/message/snapshot.rs +++ b/src/message/snapshot.rs @@ -1,6 +1,6 @@ use super::{ - event::{Event, Sent}, Body, Id, + event::{Event, Sent}, }; use crate::{channel, clock::DateTime, event::Instant, login}; diff --git a/src/normalize/string.rs b/src/normalize/string.rs index a0d178c..01bdd6b 100644 --- a/src/normalize/string.rs +++ b/src/normalize/string.rs @@ -1,8 +1,8 @@ use std::{fmt, string::String as StdString}; use sqlx::{ - encode::{Encode, IsNull}, Database, Decode, Type, + encode::{Encode, IsNull}, }; pub trait Normalize: Clone + Default { diff --git a/src/setup/app.rs b/src/setup/app.rs index c1f7b69..9553f40 100644 --- a/src/setup/app.rs +++ b/src/setup/app.rs @@ -5,11 +5,11 @@ use crate::{ clock::DateTime, event::Broadcaster, login::{ - create::{self, Create}, Login, Password, + create::{self, Create}, }, name::Name, - token::{repo::Provider as _, Secret}, + token::{Secret, repo::Provider as _}, }; pub struct Setup<'a> { diff --git a/src/setup/repo.rs b/src/setup/repo.rs index de93f51..ac01496 100644 --- a/src/setup/repo.rs +++ b/src/setup/repo.rs @@ -1,10 +1,10 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; pub trait Provider { fn setup(&mut self) -> Setup; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn setup(&mut self) -> Setup { Setup(self) } @@ -12,7 +12,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Setup<'t>(&'t mut SqliteConnection); -impl<'c> Setup<'c> { +impl Setup<'_> { pub async fn completed(&mut self) -> Result { let completed = sqlx::query_scalar!( r#" diff --git a/src/setup/routes/mod.rs b/src/setup/routes/mod.rs index 6054983..977a790 100644 --- a/src/setup/routes/mod.rs +++ b/src/setup/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{routing::post, Router}; +use axum::{Router, routing::post}; use crate::app::App; diff --git a/src/test/fixtures/cookie.rs b/src/test/fixtures/cookie.rs index 58777c8..fcb379f 100644 --- a/src/test/fixtures/cookie.rs +++ b/src/test/fixtures/cookie.rs @@ -5,7 +5,7 @@ use crate::{ clock::RequestedAt, login::Password, name::Name, - token::{extract::IdentityCookie, Secret}, + token::{Secret, extract::IdentityCookie}, }; pub fn not_logged_in() -> IdentityCookie { diff --git a/src/test/fixtures/event.rs b/src/test/fixtures/event.rs index de02d4d..e11f6ee 100644 --- a/src/test/fixtures/event.rs +++ b/src/test/fixtures/event.rs @@ -26,8 +26,8 @@ pub fn login(event: Event) -> Ready> { pub mod channel { use std::future::{self, Ready}; - use crate::channel::event; pub use crate::channel::Event; + use crate::channel::event; pub fn created(event: Event) -> Ready> { future::ready(match event { @@ -47,8 +47,8 @@ pub mod channel { pub mod message { use std::future::{self, Ready}; - use crate::message::event; pub use crate::message::Event; + use crate::message::event; pub fn sent(event: Event) -> Ready> { future::ready(match event { @@ -68,8 +68,8 @@ pub mod message { pub mod login { use std::future::{self, Ready}; - use crate::login::event; pub use crate::login::Event; + use crate::login::event; pub fn created(event: Event) -> Ready> { future::ready(match event { diff --git a/src/test/fixtures/future.rs b/src/test/fixtures/future.rs index 2f810a3..c0fa528 100644 --- a/src/test/fixtures/future.rs +++ b/src/test/fixtures/future.rs @@ -52,7 +52,7 @@ pub trait Expect: Sized { Self: Future>; } -impl<'a, St> Expect for stream::Next<'a, St> { +impl Expect for stream::Next<'_, St> { fn expect_ready(self, message: &str) -> Ready { Ready { future: self, @@ -131,7 +131,7 @@ pub struct Ready<'m, F> { message: &'m str, } -impl<'m, F> Future for Ready<'m, F> +impl Future for Ready<'_, F> where F: Future + std::fmt::Debug, { @@ -155,7 +155,7 @@ pub struct Wait<'m, F> { message: &'m str, } -impl<'m, F> Future for Wait<'m, F> +impl Future for Wait<'_, F> where F: Future + std::fmt::Debug, { @@ -179,7 +179,7 @@ pub struct Some<'m, F> { message: &'m str, } -impl<'m, F, T> Future for Some<'m, F> +impl Future for Some<'_, F> where F: Future> + std::fmt::Debug, { @@ -203,7 +203,7 @@ pub struct None<'m, F> { message: &'m str, } -impl<'m, F, T> Future for None<'m, F> +impl Future for None<'_, F> where F: Future> + std::fmt::Debug, { diff --git a/src/token/app.rs b/src/token/app.rs index 5c0aeb0..3f054ff 100644 --- a/src/token/app.rs +++ b/src/token/app.rs @@ -1,19 +1,18 @@ use chrono::TimeDelta; use futures::{ - future, + Stream, future, stream::{self, StreamExt as _}, - Stream, }; use sqlx::sqlite::SqlitePool; use super::{ - repo::{self, auth::Provider as _, Provider as _}, Broadcaster, Event as TokenEvent, Id, Secret, + repo::{self, Provider as _, auth::Provider as _}, }; use crate::{ clock::DateTime, db::NotFound as _, - login::{repo::Provider as _, Login, Password}, + login::{Login, Password, repo::Provider as _}, name::{self, Name}, }; @@ -120,12 +119,13 @@ impl<'a> Tokens<'a> { Ok((token, login)) } - pub async fn limit_stream( + pub async fn limit_stream( &self, token: Id, - events: impl Stream + std::fmt::Debug, - ) -> Result + std::fmt::Debug, ValidateError> + events: S, + ) -> Result + std::fmt::Debug + use, ValidateError> where + S: Stream + std::fmt::Debug, E: std::fmt::Debug, { // Subscribe, first. diff --git a/src/token/repo/auth.rs b/src/token/repo/auth.rs index b51db8c..0deed10 100644 --- a/src/token/repo/auth.rs +++ b/src/token/repo/auth.rs @@ -1,10 +1,10 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use crate::{ clock::DateTime, db::NotFound, event::{Instant, Sequence}, - login::{self, password::StoredHash, History, Login}, + login::{self, History, Login, password::StoredHash}, name::{self, Name}, }; @@ -12,7 +12,7 @@ pub trait Provider { fn auth(&mut self) -> Auth; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn auth(&mut self) -> Auth { Auth(self) } @@ -20,7 +20,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Auth<'t>(&'t mut SqliteConnection); -impl<'t> Auth<'t> { +impl Auth<'_> { pub async fn for_name(&mut self, name: &Name) -> Result<(History, StoredHash), LoadError> { let name = name.canonical(); let row = sqlx::query!( diff --git a/src/token/repo/token.rs b/src/token/repo/token.rs index 33b89d5..ff42fad 100644 --- a/src/token/repo/token.rs +++ b/src/token/repo/token.rs @@ -1,4 +1,4 @@ -use sqlx::{sqlite::Sqlite, SqliteConnection, Transaction}; +use sqlx::{SqliteConnection, Transaction, sqlite::Sqlite}; use uuid::Uuid; use crate::{ @@ -14,7 +14,7 @@ pub trait Provider { fn tokens(&mut self) -> Tokens; } -impl<'c> Provider for Transaction<'c, Sqlite> { +impl Provider for Transaction<'_, Sqlite> { fn tokens(&mut self) -> Tokens { Tokens(self) } @@ -22,7 +22,7 @@ impl<'c> Provider for Transaction<'c, Sqlite> { pub struct Tokens<'t>(&'t mut SqliteConnection); -impl<'c> Tokens<'c> { +impl Tokens<'_> { // Issue a new token for an existing login. The issued_at timestamp will // be used to control expiry, until the token is actually used. pub async fn issue( diff --git a/src/ui/assets.rs b/src/ui/assets.rs index 6a7563a..142cdf9 100644 --- a/src/ui/assets.rs +++ b/src/ui/assets.rs @@ -1,6 +1,6 @@ use ::mime::{FromStrError, Mime}; use axum::{ - http::{header, StatusCode}, + http::{StatusCode, header}, response::{IntoResponse, Response}, }; use rust_embed::EmbeddedFile; diff --git a/src/ui/routes/mod.rs b/src/ui/routes/mod.rs index 7877dba..80dc1e5 100644 --- a/src/ui/routes/mod.rs +++ b/src/ui/routes/mod.rs @@ -1,4 +1,4 @@ -use axum::{middleware, routing::get, Router}; +use axum::{Router, middleware, routing::get}; use crate::{app::App, ui::middleware::setup_required}; -- cgit v1.2.3 From 5cff84a375f64537c44f6418496f1dc1b24a1de8 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 13:08:14 -0500 Subject: Split "set up the event source" and "apply events to state" from one another. --- ui/lib/apiServer.js | 70 +----------------------------------------- ui/lib/store.js | 61 ++++++++++++++++++++++++++++++++++++ ui/routes/(app)/+layout.svelte | 3 +- 3 files changed, 64 insertions(+), 70 deletions(-) diff --git a/ui/lib/apiServer.js b/ui/lib/apiServer.js index e541d43..bb0a587 100644 --- a/ui/lib/apiServer.js +++ b/ui/lib/apiServer.js @@ -61,73 +61,5 @@ export function subscribeToEvents(resumePoint) { resume_point: resumePoint } }); - const evtSource = new EventSource(eventsUrl); - // TODO: this should process all incoming events and store them. - // TODO: eventually we'll need to handle expiring old info, so as not to use - // infinite browser memory. - /* - * Known message types as of now: - * - created: a channel is created. - * - action: ignore. - * - message: a message is created. - * - action: display message in channel. - * - message_deleted: a message is deleted. - * - action: replace message with <...>. - * - deleted: a channel is deleted. - * - action: remove channel from sidebar. - */ - evtSource.onmessage = (evt) => { - const data = JSON.parse(evt.data); - - switch (data.type) { - case 'login': - onLoginEvent(data); - break; - case 'channel': - onChannelEvent(data); - break; - case 'message': - onMessageEvent(data); - break; - } - }; - - return evtSource; -} - -function onLoginEvent(data) { - switch (data.event) { - case 'created': - logins.update((value) => value.addLogin(data.id, data.name)); - break; - } -} - -function onChannelEvent(data) { - switch (data.event) { - case 'created': - channelsList.update((value) => value.addChannel(data.id, data.name)); - break; - case 'deleted': - channelsList.update((value) => value.deleteChannel(data.id)); - messages.update((value) => value.deleteChannel(data.id)); - break; - } -} - -function onMessageEvent(data) { - switch (data.event) { - case 'sent': - messages.update((value) => - value.addMessage(data.channel, data.id, { - at: data.at, - sender: data.sender, - body: data.body - }) - ); - break; - case 'deleted': - messages.update((value) => value.deleteMessage(data.id)); - break; - } + return new EventSource(eventsUrl); } diff --git a/ui/lib/store.js b/ui/lib/store.js index 47ebbc2..c179dac 100644 --- a/ui/lib/store.js +++ b/ui/lib/store.js @@ -7,3 +7,64 @@ export const currentUser = writable(null); export const logins = writable(new Logins()); export const channelsList = writable(new Channels()); export const messages = writable(new Messages()); + +export function onEvent(event) { + switch (event.type) { + case 'login': + onLoginEvent(event); + break; + case 'channel': + onChannelEvent(event); + break; + case 'message': + onMessageEvent(event); + break; + } +} + +onEvent.fromJson = (event) => { + const parsed = JSON.parse(event); + return onEvent(parsed); +}; + +onEvent.fromMessage = (message) => { + const data = message.data; + return onEvent.fromJson(data); +}; + +function onLoginEvent(event) { + switch (event.event) { + case 'created': + logins.update((value) => value.addLogin(event.id, event.name)); + break; + } +} + +function onChannelEvent(event) { + switch (event.event) { + case 'created': + channelsList.update((value) => value.addChannel(event.id, event.name)); + break; + case 'deleted': + channelsList.update((value) => value.deleteChannel(event.id)); + messages.update((value) => value.deleteChannel(event.id)); + break; + } +} + +function onMessageEvent(event) { + switch (event.event) { + case 'sent': + messages.update((value) => + value.addMessage(event.channel, event.id, { + at: event.at, + sender: event.sender, + body: event.body + }) + ); + break; + case 'deleted': + messages.update((value) => value.deleteMessage(event.id)); + break; + } +} diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index 5ec84d9..dff9245 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -6,7 +6,7 @@ import TinyGesture from 'tinygesture'; import { boot, subscribeToEvents } from '$lib/apiServer'; - import { currentUser, logins, channelsList, messages } from '$lib/store'; + import { currentUser, logins, channelsList, messages, onEvent } from '$lib/store'; import ChannelList from '$lib/components/ChannelList.svelte'; import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; @@ -72,6 +72,7 @@ case 200: onBooted(response.data); events = subscribeToEvents(response.data.resume_point); + events.onmessage = onEvent.fromMessage; break; case 401: currentUser.set(null); -- cgit v1.2.3 From b94a32834b0d5e0e28a3aa9d57e3592692bf82ac Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 14:27:16 -0500 Subject: Write down a brief mission blurb. --- docs/mission.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 docs/mission.md diff --git a/docs/mission.md b/docs/mission.md new file mode 100644 index 0000000..ab86fd9 --- /dev/null +++ b/docs/mission.md @@ -0,0 +1,3 @@ +# Mission + +The one thing it has to do, pretty much no matter what, is deliver messages. If it can't do that, it should be down, so that nobody thinks it's delivering messages. -- cgit v1.2.3 From 84bd8df52c342bf809db627ea0463c6809274181 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:04:31 -0500 Subject: Remove references to nonexistent italic variants of FiraCode --- ui/styles/fonts.css | 109 ++++++++++++++++++++++++---------------------------- 1 file changed, 50 insertions(+), 59 deletions(-) diff --git a/ui/styles/fonts.css b/ui/styles/fonts.css index 06f69c8..c72f481 100644 --- a/ui/styles/fonts.css +++ b/ui/styles/fonts.css @@ -1,84 +1,75 @@ /*** Roboto ***/ @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } + @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Bold.ttf') format('truetype'); - font-weight: bold; - font-style: normal; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; + font-display: swap; } + @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Italic.ttf') format('truetype'); - font-weight: normal; - font-style: italic; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Italic.ttf') format('truetype'); + font-weight: normal; + font-style: italic; + font-display: swap; } + @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); - font-weight: bold; - font-style: italic; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; + font-display: swap; } /*** Archistico ***/ @font-face { - font-family: 'Archistico'; - src: url('../fonts/Archistico_Simple.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Archistico'; + src: url('../fonts/Archistico_Simple.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } + @font-face { - font-family: 'Archistico'; - src: url('../fonts/Archistico_Bold.ttf') format('truetype'); - font-weight: bold; - font-style: normal; - font-display: swap; + font-family: 'Archistico'; + src: url('../fonts/Archistico_Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; + font-display: swap; } /*** FiraCode ***/ @font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Regular.otf') format('opentype'); - font-weight: normal; - font-style: normal; - font-display: swap; -} -@font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Bold.otf') format('opentype'); - font-weight: bold; - font-style: normal; - font-display: swap; -} -@font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Italic.otf') format('opentype'); - font-weight: normal; - font-style: italic; - font-display: swap; + font-family: 'FiraCode'; + src: url('../fonts/FiraCode-Regular.otf') format('opentype'); + font-weight: normal; + font-style: normal; + font-display: swap; } + @font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-BoldItalic.otf') format('opentype'); - font-weight: bold; - font-style: italic; - font-display: swap; + font-family: 'FiraCode'; + src: url('../fonts/FiraCode-Bold.otf') format('opentype'); + font-weight: bold; + font-style: normal; + font-display: swap; } /*** Overlock ***/ @font-face { - font-family: 'Overlock'; - src: url('../fonts/Overlock-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Overlock'; + src: url('../fonts/Overlock-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } -- cgit v1.2.3 From f905b6fb1c3e34faaa3014009e994813842a7ffa Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:05:00 -0500 Subject: Remove nonexistent CSS variable --- ui/styles/fonts.css | 90 +++++++++++++++++++++++++------------------------- ui/styles/messages.css | 2 +- 2 files changed, 46 insertions(+), 46 deletions(-) diff --git a/ui/styles/fonts.css b/ui/styles/fonts.css index c72f481..280f8c6 100644 --- a/ui/styles/fonts.css +++ b/ui/styles/fonts.css @@ -1,75 +1,75 @@ /*** Roboto ***/ @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Bold.ttf') format('truetype'); - font-weight: bold; - font-style: normal; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-Italic.ttf') format('truetype'); - font-weight: normal; - font-style: italic; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-Italic.ttf') format('truetype'); + font-weight: normal; + font-style: italic; + font-display: swap; } @font-face { - font-family: 'Roboto'; - src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); - font-weight: bold; - font-style: italic; - font-display: swap; + font-family: 'Roboto'; + src: url('../fonts/Roboto-BoldItalic.ttf') format('truetype'); + font-weight: bold; + font-style: italic; + font-display: swap; } /*** Archistico ***/ @font-face { - font-family: 'Archistico'; - src: url('../fonts/Archistico_Simple.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Archistico'; + src: url('../fonts/Archistico_Simple.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } @font-face { - font-family: 'Archistico'; - src: url('../fonts/Archistico_Bold.ttf') format('truetype'); - font-weight: bold; - font-style: normal; - font-display: swap; + font-family: 'Archistico'; + src: url('../fonts/Archistico_Bold.ttf') format('truetype'); + font-weight: bold; + font-style: normal; + font-display: swap; } /*** FiraCode ***/ @font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Regular.otf') format('opentype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'FiraCode'; + src: url('../fonts/FiraCode-Regular.otf') format('opentype'); + font-weight: normal; + font-style: normal; + font-display: swap; } @font-face { - font-family: 'FiraCode'; - src: url('../fonts/FiraCode-Bold.otf') format('opentype'); - font-weight: bold; - font-style: normal; - font-display: swap; + font-family: 'FiraCode'; + src: url('../fonts/FiraCode-Bold.otf') format('opentype'); + font-weight: bold; + font-style: normal; + font-display: swap; } /*** Overlock ***/ @font-face { - font-family: 'Overlock'; - src: url('../fonts/Overlock-Regular.ttf') format('truetype'); - font-weight: normal; - font-style: normal; - font-display: swap; + font-family: 'Overlock'; + src: url('../fonts/Overlock-Regular.ttf') format('truetype'); + font-weight: normal; + font-style: normal; + font-display: swap; } diff --git a/ui/styles/messages.css b/ui/styles/messages.css index c4ef106..7d0b870 100644 --- a/ui/styles/messages.css +++ b/ui/styles/messages.css @@ -51,6 +51,7 @@ .message:hover { background-color: var(--colour-message-hover-bg); } + .message:hover * { color: var(--colour-message-hover-text); } @@ -112,7 +113,6 @@ .message-body pre { border: 1px solid #312e81; border-radius: 0.25rem; - background-color: var(--colour-message-run-text); padding: 0.25rem; } -- cgit v1.2.3 From d7b1a995b7da423531933888cde04ae6d807298b Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:13:30 -0500 Subject: Provide fallback generic fonts for our custom fonts --- ui/app.css | 2 +- ui/styles/app-bar.css | 2 +- ui/styles/forms.css | 4 ++-- ui/styles/messages.css | 2 +- ui/styles/textarea.css | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/app.css b/ui/app.css index e7da4d8..646c418 100644 --- a/ui/app.css +++ b/ui/app.css @@ -14,7 +14,7 @@ body { background-color: var(--colour-active-channel-bg); color: var(--dark-text); - font-family: 'Roboto'; + font-family: 'Roboto', sans-serif; } hr { diff --git a/ui/styles/app-bar.css b/ui/styles/app-bar.css index 17620ba..0d0a311 100644 --- a/ui/styles/app-bar.css +++ b/ui/styles/app-bar.css @@ -28,7 +28,7 @@ .app-bar > a { line-height: var(--app-bar-height); - font-family: 'Archistico'; + font-family: 'Archistico', serif; letter-spacing: 0.25rem; } diff --git a/ui/styles/forms.css b/ui/styles/forms.css index 88a6c41..eb98743 100644 --- a/ui/styles/forms.css +++ b/ui/styles/forms.css @@ -5,7 +5,7 @@ label { } label input { - font-family: 'Overlock'; + font-family: 'Overlock', cursive; display: block; width: 90%; padding: 0.25rem; @@ -14,7 +14,7 @@ label input { } form.form > button { - font-family: 'Overlock'; + font-family: 'Overlock', cursive; background-color: var(--colour-input-bg); color: var(--colour-input-text); padding: 0.25rem; diff --git a/ui/styles/messages.css b/ui/styles/messages.css index 7d0b870..4890f2c 100644 --- a/ui/styles/messages.css +++ b/ui/styles/messages.css @@ -118,5 +118,5 @@ .message-body code, .message-body pre { - font-family: 'FiraCode'; + font-family: 'FiraCode', monospace; } diff --git a/ui/styles/textarea.css b/ui/styles/textarea.css index d9be0d6..4b8602c 100644 --- a/ui/styles/textarea.css +++ b/ui/styles/textarea.css @@ -18,7 +18,7 @@ flex-grow: 1; background-color: var(--colour-input-bg); color: var(--colour-input-text); - font-family: 'FiraCode'; + font-family: 'FiraCode', monospace; } .create-message button { -- cgit v1.2.3 From b52075ea197baa5a8a301b04948f9286cfb107a9 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:15:40 -0500 Subject: Remove duplicate `content` hack from reset. According to , this was needed for old versions of Safari. However, since at least 2022, Safari has supported `content: none` just fine. Related Safari bug (still open as of this writing, comments relevnat): --- ui/styles/reset.css | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/styles/reset.css b/ui/styles/reset.css index f9fa505..5a17f02 100644 --- a/ui/styles/reset.css +++ b/ui/styles/reset.css @@ -93,6 +93,7 @@ video { /* font: inherit; */ vertical-align: baseline; } + /* HTML5 display-role reset for older browsers */ article, aside, @@ -107,24 +108,28 @@ nav, section { display: block; } + body { line-height: 1; } + ol, ul { list-style: none; } + blockquote, q { quotes: none; } + blockquote:before, blockquote:after, q:before, q:after { - content: ''; content: none; } + table { border-collapse: collapse; border-spacing: 0; -- cgit v1.2.3 From 01368abc9893caa7acd3d632fd7a2d4284e64167 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:21:43 -0500 Subject: Remove unused type attribute on textarea --- ui/lib/components/MessageInput.svelte | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/lib/components/MessageInput.svelte b/ui/lib/components/MessageInput.svelte index 5869654..1eb1d7b 100644 --- a/ui/lib/components/MessageInput.svelte +++ b/ui/lib/components/MessageInput.svelte @@ -24,7 +24,6 @@
- +
-- cgit v1.2.3 From 5245aba8b80f458d25eb3249c0b8cc3fe744311d Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:27:00 -0500 Subject: Be a bit more careful with the nesting of anchors and list items. Browsers cope with weird nestings mostly fine, but there's no upside for us in testing that. --- ui/lib/components/Channel.svelte | 8 ++++---- ui/styles/sidebar.css | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/ui/lib/components/Channel.svelte b/ui/lib/components/Channel.svelte index c73340f..4f908d2 100644 --- a/ui/lib/components/Channel.svelte +++ b/ui/lib/components/Channel.svelte @@ -2,13 +2,13 @@ let { id, name, active, hasUnreads } = $props(); - -
  • +
  • + {#if hasUnreads} {:else} {/if} {name} -
  • - + + diff --git a/ui/styles/sidebar.css b/ui/styles/sidebar.css index 5e5e16a..c6aab6a 100644 --- a/ui/styles/sidebar.css +++ b/ui/styles/sidebar.css @@ -4,6 +4,8 @@ } .list-nav a { + display: block; + padding: 0.5rem; text-decoration: none; } @@ -12,7 +14,6 @@ } .list-nav li { - padding: 0.5rem; border-radius: 0.5rem; border: 1px solid var(--colour-navbar-border); margin: 0.25rem; -- cgit v1.2.3 From eba98f5a5ccfb81969be9f4fabcb82891bbc1c9e Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:31:53 -0500 Subject: Fix invalid JSON --- docs/api/channels-messages.md | 122 +++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 55 deletions(-) diff --git a/docs/api/channels-messages.md b/docs/api/channels-messages.md index 2aa8ac5..a3c90be 100644 --- a/docs/api/channels-messages.md +++ b/docs/api/channels-messages.md @@ -26,25 +26,30 @@ Messages allow logins to communicate with one another. Channels are the conversa Every channel has a unique name, chosen when the channel is created. - ## Names The service handles channel names using two separate forms. -The first form is as given in the request used to create the channel. This form of the channel name is used throughout the API, and the service will preserve the name as entered (other than applying normalization), so that users' preferences around capitalization and accent marks are preserved. - -The second form is a "canonical" form, used internally by the service to control uniqueness and match names to channels. The canonical form is both case-folded and normalized. +The first form is as given in the request used to create the channel. This form of the channel name is used throughout +the API, and the service will preserve the name as entered (other than applying normalization), so that users' +preferences around capitalization and accent marks are preserved. -The canonical form is not available to API clients, but its use has practical consequences. Names that differ only by case or only by code point sequence are treated as the same name. If the name is in use, changing the capitalization or changing the sequence of combining marks will not allow the creation of a second "identical" channel. +The second form is a "canonical" form, used internally by the service to control uniqueness and match names to channels. +The canonical form is both case-folded and normalized. +The canonical form is not available to API clients, but its use has practical consequences. Names that differ only by +case or only by code point sequence are treated as the same name. If the name is in use, changing the capitalization or +changing the sequence of combining marks will not allow the creation of a second "identical" channel. ## Expiry and purging -Both channels and messages expire after a time. Messages expire 90 days after being sent. Channels expire 90 days after the last message sent to them, or after creation if no messages are sent in that time. - -Deleted channels and messages, including those that have expired, are temporarily retained by the service, to allow clients that are not connected to receive the corresponding deletion [events](./events.md). To limit storage growth, deleted channels and messages are purged from the service seven days after they were deleted. +Both channels and messages expire after a time. Messages expire 90 days after being sent. Channels expire 90 days after +the last message sent to them, or after creation if no messages are sent in that time. +Deleted channels and messages, including those that have expired, are temporarily retained by the service, to allow +clients that are not connected to receive the corresponding deletion [events](./events.md). To limit storage growth, +deleted channels and messages are purged from the service seven days after they were deleted. ## `POST /api/channels` @@ -54,14 +59,14 @@ Creates a channel. ```json { - "name": "a unique channel name" + "name": "a unique channel name" } ``` The request must have the following fields: -| Field | Type | Description | -|:-------|:-------|:--| +| Field | Type | Description | +|:-------|:-------|:--------------------| | `name` | string | The channel's name. | The proposed `name` must be valid. The precise definition of valid is still up in the air, but, at minimum: @@ -74,23 +79,26 @@ The proposed `name` must be valid. The precise definition of valid is still up i ### Success -This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the new channel: +This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON +object describing the new channel: ```json { - "id": "C9876cyyz" - "name": "a unique channel name", + "id": "C9876cyyz", + "name": "a unique channel name" } ``` The response will have the following fields: -| Field | Type | Description | -|:-------|:-------|:--| +| Field | Type | Description | +|:-------|:-------|:----------------------------------------------------------------------------------------------------------------------------------------| | `id` | string | A unique identifier for the channel. This can be used to associate the channel with events, or to make API calls targeting the channel. | -| `name` | string | The channel's name. | +| `name` | string | The channel's name. | -The returned name may not be identical to the name requested, as the name will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this normalization; the service will use the normalized name elsewhere, and does not store the originally requested name. +The returned name may not be identical to the name requested, as the name will be converted +to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned name will include this +normalization; the service will use the normalized name elsewhere, and does not store the originally requested name. When completed, the service will emit a [channel created](events.md#channel-created) event with the channel's ID. @@ -102,56 +110,58 @@ This endpoint will respond with a status of `400 Bad Request` if the proposed `n This endpoint will respond with a status of `409 Conflict` if a channel with the requested name already exists. - ## `POST /api/channels/:id` Sends a message to a channel. This endpoint requires the following path parameter: -| Parameter | Type | Description | -|:----------|:-------|:--| +| Parameter | Type | Description | +|:----------|:-------|:--------------| | `id` | string | A channel ID. | ### Request ```json { - "body": "my amazing thoughts, by bob" + "body": "my amazing thoughts, by bob" } ``` The request must have the following fields: -| Field | Type | Description | -|:-------|:-------|:--| +| Field | Type | Description | +|:-------|:-------|:---------------------------------------| | `body` | string | The message to deliver to the channel. | ### Success -This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the newly-sent message: +This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON +object describing the newly-sent message: ```json { - "at": "2024-10-19T04:37:09.467325Z", - "channel": "Cfqdn1234", - "sender": "Labcd1234", - "id": "Mgh98yp75", - "body": "an elaborate example message" + "at": "2024-10-19T04:37:09.467325Z", + "channel": "Cfqdn1234", + "sender": "Labcd1234", + "id": "Mgh98yp75", + "body": "an elaborate example message" } ``` The response will have the following fields: -| Field | Type | Description | -|:----------|:----------|:--| -| `at` | timestamp | The moment the message was sent. | -| `channel` | string | The ID of the channel the message was sent to. | -| `sender` | string | The ID of the login that sent the message. | +| Field | Type | Description | +|:----------|:----------|:----------------------------------------------------------------------------------------------------------------------------------------| +| `at` | timestamp | The moment the message was sent. | +| `channel` | string | The ID of the channel the message was sent to. | +| `sender` | string | The ID of the login that sent the message. | | `id` | string | A unique identifier for the message. This can be used to associate the message with events, or to make API calls targeting the message. | -| `body` | string | The message's body. | +| `body` | string | The message's body. | -The returned message body may not be identical to the body as sent, as the body will be converted to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned body will include this normalization; the service will use the normalized body elsewhere, and does not store the originally submitted body. +The returned message body may not be identical to the body as sent, as the body will be converted +to [normalization form C](http://www.unicode.org/reports/tr15/) automatically. The returned body will include this +normalization; the service will use the normalized body elsewhere, and does not store the originally submitted body. When completed, the service will emit a [message sent](events.md#message-sent) event with the message's ID. @@ -159,36 +169,38 @@ When completed, the service will emit a [message sent](events.md#message-sent) e This endpoint will respond with a status of `404 Not Found` if the channel ID is not valid. - ## `DELETE /api/channels/:id` Deletes a channel. -Deleting a channel prevents it from receiving any further messages. The channel must be empty; to delete a channel with messages in it, delete the messages first (or wait for them to expire). +Deleting a channel prevents it from receiving any further messages. The channel must be empty; to delete a channel with +messages in it, delete the messages first (or wait for them to expire). This endpoint requires the following path parameter: -| Parameter | Type | Description | -|:----------|:-------|:--| +| Parameter | Type | Description | +|:----------|:-------|:--------------| | `id` | string | A channel ID. | ### Success -This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the deleted channel: +This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON +object describing the deleted channel: ```json { - "id": "Cfqdn1234" + "id": "Cfqdn1234" } ``` The response will have the following fields: -| Field | Type | Description | -|:----------|:----------|:--| -| `id` | string | The channel's ID. | +| Field | Type | Description | +|:------|:-------|:------------------| +| `id` | string | The channel's ID. | -When completed, the service will emit a [message deleted](events.md#message-deleted) event for each message in the channel, followed by a [channel deleted](events.md#channel-deleted) event with the channel's ID. +When completed, the service will emit a [message deleted](events.md#message-deleted) event for each message in the +channel, followed by a [channel deleted](events.md#channel-deleted) event with the channel's ID. ### Channel not empty @@ -198,32 +210,32 @@ This endpoint will respond with a status of `409 Conflict` if the channel contai This endpoint will respond with a status of `404 Not Found` if the channel ID is not valid. - ## `DELETE /api/messages/:id` Deletes a message. This endpoint requires the following path parameter: -| Parameter | Type | Description | -|:----------|:-------|:--| +| Parameter | Type | Description | +|:----------|:-------|:--------------| | `id` | string | A message ID. | ### Success -This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON object describing the deleted message: +This endpoint will respond with a status of `202 Accepted` when successful. The body of the response will be a JSON +object describing the deleted message: ```json { - "id": "Mgh98yp75" + "id": "Mgh98yp75" } ``` The response will have the following fields: -| Field | Type | Description | -|:----------|:----------|:--| -| `id` | string | The message's ID. | +| Field | Type | Description | +|:------|:-------|:------------------| +| `id` | string | The message's ID. | When completed, the service will emit a [message deleted](events.md#message-deleted) event with the message's ID. -- cgit v1.2.3 From b9bc643af0d0523a70d76236d9277539da083b36 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:35:56 -0500 Subject: Add missing awaits on goto() calls --- ui/lib/components/LogOut.svelte | 2 +- ui/routes/(app)/+layout.svelte | 8 ++++---- ui/routes/(login)/invite/[invite]/+page.svelte | 2 +- ui/routes/(login)/login/+page.svelte | 2 +- ui/routes/(login)/setup/+page.svelte | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/lib/components/LogOut.svelte b/ui/lib/components/LogOut.svelte index b699cfd..1cb8fb5 100644 --- a/ui/lib/components/LogOut.svelte +++ b/ui/lib/components/LogOut.svelte @@ -8,7 +8,7 @@ const response = await logOut(); if (200 <= response.status && response.status < 300) { currentUser.set(null); - goto('/login'); + await goto('/login'); } } diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index dff9245..a81efce 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -2,11 +2,11 @@ import { page } from '$app/stores'; import { goto } from '$app/navigation'; import { browser } from '$app/environment'; - import { onMount, onDestroy, getContext } from 'svelte'; + import { getContext, onDestroy, onMount } from 'svelte'; import TinyGesture from 'tinygesture'; import { boot, subscribeToEvents } from '$lib/apiServer'; - import { currentUser, logins, channelsList, messages, onEvent } from '$lib/store'; + import { channelsList, currentUser, logins, messages, onEvent } from '$lib/store'; import ChannelList from '$lib/components/ChannelList.svelte'; import CreateChannelForm from '$lib/components/CreateChannelForm.svelte'; @@ -76,11 +76,11 @@ break; case 401: currentUser.set(null); - goto('/login'); + await goto('/login'); break; case 503: currentUser.set(null); - goto('/setup'); + await goto('/setup'); break; default: // TODO: display error. diff --git a/ui/routes/(login)/invite/[invite]/+page.svelte b/ui/routes/(login)/invite/[invite]/+page.svelte index 132cbc1..0c01286 100644 --- a/ui/routes/(login)/invite/[invite]/+page.svelte +++ b/ui/routes/(login)/invite/[invite]/+page.svelte @@ -18,7 +18,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } diff --git a/ui/routes/(login)/login/+page.svelte b/ui/routes/(login)/login/+page.svelte index a1291ea..9157cef 100644 --- a/ui/routes/(login)/login/+page.svelte +++ b/ui/routes/(login)/login/+page.svelte @@ -16,7 +16,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } diff --git a/ui/routes/(login)/setup/+page.svelte b/ui/routes/(login)/setup/+page.svelte index f162ded..c63f198 100644 --- a/ui/routes/(login)/setup/+page.svelte +++ b/ui/routes/(login)/setup/+page.svelte @@ -16,7 +16,7 @@ if (200 <= response.status && response.status < 300) { username = ''; password = ''; - goto('/'); + await goto('/'); } pending = false; } -- cgit v1.2.3 From 14322855767838a17058ffe3776b0887a13e5fa6 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:52:07 -0500 Subject: Reorder impl to match trait --- src/normalize/string.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/normalize/string.rs b/src/normalize/string.rs index 01bdd6b..ee33267 100644 --- a/src/normalize/string.rs +++ b/src/normalize/string.rs @@ -1,8 +1,8 @@ use std::{fmt, string::String as StdString}; use sqlx::{ - Database, Decode, Type, - encode::{Encode, IsNull}, + encode::{Encode, IsNull}, Database, Decode, + Type, }; pub trait Normalize: Clone + Default { @@ -84,20 +84,20 @@ where DB: Database, StdString: Encode<'q, DB>, { - fn encode_by_ref( - &self, + fn encode( + self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { let Self(value, _) = self; - value.encode_by_ref(buf) + value.encode(buf) } - fn encode( - self, + fn encode_by_ref( + &self, buf: &mut ::ArgumentBuffer<'q>, ) -> Result { let Self(value, _) = self; - value.encode(buf) + value.encode_by_ref(buf) } fn produces(&self) -> Option<::TypeInfo> { -- cgit v1.2.3 From 75aa3b41cbf1c41ce08f307a12a1f101cb357f29 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 16:58:02 -0500 Subject: Be a little more pedantic about constant str ref lifetimes --- src/token/extract/cookie.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/token/extract/cookie.rs b/src/token/extract/cookie.rs index df03aea..a8e15d4 100644 --- a/src/token/extract/cookie.rs +++ b/src/token/extract/cookie.rs @@ -25,7 +25,7 @@ impl fmt::Debug for Identity { } impl Identity { - const COOKIE_NAME: &str = "identity"; + const COOKIE_NAME: &'static str = "identity"; // Creates a new, unpopulated identity token store. #[cfg(test)] -- cgit v1.2.3 From 25daf36b18086096651b381223ad8f03fbca5045 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Fri, 21 Feb 2025 17:07:35 -0500 Subject: Retire use of $page store in favour of Sv5 page state --- ui/routes/(app)/+layout.svelte | 4 ++-- ui/routes/(app)/ch/[channel]/+page.svelte | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/routes/(app)/+layout.svelte b/ui/routes/(app)/+layout.svelte index a81efce..6339abd 100644 --- a/ui/routes/(app)/+layout.svelte +++ b/ui/routes/(app)/+layout.svelte @@ -1,5 +1,5 @@