use axum::{ extract::{Json, Path, State}, http::StatusCode, response::{IntoResponse, Response}, routing::{delete, post}, Router, }; use super::{app, Channel, Id}; use crate::{ app::App, clock::RequestedAt, error::{Internal, NotFound}, login::Login, message::app::SendError, }; #[cfg(test)] mod test; pub fn router() -> Router { Router::new() .route("/api/channels", post(on_create)) .route("/api/channels/:channel", post(on_send)) .route("/api/channels/:channel", delete(on_delete)) } #[derive(Clone, serde::Deserialize)] struct CreateRequest { name: String, } async fn on_create( State(app): State, _: Login, // requires auth, but doesn't actually care who you are RequestedAt(created_at): RequestedAt, Json(form): Json, ) -> Result, CreateError> { let channel = app .channels() .create(&form.name, &created_at) .await .map_err(CreateError)?; Ok(Json(channel)) } #[derive(Debug)] struct CreateError(app::CreateError); impl IntoResponse for CreateError { fn into_response(self) -> Response { let Self(error) = self; match error { duplicate @ app::CreateError::DuplicateName(_) => { (StatusCode::CONFLICT, duplicate.to_string()).into_response() } other => Internal::from(other).into_response(), } } } #[derive(Clone, serde::Deserialize)] struct SendRequest { body: String, } async fn on_send( State(app): State, Path(channel): Path, RequestedAt(sent_at): RequestedAt, login: Login, Json(request): Json, ) -> Result { app.messages() .send(&channel, &login, &sent_at, &request.body) .await?; Ok(StatusCode::ACCEPTED) } #[derive(Debug, thiserror::Error)] #[error(transparent)] struct SendErrorResponse(#[from] SendError); impl IntoResponse for SendErrorResponse { fn into_response(self) -> Response { let Self(error) = self; match error { not_found @ SendError::ChannelNotFound(_) => NotFound(not_found).into_response(), other => Internal::from(other).into_response(), } } } async fn on_delete( State(app): State, Path(channel): Path, RequestedAt(deleted_at): RequestedAt, _: Login, ) -> Result { app.channels().delete(&channel, &deleted_at).await?; Ok(StatusCode::ACCEPTED) } #[derive(Debug, thiserror::Error)] #[error(transparent)] struct ErrorResponse(#[from] app::Error); impl IntoResponse for ErrorResponse { fn into_response(self) -> Response { let Self(error) = self; match error { not_found @ app::Error::NotFound(_) => { (StatusCode::NOT_FOUND, not_found.to_string()).into_response() } other => Internal::from(other).into_response(), } } }