From 4e3d5ccac99b24934c972e088cd7eb02bb95df06 Mon Sep 17 00:00:00 2001 From: Owen Jacobson Date: Tue, 17 Jun 2025 02:11:45 -0400 Subject: Handlers are _named operations_, which can be exposed via routes. Each domain module that exposes handlers does so through a `handlers` child module, ideally as a top-level symbol that can be plugged directly into Axum's `MethodRouter`. Modules could make exceptions to this - kill the doctrinaire inside yourself, after all - but none of the API modules that actually exist need such exceptions, and consistency is useful. The related details of request types, URL types, response types, errors, &c &c are then organized into modules under `handlers`, along with their respective tests. --- src/ui/handlers/asset.rs | 7 ++++++ src/ui/handlers/channel.rs | 58 ++++++++++++++++++++++++++++++++++++++++++++++ src/ui/handlers/index.rs | 30 ++++++++++++++++++++++++ src/ui/handlers/invite.rs | 53 ++++++++++++++++++++++++++++++++++++++++++ src/ui/handlers/login.rs | 8 +++++++ src/ui/handlers/me.rs | 30 ++++++++++++++++++++++++ src/ui/handlers/mod.rs | 15 ++++++++++++ src/ui/handlers/setup.rs | 41 ++++++++++++++++++++++++++++++++ 8 files changed, 242 insertions(+) create mode 100644 src/ui/handlers/asset.rs create mode 100644 src/ui/handlers/channel.rs create mode 100644 src/ui/handlers/index.rs create mode 100644 src/ui/handlers/invite.rs create mode 100644 src/ui/handlers/login.rs create mode 100644 src/ui/handlers/me.rs create mode 100644 src/ui/handlers/mod.rs create mode 100644 src/ui/handlers/setup.rs (limited to 'src/ui/handlers') 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) -> Result { + 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, + identity: Option, + Path(channel): Path, +) -> Result { + 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 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/handlers/index.rs b/src/ui/handlers/index.rs new file mode 100644 index 0000000..2fcb51c --- /dev/null +++ b/src/ui/handlers/index.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) -> Result { + 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/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, + Path(invite): Path, +) -> Result { + 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) -> 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 { + 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) -> Result { + 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) -> Result { + 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(), + } + } +} -- cgit v1.2.3