summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorojacobson <ojacobson@noreply.codeberg.org>2025-06-21 04:22:52 +0200
committerojacobson <ojacobson@noreply.codeberg.org>2025-06-21 04:22:52 +0200
commitcd1dc0dab4b46bc5712070812192d5ce34071470 (patch)
treec94f5a42f7e734b81892c1289a1d2b566706ba7c /src/ui
parentd84ba5cd09b713fac2f193d5c05af7415ea6742d (diff)
parent4e3d5ccac99b24934c972e088cd7eb02bb95df06 (diff)
Reorganize and consolidate HTTP routes.
HTTP routes are now defined in a single, unified module, pulling them out of the topical modules they were formerly part of. This is intended to improve the navigability of the codebase. Previously, finding the handler corresponding to a specific endpoint required prior familiarity, though in practice you could usually guess from topic area. Now, all routes are defined in one place; if you know the path, you can read down the list to find the handler. Handlers themselves live with the domain they are most appropriately "part of," generally (in this version, universally) in a `handlers` submodule. The handlers themselves have been flattened down; rather than representing a path and a method, they now represent a named operation (which is suspiciously similar to the path in most cases). This means that we no longer have constructs like `crate::ui::routes::ch::channel` - it's now `crate::ui::handlers::channel` instead. ## Disclaimer I Solemnly Swear I Didn't Change Any Handlers. ## Prior art I've inadvertently reinvented Django's `urls.py` convention, and I've opted to lean into that. Merges flatter-routes-reorg into main.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/handlers/asset.rs7
-rw-r--r--src/ui/handlers/channel.rs58
-rw-r--r--src/ui/handlers/index.rs (renamed from src/ui/routes/get.rs)0
-rw-r--r--src/ui/handlers/invite.rs53
-rw-r--r--src/ui/handlers/login.rs8
-rw-r--r--src/ui/handlers/me.rs30
-rw-r--r--src/ui/handlers/mod.rs15
-rw-r--r--src/ui/handlers/setup.rs41
-rw-r--r--src/ui/mod.rs4
-rw-r--r--src/ui/routes/ch/channel.rs60
-rw-r--r--src/ui/routes/ch/mod.rs1
-rw-r--r--src/ui/routes/invite/invite.rs55
-rw-r--r--src/ui/routes/invite/mod.rs4
-rw-r--r--src/ui/routes/login.rs10
-rw-r--r--src/ui/routes/me.rs32
-rw-r--r--src/ui/routes/mod.rs28
-rw-r--r--src/ui/routes/path.rs9
-rw-r--r--src/ui/routes/setup.rs43
18 files changed, 213 insertions, 245 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/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(),
- }
- }
- }
-}