diff options
| -rw-r--r-- | src/boot/handlers/boot/mod.rs (renamed from src/boot/routes/get.rs) | 3 | ||||
| -rw-r--r-- | src/boot/handlers/boot/test.rs (renamed from src/boot/routes/test.rs) | 17 | ||||
| -rw-r--r-- | src/boot/handlers/mod.rs | 3 | ||||
| -rw-r--r-- | src/boot/mod.rs | 10 | ||||
| -rw-r--r-- | src/boot/routes/mod.rs | 11 | ||||
| -rw-r--r-- | src/broadcast.rs | 7 | ||||
| -rw-r--r-- | src/channel/handlers/create/mod.rs (renamed from src/channel/routes/post.rs) | 3 | ||||
| -rw-r--r-- | src/channel/handlers/create/test.rs (renamed from src/channel/routes/test.rs) | 53 | ||||
| -rw-r--r-- | src/channel/handlers/delete/mod.rs (renamed from src/channel/routes/channel/delete.rs) | 7 | ||||
| -rw-r--r-- | src/channel/handlers/delete/test.rs (renamed from src/channel/routes/channel/test/delete.rs) | 17 | ||||
| -rw-r--r-- | src/channel/handlers/mod.rs | 9 | ||||
| -rw-r--r-- | src/channel/handlers/send/mod.rs (renamed from src/channel/routes/channel/post.rs) | 6 | ||||
| -rw-r--r-- | src/channel/handlers/send/test.rs (renamed from src/channel/routes/channel/test/post.rs) | 14 | ||||
| -rw-r--r-- | src/channel/mod.rs | 4 | ||||
| -rw-r--r-- | src/channel/routes/channel/mod.rs | 9 | ||||
| -rw-r--r-- | src/channel/routes/channel/test/mod.rs | 2 | ||||
| -rw-r--r-- | src/channel/routes/mod.rs | 19 | ||||
| -rw-r--r-- | src/cli.rs | 39 | ||||
| -rw-r--r-- | src/event/handlers/mod.rs | 3 | ||||
| -rw-r--r-- | src/event/handlers/stream/mod.rs (renamed from src/event/routes/get.rs) | 3 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/channel.rs (renamed from src/event/routes/test/channel.rs) | 33 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/invite.rs (renamed from src/event/routes/test/invite.rs) | 13 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/message.rs (renamed from src/event/routes/test/message.rs) | 41 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/mod.rs (renamed from src/event/routes/test/mod.rs) | 2 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/resume.rs (renamed from src/event/routes/test/resume.rs) | 22 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/setup.rs (renamed from src/event/routes/test/setup.rs) | 9 | ||||
| -rw-r--r-- | src/event/handlers/stream/test/token.rs (renamed from src/event/routes/test/token.rs) | 17 | ||||
| -rw-r--r-- | src/event/mod.rs | 10 | ||||
| -rw-r--r-- | src/event/routes/mod.rs | 11 | ||||
| -rw-r--r-- | src/invite/handlers/accept/mod.rs (renamed from src/invite/routes/invite/post.rs) | 7 | ||||
| -rw-r--r-- | src/invite/handlers/accept/test.rs (renamed from src/invite/routes/invite/test/post.rs) | 30 | ||||
| -rw-r--r-- | src/invite/handlers/get/mod.rs (renamed from src/invite/routes/invite/get.rs) | 7 | ||||
| -rw-r--r-- | src/invite/handlers/get/test.rs (renamed from src/invite/routes/invite/test/get.rs) | 12 | ||||
| -rw-r--r-- | src/invite/handlers/issue/mod.rs (renamed from src/invite/routes/post.rs) | 3 | ||||
| -rw-r--r-- | src/invite/handlers/issue/test.rs (renamed from src/invite/routes/test.rs) | 5 | ||||
| -rw-r--r-- | src/invite/handlers/mod.rs | 9 | ||||
| -rw-r--r-- | src/invite/mod.rs | 8 | ||||
| -rw-r--r-- | src/invite/routes/invite/mod.rs | 6 | ||||
| -rw-r--r-- | src/invite/routes/invite/test/mod.rs | 2 | ||||
| -rw-r--r-- | src/invite/routes/mod.rs | 18 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/message/handlers/delete/mod.rs | 55 | ||||
| -rw-r--r-- | src/message/handlers/delete/test.rs (renamed from src/message/routes/message/test.rs) | 13 | ||||
| -rw-r--r-- | src/message/handlers/mod.rs | 3 | ||||
| -rw-r--r-- | src/message/mod.rs | 6 | ||||
| -rw-r--r-- | src/message/routes/message/mod.rs | 61 | ||||
| -rw-r--r-- | src/message/routes/mod.rs | 9 | ||||
| -rw-r--r-- | src/routes.rs | 62 | ||||
| -rw-r--r-- | src/setup/handlers/mod.rs | 3 | ||||
| -rw-r--r-- | src/setup/handlers/setup/mod.rs (renamed from src/setup/routes/post.rs) | 3 | ||||
| -rw-r--r-- | src/setup/handlers/setup/test.rs (renamed from src/setup/routes/test.rs) | 17 | ||||
| -rw-r--r-- | src/setup/mod.rs | 4 | ||||
| -rw-r--r-- | src/setup/routes/mod.rs | 11 | ||||
| -rw-r--r-- | src/ui/handlers/asset.rs | 7 | ||||
| -rw-r--r-- | src/ui/handlers/channel.rs | 58 | ||||
| -rw-r--r-- | src/ui/handlers/index.rs (renamed from src/ui/routes/get.rs) | 0 | ||||
| -rw-r--r-- | src/ui/handlers/invite.rs | 53 | ||||
| -rw-r--r-- | src/ui/handlers/login.rs | 8 | ||||
| -rw-r--r-- | src/ui/handlers/me.rs | 30 | ||||
| -rw-r--r-- | src/ui/handlers/mod.rs | 15 | ||||
| -rw-r--r-- | src/ui/handlers/setup.rs | 41 | ||||
| -rw-r--r-- | src/ui/mod.rs | 4 | ||||
| -rw-r--r-- | src/ui/routes/ch/channel.rs | 60 | ||||
| -rw-r--r-- | src/ui/routes/ch/mod.rs | 1 | ||||
| -rw-r--r-- | src/ui/routes/invite/invite.rs | 55 | ||||
| -rw-r--r-- | src/ui/routes/invite/mod.rs | 4 | ||||
| -rw-r--r-- | src/ui/routes/login.rs | 10 | ||||
| -rw-r--r-- | src/ui/routes/me.rs | 32 | ||||
| -rw-r--r-- | src/ui/routes/mod.rs | 28 | ||||
| -rw-r--r-- | src/ui/routes/path.rs | 9 | ||||
| -rw-r--r-- | src/ui/routes/setup.rs | 43 | ||||
| -rw-r--r-- | src/user/handlers/login/mod.rs (renamed from src/user/routes/login/post.rs) | 3 | ||||
| -rw-r--r-- | src/user/handlers/login/test.rs (renamed from src/user/routes/login/test.rs) | 21 | ||||
| -rw-r--r-- | src/user/handlers/logout/mod.rs (renamed from src/user/routes/logout/post.rs) | 3 | ||||
| -rw-r--r-- | src/user/handlers/logout/test.rs (renamed from src/user/routes/logout/test.rs) | 12 | ||||
| -rw-r--r-- | src/user/handlers/mod.rs | 7 | ||||
| -rw-r--r-- | src/user/handlers/password/mod.rs (renamed from src/user/routes/password/post.rs) | 3 | ||||
| -rw-r--r-- | src/user/handlers/password/test.rs (renamed from src/user/routes/password/test.rs) | 5 | ||||
| -rw-r--r-- | src/user/mod.rs | 6 | ||||
| -rw-r--r-- | src/user/routes/login/mod.rs | 4 | ||||
| -rw-r--r-- | src/user/routes/logout/mod.rs | 4 | ||||
| -rw-r--r-- | src/user/routes/mod.rs | 14 | ||||
| -rw-r--r-- | src/user/routes/password/mod.rs | 4 |
83 files changed, 604 insertions, 692 deletions
diff --git a/src/boot/routes/get.rs b/src/boot/handlers/boot/mod.rs index 4873b7a..010f57b 100644 --- a/src/boot/routes/get.rs +++ b/src/boot/handlers/boot/mod.rs @@ -5,6 +5,9 @@ use axum::{ use crate::{app::App, boot::Snapshot, error::Internal, token::extract::Identity, user::User}; +#[cfg(test)] +mod test; + pub async fn handler(State(app): State<App>, identity: Identity) -> Result<Response, Internal> { let snapshot = app.boot().snapshot().await?; Ok(Response { diff --git a/src/boot/routes/test.rs b/src/boot/handlers/boot/test.rs index 55802fe..0a7622b 100644 --- a/src/boot/routes/test.rs +++ b/src/boot/handlers/boot/test.rs @@ -1,6 +1,5 @@ use axum::extract::State; -use super::get; use crate::test::fixtures; #[tokio::test] @@ -8,7 +7,7 @@ async fn returns_identity() { let app = fixtures::scratch_app().await; let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer.clone()) + let response = super::handler(State(app), viewer.clone()) .await .expect("boot always succeeds"); @@ -21,7 +20,7 @@ async fn includes_logins() { let spectator = fixtures::user::create(&app, &fixtures::now()).await; let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -34,7 +33,7 @@ async fn includes_channels() { let channel = fixtures::channel::create(&app, &fixtures::now()).await; let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -49,7 +48,7 @@ async fn includes_messages() { let message = fixtures::message::send(&app, &channel, &sender, &fixtures::now()).await; let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -70,7 +69,7 @@ async fn excludes_expired_messages() { .expect("expiry never fails"); let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -90,7 +89,7 @@ async fn excludes_deleted_messages() { .expect("deleting valid message succeeds"); let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -108,7 +107,7 @@ async fn excludes_expired_channels() { .expect("expiry never fails"); let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); @@ -126,7 +125,7 @@ async fn excludes_deleted_channels() { .expect("deleting a valid channel succeeds"); let viewer = fixtures::identity::fictitious(); - let response = get::handler(State(app), viewer) + let response = super::handler(State(app), viewer) .await .expect("boot always succeeds"); diff --git a/src/boot/handlers/mod.rs b/src/boot/handlers/mod.rs new file mode 100644 index 0000000..194c4a9 --- /dev/null +++ b/src/boot/handlers/mod.rs @@ -0,0 +1,3 @@ +mod boot; + +pub use boot::handler as boot; diff --git a/src/boot/mod.rs b/src/boot/mod.rs index 122bd53..48da4f0 100644 --- a/src/boot/mod.rs +++ b/src/boot/mod.rs @@ -1,11 +1,11 @@ -use crate::{channel::Channel, event::Sequence, message::Message, user::User}; -use serde::Serialize; use std::time::Duration; -pub mod app; -mod routes; +use serde::Serialize; -pub use self::routes::router; +use crate::{channel::Channel, event::Sequence, message::Message, user::User}; + +pub mod app; +pub mod handlers; #[derive(serde::Serialize)] pub struct Snapshot { diff --git a/src/boot/routes/mod.rs b/src/boot/routes/mod.rs deleted file mode 100644 index 8fd99d3..0000000 --- a/src/boot/routes/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use axum::{Router, routing::get}; - -use crate::app::App; - -mod get; -#[cfg(test)] -mod test; - -pub fn router() -> Router<App> { - Router::new().route("/api/boot", get(get::handler)) -} diff --git a/src/broadcast.rs b/src/broadcast.rs index 174016e..41a5e23 100644 --- a/src/broadcast.rs +++ b/src/broadcast.rs @@ -58,10 +58,9 @@ where #[allow(clippy::manual_ok_err)] future::ready(match r { Ok(event) => Some(event), - // Stop the stream here. This will disconnect SSE clients - // (see `routes.rs`), who will then resume from - // `Last-Event-ID`, allowing them to catch up by reading - // the skipped messages from the database. + // Stop the stream here. This will disconnect SSE clients (see the `/api/events` + // endpoint), who will then resume from `Last-Event-ID`, allowing them to catch up + // by reading the skipped messages from the database. // // See also: // <https://users.rust-lang.org/t/taking-from-stream-while-ok/48854> diff --git a/src/channel/routes/post.rs b/src/channel/handlers/create/mod.rs index 6ea9b61..2c860fc 100644 --- a/src/channel/routes/post.rs +++ b/src/channel/handlers/create/mod.rs @@ -13,6 +13,9 @@ use crate::{ token::extract::Identity, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, _: Identity, // requires auth, but doesn't actually care who you are diff --git a/src/channel/routes/test.rs b/src/channel/handlers/create/test.rs index cba8f2e..3c770cf 100644 --- a/src/channel/routes/test.rs +++ b/src/channel/handlers/create/test.rs @@ -3,7 +3,6 @@ use std::future; use axum::extract::{Json, State}; use futures::stream::StreamExt as _; -use super::post; use crate::{ channel::app, name::Name, @@ -21,9 +20,9 @@ async fn new_channel() { // Call the endpoint let name = fixtures::channel::propose(); - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) + 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 channel in an empty app succeeds"); @@ -67,11 +66,11 @@ async fn duplicate_name() { // Call the endpoint - let request = post::Request { + let request = super::Request { name: channel.name.clone(), }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) + let super::Error(error) = + super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) .await .expect_err("duplicate channel name should fail the request"); @@ -100,11 +99,11 @@ async fn conflicting_canonical_name() { // Call the endpoint - let request = post::Request { + let request = super::Request { name: conflicting_name.clone(), }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) + let super::Error(error) = + super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) .await .expect_err("duplicate channel name should fail the request"); @@ -126,11 +125,15 @@ async fn invalid_name() { // Call the endpoint let name = fixtures::channel::propose_invalid_name(); - let request = post::Request { name: name.clone() }; - let post::Error(error) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("invalid channel name should fail the request"); + let request = super::Request { name: name.clone() }; + let super::Error(error) = crate::channel::handlers::create::handler( + State(app.clone()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect_err("invalid channel name should fail the request"); // Verify the structure of the response @@ -150,8 +153,8 @@ async fn name_reusable_after_delete() { // Call the endpoint (first time) - let request = post::Request { name: name.clone() }; - let post::Response(response) = post::handler( + let request = super::Request { name: name.clone() }; + let super::Response(response) = super::handler( State(app.clone()), creator.clone(), fixtures::now(), @@ -169,9 +172,9 @@ async fn name_reusable_after_delete() { // Call the endpoint (second time) - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) + 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 channel deleted"); @@ -199,8 +202,8 @@ async fn name_reusable_after_expiry() { // Call the endpoint (first time) - let request = post::Request { name: name.clone() }; - let post::Response(_) = post::handler( + let request = super::Request { name: name.clone() }; + let super::Response(_) = super::handler( State(app.clone()), creator.clone(), fixtures::ancient(), @@ -218,9 +221,9 @@ async fn name_reusable_after_expiry() { // Call the endpoint (second time) - let request = post::Request { name: name.clone() }; - let post::Response(response) = - post::handler(State(app.clone()), creator, fixtures::now(), Json(request)) + 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 channel expired"); diff --git a/src/channel/routes/channel/delete.rs b/src/channel/handlers/delete/mod.rs index 3db7772..b986bec 100644 --- a/src/channel/routes/channel/delete.rs +++ b/src/channel/handlers/delete/mod.rs @@ -6,15 +6,18 @@ use axum::{ use crate::{ app::App, - channel::{self, app}, + channel::{self, app, handlers::PathInfo}, clock::RequestedAt, error::{Internal, NotFound}, token::extract::Identity, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, - Path(channel): Path<super::PathInfo>, + Path(channel): Path<PathInfo>, RequestedAt(deleted_at): RequestedAt, _: Identity, ) -> Result<Response, Error> { diff --git a/src/channel/routes/channel/test/delete.rs b/src/channel/handlers/delete/test.rs index bd9261d..b1e42ea 100644 --- a/src/channel/routes/channel/test/delete.rs +++ b/src/channel/handlers/delete/test.rs @@ -1,9 +1,6 @@ use axum::extract::{Path, State}; -use crate::{ - channel::{app, routes::channel::delete}, - test::fixtures, -}; +use crate::{channel::app, test::fixtures}; #[tokio::test] pub async fn valid_channel() { @@ -15,7 +12,7 @@ pub async fn valid_channel() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let response = delete::handler( + let response = super::handler( State(app.clone()), Path(channel.id.clone()), fixtures::now(), @@ -44,7 +41,7 @@ pub async fn invalid_channel_id() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let channel = fixtures::channel::fictitious(); - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(channel.clone()), fixtures::now(), @@ -73,7 +70,7 @@ pub async fn channel_deleted() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(channel.id.clone()), fixtures::now(), @@ -102,7 +99,7 @@ pub async fn channel_expired() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(channel.id.clone()), fixtures::now(), @@ -136,7 +133,7 @@ pub async fn channel_purged() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(channel.id.clone()), fixtures::now(), @@ -162,7 +159,7 @@ pub async fn channel_not_empty() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(channel.id.clone()), fixtures::now(), diff --git a/src/channel/handlers/mod.rs b/src/channel/handlers/mod.rs new file mode 100644 index 0000000..f2ffd0d --- /dev/null +++ b/src/channel/handlers/mod.rs @@ -0,0 +1,9 @@ +mod create; +mod delete; +mod send; + +pub use create::handler as create; +pub use delete::handler as delete; +pub use send::handler as send; + +type PathInfo = crate::channel::Id; diff --git a/src/channel/routes/channel/post.rs b/src/channel/handlers/send/mod.rs index 2547122..aa241e2 100644 --- a/src/channel/routes/channel/post.rs +++ b/src/channel/handlers/send/mod.rs @@ -4,6 +4,7 @@ use axum::{ response::{self, IntoResponse}, }; +use crate::channel::handlers::PathInfo; use crate::{ app::App, clock::RequestedAt, @@ -12,9 +13,12 @@ use crate::{ token::extract::Identity, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, - Path(channel): Path<super::PathInfo>, + Path(channel): Path<PathInfo>, RequestedAt(sent_at): RequestedAt, identity: Identity, Json(request): Json<Request>, diff --git a/src/channel/routes/channel/test/post.rs b/src/channel/handlers/send/test.rs index d9527ac..f43f901 100644 --- a/src/channel/routes/channel/test/post.rs +++ b/src/channel/handlers/send/test.rs @@ -2,7 +2,7 @@ use axum::extract::{Json, Path, State}; use futures::stream::{self, StreamExt as _}; use crate::{ - channel::{self, routes::channel::post}, + channel, event::Sequenced, message::app::SendError, test::fixtures::{self, future::Expect as _}, @@ -25,9 +25,9 @@ async fn messages_in_order() { ]; for (sent_at, body) in &requests { - let request = post::Request { body: body.clone() }; + let request = super::Request { body: body.clone() }; - let _ = post::handler( + let _ = super::handler( State(app.clone()), Path(channel.id.clone()), sent_at.clone(), @@ -71,10 +71,10 @@ async fn nonexistent_channel() { let sent_at = fixtures::now(); let channel = channel::Id::generate(); - let request = post::Request { + let request = super::Request { body: fixtures::message::propose(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app), Path(channel.clone()), sent_at, @@ -109,10 +109,10 @@ async fn deleted_channel() { let sent_at = fixtures::now(); let channel = channel::Id::generate(); - let request = post::Request { + let request = super::Request { body: fixtures::message::propose(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app), Path(channel.clone()), sent_at, diff --git a/src/channel/mod.rs b/src/channel/mod.rs index d5ba828..bbaf33e 100644 --- a/src/channel/mod.rs +++ b/src/channel/mod.rs @@ -1,10 +1,10 @@ pub mod app; pub mod event; +pub mod handlers; mod history; mod id; pub mod repo; -mod routes; mod snapshot; mod validate; -pub use self::{event::Event, history::History, id::Id, routes::router, snapshot::Channel}; +pub use self::{event::Event, history::History, id::Id, snapshot::Channel}; diff --git a/src/channel/routes/channel/mod.rs b/src/channel/routes/channel/mod.rs deleted file mode 100644 index 31a9142..0000000 --- a/src/channel/routes/channel/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use crate::channel::Id; - -pub mod delete; -pub mod post; - -#[cfg(test)] -mod test; - -type PathInfo = Id; diff --git a/src/channel/routes/channel/test/mod.rs b/src/channel/routes/channel/test/mod.rs deleted file mode 100644 index 78bf86e..0000000 --- a/src/channel/routes/channel/test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod delete; -mod post; diff --git a/src/channel/routes/mod.rs b/src/channel/routes/mod.rs deleted file mode 100644 index c917348..0000000 --- a/src/channel/routes/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use axum::{ - Router, - routing::{delete, post}, -}; - -use crate::app::App; - -mod channel; -mod post; - -#[cfg(test)] -mod test; - -pub fn router() -> Router<App> { - Router::new() - .route("/api/channels", post(post::handler)) - .route("/api/channels/{channel}", post(channel::post::handler)) - .route("/api/channels/{channel}", delete(channel::delete::handler)) -} @@ -6,7 +6,6 @@ use std::{future, io}; use axum::{ - Router, http::header, middleware, response::{IntoResponse, Response}, @@ -15,7 +14,7 @@ use clap::{CommandFactory, Parser}; use sqlx::sqlite::SqlitePool; use tokio::net; -use crate::{app::App, boot, channel, clock, db, event, expire, invite, message, setup, ui, user}; +use crate::{app::App, clock, db, routes}; /// Command-line entry point for running the `pilcrow` server. /// @@ -82,7 +81,7 @@ impl Args { let pool = self.pool().await?; let app = App::from(pool); - let app = routers(&app) + let app = routes::routes(&app) .route_layer(middleware::from_fn(clock::middleware)) .route_layer(middleware::map_response(Self::server_info())) .with_state(app); @@ -123,40 +122,6 @@ impl Args { } } -fn routers(app: &App) -> Router<App> { - [ - [ - // API endpoints that require setup to function - boot::router(), - channel::router(), - event::router(), - invite::router(), - user::router(), - message::router(), - ] - .into_iter() - .fold(Router::default(), Router::merge) - // Run expiry whenever someone accesses the API. This was previously a blanket middleware - // affecting the whole service, but loading the client makes a several requests before the - // client can completely load, each of which was triggering expiry. There is absolutely no - // upside to re-checking expiry tens of times back-to-back like that; the API is accessed - // more regularly and with less of a traffic rush. - // - // This should, probably, be moved to a background job at some point. - .route_layer(middleware::from_fn_with_state( - app.clone(), - expire::middleware, - )) - .route_layer(setup::Required(app.clone())), - // API endpoints that handle setup - setup::router(), - // The UI (handles setup state itself) - ui::router(app), - ] - .into_iter() - .fold(Router::default(), Router::merge) -} - fn started_msg(listener: &net::TcpListener) -> io::Result<String> { let local_addr = listener.local_addr()?; Ok(format!("listening on http://{local_addr}/")) diff --git a/src/event/handlers/mod.rs b/src/event/handlers/mod.rs new file mode 100644 index 0000000..22d988c --- /dev/null +++ b/src/event/handlers/mod.rs @@ -0,0 +1,3 @@ +mod stream; + +pub use stream::handler as stream; diff --git a/src/event/routes/get.rs b/src/event/handlers/stream/mod.rs index f6c91fa..d0d3f08 100644 --- a/src/event/routes/get.rs +++ b/src/event/handlers/stream/mod.rs @@ -15,6 +15,9 @@ use crate::{ token::{app::ValidateError, extract::Identity}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, identity: Identity, diff --git a/src/event/routes/test/channel.rs b/src/event/handlers/stream/test/channel.rs index 0695ab1..187c3c3 100644 --- a/src/event/routes/test/channel.rs +++ b/src/event/handlers/stream/test/channel.rs @@ -2,10 +2,7 @@ use axum::extract::State; use axum_extra::extract::Query; use futures::{future, stream::StreamExt as _}; -use crate::{ - event::routes::get, - test::fixtures::{self, future::Expect as _}, -}; +use crate::test::fixtures::{self, future::Expect as _}; #[tokio::test] async fn creating() { @@ -17,11 +14,11 @@ async fn creating() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -65,11 +62,11 @@ async fn previously_created() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -96,11 +93,11 @@ async fn expiring() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -140,11 +137,11 @@ async fn previously_expired() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -170,11 +167,11 @@ async fn deleting() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -214,11 +211,11 @@ async fn previously_deleted() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -256,11 +253,11 @@ async fn previously_purged() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/routes/test/invite.rs b/src/event/handlers/stream/test/invite.rs index 1d1bec6..c8e12fb 100644 --- a/src/event/routes/test/invite.rs +++ b/src/event/handlers/stream/test/invite.rs @@ -2,10 +2,7 @@ use axum::extract::State; use axum_extra::extract::Query; use futures::{future, stream::StreamExt as _}; -use crate::{ - event::routes::get, - test::fixtures::{self, future::Expect as _}, -}; +use crate::test::fixtures::{self, future::Expect as _}; #[tokio::test] async fn accepting_invite() { @@ -19,11 +16,11 @@ async fn accepting_invite() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -69,11 +66,11 @@ async fn previously_accepted_invite() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/routes/test/message.rs b/src/event/handlers/stream/test/message.rs index 84a3aec..a80c896 100644 --- a/src/event/routes/test/message.rs +++ b/src/event/handlers/stream/test/message.rs @@ -5,10 +5,7 @@ use futures::{ stream::{self, StreamExt as _}, }; -use crate::{ - event::routes::get, - test::fixtures::{self, future::Expect as _}, -}; +use crate::test::fixtures::{self, future::Expect as _}; #[tokio::test] async fn sending() { @@ -21,11 +18,11 @@ async fn sending() { // Call the endpoint let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -80,11 +77,11 @@ async fn previously_sent() { // Call the endpoint let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -126,11 +123,11 @@ async fn sent_in_multiple_channels() { // Call the endpoint let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -168,11 +165,11 @@ async fn sent_sequentially() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -207,11 +204,11 @@ async fn expiring() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -253,11 +250,11 @@ async fn previously_expired() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -285,11 +282,11 @@ async fn deleting() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -331,11 +328,11 @@ async fn previously_deleted() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -375,11 +372,11 @@ async fn previously_purged() { // Subscribe let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/routes/test/mod.rs b/src/event/handlers/stream/test/mod.rs index e7e35f1..df43deb 100644 --- a/src/event/routes/test/mod.rs +++ b/src/event/handlers/stream/test/mod.rs @@ -4,3 +4,5 @@ mod message; mod resume; mod setup; mod token; + +use super::{QueryParams, Response, handler}; diff --git a/src/event/routes/test/resume.rs b/src/event/handlers/stream/test/resume.rs index 633eae3..34fee4d 100644 --- a/src/event/routes/test/resume.rs +++ b/src/event/handlers/stream/test/resume.rs @@ -5,7 +5,7 @@ use axum_extra::extract::Query; use futures::stream::{self, StreamExt as _}; use crate::{ - event::{Sequenced as _, routes::get}, + event::Sequenced as _, test::fixtures::{self, future::Expect as _}, }; @@ -31,11 +31,11 @@ async fn resume() { let resume_at = { // First subscription - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -52,11 +52,11 @@ async fn resume() { }; // Resume after disconnect - let get::Response(resumed) = get::handler( + let super::Response(resumed) = super::handler( State(app), subscriber, Some(resume_at.into()), - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -113,11 +113,11 @@ async fn serial_resume() { // First subscription - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -156,11 +156,11 @@ async fn serial_resume() { ]; // Second subscription - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), Some(resume_at.into()), - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -199,11 +199,11 @@ async fn serial_resume() { ]; // Third subscription - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), Some(resume_at.into()), - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/routes/test/setup.rs b/src/event/handlers/stream/test/setup.rs index 1170fe4..5335055 100644 --- a/src/event/routes/test/setup.rs +++ b/src/event/handlers/stream/test/setup.rs @@ -2,10 +2,7 @@ use axum::extract::State; use axum_extra::extract::Query; use futures::{future, stream::StreamExt as _}; -use crate::{ - event::routes::get, - test::fixtures::{self, future::Expect as _}, -}; +use crate::test::fixtures::{self, future::Expect as _}; // There's no test for this in subscribe-then-setup order because creating an // identity to subscribe with also completes initial setup, preventing the @@ -29,11 +26,11 @@ async fn previously_completed() { // Subscribe to events let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/routes/test/token.rs b/src/event/handlers/stream/test/token.rs index a467de5..2008323 100644 --- a/src/event/routes/test/token.rs +++ b/src/event/handlers/stream/test/token.rs @@ -2,10 +2,7 @@ use axum::extract::State; use axum_extra::extract::Query; use futures::{future, stream::StreamExt as _}; -use crate::{ - event::routes::get, - test::fixtures::{self, future::Expect as _}, -}; +use crate::test::fixtures::{self, future::Expect as _}; #[tokio::test] async fn terminates_on_token_expiry() { @@ -22,11 +19,11 @@ async fn terminates_on_token_expiry() { let subscriber = fixtures::identity::logged_in(&app, &subscriber_creds, &fixtures::ancient()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber, None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -67,11 +64,11 @@ async fn terminates_on_logout() { let subscriber = fixtures::identity::create(&app, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); @@ -115,11 +112,11 @@ async fn terminates_on_password_change() { let cookie = fixtures::cookie::logged_in(&app, &creds, &fixtures::now()).await; let subscriber = fixtures::identity::from_cookie(&app, &cookie, &fixtures::now()).await; - let get::Response(events) = get::handler( + let super::Response(events) = super::handler( State(app.clone()), subscriber.clone(), None, - Query(get::QueryParams { resume_point }), + Query(super::QueryParams { resume_point }), ) .await .expect("subscribe never fails"); diff --git a/src/event/mod.rs b/src/event/mod.rs index 1f2ec42..6657243 100644 --- a/src/event/mod.rs +++ b/src/event/mod.rs @@ -1,18 +1,18 @@ -use crate::{channel, message, user}; -use axum::response::sse; -use axum::response::sse::KeepAlive; use std::time::Duration; +use axum::response::sse::{self, KeepAlive}; + +use crate::{channel, message, user}; + pub mod app; mod broadcaster; mod extract; +pub mod handlers; pub mod repo; -mod routes; mod sequence; pub use self::{ broadcaster::Broadcaster, - routes::router, sequence::{Instant, Sequence, Sequenced}, }; diff --git a/src/event/routes/mod.rs b/src/event/routes/mod.rs deleted file mode 100644 index 742d397..0000000 --- a/src/event/routes/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use axum::{Router, routing::get}; - -use crate::app::App; - -mod get; -#[cfg(test)] -mod test; - -pub fn router() -> Router<App> { - Router::new().route("/api/events", get(get::handler)) -} diff --git a/src/invite/routes/invite/post.rs b/src/invite/handlers/accept/mod.rs index 58d15c2..0d5f08a 100644 --- a/src/invite/routes/invite/post.rs +++ b/src/invite/handlers/accept/mod.rs @@ -8,17 +8,20 @@ use crate::{ app::App, clock::RequestedAt, error::{Internal, NotFound}, - invite::app, + invite::{app, handlers::PathInfo}, name::Name, token::extract::IdentityCookie, user::{Password, User}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(accepted_at): RequestedAt, identity: IdentityCookie, - Path(invite): Path<super::PathInfo>, + Path(invite): Path<PathInfo>, Json(request): Json<Request>, ) -> Result<(IdentityCookie, Json<User>), Error> { let (login, secret) = app diff --git a/src/invite/routes/invite/test/post.rs b/src/invite/handlers/accept/test.rs index b204b32..cb13900 100644 --- a/src/invite/routes/invite/test/post.rs +++ b/src/invite/handlers/accept/test.rs @@ -1,10 +1,6 @@ use axum::extract::{Json, Path, State}; -use crate::{ - invite::{app::AcceptError, routes::invite::post}, - name::Name, - test::fixtures, -}; +use crate::{invite::app::AcceptError, name::Name, test::fixtures}; #[tokio::test] async fn valid_invite() { @@ -18,11 +14,11 @@ async fn valid_invite() { let (name, password) = fixtures::user::propose(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let (identity, Json(response)) = post::handler( + let (identity, Json(response)) = super::handler( State(app.clone()), fixtures::now(), identity, @@ -70,11 +66,11 @@ async fn nonexistent_invite() { let (name, password) = fixtures::user::propose(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app.clone()), fixtures::now(), identity, @@ -106,11 +102,11 @@ async fn expired_invite() { let (name, password) = fixtures::user::propose(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app.clone()), fixtures::now(), identity, @@ -143,11 +139,11 @@ async fn accepted_invite() { let (name, password) = fixtures::user::propose(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app.clone()), fixtures::now(), identity, @@ -186,11 +182,11 @@ async fn conflicting_name() { let password = fixtures::user::propose_password(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: conflicting_name.clone(), password: password.clone(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app.clone()), fixtures::now(), identity, @@ -220,11 +216,11 @@ async fn invalid_name() { let name = fixtures::user::propose_invalid_name(); let password = fixtures::user::propose_password(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let post::Error(error) = post::handler( + let super::Error(error) = super::handler( State(app.clone()), fixtures::now(), identity, diff --git a/src/invite/routes/invite/get.rs b/src/invite/handlers/get/mod.rs index f862833..bb72586 100644 --- a/src/invite/routes/invite/get.rs +++ b/src/invite/handlers/get/mod.rs @@ -6,12 +6,15 @@ use axum::{ use crate::{ app::App, error::{Internal, NotFound}, - invite::{Id, Summary}, + invite::{Id, Summary, handlers::PathInfo}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, - Path(invite): Path<super::PathInfo>, + Path(invite): Path<PathInfo>, ) -> Result<Json<Summary>, Error> { app.invites() .get(&invite) diff --git a/src/invite/routes/invite/test/get.rs b/src/invite/handlers/get/test.rs index 0dc8a79..0f2f725 100644 --- a/src/invite/routes/invite/test/get.rs +++ b/src/invite/handlers/get/test.rs @@ -1,6 +1,6 @@ use axum::extract::{Json, Path, State}; -use crate::{invite::routes::invite::get, test::fixtures}; +use crate::test::fixtures; #[tokio::test] async fn valid_invite() { @@ -12,7 +12,7 @@ async fn valid_invite() { // Call endpoint - let Json(response) = get::handler(State(app), Path(invite.id)) + let Json(response) = super::handler(State(app), Path(invite.id)) .await .expect("get for an existing invite succeeds"); @@ -31,13 +31,13 @@ async fn nonexistent_invite() { // Call endpoint let invite = fixtures::invite::fictitious(); - let error = get::handler(State(app), Path(invite.clone())) + let error = super::handler(State(app), Path(invite.clone())) .await .expect_err("get for a nonexistent invite fails"); // Verify response - assert!(matches!(error, get::Error::NotFound(error_id) if invite == error_id)); + assert!(matches!(error, super::Error::NotFound(error_id) if invite == error_id)); } #[tokio::test] @@ -55,11 +55,11 @@ async fn expired_invite() { // Call endpoint - let error = get::handler(State(app), Path(invite.id.clone())) + let error = super::handler(State(app), Path(invite.id.clone())) .await .expect_err("get for an expired invite fails"); // Verify response - assert!(matches!(error, get::Error::NotFound(error_id) if invite.id == error_id)); + assert!(matches!(error, super::Error::NotFound(error_id) if invite.id == error_id)); } diff --git a/src/invite/routes/post.rs b/src/invite/handlers/issue/mod.rs index f7ca76c..6085f7a 100644 --- a/src/invite/routes/post.rs +++ b/src/invite/handlers/issue/mod.rs @@ -4,6 +4,9 @@ use crate::{ app::App, clock::RequestedAt, error::Internal, invite::Invite, token::extract::Identity, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(issued_at): RequestedAt, diff --git a/src/invite/routes/test.rs b/src/invite/handlers/issue/test.rs index 4ea8a3d..2bf5400 100644 --- a/src/invite/routes/test.rs +++ b/src/invite/handlers/issue/test.rs @@ -1,6 +1,5 @@ use axum::extract::{Json, State}; -use super::post; use crate::test::fixtures; #[tokio::test] @@ -13,11 +12,11 @@ async fn create_invite() { // Call the endpoint - let Json(invite) = post::handler( + let Json(invite) = super::handler( State(app), issued_at.clone(), issuer.clone(), - Json(post::Request {}), + Json(super::Request {}), ) .await .expect("creating an invite always succeeds"); diff --git a/src/invite/handlers/mod.rs b/src/invite/handlers/mod.rs new file mode 100644 index 0000000..07a2bbe --- /dev/null +++ b/src/invite/handlers/mod.rs @@ -0,0 +1,9 @@ +mod accept; +mod get; +mod issue; + +type PathInfo = crate::invite::Id; + +pub use accept::handler as accept; +pub use get::handler as get; +pub use issue::handler as issue; diff --git a/src/invite/mod.rs b/src/invite/mod.rs index 2d32fda..74b9ceb 100644 --- a/src/invite/mod.rs +++ b/src/invite/mod.rs @@ -1,11 +1,11 @@ +use crate::{clock::DateTime, normalize::nfc, user}; + pub mod app; +pub mod handlers; mod id; mod repo; -mod routes; - -use crate::{clock::DateTime, normalize::nfc, user}; -pub use self::{id::Id, routes::router}; +pub use self::id::Id; #[derive(Debug, serde::Serialize)] pub struct Invite { diff --git a/src/invite/routes/invite/mod.rs b/src/invite/routes/invite/mod.rs deleted file mode 100644 index c22029a..0000000 --- a/src/invite/routes/invite/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod get; -pub mod post; -#[cfg(test)] -pub mod test; - -type PathInfo = crate::invite::Id; diff --git a/src/invite/routes/invite/test/mod.rs b/src/invite/routes/invite/test/mod.rs deleted file mode 100644 index d6c1f06..0000000 --- a/src/invite/routes/invite/test/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod get; -mod post; diff --git a/src/invite/routes/mod.rs b/src/invite/routes/mod.rs deleted file mode 100644 index d83efc6..0000000 --- a/src/invite/routes/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -use axum::{ - Router, - routing::{get, post}, -}; - -use crate::app::App; - -mod invite; -mod post; -#[cfg(test)] -mod test; - -pub fn router() -> Router<App> { - Router::new() - .route("/api/invite", post(post::handler)) - .route("/api/invite/{invite}", get(invite::get::handler)) - .route("/api/invite/{invite}", post(invite::post::handler)) -} @@ -17,6 +17,7 @@ mod invite; mod message; mod name; mod normalize; +mod routes; mod setup; #[cfg(test)] mod test; diff --git a/src/message/handlers/delete/mod.rs b/src/message/handlers/delete/mod.rs new file mode 100644 index 0000000..5eac4eb --- /dev/null +++ b/src/message/handlers/delete/mod.rs @@ -0,0 +1,55 @@ +use axum::{ + extract::{Json, Path, State}, + http::StatusCode, + response::{self, IntoResponse}, +}; + +use crate::{ + app::App, + clock::RequestedAt, + error::{Internal, NotFound}, + message::{self, app::DeleteError}, + token::extract::Identity, +}; + +#[cfg(test)] +mod test; + +pub async fn handler( + State(app): State<App>, + Path(message): Path<message::Id>, + RequestedAt(deleted_at): RequestedAt, + identity: Identity, +) -> Result<Response, Error> { + app.messages() + .delete(&identity.user, &message, &deleted_at) + .await?; + + Ok(Response { id: message }) +} + +#[derive(Debug, serde::Serialize)] +pub struct Response { + pub id: message::Id, +} + +impl IntoResponse for Response { + fn into_response(self) -> response::Response { + (StatusCode::ACCEPTED, Json(self)).into_response() + } +} + +#[derive(Debug, thiserror::Error)] +#[error(transparent)] +pub struct Error(#[from] pub DeleteError); + +impl IntoResponse for Error { + fn into_response(self) -> response::Response { + let Self(error) = self; + match error { + DeleteError::NotSender(_) => (StatusCode::FORBIDDEN, error.to_string()).into_response(), + DeleteError::NotFound(_) | DeleteError::Deleted(_) => NotFound(error).into_response(), + DeleteError::Database(_) => Internal::from(error).into_response(), + } + } +} diff --git a/src/message/routes/message/test.rs b/src/message/handlers/delete/test.rs index 1888be7..15aa2c2 100644 --- a/src/message/routes/message/test.rs +++ b/src/message/handlers/delete/test.rs @@ -1,6 +1,5 @@ use axum::extract::{Path, State}; -use super::delete; use crate::{message::app, test::fixtures}; #[tokio::test] @@ -14,7 +13,7 @@ pub async fn delete_message() { // Send the request - let response = delete::handler( + let response = super::handler( State(app.clone()), Path(message.id.clone()), fixtures::now(), @@ -43,7 +42,7 @@ pub async fn delete_invalid_message_id() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let message = fixtures::message::fictitious(); - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(message.clone()), fixtures::now(), @@ -74,7 +73,7 @@ pub async fn delete_deleted() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(message.id.clone()), fixtures::now(), @@ -105,7 +104,7 @@ pub async fn delete_expired() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(message.id.clone()), fixtures::now(), @@ -141,7 +140,7 @@ pub async fn delete_purged() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(message.id.clone()), fixtures::now(), @@ -167,7 +166,7 @@ pub async fn delete_not_sender() { // Send the request let deleter = fixtures::identity::create(&app, &fixtures::now()).await; - let delete::Error(error) = delete::handler( + let super::Error(error) = super::handler( State(app.clone()), Path(message.id.clone()), fixtures::now(), diff --git a/src/message/handlers/mod.rs b/src/message/handlers/mod.rs new file mode 100644 index 0000000..7e78475 --- /dev/null +++ b/src/message/handlers/mod.rs @@ -0,0 +1,3 @@ +mod delete; + +pub use delete::handler as delete; diff --git a/src/message/mod.rs b/src/message/mod.rs index c2687bc..e1643e6 100644 --- a/src/message/mod.rs +++ b/src/message/mod.rs @@ -1,12 +1,10 @@ pub mod app; mod body; pub mod event; +pub mod handlers; mod history; mod id; pub mod repo; -mod routes; mod snapshot; -pub use self::{ - body::Body, event::Event, history::History, id::Id, routes::router, snapshot::Message, -}; +pub use self::{body::Body, event::Event, history::History, id::Id, snapshot::Message}; diff --git a/src/message/routes/message/mod.rs b/src/message/routes/message/mod.rs deleted file mode 100644 index a05d344..0000000 --- a/src/message/routes/message/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -#[cfg(test)] -mod test; - -pub mod delete { - use axum::{ - extract::{Json, Path, State}, - http::StatusCode, - response::{self, IntoResponse}, - }; - - use crate::{ - app::App, - clock::RequestedAt, - error::{Internal, NotFound}, - message::{self, app::DeleteError}, - token::extract::Identity, - }; - - pub async fn handler( - State(app): State<App>, - Path(message): Path<message::Id>, - RequestedAt(deleted_at): RequestedAt, - identity: Identity, - ) -> Result<Response, Error> { - app.messages() - .delete(&identity.user, &message, &deleted_at) - .await?; - - Ok(Response { id: message }) - } - - #[derive(Debug, serde::Serialize)] - pub struct Response { - pub id: message::Id, - } - - impl IntoResponse for Response { - fn into_response(self) -> response::Response { - (StatusCode::ACCEPTED, Json(self)).into_response() - } - } - - #[derive(Debug, thiserror::Error)] - #[error(transparent)] - pub struct Error(#[from] pub DeleteError); - - impl IntoResponse for Error { - fn into_response(self) -> response::Response { - let Self(error) = self; - match error { - DeleteError::NotSender(_) => { - (StatusCode::FORBIDDEN, error.to_string()).into_response() - } - DeleteError::NotFound(_) | DeleteError::Deleted(_) => { - NotFound(error).into_response() - } - DeleteError::Database(_) => Internal::from(error).into_response(), - } - } - } -} diff --git a/src/message/routes/mod.rs b/src/message/routes/mod.rs deleted file mode 100644 index 00b2b1a..0000000 --- a/src/message/routes/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -use axum::{Router, routing::delete}; - -use crate::app::App; - -mod message; - -pub fn router() -> Router<App> { - Router::new().route("/api/messages/{message}", delete(message::delete::handler)) -} diff --git a/src/routes.rs b/src/routes.rs new file mode 100644 index 0000000..1e66582 --- /dev/null +++ b/src/routes.rs @@ -0,0 +1,62 @@ +use axum::{ + Router, middleware, + response::Redirect, + routing::{delete, get, post}, +}; + +use crate::{app::App, boot, channel, event, expire, invite, message, setup, ui, user}; + +pub fn routes(app: &App) -> Router<App> { + // UI routes that can be accessed before the administrator completes setup. + let ui_bootstrap = Router::new() + .route("/{*path}", get(ui::handlers::asset)) + .route("/setup", get(ui::handlers::setup)); + + // UI routes that require the administrator to complete setup first. + let ui_setup_required = Router::new() + .route("/", get(ui::handlers::index)) + .route("/ch/{channel}", get(ui::handlers::channel)) + .route("/invite/{invite}", get(ui::handlers::invite)) + .route("/login", get(ui::handlers::login)) + .route("/me", get(ui::handlers::me)) + .route_layer(crate::setup::Required(app.clone()).with_fallback(Redirect::to("/setup"))); + + // API routes that can run before the administrator completes setup. + let api_bootstrap = Router::new().route("/api/setup", post(setup::handlers::setup)); + + // API routes that require the administrator to complete setup first. + let api_setup_required = Router::new() + .route("/api/auth/login", post(user::handlers::login)) + .route("/api/auth/logout", post(user::handlers::logout)) + .route("/api/boot", get(boot::handlers::boot)) + .route("/api/channels", post(channel::handlers::create)) + .route("/api/channels/{channel}", post(channel::handlers::send)) + .route("/api/channels/{channel}", delete(channel::handlers::delete)) + .route("/api/events", get(event::handlers::stream)) + .route("/api/invite", post(invite::handlers::issue)) + .route("/api/invite/{invite}", get(invite::handlers::get)) + .route("/api/invite/{invite}", post(invite::handlers::accept)) + .route("/api/messages/{message}", delete(message::handlers::delete)) + .route("/api/password", post(user::handlers::change_password)) + // Run expiry whenever someone accesses the API. This was previously a blanket middleware + // affecting the whole service, but loading the client makes a several requests before the + // client can completely load, each of which was triggering expiry. There is absolutely no + // upside to re-checking expiry tens of times back-to-back like that; the API is accessed + // more regularly and with less of a traffic rush. + // + // This should, probably, be moved to a background job at some point. + .route_layer(middleware::from_fn_with_state( + app.clone(), + expire::middleware, + )) + .route_layer(setup::Required(app.clone())); + + [ + ui_bootstrap, + ui_setup_required, + api_bootstrap, + api_setup_required, + ] + .into_iter() + .fold(Router::default(), Router::merge) +} diff --git a/src/setup/handlers/mod.rs b/src/setup/handlers/mod.rs new file mode 100644 index 0000000..3d4a4e6 --- /dev/null +++ b/src/setup/handlers/mod.rs @@ -0,0 +1,3 @@ +mod setup; + +pub use setup::handler as setup; diff --git a/src/setup/routes/post.rs b/src/setup/handlers/setup/mod.rs index 0ff5d69..cbb3072 100644 --- a/src/setup/routes/post.rs +++ b/src/setup/handlers/setup/mod.rs @@ -14,6 +14,9 @@ use crate::{ user::{Password, User}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(setup_at): RequestedAt, diff --git a/src/setup/routes/test.rs b/src/setup/handlers/setup/test.rs index e9f5cd6..8243ac3 100644 --- a/src/setup/routes/test.rs +++ b/src/setup/handlers/setup/test.rs @@ -1,6 +1,5 @@ use axum::extract::{Json, State}; -use super::post; use crate::{setup::app, test::fixtures}; #[tokio::test] @@ -12,12 +11,12 @@ async fn fresh_instance() { // Call the endpoint let identity = fixtures::cookie::not_logged_in(); let (name, password) = fixtures::user::propose(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; let (identity, Json(response)) = - post::handler(State(app.clone()), fixtures::now(), identity, Json(request)) + super::handler(State(app.clone()), fixtures::now(), identity, Json(request)) .await .expect("setup in a fresh app succeeds"); @@ -57,9 +56,9 @@ async fn login_exists() { // Call the endpoint let identity = fixtures::cookie::not_logged_in(); let (name, password) = fixtures::user::propose(); - let request = post::Request { name, password }; - let post::Error(error) = - post::handler(State(app.clone()), fixtures::now(), identity, Json(request)) + let request = super::Request { name, password }; + let super::Error(error) = + super::handler(State(app.clone()), fixtures::now(), identity, Json(request)) .await .expect_err("setup in a populated app fails"); @@ -79,12 +78,12 @@ async fn invalid_name() { let name = fixtures::user::propose_invalid_name(); let password = fixtures::user::propose_password(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: name.clone(), password: password.clone(), }; - let post::Error(error) = - post::handler(State(app.clone()), fixtures::now(), identity, Json(request)) + let super::Error(error) = + super::handler(State(app.clone()), fixtures::now(), identity, Json(request)) .await .expect_err("setup with an invalid name fails"); diff --git a/src/setup/mod.rs b/src/setup/mod.rs index a4b821c..f5d12df 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -1,6 +1,6 @@ pub mod app; +pub mod handlers; pub mod repo; mod required; -mod routes; -pub use self::{required::Required, routes::router}; +pub use self::required::Required; diff --git a/src/setup/routes/mod.rs b/src/setup/routes/mod.rs deleted file mode 100644 index 977a790..0000000 --- a/src/setup/routes/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use axum::{Router, routing::post}; - -use crate::app::App; - -mod post; -#[cfg(test)] -mod test; - -pub fn router() -> Router<App> { - Router::new().route("/api/setup", post(post::handler)) -} diff --git a/src/ui/handlers/asset.rs b/src/ui/handlers/asset.rs new file mode 100644 index 0000000..1d5b8be --- /dev/null +++ b/src/ui/handlers/asset.rs @@ -0,0 +1,7 @@ +use axum::extract::Path; + +use crate::ui::assets::{Asset, Assets, Error}; + +pub async fn handler(Path(path): Path<String>) -> Result<Asset, Error> { + Assets::load(path) +} diff --git a/src/ui/handlers/channel.rs b/src/ui/handlers/channel.rs new file mode 100644 index 0000000..d3199dd --- /dev/null +++ b/src/ui/handlers/channel.rs @@ -0,0 +1,58 @@ +use axum::{ + extract::{Path, State}, + response::{self, IntoResponse, Redirect}, +}; + +use crate::{ + app::App, + channel::{self, app}, + error::Internal, + token::extract::Identity, + ui::{ + assets::{Asset, Assets}, + error::NotFound, + }, +}; + +pub async fn handler( + State(app): State<App>, + identity: Option<Identity>, + Path(channel): Path<channel::Id>, +) -> Result<Asset, Error> { + let _ = identity.ok_or(Error::NotLoggedIn)?; + app.channels().get(&channel).await.map_err(Error::from)?; + + Assets::index().map_err(Error::Internal) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("channel not found")] + NotFound, + #[error("not logged in")] + NotLoggedIn, + #[error("{0}")] + Internal(Internal), +} + +impl From<app::Error> for Error { + fn from(error: app::Error) -> Self { + match error { + app::Error::NotFound(_) | app::Error::Deleted(_) => Self::NotFound, + other => Self::Internal(other.into()), + } + } +} + +impl IntoResponse for Error { + fn into_response(self) -> response::Response { + match self { + Self::NotFound => match Assets::index() { + Ok(asset) => NotFound(asset).into_response(), + Err(internal) => internal.into_response(), + }, + Self::NotLoggedIn => Redirect::temporary("/login").into_response(), + Self::Internal(error) => error.into_response(), + } + } +} diff --git a/src/ui/routes/get.rs b/src/ui/handlers/index.rs index 2fcb51c..2fcb51c 100644 --- a/src/ui/routes/get.rs +++ b/src/ui/handlers/index.rs diff --git a/src/ui/handlers/invite.rs b/src/ui/handlers/invite.rs new file mode 100644 index 0000000..0f9580a --- /dev/null +++ b/src/ui/handlers/invite.rs @@ -0,0 +1,53 @@ +use axum::{ + extract::{Path, State}, + response::{self, IntoResponse}, +}; + +use crate::{ + app::App, + error::Internal, + invite, + ui::{ + assets::{Asset, Assets}, + error::NotFound, + }, +}; + +pub async fn handler( + State(app): State<App>, + Path(invite): Path<invite::Id>, +) -> Result<Asset, Error> { + app.invites() + .get(&invite) + .await + .map_err(Error::internal)? + .ok_or(Error::NotFound)?; + + Assets::index().map_err(Error::Internal) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invite not found")] + NotFound, + #[error("{0}")] + Internal(Internal), +} + +impl Error { + fn internal(err: impl Into<Internal>) -> Self { + Self::Internal(err.into()) + } +} + +impl IntoResponse for Error { + fn into_response(self) -> response::Response { + match self { + Self::NotFound => match Assets::index() { + Ok(asset) => NotFound(asset).into_response(), + Err(internal) => internal.into_response(), + }, + Self::Internal(error) => error.into_response(), + } + } +} diff --git a/src/ui/handlers/login.rs b/src/ui/handlers/login.rs new file mode 100644 index 0000000..4562b04 --- /dev/null +++ b/src/ui/handlers/login.rs @@ -0,0 +1,8 @@ +use crate::{ + error::Internal, + ui::assets::{Asset, Assets}, +}; + +pub async fn handler() -> Result<Asset, Internal> { + Assets::index() +} diff --git a/src/ui/handlers/me.rs b/src/ui/handlers/me.rs new file mode 100644 index 0000000..2fcb51c --- /dev/null +++ b/src/ui/handlers/me.rs @@ -0,0 +1,30 @@ +use axum::response::{self, IntoResponse, Redirect}; + +use crate::{ + error::Internal, + token::extract::Identity, + ui::assets::{Asset, Assets}, +}; + +pub async fn handler(identity: Option<Identity>) -> Result<Asset, Error> { + let _ = identity.ok_or(Error::NotLoggedIn)?; + + Assets::index().map_err(Error::Internal) +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("not logged in")] + NotLoggedIn, + #[error("{0}")] + Internal(Internal), +} + +impl IntoResponse for Error { + fn into_response(self) -> response::Response { + match self { + Self::NotLoggedIn => Redirect::temporary("/login").into_response(), + Self::Internal(error) => error.into_response(), + } + } +} diff --git a/src/ui/handlers/mod.rs b/src/ui/handlers/mod.rs new file mode 100644 index 0000000..5bfd0d6 --- /dev/null +++ b/src/ui/handlers/mod.rs @@ -0,0 +1,15 @@ +mod asset; +mod channel; +mod index; +mod invite; +mod login; +mod me; +mod setup; + +pub use asset::handler as asset; +pub use channel::handler as channel; +pub use index::handler as index; +pub use invite::handler as invite; +pub use login::handler as login; +pub use me::handler as me; +pub use setup::handler as setup; diff --git a/src/ui/handlers/setup.rs b/src/ui/handlers/setup.rs new file mode 100644 index 0000000..49821cf --- /dev/null +++ b/src/ui/handlers/setup.rs @@ -0,0 +1,41 @@ +use axum::{ + extract::State, + response::{self, IntoResponse, Redirect}, +}; + +use crate::{ + app::App, + error::Internal, + ui::assets::{Asset, Assets}, +}; + +pub async fn handler(State(app): State<App>) -> Result<Asset, Error> { + if app + .setup() + .completed() + .await + .map_err(Internal::from) + .map_err(Error::Internal)? + { + Err(Error::SetupCompleted) + } else { + Assets::index().map_err(Error::Internal) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("setup already completed")] + SetupCompleted, + #[error("{0}")] + Internal(Internal), +} + +impl IntoResponse for Error { + fn into_response(self) -> response::Response { + match self { + Self::SetupCompleted => Redirect::to("/login").into_response(), + Self::Internal(error) => error.into_response(), + } + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index e834bba..9668300 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,6 +1,4 @@ mod assets; mod error; +pub mod handlers; mod mime; -mod routes; - -pub use self::routes::router; diff --git a/src/ui/routes/ch/channel.rs b/src/ui/routes/ch/channel.rs deleted file mode 100644 index a854f14..0000000 --- a/src/ui/routes/ch/channel.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub mod get { - use axum::{ - extract::{Path, State}, - response::{self, IntoResponse, Redirect}, - }; - - use crate::{ - app::App, - channel::{self, app}, - error::Internal, - token::extract::Identity, - ui::{ - assets::{Asset, Assets}, - error::NotFound, - }, - }; - - pub async fn handler( - State(app): State<App>, - identity: Option<Identity>, - Path(channel): Path<channel::Id>, - ) -> Result<Asset, Error> { - let _ = identity.ok_or(Error::NotLoggedIn)?; - app.channels().get(&channel).await.map_err(Error::from)?; - - Assets::index().map_err(Error::Internal) - } - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error("channel not found")] - NotFound, - #[error("not logged in")] - NotLoggedIn, - #[error("{0}")] - Internal(Internal), - } - - impl From<app::Error> for Error { - fn from(error: app::Error) -> Self { - match error { - app::Error::NotFound(_) | app::Error::Deleted(_) => Self::NotFound, - other => Self::Internal(other.into()), - } - } - } - - impl IntoResponse for Error { - fn into_response(self) -> response::Response { - match self { - Self::NotFound => match Assets::index() { - Ok(asset) => NotFound(asset).into_response(), - Err(internal) => internal.into_response(), - }, - Self::NotLoggedIn => Redirect::temporary("/login").into_response(), - Self::Internal(error) => error.into_response(), - } - } - } -} diff --git a/src/ui/routes/ch/mod.rs b/src/ui/routes/ch/mod.rs deleted file mode 100644 index ff02972..0000000 --- a/src/ui/routes/ch/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod channel; diff --git a/src/ui/routes/invite/invite.rs b/src/ui/routes/invite/invite.rs deleted file mode 100644 index 06e5792..0000000 --- a/src/ui/routes/invite/invite.rs +++ /dev/null @@ -1,55 +0,0 @@ -pub mod get { - use axum::{ - extract::{Path, State}, - response::{self, IntoResponse}, - }; - - use crate::{ - app::App, - error::Internal, - invite, - ui::{ - assets::{Asset, Assets}, - error::NotFound, - }, - }; - - pub async fn handler( - State(app): State<App>, - Path(invite): Path<invite::Id>, - ) -> Result<Asset, Error> { - app.invites() - .get(&invite) - .await - .map_err(Error::internal)? - .ok_or(Error::NotFound)?; - - Assets::index().map_err(Error::Internal) - } - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error("invite not found")] - NotFound, - #[error("{0}")] - Internal(Internal), - } - - impl Error { - fn internal(err: impl Into<Internal>) -> Self { - Self::Internal(err.into()) - } - } - - impl IntoResponse for Error { - fn into_response(self) -> response::Response { - match self { - Self::NotFound => match Assets::index() { - Ok(asset) => NotFound(asset).into_response(), - Err(internal) => internal.into_response(), - }, - Self::Internal(error) => error.into_response(), - } - } - } -} diff --git a/src/ui/routes/invite/mod.rs b/src/ui/routes/invite/mod.rs deleted file mode 100644 index 50af8be..0000000 --- a/src/ui/routes/invite/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -// In this case, the first redundant `invite` is a literal path segment, and the -// second `invite` reflects a placeholder. -#[allow(clippy::module_inception)] -pub mod invite; diff --git a/src/ui/routes/login.rs b/src/ui/routes/login.rs deleted file mode 100644 index 39d45b9..0000000 --- a/src/ui/routes/login.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod get { - use crate::{ - error::Internal, - ui::assets::{Asset, Assets}, - }; - - pub async fn handler() -> Result<Asset, Internal> { - Assets::index() - } -} diff --git a/src/ui/routes/me.rs b/src/ui/routes/me.rs deleted file mode 100644 index f1f118f..0000000 --- a/src/ui/routes/me.rs +++ /dev/null @@ -1,32 +0,0 @@ -pub mod get { - use axum::response::{self, IntoResponse, Redirect}; - - use crate::{ - error::Internal, - token::extract::Identity, - ui::assets::{Asset, Assets}, - }; - - pub async fn handler(identity: Option<Identity>) -> Result<Asset, Error> { - let _ = identity.ok_or(Error::NotLoggedIn)?; - - Assets::index().map_err(Error::Internal) - } - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error("not logged in")] - NotLoggedIn, - #[error("{0}")] - Internal(Internal), - } - - impl IntoResponse for Error { - fn into_response(self) -> response::Response { - match self { - Self::NotLoggedIn => Redirect::temporary("/login").into_response(), - Self::Internal(error) => error.into_response(), - } - } - } -} diff --git a/src/ui/routes/mod.rs b/src/ui/routes/mod.rs deleted file mode 100644 index dc94773..0000000 --- a/src/ui/routes/mod.rs +++ /dev/null @@ -1,28 +0,0 @@ -use axum::{Router, response::Redirect, routing::get}; - -use crate::app::App; - -mod ch; -mod get; -mod invite; -mod login; -mod me; -mod path; -mod setup; - -pub fn router(app: &App) -> Router<App> { - [ - Router::new() - .route("/{*path}", get(path::get::handler)) - .route("/setup", get(setup::get::handler)), - Router::new() - .route("/", get(get::handler)) - .route("/me", get(me::get::handler)) - .route("/login", get(login::get::handler)) - .route("/ch/{channel}", get(ch::channel::get::handler)) - .route("/invite/{invite}", get(invite::invite::get::handler)) - .route_layer(crate::setup::Required(app.clone()).with_fallback(Redirect::to("/setup"))), - ] - .into_iter() - .fold(Router::default(), Router::merge) -} diff --git a/src/ui/routes/path.rs b/src/ui/routes/path.rs deleted file mode 100644 index a387552..0000000 --- a/src/ui/routes/path.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod get { - use axum::extract::Path; - - use crate::ui::assets::{Asset, Assets, Error}; - - pub async fn handler(Path(path): Path<String>) -> Result<Asset, Error> { - Assets::load(path) - } -} diff --git a/src/ui/routes/setup.rs b/src/ui/routes/setup.rs deleted file mode 100644 index 649cc5f..0000000 --- a/src/ui/routes/setup.rs +++ /dev/null @@ -1,43 +0,0 @@ -pub mod get { - use axum::{ - extract::State, - response::{self, IntoResponse, Redirect}, - }; - - use crate::{ - app::App, - error::Internal, - ui::assets::{Asset, Assets}, - }; - - pub async fn handler(State(app): State<App>) -> Result<Asset, Error> { - if app - .setup() - .completed() - .await - .map_err(Internal::from) - .map_err(Error::Internal)? - { - Err(Error::SetupCompleted) - } else { - Assets::index().map_err(Error::Internal) - } - } - - #[derive(Debug, thiserror::Error)] - pub enum Error { - #[error("setup already completed")] - SetupCompleted, - #[error("{0}")] - Internal(Internal), - } - - impl IntoResponse for Error { - fn into_response(self) -> response::Response { - match self { - Self::SetupCompleted => Redirect::to("/login").into_response(), - Self::Internal(error) => error.into_response(), - } - } - } -} diff --git a/src/user/routes/login/post.rs b/src/user/handlers/login/mod.rs index 39f9eea..e80377e 100644 --- a/src/user/routes/login/post.rs +++ b/src/user/handlers/login/mod.rs @@ -13,6 +13,9 @@ use crate::{ user::{Password, User}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(now): RequestedAt, diff --git a/src/user/routes/login/test.rs b/src/user/handlers/login/test.rs index d2e7ee2..b8f24f6 100644 --- a/src/user/routes/login/test.rs +++ b/src/user/handlers/login/test.rs @@ -1,6 +1,5 @@ use axum::extract::{Json, State}; -use super::post; use crate::{test::fixtures, token::app}; #[tokio::test] @@ -14,12 +13,12 @@ async fn correct_credentials() { let identity = fixtures::cookie::not_logged_in(); let logged_in_at = fixtures::now(); - let request = post::Request { + let request = super::Request { name: name.clone(), password, }; let (identity, Json(response)) = - post::handler(State(app.clone()), logged_in_at, identity, Json(request)) + super::handler(State(app.clone()), logged_in_at, identity, Json(request)) .await .expect("logged in with valid credentials"); @@ -53,12 +52,12 @@ async fn invalid_name() { let identity = fixtures::cookie::not_logged_in(); let logged_in_at = fixtures::now(); let (name, password) = fixtures::user::propose(); - let request = post::Request { + let request = super::Request { name: name.clone(), password, }; - let post::Error(error) = - post::handler(State(app.clone()), logged_in_at, identity, Json(request)) + let super::Error(error) = + super::handler(State(app.clone()), logged_in_at, identity, Json(request)) .await .expect_err("logged in with an incorrect password fails"); @@ -78,12 +77,12 @@ async fn incorrect_password() { let logged_in_at = fixtures::now(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { + let request = super::Request { name: login.name, password: fixtures::user::propose_password(), }; - let post::Error(error) = - post::handler(State(app.clone()), logged_in_at, identity, Json(request)) + let super::Error(error) = + super::handler(State(app.clone()), logged_in_at, identity, Json(request)) .await .expect_err("logged in with an incorrect password"); @@ -103,8 +102,8 @@ async fn token_expires() { let logged_in_at = fixtures::ancient(); let identity = fixtures::cookie::not_logged_in(); - let request = post::Request { name, password }; - let (identity, _) = post::handler(State(app.clone()), logged_in_at, identity, Json(request)) + let request = super::Request { name, password }; + let (identity, _) = super::handler(State(app.clone()), 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/user/routes/logout/post.rs b/src/user/handlers/logout/mod.rs index 0ac663e..45a376a 100644 --- a/src/user/routes/logout/post.rs +++ b/src/user/handlers/logout/mod.rs @@ -11,6 +11,9 @@ use crate::{ token::{app, extract::IdentityCookie}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(now): RequestedAt, diff --git a/src/user/routes/logout/test.rs b/src/user/handlers/logout/test.rs index ce93760..8dc4636 100644 --- a/src/user/routes/logout/test.rs +++ b/src/user/handlers/logout/test.rs @@ -3,7 +3,6 @@ use axum::{ http::StatusCode, }; -use super::post; use crate::{test::fixtures, token::app}; #[tokio::test] @@ -18,7 +17,7 @@ async fn successful() { // Call the endpoint - let (response_identity, response_status) = post::handler( + let (response_identity, response_status) = super::handler( State(app.clone()), fixtures::now(), identity.clone(), @@ -50,7 +49,7 @@ async fn no_identity() { // Call the endpoint let identity = fixtures::cookie::not_logged_in(); - let (identity, status) = post::handler(State(app), fixtures::now(), identity, Json::default()) + let (identity, status) = super::handler(State(app), fixtures::now(), identity, Json::default()) .await .expect("logged out with no token succeeds"); @@ -69,9 +68,10 @@ async fn invalid_token() { // Call the endpoint let identity = fixtures::cookie::fictitious(); - let post::Error(error) = post::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), 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/user/handlers/mod.rs b/src/user/handlers/mod.rs new file mode 100644 index 0000000..5cadbb5 --- /dev/null +++ b/src/user/handlers/mod.rs @@ -0,0 +1,7 @@ +mod login; +mod logout; +mod password; + +pub use login::handler as login; +pub use logout::handler as logout; +pub use password::handler as change_password; diff --git a/src/user/routes/password/post.rs b/src/user/handlers/password/mod.rs index 296f6cd..9158325 100644 --- a/src/user/routes/password/post.rs +++ b/src/user/handlers/password/mod.rs @@ -15,6 +15,9 @@ use crate::{ user::{Password, User}, }; +#[cfg(test)] +mod test; + pub async fn handler( State(app): State<App>, RequestedAt(now): RequestedAt, diff --git a/src/user/routes/password/test.rs b/src/user/handlers/password/test.rs index f977327..42e41d8 100644 --- a/src/user/routes/password/test.rs +++ b/src/user/handlers/password/test.rs @@ -1,6 +1,5 @@ use axum::extract::{Json, State}; -use super::post; use crate::{ test::fixtures, token::app::{LoginError, ValidateError}, @@ -17,11 +16,11 @@ async fn password_change() { // Call the endpoint let (name, password) = creds; let to = fixtures::user::propose_password(); - let request = post::Request { + let request = super::Request { password: password.clone(), to: to.clone(), }; - let (new_cookie, Json(response)) = post::handler( + let (new_cookie, Json(response)) = super::handler( State(app.clone()), fixtures::now(), identity.clone(), diff --git a/src/user/mod.rs b/src/user/mod.rs index f4c66ab..44e1633 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -2,14 +2,12 @@ pub mod app; pub mod create; pub mod event; +pub mod handlers; mod history; mod id; pub mod password; pub mod repo; -mod routes; mod snapshot; mod validate; -pub use self::{ - event::Event, history::History, id::Id, password::Password, routes::router, snapshot::User, -}; +pub use self::{event::Event, history::History, id::Id, password::Password, snapshot::User}; diff --git a/src/user/routes/login/mod.rs b/src/user/routes/login/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/user/routes/login/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/user/routes/logout/mod.rs b/src/user/routes/logout/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/user/routes/logout/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; diff --git a/src/user/routes/mod.rs b/src/user/routes/mod.rs deleted file mode 100644 index ade96cb..0000000 --- a/src/user/routes/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use axum::{Router, routing::post}; - -use crate::app::App; - -mod login; -mod logout; -mod password; - -pub fn router() -> Router<App> { - Router::new() - .route("/api/password", post(password::post::handler)) - .route("/api/auth/login", post(login::post::handler)) - .route("/api/auth/logout", post(logout::post::handler)) -} diff --git a/src/user/routes/password/mod.rs b/src/user/routes/password/mod.rs deleted file mode 100644 index 36b384e..0000000 --- a/src/user/routes/password/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod post; - -#[cfg(test)] -mod test; |
