diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-06-17 02:11:45 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-06-18 18:31:40 -0400 |
| commit | 4e3d5ccac99b24934c972e088cd7eb02bb95df06 (patch) | |
| tree | c94f5a42f7e734b81892c1289a1d2b566706ba7c /src/ui/handlers | |
| parent | 5ed96f8e8b9d9f19ee249f5c73a5a21ef6bca09f (diff) | |
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.
Diffstat (limited to 'src/ui/handlers')
| -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 | 30 | ||||
| -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 |
8 files changed, 242 insertions, 0 deletions
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/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<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/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(), + } + } +} |
