diff options
Diffstat (limited to 'src/channel')
| -rw-r--r-- | src/channel/app.rs | 32 | ||||
| -rw-r--r-- | src/channel/routes.rs | 68 |
2 files changed, 81 insertions, 19 deletions
diff --git a/src/channel/app.rs b/src/channel/app.rs index 2f37878..8ae0c3c 100644 --- a/src/channel/app.rs +++ b/src/channel/app.rs @@ -31,13 +31,17 @@ impl<'a> Channels<'a> { Self { db, broadcaster } } - pub async fn create(&self, name: &str) -> Result<(), InternalError> { + pub async fn create(&self, name: &str) -> Result<Channel, CreateError> { let mut tx = self.db.begin().await?; - let channel = tx.channels().create(name).await?; - self.broadcaster.register_channel(&channel); + let channel = tx + .channels() + .create(name) + .await + .map_err(|err| CreateError::from_duplicate_name(err, name))?; + self.broadcaster.register_channel(&channel.id); tx.commit().await?; - Ok(()) + Ok(channel) } pub async fn all(&self) -> Result<Vec<Channel>, InternalError> { @@ -122,6 +126,26 @@ impl<'a> Channels<'a> { } #[derive(Debug, thiserror::Error)] +pub enum CreateError { + #[error("channel named {0} already exists")] + DuplicateName(String), + #[error(transparent)] + DatabaseError(#[from] sqlx::Error), +} + +impl CreateError { + fn from_duplicate_name(error: sqlx::Error, name: &str) -> Self { + if let Some(error) = error.as_database_error() { + if error.is_unique_violation() { + return Self::DuplicateName(name.into()); + } + } + + Self::from(error) + } +} + +#[derive(Debug, thiserror::Error)] pub enum InternalError { #[error(transparent)] DatabaseError(#[from] sqlx::Error), diff --git a/src/channel/routes.rs b/src/channel/routes.rs index 847e0b4..383ec58 100644 --- a/src/channel/routes.rs +++ b/src/channel/routes.rs @@ -1,23 +1,43 @@ use axum::{ - extract::{Form, Path, State}, + extract::{Json, Path, State}, http::StatusCode, - response::{IntoResponse, Redirect, Response}, - routing::post, + response::{IntoResponse, Response}, + routing::{get, post}, Router, }; -use super::app::EventsError; +use super::app::{self, EventsError}; use crate::{ app::App, clock::RequestedAt, error::InternalError, - repo::{channel, login::Login}, + repo::{ + channel::{self, Channel}, + login::Login, + }, }; pub fn router() -> Router<App> { Router::new() - .route("/create", post(on_create)) - .route("/:channel/send", post(on_send)) + .route("/api/channels", get(list_channels)) + .route("/api/channels", post(on_create)) + .route("/api/channels/:channel", post(on_send)) +} + +async fn list_channels(State(app): State<App>, _: Login) -> Result<Channels, InternalError> { + let channels = app.channels().all().await?; + let response = Channels(channels); + + Ok(response) +} + +struct Channels(Vec<Channel>); + +impl IntoResponse for Channels { + fn into_response(self) -> Response { + let Self(channels) = self; + Json(channels).into_response() + } } #[derive(serde::Deserialize)] @@ -28,11 +48,29 @@ struct CreateRequest { async fn on_create( State(app): State<App>, _: Login, // requires auth, but doesn't actually care who you are - Form(form): Form<CreateRequest>, -) -> Result<impl IntoResponse, InternalError> { - app.channels().create(&form.name).await?; + Json(form): Json<CreateRequest>, +) -> Result<Json<Channel>, CreateError> { + let channel = app + .channels() + .create(&form.name) + .await + .map_err(CreateError)?; - Ok(Redirect::to("/")) + Ok(Json(channel)) +} + +struct CreateError(app::CreateError); + +impl IntoResponse for CreateError { + fn into_response(self) -> Response { + let Self(error) = self; + match error { + duplicate @ app::CreateError::DuplicateName(_) => { + (StatusCode::BAD_REQUEST, duplicate.to_string()).into_response() + } + other => InternalError::from(other).into_response(), + } + } } #[derive(serde::Deserialize)] @@ -45,15 +83,15 @@ async fn on_send( RequestedAt(sent_at): RequestedAt, State(app): State<App>, login: Login, - Form(form): Form<SendRequest>, -) -> Result<impl IntoResponse, ErrorResponse> { + Json(form): Json<SendRequest>, +) -> Result<StatusCode, ErrorResponse> { app.channels() .send(&login, &channel, &form.message, &sent_at) .await // Could impl `From` here, but it's more code and this is used once. .map_err(ErrorResponse)?; - Ok(Redirect::to(&format!("/{}", channel))) + Ok(StatusCode::ACCEPTED) } struct ErrorResponse(EventsError); @@ -65,7 +103,7 @@ impl IntoResponse for ErrorResponse { not_found @ EventsError::ChannelNotFound(_) => { (StatusCode::NOT_FOUND, not_found.to_string()).into_response() } - EventsError::DatabaseError(error) => InternalError::from(error).into_response(), + other => InternalError::from(other).into_response(), } } } |
