summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-10-28 14:41:50 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-10-28 14:41:50 -0400
commit4a91792e023a5877f8ac9b8a352e99c4486d698f (patch)
tree0b0e5466d0945a5f853e98eb8d0b0215e67ed3fb
parent9c271b27ff03cf4976326090ff54e3b5dfc04962 (diff)
parent0ef69c7d256380e660edc45ace7f1d6151226340 (diff)
Merge remote-tracking branch 'codeberg/main' into push-notify
-rw-r--r--src/app.rs79
-rw-r--r--src/boot/app.rs8
-rw-r--r--src/boot/handlers/boot/mod.rs9
-rw-r--r--src/boot/handlers/boot/test.rs16
-rw-r--r--src/conversation/app.rs10
-rw-r--r--src/conversation/handlers/create/mod.rs8
-rw-r--r--src/conversation/handlers/create/test.rs66
-rw-r--r--src/conversation/handlers/delete/mod.rs9
-rw-r--r--src/conversation/handlers/delete/test.rs12
-rw-r--r--src/conversation/handlers/send/mod.rs11
-rw-r--r--src/conversation/handlers/send/test.rs6
-rw-r--r--src/event/app.rs10
-rw-r--r--src/invite/app.rs12
-rw-r--r--src/invite/handlers/accept/mod.rs8
-rw-r--r--src/invite/handlers/accept/test.rs12
-rw-r--r--src/invite/handlers/get/mod.rs7
-rw-r--r--src/invite/handlers/get/test.rs6
-rw-r--r--src/invite/handlers/issue/mod.rs9
-rw-r--r--src/invite/handlers/issue/test.rs2
-rw-r--r--src/login/app.rs10
-rw-r--r--src/login/handlers/login/mod.rs14
-rw-r--r--src/login/handlers/login/test.rs8
-rw-r--r--src/login/handlers/logout/mod.rs9
-rw-r--r--src/login/handlers/logout/test.rs25
-rw-r--r--src/login/handlers/password/mod.rs8
-rw-r--r--src/login/handlers/password/test.rs2
-rw-r--r--src/message/app.rs10
-rw-r--r--src/message/handlers/delete/mod.rs10
-rw-r--r--src/message/handlers/delete/test.rs12
-rw-r--r--src/setup/app.rs13
-rw-r--r--src/setup/handlers/setup/mod.rs14
-rw-r--r--src/setup/handlers/setup/test.rs6
-rw-r--r--src/setup/required.rs31
-rw-r--r--src/test/fixtures/boot.rs11
-rw-r--r--src/test/fixtures/conversation.rs11
-rw-r--r--src/test/fixtures/cookie.rs14
-rw-r--r--src/test/fixtures/identity.rs21
-rw-r--r--src/test/fixtures/invite.rs12
-rw-r--r--src/test/fixtures/message.rs13
-rw-r--r--src/test/verify/identity.rs26
-rw-r--r--src/test/verify/login.rs23
-rw-r--r--src/test/verify/token.rs29
-rw-r--r--src/token/app.rs10
-rw-r--r--src/token/extract/identity.rs24
-rw-r--r--src/ui/handlers/conversation.rs7
-rw-r--r--src/ui/handlers/invite.rs6
-rw-r--r--src/ui/handlers/setup.rs7
-rw-r--r--src/user/app.rs12
48 files changed, 423 insertions, 275 deletions
diff --git a/src/app.rs b/src/app.rs
index 0f3f6ad..8f16e02 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,3 +1,4 @@
+use axum::extract::FromRef;
use sqlx::sqlite::SqlitePool;
#[cfg(test)]
@@ -34,41 +35,83 @@ impl App {
}
impl App {
- pub const fn boot(&self) -> Boot<'_> {
- Boot::new(&self.db)
+ pub fn boot(&self) -> Boot {
+ Boot::new(self.db.clone())
}
- pub const fn conversations(&self) -> Conversations<'_> {
- Conversations::new(&self.db, &self.events)
+ pub fn conversations(&self) -> Conversations {
+ Conversations::new(self.db.clone(), self.events.clone())
}
- pub const fn events(&self) -> Events<'_> {
- Events::new(&self.db, &self.events)
+ pub fn events(&self) -> Events {
+ Events::new(self.db.clone(), self.events.clone())
}
- pub const fn invites(&self) -> Invites<'_> {
- Invites::new(&self.db, &self.events)
+ pub fn invites(&self) -> Invites {
+ Invites::new(self.db.clone(), self.events.clone())
}
- pub const fn logins(&self) -> Logins<'_> {
- Logins::new(&self.db, &self.token_events)
+ pub fn logins(&self) -> Logins {
+ Logins::new(self.db.clone(), self.token_events.clone())
}
- pub const fn messages(&self) -> Messages<'_> {
- Messages::new(&self.db, &self.events)
+ pub fn messages(&self) -> Messages {
+ Messages::new(self.db.clone(), self.events.clone())
}
- pub const fn setup(&self) -> Setup<'_> {
- Setup::new(&self.db, &self.events)
+ pub fn setup(&self) -> Setup {
+ Setup::new(self.db.clone(), self.events.clone())
}
- pub const fn tokens(&self) -> Tokens<'_> {
- Tokens::new(&self.db, &self.token_events)
+ pub fn tokens(&self) -> Tokens {
+ Tokens::new(self.db.clone(), self.token_events.clone())
}
#[cfg(test)]
- pub const fn users(&self) -> Users<'_> {
- Users::new(&self.db, &self.events)
+ pub fn users(&self) -> Users {
+ Users::new(self.db.clone(), self.events.clone())
+ }
+}
+
+impl FromRef<App> for Boot {
+ fn from_ref(app: &App) -> Self {
+ app.boot()
+ }
+}
+
+impl FromRef<App> for Conversations {
+ fn from_ref(app: &App) -> Self {
+ app.conversations()
+ }
+}
+
+impl FromRef<App> for Invites {
+ fn from_ref(app: &App) -> Self {
+ app.invites()
+ }
+}
+
+impl FromRef<App> for Logins {
+ fn from_ref(app: &App) -> Self {
+ app.logins()
+ }
+}
+
+impl FromRef<App> for Messages {
+ fn from_ref(app: &App) -> Self {
+ app.messages()
+ }
+}
+
+impl FromRef<App> for Setup {
+ fn from_ref(app: &App) -> Self {
+ app.setup()
+ }
+}
+
+impl FromRef<App> for Tokens {
+ fn from_ref(app: &App) -> Self {
+ app.tokens()
}
pub const fn vapid(&self) -> Vapid<'_> {
diff --git a/src/boot/app.rs b/src/boot/app.rs
index 543429f..8da3e90 100644
--- a/src/boot/app.rs
+++ b/src/boot/app.rs
@@ -12,12 +12,12 @@ use crate::{
vapid::{self, repo::Provider as _},
};
-pub struct Boot<'a> {
- db: &'a SqlitePool,
+pub struct Boot {
+ db: SqlitePool,
}
-impl<'a> Boot<'a> {
- pub const fn new(db: &'a SqlitePool) -> Self {
+impl Boot {
+ pub const fn new(db: SqlitePool) -> Self {
Self { db }
}
diff --git a/src/boot/handlers/boot/mod.rs b/src/boot/handlers/boot/mod.rs
index 3e022b1..5ff7802 100644
--- a/src/boot/handlers/boot/mod.rs
+++ b/src/boot/handlers/boot/mod.rs
@@ -7,15 +7,18 @@ use axum::{
use serde::Serialize;
use crate::{
- app::App, boot::Snapshot, error::Internal, event::Heartbeat, login::Login,
+ boot::{Snapshot, app::Boot},
+ error::Internal,
+ event::Heartbeat,
+ login::Login,
token::extract::Identity,
};
#[cfg(test)]
mod test;
-pub async fn handler(State(app): State<App>, identity: Identity) -> Result<Response, Internal> {
- let snapshot = app.boot().snapshot().await?;
+pub async fn handler(State(boot): State<Boot>, identity: Identity) -> Result<Response, Internal> {
+ let snapshot = boot.snapshot().await?;
let heartbeat = Heartbeat::TIMEOUT;
Ok(Response {
diff --git a/src/boot/handlers/boot/test.rs b/src/boot/handlers/boot/test.rs
index 3c09b0f..7eb4e52 100644
--- a/src/boot/handlers/boot/test.rs
+++ b/src/boot/handlers/boot/test.rs
@@ -8,7 +8,7 @@ async fn returns_identity() {
let app = fixtures::scratch_app().await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer.clone())
+ let response = super::handler(State(app.boot()), viewer.clone())
.await
.expect("boot always succeeds");
@@ -21,7 +21,7 @@ async fn includes_users() {
let spectator = fixtures::user::create(&app, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -42,7 +42,7 @@ async fn includes_conversations() {
let conversation = fixtures::conversation::create(&app, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -65,7 +65,7 @@ async fn includes_messages() {
let message = fixtures::message::send(&app, &conversation, &sender, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -162,7 +162,7 @@ async fn includes_expired_messages() {
.expect("expiry never fails");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -204,7 +204,7 @@ async fn includes_deleted_messages() {
.expect("deleting valid message succeeds");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -243,7 +243,7 @@ async fn includes_expired_conversations() {
.expect("expiry never fails");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -282,7 +282,7 @@ async fn includes_deleted_conversations() {
.expect("deleting a valid conversation succeeds");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
diff --git a/src/conversation/app.rs b/src/conversation/app.rs
index 26886af..2b62e77 100644
--- a/src/conversation/app.rs
+++ b/src/conversation/app.rs
@@ -15,13 +15,13 @@ use crate::{
name::{self, Name},
};
-pub struct Conversations<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+pub struct Conversations {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Conversations<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Conversations {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
diff --git a/src/conversation/handlers/create/mod.rs b/src/conversation/handlers/create/mod.rs
index 18eca1f..2b7fa39 100644
--- a/src/conversation/handlers/create/mod.rs
+++ b/src/conversation/handlers/create/mod.rs
@@ -5,9 +5,8 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
- conversation::{Conversation, app},
+ conversation::{Conversation, app, app::Conversations},
error::Internal,
name::Name,
token::extract::Identity,
@@ -17,13 +16,12 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(conversations): State<Conversations>,
_: Identity, // requires auth, but doesn't actually care who you are
RequestedAt(created_at): RequestedAt,
Json(request): Json<Request>,
) -> Result<Response, Error> {
- let conversation = app
- .conversations()
+ let conversation = conversations
.create(&request.name, &created_at)
.await
.map_err(Error)?;
diff --git a/src/conversation/handlers/create/test.rs b/src/conversation/handlers/create/test.rs
index bc05b00..380bb13 100644
--- a/src/conversation/handlers/create/test.rs
+++ b/src/conversation/handlers/create/test.rs
@@ -22,10 +22,14 @@ async fn new_conversation() {
let name = fixtures::conversation::propose();
let request = super::Request { name: name.clone() };
- let super::Response(response) =
- super::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect("creating a conversation in an empty app succeeds");
+ let super::Response(response) = super::handler(
+ State(app.conversations()),
+ creator,
+ fixtures::now(),
+ Json(request),
+ )
+ .await
+ .expect("creating a conversation in an empty app succeeds");
// Verify the structure of the response
@@ -77,10 +81,14 @@ async fn duplicate_name() {
let request = super::Request {
name: conversation.name.clone(),
};
- let super::Error(error) =
- super::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect_err("duplicate conversation name should fail the request");
+ let super::Error(error) = super::handler(
+ State(app.conversations()),
+ creator,
+ fixtures::now(),
+ Json(request),
+ )
+ .await
+ .expect_err("duplicate conversation name should fail the request");
// Verify the structure of the response
@@ -110,10 +118,14 @@ async fn conflicting_canonical_name() {
let request = super::Request {
name: conflicting_name.clone(),
};
- let super::Error(error) =
- super::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect_err("duplicate conversation name should fail the request");
+ let super::Error(error) = super::handler(
+ State(app.conversations()),
+ creator,
+ fixtures::now(),
+ Json(request),
+ )
+ .await
+ .expect_err("duplicate conversation name should fail the request");
// Verify the structure of the response
@@ -135,7 +147,7 @@ async fn invalid_name() {
let name = fixtures::conversation::propose_invalid_name();
let request = super::Request { name: name.clone() };
let super::Error(error) = crate::conversation::handlers::create::handler(
- State(app.clone()),
+ State(app.conversations()),
creator,
fixtures::now(),
Json(request),
@@ -163,7 +175,7 @@ async fn name_reusable_after_delete() {
let request = super::Request { name: name.clone() };
let super::Response(response) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
creator.clone(),
fixtures::now(),
Json(request),
@@ -181,10 +193,14 @@ async fn name_reusable_after_delete() {
// Call the endpoint (second time)
let request = super::Request { name: name.clone() };
- let super::Response(response) =
- super::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect("creation succeeds after original conversation deleted");
+ let super::Response(response) = super::handler(
+ State(app.conversations()),
+ creator,
+ fixtures::now(),
+ Json(request),
+ )
+ .await
+ .expect("creation succeeds after original conversation deleted");
// Verify the structure of the response
@@ -212,7 +228,7 @@ async fn name_reusable_after_expiry() {
let request = super::Request { name: name.clone() };
let super::Response(_) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
creator.clone(),
fixtures::ancient(),
Json(request),
@@ -230,10 +246,14 @@ async fn name_reusable_after_expiry() {
// Call the endpoint (second time)
let request = super::Request { name: name.clone() };
- let super::Response(response) =
- super::handler(State(app.clone()), creator, fixtures::now(), Json(request))
- .await
- .expect("creation succeeds after original conversation expired");
+ let super::Response(response) = super::handler(
+ State(app.conversations()),
+ creator,
+ fixtures::now(),
+ Json(request),
+ )
+ .await
+ .expect("creation succeeds after original conversation expired");
// Verify the structure of the response
diff --git a/src/conversation/handlers/delete/mod.rs b/src/conversation/handlers/delete/mod.rs
index 272165a..231e433 100644
--- a/src/conversation/handlers/delete/mod.rs
+++ b/src/conversation/handlers/delete/mod.rs
@@ -5,9 +5,8 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
- conversation::{self, app, handlers::PathInfo},
+ conversation::{self, app, app::Conversations, handlers::PathInfo},
error::{Internal, NotFound},
token::extract::Identity,
};
@@ -16,14 +15,12 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(conversations): State<Conversations>,
Path(conversation): Path<PathInfo>,
RequestedAt(deleted_at): RequestedAt,
_: Identity,
) -> Result<Response, Error> {
- app.conversations()
- .delete(&conversation, &deleted_at)
- .await?;
+ conversations.delete(&conversation, &deleted_at).await?;
Ok(Response { id: conversation })
}
diff --git a/src/conversation/handlers/delete/test.rs b/src/conversation/handlers/delete/test.rs
index 2718d3b..e9e882a 100644
--- a/src/conversation/handlers/delete/test.rs
+++ b/src/conversation/handlers/delete/test.rs
@@ -14,7 +14,7 @@ pub async fn valid_conversation() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let response = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.id.clone()),
fixtures::now(),
deleter,
@@ -52,7 +52,7 @@ pub async fn invalid_conversation_id() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let conversation = fixtures::conversation::fictitious();
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.clone()),
fixtures::now(),
deleter,
@@ -81,7 +81,7 @@ pub async fn conversation_deleted() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.id.clone()),
fixtures::now(),
deleter,
@@ -110,7 +110,7 @@ pub async fn conversation_expired() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.id.clone()),
fixtures::now(),
deleter,
@@ -144,7 +144,7 @@ pub async fn conversation_purged() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.id.clone()),
fixtures::now(),
deleter,
@@ -170,7 +170,7 @@ pub async fn conversation_not_empty() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.conversations()),
Path(conversation.id.clone()),
fixtures::now(),
deleter,
diff --git a/src/conversation/handlers/send/mod.rs b/src/conversation/handlers/send/mod.rs
index c8be59c..ff63652 100644
--- a/src/conversation/handlers/send/mod.rs
+++ b/src/conversation/handlers/send/mod.rs
@@ -5,11 +5,13 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
conversation::handlers::PathInfo,
error::{Internal, NotFound},
- message::{Body, Message, app::SendError},
+ message::{
+ Body, Message,
+ app::{Messages, SendError},
+ },
token::extract::Identity,
};
@@ -17,14 +19,13 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(messages): State<Messages>,
Path(conversation): Path<PathInfo>,
RequestedAt(sent_at): RequestedAt,
identity: Identity,
Json(request): Json<Request>,
) -> Result<Response, Error> {
- let message = app
- .messages()
+ let message = messages
.send(&conversation, &identity.login, &sent_at, &request.body)
.await?;
diff --git a/src/conversation/handlers/send/test.rs b/src/conversation/handlers/send/test.rs
index 8863090..013aaa4 100644
--- a/src/conversation/handlers/send/test.rs
+++ b/src/conversation/handlers/send/test.rs
@@ -28,7 +28,7 @@ async fn messages_in_order() {
let request = super::Request { body: body.clone() };
let _ = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(conversation.id.clone()),
sent_at.clone(),
sender.clone(),
@@ -75,7 +75,7 @@ async fn nonexistent_conversation() {
body: fixtures::message::propose(),
};
let super::Error(error) = super::handler(
- State(app),
+ State(app.messages()),
Path(conversation.clone()),
sent_at,
sender,
@@ -112,7 +112,7 @@ async fn deleted_conversation() {
body: fixtures::message::propose(),
};
let super::Error(error) = super::handler(
- State(app),
+ State(app.messages()),
Path(conversation.id.clone()),
sent_at,
sender,
diff --git a/src/event/app.rs b/src/event/app.rs
index fe90465..6c657c7 100644
--- a/src/event/app.rs
+++ b/src/event/app.rs
@@ -16,13 +16,13 @@ use crate::{
vapid::repo::Provider as _,
};
-pub struct Events<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+pub struct Events {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Events<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Events {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
diff --git a/src/invite/app.rs b/src/invite/app.rs
index 6684d03..6f58d0a 100644
--- a/src/invite/app.rs
+++ b/src/invite/app.rs
@@ -17,13 +17,13 @@ use crate::{
},
};
-pub struct Invites<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+pub struct Invites {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Invites<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Invites {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
@@ -88,7 +88,7 @@ impl<'a> Invites<'a> {
tx.tokens().create(&token, &secret).await?;
tx.commit().await?;
- stored.publish(self.events);
+ stored.publish(&self.events);
Ok(secret)
}
diff --git a/src/invite/handlers/accept/mod.rs b/src/invite/handlers/accept/mod.rs
index cdf385f..8bdaa51 100644
--- a/src/invite/handlers/accept/mod.rs
+++ b/src/invite/handlers/accept/mod.rs
@@ -5,11 +5,10 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
empty::Empty,
error::{Internal, NotFound},
- invite::{app, handlers::PathInfo},
+ invite::{app, app::Invites, handlers::PathInfo},
name::Name,
password::Password,
token::extract::IdentityCookie,
@@ -19,14 +18,13 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(invites): State<Invites>,
RequestedAt(accepted_at): RequestedAt,
identity: IdentityCookie,
Path(invite): Path<PathInfo>,
Json(request): Json<Request>,
) -> Result<(IdentityCookie, Empty), Error> {
- let secret = app
- .invites()
+ let secret = invites
.accept(&invite, &request.name, &request.password, &accepted_at)
.await
.map_err(Error)?;
diff --git a/src/invite/handlers/accept/test.rs b/src/invite/handlers/accept/test.rs
index 283ec76..446dbf9 100644
--- a/src/invite/handlers/accept/test.rs
+++ b/src/invite/handlers/accept/test.rs
@@ -24,7 +24,7 @@ async fn valid_invite() {
password: password.clone(),
};
let (identity, Empty) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.id),
@@ -67,7 +67,7 @@ async fn nonexistent_invite() {
password: password.clone(),
};
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.clone()),
@@ -103,7 +103,7 @@ async fn expired_invite() {
password: password.clone(),
};
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.id.clone()),
@@ -140,7 +140,7 @@ async fn accepted_invite() {
password: password.clone(),
};
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.id.clone()),
@@ -183,7 +183,7 @@ async fn conflicting_name() {
password: password.clone(),
};
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.id.clone()),
@@ -217,7 +217,7 @@ async fn invalid_name() {
password: password.clone(),
};
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.invites()),
fixtures::now(),
identity,
Path(invite.id),
diff --git a/src/invite/handlers/get/mod.rs b/src/invite/handlers/get/mod.rs
index bb72586..d5fd9c2 100644
--- a/src/invite/handlers/get/mod.rs
+++ b/src/invite/handlers/get/mod.rs
@@ -4,19 +4,18 @@ use axum::{
};
use crate::{
- app::App,
error::{Internal, NotFound},
- invite::{Id, Summary, handlers::PathInfo},
+ invite::{Id, Summary, app::Invites, handlers::PathInfo},
};
#[cfg(test)]
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(invites): State<Invites>,
Path(invite): Path<PathInfo>,
) -> Result<Json<Summary>, Error> {
- app.invites()
+ invites
.get(&invite)
.await?
.map(Json)
diff --git a/src/invite/handlers/get/test.rs b/src/invite/handlers/get/test.rs
index 0f2f725..a08c510 100644
--- a/src/invite/handlers/get/test.rs
+++ b/src/invite/handlers/get/test.rs
@@ -12,7 +12,7 @@ async fn valid_invite() {
// Call endpoint
- let Json(response) = super::handler(State(app), Path(invite.id))
+ let Json(response) = super::handler(State(app.invites()), Path(invite.id))
.await
.expect("get for an existing invite succeeds");
@@ -31,7 +31,7 @@ async fn nonexistent_invite() {
// Call endpoint
let invite = fixtures::invite::fictitious();
- let error = super::handler(State(app), Path(invite.clone()))
+ let error = super::handler(State(app.invites()), Path(invite.clone()))
.await
.expect_err("get for a nonexistent invite fails");
@@ -55,7 +55,7 @@ async fn expired_invite() {
// Call endpoint
- let error = super::handler(State(app), Path(invite.id.clone()))
+ let error = super::handler(State(app.invites()), Path(invite.id.clone()))
.await
.expect_err("get for an expired invite fails");
diff --git a/src/invite/handlers/issue/mod.rs b/src/invite/handlers/issue/mod.rs
index 4ac74cc..0549c78 100644
--- a/src/invite/handlers/issue/mod.rs
+++ b/src/invite/handlers/issue/mod.rs
@@ -1,19 +1,22 @@
use axum::extract::{Json, State};
use crate::{
- app::App, clock::RequestedAt, error::Internal, invite::Invite, token::extract::Identity,
+ clock::RequestedAt,
+ error::Internal,
+ invite::{Invite, app::Invites},
+ token::extract::Identity,
};
#[cfg(test)]
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(invites): State<Invites>,
RequestedAt(issued_at): RequestedAt,
identity: Identity,
_: Json<Request>,
) -> Result<Json<Invite>, Internal> {
- let invite = app.invites().issue(&identity.login, &issued_at).await?;
+ let invite = invites.issue(&identity.login, &issued_at).await?;
Ok(Json(invite))
}
diff --git a/src/invite/handlers/issue/test.rs b/src/invite/handlers/issue/test.rs
index 4421705..dc89243 100644
--- a/src/invite/handlers/issue/test.rs
+++ b/src/invite/handlers/issue/test.rs
@@ -13,7 +13,7 @@ async fn create_invite() {
// Call the endpoint
let Json(invite) = super::handler(
- State(app),
+ State(app.invites()),
issued_at.clone(),
issuer.clone(),
Json(super::Request {}),
diff --git a/src/login/app.rs b/src/login/app.rs
index e471000..a2f9636 100644
--- a/src/login/app.rs
+++ b/src/login/app.rs
@@ -9,13 +9,13 @@ use crate::{
token::{Broadcaster, Event as TokenEvent, Secret, Token, repo::Provider as _},
};
-pub struct Logins<'a> {
- db: &'a SqlitePool,
- token_events: &'a Broadcaster,
+pub struct Logins {
+ db: SqlitePool,
+ token_events: Broadcaster,
}
-impl<'a> Logins<'a> {
- pub const fn new(db: &'a SqlitePool, token_events: &'a Broadcaster) -> Self {
+impl Logins {
+ pub const fn new(db: SqlitePool, token_events: Broadcaster) -> Self {
Self { db, token_events }
}
diff --git a/src/login/handlers/login/mod.rs b/src/login/handlers/login/mod.rs
index 6591984..2ce8a67 100644
--- a/src/login/handlers/login/mod.rs
+++ b/src/login/handlers/login/mod.rs
@@ -5,21 +5,25 @@ use axum::{
};
use crate::{
- app::App, clock::RequestedAt, empty::Empty, error::Internal, login::app, name::Name,
- password::Password, token::extract::IdentityCookie,
+ clock::RequestedAt,
+ empty::Empty,
+ error::Internal,
+ login::{app, app::Logins},
+ name::Name,
+ password::Password,
+ token::extract::IdentityCookie,
};
#[cfg(test)]
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(logins): State<Logins>,
RequestedAt(now): RequestedAt,
identity: IdentityCookie,
Json(request): Json<Request>,
) -> Result<(IdentityCookie, Empty), Error> {
- let secret = app
- .logins()
+ let secret = logins
.with_password(&request.name, &request.password, &now)
.await
.map_err(Error)?;
diff --git a/src/login/handlers/login/test.rs b/src/login/handlers/login/test.rs
index f3911d0..7bb56b6 100644
--- a/src/login/handlers/login/test.rs
+++ b/src/login/handlers/login/test.rs
@@ -22,7 +22,7 @@ async fn correct_credentials() {
password,
};
let (identity, Empty) =
- super::handler(State(app.clone()), logged_in_at, identity, Json(request))
+ super::handler(State(app.logins()), logged_in_at, identity, Json(request))
.await
.expect("logged in with valid credentials");
@@ -52,7 +52,7 @@ async fn invalid_name() {
password,
};
let super::Error(error) =
- super::handler(State(app.clone()), logged_in_at, identity, Json(request))
+ super::handler(State(app.logins()), logged_in_at, identity, Json(request))
.await
.expect_err("logged in with an incorrect password fails");
@@ -77,7 +77,7 @@ async fn incorrect_password() {
password: fixtures::user::propose_password(),
};
let super::Error(error) =
- super::handler(State(app.clone()), logged_in_at, identity, Json(request))
+ super::handler(State(app.logins()), logged_in_at, identity, Json(request))
.await
.expect_err("logged in with an incorrect password");
@@ -98,7 +98,7 @@ async fn token_expires() {
let logged_in_at = fixtures::ancient();
let identity = fixtures::cookie::not_logged_in();
let request = super::Request { name, password };
- let (identity, _) = super::handler(State(app.clone()), logged_in_at, identity, Json(request))
+ let (identity, _) = super::handler(State(app.logins()), logged_in_at, identity, Json(request))
.await
.expect("logged in with valid credentials");
let secret = identity.secret().expect("logged in with valid credentials");
diff --git a/src/login/handlers/logout/mod.rs b/src/login/handlers/logout/mod.rs
index 73efe73..ce4cb1a 100644
--- a/src/login/handlers/logout/mod.rs
+++ b/src/login/handlers/logout/mod.rs
@@ -4,25 +4,24 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
empty::Empty,
error::{Internal, Unauthorized},
- token::{app, extract::IdentityCookie},
+ token::{app, app::Tokens, extract::IdentityCookie},
};
#[cfg(test)]
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(tokens): State<Tokens>,
RequestedAt(now): RequestedAt,
identity: IdentityCookie,
Json(_): Json<Request>,
) -> Result<(IdentityCookie, Empty), Error> {
if let Some(secret) = identity.secret() {
- let identity = app.tokens().validate(&secret, &now).await?;
- app.tokens().logout(&identity.token).await?;
+ let identity = tokens.validate(&secret, &now).await?;
+ tokens.logout(&identity.token).await?;
}
let identity = identity.clear();
diff --git a/src/login/handlers/logout/test.rs b/src/login/handlers/logout/test.rs
index e7b7dd4..18744ed 100644
--- a/src/login/handlers/logout/test.rs
+++ b/src/login/handlers/logout/test.rs
@@ -18,7 +18,7 @@ async fn successful() {
// Call the endpoint
let (response_identity, Empty) = super::handler(
- State(app.clone()),
+ State(app.tokens()),
fixtures::now(),
identity.clone(),
Json::default(),
@@ -42,9 +42,14 @@ async fn no_identity() {
// Call the endpoint
let identity = fixtures::cookie::not_logged_in();
- let (identity, Empty) = super::handler(State(app), fixtures::now(), identity, Json::default())
- .await
- .expect("logged out with no token succeeds");
+ let (identity, Empty) = super::handler(
+ State(app.tokens()),
+ fixtures::now(),
+ identity,
+ Json::default(),
+ )
+ .await
+ .expect("logged out with no token succeeds");
// Verify the return value's basic structure
@@ -60,10 +65,14 @@ async fn invalid_token() {
// Call the endpoint
let identity = fixtures::cookie::fictitious();
- let super::Error(error) =
- super::handler(State(app), fixtures::now(), identity, Json::default())
- .await
- .expect_err("logged out with an invalid token fails");
+ let super::Error(error) = super::handler(
+ State(app.tokens()),
+ fixtures::now(),
+ identity,
+ Json::default(),
+ )
+ .await
+ .expect_err("logged out with an invalid token fails");
// Verify the return value's basic structure
diff --git a/src/login/handlers/password/mod.rs b/src/login/handlers/password/mod.rs
index 94c7fb4..8b82605 100644
--- a/src/login/handlers/password/mod.rs
+++ b/src/login/handlers/password/mod.rs
@@ -5,11 +5,10 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
empty::Empty,
error::Internal,
- login::app,
+ login::{app, app::Logins},
password::Password,
token::extract::{Identity, IdentityCookie},
};
@@ -18,14 +17,13 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(logins): State<Logins>,
RequestedAt(now): RequestedAt,
identity: Identity,
cookie: IdentityCookie,
Json(request): Json<Request>,
) -> Result<(IdentityCookie, Empty), Error> {
- let secret = app
- .logins()
+ let secret = logins
.change_password(&identity.login, &request.password, &request.to, &now)
.await
.map_err(Error)?;
diff --git a/src/login/handlers/password/test.rs b/src/login/handlers/password/test.rs
index ba2f28f..61d5b5a 100644
--- a/src/login/handlers/password/test.rs
+++ b/src/login/handlers/password/test.rs
@@ -21,7 +21,7 @@ async fn password_change() {
to: to.clone(),
};
let (new_cookie, Empty) = super::handler(
- State(app.clone()),
+ State(app.logins()),
fixtures::now(),
identity.clone(),
cookie.clone(),
diff --git a/src/message/app.rs b/src/message/app.rs
index 647152e..cbcbff9 100644
--- a/src/message/app.rs
+++ b/src/message/app.rs
@@ -13,13 +13,13 @@ use crate::{
user::{self, repo::Provider as _},
};
-pub struct Messages<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+pub struct Messages {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Messages<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Messages {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
diff --git a/src/message/handlers/delete/mod.rs b/src/message/handlers/delete/mod.rs
index 3e9a212..c09a752 100644
--- a/src/message/handlers/delete/mod.rs
+++ b/src/message/handlers/delete/mod.rs
@@ -5,10 +5,12 @@ use axum::{
};
use crate::{
- app::App,
clock::RequestedAt,
error::{Internal, NotFound},
- message::{self, app::DeleteError},
+ message::{
+ self,
+ app::{DeleteError, Messages},
+ },
token::extract::Identity,
};
@@ -16,12 +18,12 @@ use crate::{
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(messages): State<Messages>,
Path(message): Path<message::Id>,
RequestedAt(deleted_at): RequestedAt,
identity: Identity,
) -> Result<Response, Error> {
- app.messages()
+ messages
.delete(&identity.login, &message, &deleted_at)
.await?;
diff --git a/src/message/handlers/delete/test.rs b/src/message/handlers/delete/test.rs
index 05d9344..198728b 100644
--- a/src/message/handlers/delete/test.rs
+++ b/src/message/handlers/delete/test.rs
@@ -16,7 +16,7 @@ pub async fn delete_message() {
// Send the request
let response = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.id.clone()),
fixtures::now(),
sender,
@@ -52,7 +52,7 @@ pub async fn delete_invalid_message_id() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let message = fixtures::message::fictitious();
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.clone()),
fixtures::now(),
deleter,
@@ -83,7 +83,7 @@ pub async fn delete_deleted() {
// Send the request
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.id.clone()),
fixtures::now(),
sender,
@@ -114,7 +114,7 @@ pub async fn delete_expired() {
// Send the request
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.id.clone()),
fixtures::now(),
sender,
@@ -150,7 +150,7 @@ pub async fn delete_purged() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.id.clone()),
fixtures::now(),
deleter,
@@ -176,7 +176,7 @@ pub async fn delete_not_sender() {
let deleter = fixtures::identity::create(&app, &fixtures::now()).await;
let super::Error(error) = super::handler(
- State(app.clone()),
+ State(app.messages()),
Path(message.id.clone()),
fixtures::now(),
deleter.clone(),
diff --git a/src/setup/app.rs b/src/setup/app.rs
index 2a8ec30..9539406 100644
--- a/src/setup/app.rs
+++ b/src/setup/app.rs
@@ -10,13 +10,14 @@ use crate::{
user::create::{self, Create},
};
-pub struct Setup<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+#[derive(Clone)]
+pub struct Setup {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Setup<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Setup {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
@@ -41,7 +42,7 @@ impl<'a> Setup<'a> {
tx.tokens().create(&token, &secret).await?;
tx.commit().await?;
- stored.publish(self.events);
+ stored.publish(&self.events);
Ok(secret)
}
diff --git a/src/setup/handlers/setup/mod.rs b/src/setup/handlers/setup/mod.rs
index fe24798..2977da8 100644
--- a/src/setup/handlers/setup/mod.rs
+++ b/src/setup/handlers/setup/mod.rs
@@ -5,21 +5,25 @@ use axum::{
};
use crate::{
- app::App, clock::RequestedAt, empty::Empty, error::Internal, name::Name, password::Password,
- setup::app, token::extract::IdentityCookie,
+ clock::RequestedAt,
+ empty::Empty,
+ error::Internal,
+ name::Name,
+ password::Password,
+ setup::{app, app::Setup},
+ token::extract::IdentityCookie,
};
#[cfg(test)]
mod test;
pub async fn handler(
- State(app): State<App>,
+ State(setup): State<Setup>,
RequestedAt(setup_at): RequestedAt,
identity: IdentityCookie,
Json(request): Json<Request>,
) -> Result<(IdentityCookie, Empty), Error> {
- let secret = app
- .setup()
+ let secret = setup
.initial(&request.name, &request.password, &setup_at)
.await
.map_err(Error)?;
diff --git a/src/setup/handlers/setup/test.rs b/src/setup/handlers/setup/test.rs
index 283fe8b..670c111 100644
--- a/src/setup/handlers/setup/test.rs
+++ b/src/setup/handlers/setup/test.rs
@@ -20,7 +20,7 @@ async fn fresh_instance() {
password: password.clone(),
};
let (identity, Empty) =
- super::handler(State(app.clone()), fixtures::now(), identity, Json(request))
+ super::handler(State(app.setup()), fixtures::now(), identity, Json(request))
.await
.expect("setup in a fresh app succeeds");
@@ -43,7 +43,7 @@ async fn login_exists() {
let (name, password) = fixtures::user::propose();
let request = super::Request { name, password };
let super::Error(error) =
- super::handler(State(app.clone()), fixtures::now(), identity, Json(request))
+ super::handler(State(app.setup()), fixtures::now(), identity, Json(request))
.await
.expect_err("setup in a populated app fails");
@@ -68,7 +68,7 @@ async fn invalid_name() {
password: password.clone(),
};
let super::Error(error) =
- super::handler(State(app.clone()), fixtures::now(), identity, Json(request))
+ super::handler(State(app.setup()), fixtures::now(), identity, Json(request))
.await
.expect_err("setup with an invalid name fails");
diff --git a/src/setup/required.rs b/src/setup/required.rs
index a2aed18..e475381 100644
--- a/src/setup/required.rs
+++ b/src/setup/required.rs
@@ -4,26 +4,29 @@ use std::{
};
use axum::{
- extract::Request,
+ extract::{FromRef, Request},
http::StatusCode,
response::{IntoResponse, Response},
};
use tower::{Layer, Service};
-use crate::{app::App, error::Internal};
+use crate::{error::Internal, setup::app::Setup};
#[derive(Clone)]
-pub struct Required(pub App);
+pub struct Required<App>(pub App);
-impl Required {
- pub fn with_fallback<F>(self, fallback: F) -> WithFallback<F> {
+impl<App> Required<App> {
+ pub fn with_fallback<F>(self, fallback: F) -> WithFallback<App, F> {
let Self(app) = self;
WithFallback { app, fallback }
}
}
-impl<S> Layer<S> for Required {
- type Service = Middleware<S, Unavailable>;
+impl<S, App> Layer<S> for Required<App>
+where
+ Self: Clone,
+{
+ type Service = Middleware<S, App, Unavailable>;
fn layer(&self, inner: S) -> Self::Service {
let Self(app) = self.clone();
@@ -36,16 +39,16 @@ impl<S> Layer<S> for Required {
}
#[derive(Clone)]
-pub struct WithFallback<F> {
+pub struct WithFallback<App, F> {
app: App,
fallback: F,
}
-impl<S, F> Layer<S> for WithFallback<F>
+impl<S, App, F> Layer<S> for WithFallback<App, F>
where
Self: Clone,
{
- type Service = Middleware<S, F>;
+ type Service = Middleware<S, App, F>;
fn layer(&self, inner: S) -> Self::Service {
let Self { app, fallback } = self.clone();
@@ -58,17 +61,19 @@ where
}
#[derive(Clone)]
-pub struct Middleware<S, F> {
+pub struct Middleware<S, App, F> {
inner: S,
app: App,
fallback: F,
}
-impl<S, F> Service<Request> for Middleware<S, F>
+impl<S, App, F> Service<Request> for Middleware<S, App, F>
where
+ Setup: FromRef<App>,
Self: Clone,
S: Service<Request, Response = Response> + Send + 'static,
S::Future: Send,
+ App: Send + 'static,
F: IntoResponse + Clone + Send + 'static,
{
type Response = S::Response;
@@ -87,7 +92,7 @@ where
} = self.clone();
Box::pin(async move {
- match app.setup().completed().await {
+ match Setup::from_ref(&app).completed().await {
Ok(true) => inner.call(req).await,
Ok(false) => Ok(fallback.into_response()),
Err(error) => Ok(Internal::from(error).into_response()),
diff --git a/src/test/fixtures/boot.rs b/src/test/fixtures/boot.rs
index 120726f..7421512 100644
--- a/src/test/fixtures/boot.rs
+++ b/src/test/fixtures/boot.rs
@@ -1,7 +1,12 @@
-use crate::{app::App, event::Sequence};
+use axum::extract::FromRef;
-pub async fn resume_point(app: &App) -> Sequence {
- app.boot()
+use crate::{boot::app::Boot, event::Sequence};
+
+pub async fn resume_point<App>(app: &App) -> Sequence
+where
+ Boot: FromRef<App>,
+{
+ Boot::from_ref(app)
.snapshot()
.await
.expect("boot always succeeds")
diff --git a/src/test/fixtures/conversation.rs b/src/test/fixtures/conversation.rs
index fb2f58d..f0d8c8c 100644
--- a/src/test/fixtures/conversation.rs
+++ b/src/test/fixtures/conversation.rs
@@ -1,3 +1,4 @@
+use axum::extract::FromRef;
use faker_rand::{
en_us::{addresses::CityName, names::FullName},
faker_impl_from_templates,
@@ -6,15 +7,17 @@ use faker_rand::{
use rand;
use crate::{
- app::App,
clock::RequestedAt,
- conversation::{self, Conversation},
+ conversation::{self, Conversation, app::Conversations},
name::Name,
};
-pub async fn create(app: &App, created_at: &RequestedAt) -> Conversation {
+pub async fn create<App>(app: &App, created_at: &RequestedAt) -> Conversation
+where
+ Conversations: FromRef<App>,
+{
let name = propose();
- app.conversations()
+ Conversations::from_ref(app)
.create(&name, created_at)
.await
.expect("should always succeed if the conversation is actually new")
diff --git a/src/test/fixtures/cookie.rs b/src/test/fixtures/cookie.rs
index 7dc5083..0b5ec9b 100644
--- a/src/test/fixtures/cookie.rs
+++ b/src/test/fixtures/cookie.rs
@@ -1,21 +1,25 @@
+use axum::extract::FromRef;
use uuid::Uuid;
use crate::{
- app::App, clock::RequestedAt, name::Name, password::Password, token::extract::IdentityCookie,
+ clock::RequestedAt, login::app::Logins, name::Name, password::Password,
+ token::extract::IdentityCookie,
};
pub fn not_logged_in() -> IdentityCookie {
IdentityCookie::new()
}
-pub async fn logged_in(
+pub async fn logged_in<App>(
app: &App,
credentials: &(Name, Password),
now: &RequestedAt,
-) -> IdentityCookie {
+) -> IdentityCookie
+where
+ Logins: FromRef<App>,
+{
let (name, password) = credentials;
- let secret = app
- .logins()
+ let secret = Logins::from_ref(app)
.with_password(name, password, now)
.await
.expect("should succeed given known-valid credentials");
diff --git a/src/test/fixtures/identity.rs b/src/test/fixtures/identity.rs
index 93e4a38..20929f9 100644
--- a/src/test/fixtures/identity.rs
+++ b/src/test/fixtures/identity.rs
@@ -1,11 +1,15 @@
+use axum::extract::FromRef;
+
use crate::{
app::App,
clock::RequestedAt,
+ login::app::Logins,
name::Name,
password::Password,
test::fixtures,
token::{
Token,
+ app::Tokens,
extract::{Identity, IdentityCookie},
},
};
@@ -15,23 +19,30 @@ pub async fn create(app: &App, created_at: &RequestedAt) -> Identity {
logged_in(app, &credentials, created_at).await
}
-pub async fn from_cookie(
+pub async fn from_cookie<App>(
app: &App,
cookie: &IdentityCookie,
validated_at: &RequestedAt,
-) -> Identity {
+) -> Identity
+where
+ Tokens: FromRef<App>,
+{
let secret = cookie.secret().expect("identity token has a secret");
- app.tokens()
+ Tokens::from_ref(app)
.validate(&secret, validated_at)
.await
.expect("always validates newly-issued secret")
}
-pub async fn logged_in(
+pub async fn logged_in<App>(
app: &App,
credentials: &(Name, Password),
issued_at: &RequestedAt,
-) -> Identity {
+) -> Identity
+where
+ Tokens: FromRef<App>,
+ Logins: FromRef<App>,
+{
let secret = fixtures::cookie::logged_in(app, credentials, issued_at).await;
from_cookie(app, &secret, issued_at).await
}
diff --git a/src/test/fixtures/invite.rs b/src/test/fixtures/invite.rs
index 654d1b4..5a5d4d0 100644
--- a/src/test/fixtures/invite.rs
+++ b/src/test/fixtures/invite.rs
@@ -1,12 +1,16 @@
+use axum::extract::FromRef;
+
use crate::{
- app::App,
clock::DateTime,
- invite::{self, Invite},
+ invite::{self, Invite, app::Invites},
login::Login,
};
-pub async fn issue(app: &App, issuer: &Login, issued_at: &DateTime) -> Invite {
- app.invites()
+pub async fn issue<App>(app: &App, issuer: &Login, issued_at: &DateTime) -> Invite
+where
+ Invites: FromRef<App>,
+{
+ Invites::from_ref(app)
.issue(issuer, issued_at)
.await
.expect("issuing invites never fails")
diff --git a/src/test/fixtures/message.rs b/src/test/fixtures/message.rs
index 92ac1f5..0bd0b7a 100644
--- a/src/test/fixtures/message.rs
+++ b/src/test/fixtures/message.rs
@@ -1,22 +1,25 @@
+use axum::extract::FromRef;
use faker_rand::lorem::Paragraphs;
use crate::{
- app::App,
clock::RequestedAt,
conversation::Conversation,
login::Login,
- message::{self, Body, Message},
+ message::{self, Body, Message, app::Messages},
};
-pub async fn send(
+pub async fn send<App>(
app: &App,
conversation: &Conversation,
sender: &Login,
sent_at: &RequestedAt,
-) -> Message {
+) -> Message
+where
+ Messages: FromRef<App>,
+{
let body = propose();
- app.messages()
+ Messages::from_ref(app)
.send(&conversation.id, sender, sent_at, &body)
.await
.expect("should succeed if the conversation exists")
diff --git a/src/test/verify/identity.rs b/src/test/verify/identity.rs
index 8e2d36e..fba2a4d 100644
--- a/src/test/verify/identity.rs
+++ b/src/test/verify/identity.rs
@@ -1,31 +1,43 @@
+use axum::extract::FromRef;
+
use crate::{
- app::App,
login::Login,
name::Name,
test::{fixtures, verify},
- token::{app::ValidateError, extract::IdentityCookie},
+ token::{
+ app::{Tokens, ValidateError},
+ extract::IdentityCookie,
+ },
};
-pub async fn valid_for_name(app: &App, identity: &IdentityCookie, name: &Name) {
+pub async fn valid_for_name<App>(app: &App, identity: &IdentityCookie, name: &Name)
+where
+ Tokens: FromRef<App>,
+{
let secret = identity
.secret()
.expect("identity cookie must be set to be valid");
verify::token::valid_for_name(app, &secret, name).await;
}
-pub async fn valid_for_login(app: &App, identity: &IdentityCookie, login: &Login) {
+pub async fn valid_for_login<App>(app: &App, identity: &IdentityCookie, login: &Login)
+where
+ Tokens: FromRef<App>,
+{
let secret = identity
.secret()
.expect("identity cookie must be set to be valid");
verify::token::valid_for_login(app, &secret, login).await;
}
-pub async fn invalid(app: &App, identity: &IdentityCookie) {
+pub async fn invalid<App>(app: &App, identity: &IdentityCookie)
+where
+ Tokens: FromRef<App>,
+{
let secret = identity
.secret()
.expect("identity cookie must be set to be invalid");
- let validate_err = app
- .tokens()
+ let validate_err = Tokens::from_ref(app)
.validate(&secret, &fixtures::now())
.await
.expect_err("identity cookie secret must be invalid");
diff --git a/src/test/verify/login.rs b/src/test/verify/login.rs
index ae2e91e..aad01bc 100644
--- a/src/test/verify/login.rs
+++ b/src/test/verify/login.rs
@@ -1,23 +1,30 @@
+use axum::extract::FromRef;
+
use crate::{
- app::App,
- login::app::LoginError,
+ login::app::{LoginError, Logins},
name::Name,
password::Password,
test::{fixtures, verify},
+ token::app::Tokens,
};
-pub async fn valid_login(app: &App, name: &Name, password: &Password) {
- let secret = app
- .logins()
+pub async fn valid_login<App>(app: &App, name: &Name, password: &Password)
+where
+ Logins: FromRef<App>,
+ Tokens: FromRef<App>,
+{
+ let secret = Logins::from_ref(app)
.with_password(name, password, &fixtures::now())
.await
.expect("login credentials expected to be valid");
verify::token::valid_for_name(&app, &secret, &name).await;
}
-pub async fn invalid_login(app: &App, name: &Name, password: &Password) {
- let error = app
- .logins()
+pub async fn invalid_login<App>(app: &App, name: &Name, password: &Password)
+where
+ Logins: FromRef<App>,
+{
+ let error = Logins::from_ref(app)
.with_password(name, password, &fixtures::now())
.await
.expect_err("login credentials expected not to be valid");
diff --git a/src/test/verify/token.rs b/src/test/verify/token.rs
index adc4397..1b61a19 100644
--- a/src/test/verify/token.rs
+++ b/src/test/verify/token.rs
@@ -1,32 +1,39 @@
+use axum::extract::FromRef;
+
use crate::{
- app::App,
login::Login,
name::Name,
test::fixtures,
- token::{Secret, app},
+ token::{Secret, app, app::Tokens},
};
-pub async fn valid_for_name(app: &App, secret: &Secret, name: &Name) {
- let identity = app
- .tokens()
+pub async fn valid_for_name<App>(app: &App, secret: &Secret, name: &Name)
+where
+ Tokens: FromRef<App>,
+{
+ let identity = Tokens::from_ref(app)
.validate(secret, &fixtures::now())
.await
.expect("provided secret is valid");
assert_eq!(name, &identity.login.name);
}
-pub async fn valid_for_login(app: &App, secret: &Secret, login: &Login) {
- let identity = app
- .tokens()
+pub async fn valid_for_login<App>(app: &App, secret: &Secret, login: &Login)
+where
+ Tokens: FromRef<App>,
+{
+ let identity = Tokens::from_ref(app)
.validate(secret, &fixtures::now())
.await
.expect("provided secret is valid");
assert_eq!(login, &identity.login);
}
-pub async fn invalid(app: &App, secret: &Secret) {
- let error = app
- .tokens()
+pub async fn invalid<App>(app: &App, secret: &Secret)
+where
+ Tokens: FromRef<App>,
+{
+ let error = Tokens::from_ref(app)
.validate(secret, &fixtures::now())
.await
.expect_err("provided secret is invalid");
diff --git a/src/token/app.rs b/src/token/app.rs
index 1d68f32..332473d 100644
--- a/src/token/app.rs
+++ b/src/token/app.rs
@@ -12,13 +12,13 @@ use super::{
};
use crate::{clock::DateTime, db::NotFound as _, name};
-pub struct Tokens<'a> {
- db: &'a SqlitePool,
- token_events: &'a Broadcaster,
+pub struct Tokens {
+ db: SqlitePool,
+ token_events: Broadcaster,
}
-impl<'a> Tokens<'a> {
- pub const fn new(db: &'a SqlitePool, token_events: &'a Broadcaster) -> Self {
+impl Tokens {
+ pub const fn new(db: SqlitePool, token_events: Broadcaster) -> Self {
Self { db, token_events }
}
diff --git a/src/token/extract/identity.rs b/src/token/extract/identity.rs
index bee4e31..5c004ef 100644
--- a/src/token/extract/identity.rs
+++ b/src/token/extract/identity.rs
@@ -1,16 +1,18 @@
use axum::{
- extract::{FromRequestParts, OptionalFromRequestParts, State},
+ extract::{FromRef, FromRequestParts, OptionalFromRequestParts, State},
http::request::Parts,
response::{IntoResponse, Response},
};
use super::IdentityCookie;
use crate::{
- app::App,
clock::RequestedAt,
error::{Internal, Unauthorized},
login::Login,
- token::{Token, app::ValidateError},
+ token::{
+ Token,
+ app::{Tokens, ValidateError},
+ },
};
#[derive(Clone, Debug)]
@@ -19,7 +21,11 @@ pub struct Identity {
pub login: Login,
}
-impl FromRequestParts<App> for Identity {
+impl<App> FromRequestParts<App> for Identity
+where
+ Tokens: FromRef<App>,
+ App: Send + Sync,
+{
type Rejection = LoginError<Internal>;
async fn from_request_parts(parts: &mut Parts, state: &App) -> Result<Self, Self::Rejection> {
@@ -28,8 +34,8 @@ impl FromRequestParts<App> for Identity {
let secret = cookie.secret().ok_or(LoginError::Unauthorized)?;
- let app = State::<App>::from_request_parts(parts, state).await?;
- app.tokens()
+ let tokens = State::<Tokens>::from_request_parts(parts, state).await?;
+ tokens
.validate(&secret, &used_at)
.await
.map_err(|err| match err {
@@ -39,7 +45,11 @@ impl FromRequestParts<App> for Identity {
}
}
-impl OptionalFromRequestParts<App> for Identity {
+impl<App> OptionalFromRequestParts<App> for Identity
+where
+ Tokens: FromRef<App>,
+ App: Send + Sync,
+{
type Rejection = LoginError<Internal>;
async fn from_request_parts(
diff --git a/src/ui/handlers/conversation.rs b/src/ui/handlers/conversation.rs
index f1bb319..2ff090c 100644
--- a/src/ui/handlers/conversation.rs
+++ b/src/ui/handlers/conversation.rs
@@ -4,8 +4,7 @@ use axum::{
};
use crate::{
- app::App,
- conversation::{self, app},
+ conversation::{self, app, app::Conversations},
error::Internal,
token::extract::Identity,
ui::{
@@ -15,12 +14,12 @@ use crate::{
};
pub async fn handler(
- State(app): State<App>,
+ State(conversations): State<Conversations>,
identity: Option<Identity>,
Path(conversation): Path<conversation::Id>,
) -> Result<Asset, Error> {
let _ = identity.ok_or(Error::NotLoggedIn)?;
- app.conversations()
+ conversations
.get(&conversation)
.await
.map_err(Error::from)?;
diff --git a/src/ui/handlers/invite.rs b/src/ui/handlers/invite.rs
index 0f9580a..edd6dc1 100644
--- a/src/ui/handlers/invite.rs
+++ b/src/ui/handlers/invite.rs
@@ -4,9 +4,9 @@ use axum::{
};
use crate::{
- app::App,
error::Internal,
invite,
+ invite::app::Invites,
ui::{
assets::{Asset, Assets},
error::NotFound,
@@ -14,10 +14,10 @@ use crate::{
};
pub async fn handler(
- State(app): State<App>,
+ State(invites): State<Invites>,
Path(invite): Path<invite::Id>,
) -> Result<Asset, Error> {
- app.invites()
+ invites
.get(&invite)
.await
.map_err(Error::internal)?
diff --git a/src/ui/handlers/setup.rs b/src/ui/handlers/setup.rs
index 49821cf..5707765 100644
--- a/src/ui/handlers/setup.rs
+++ b/src/ui/handlers/setup.rs
@@ -4,14 +4,13 @@ use axum::{
};
use crate::{
- app::App,
error::Internal,
+ setup::app::Setup,
ui::assets::{Asset, Assets},
};
-pub async fn handler(State(app): State<App>) -> Result<Asset, Error> {
- if app
- .setup()
+pub async fn handler(State(setup): State<Setup>) -> Result<Asset, Error> {
+ if setup
.completed()
.await
.map_err(Internal::from)
diff --git a/src/user/app.rs b/src/user/app.rs
index 0d6046c..891e3b9 100644
--- a/src/user/app.rs
+++ b/src/user/app.rs
@@ -3,13 +3,13 @@ use sqlx::sqlite::SqlitePool;
use super::create::{self, Create};
use crate::{clock::DateTime, event::Broadcaster, login::Login, name::Name, password::Password};
-pub struct Users<'a> {
- db: &'a SqlitePool,
- events: &'a Broadcaster,
+pub struct Users {
+ db: SqlitePool,
+ events: Broadcaster,
}
-impl<'a> Users<'a> {
- pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self {
+impl Users {
+ pub const fn new(db: SqlitePool, events: Broadcaster) -> Self {
Self { db, events }
}
@@ -27,7 +27,7 @@ impl<'a> Users<'a> {
tx.commit().await?;
let login = stored.login().to_owned();
- stored.publish(self.events);
+ stored.publish(&self.events);
Ok(login)
}