diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-10-28 14:41:50 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-10-28 14:41:50 -0400 |
| commit | 4a91792e023a5877f8ac9b8a352e99c4486d698f (patch) | |
| tree | 0b0e5466d0945a5f853e98eb8d0b0215e67ed3fb | |
| parent | 9c271b27ff03cf4976326090ff54e3b5dfc04962 (diff) | |
| parent | 0ef69c7d256380e660edc45ace7f1d6151226340 (diff) | |
Merge remote-tracking branch 'codeberg/main' into push-notify
48 files changed, 423 insertions, 275 deletions
@@ -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) } |
