use axum::{ extract::{Json, Path, State}, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Router, }; use axum_extra::extract::Query; use super::app; use crate::{ app::App, channel::{self, Channel}, clock::RequestedAt, error::Internal, event::{app::EventsError, Sequence}, login::Login, }; #[cfg(test)] mod test; pub fn router() -> Router { Router::new() .route("/api/channels", get(list)) .route("/api/channels", post(on_create)) .route("/api/channels/:channel", post(on_send)) } #[derive(Default, serde::Deserialize)] struct ListQuery { resume_point: Option, } async fn list( State(app): State, _: Login, Query(query): Query, ) -> Result { let channels = app.channels().all(query.resume_point).await?; let response = Channels(channels); Ok(response) } struct Channels(Vec); impl IntoResponse for Channels { fn into_response(self) -> Response { let Self(channels) = self; Json(channels).into_response() } } #[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::BAD_REQUEST, duplicate.to_string()).into_response() } other => Internal::from(other).into_response(), } } } #[derive(Clone, serde::Deserialize)] struct SendRequest { message: String, } async fn on_send( State(app): State, Path(channel): Path, RequestedAt(sent_at): RequestedAt, login: Login, Json(request): Json, ) -> Result { app.events() .send(&login, &channel, &request.message, &sent_at) .await // Could impl `From` here, but it's more code and this is used once. .map_err(ErrorResponse)?; Ok(StatusCode::ACCEPTED) } #[derive(Debug)] struct ErrorResponse(EventsError); impl IntoResponse for ErrorResponse { fn into_response(self) -> Response { let Self(error) = self; match error { not_found @ EventsError::ChannelNotFound(_) => { (StatusCode::NOT_FOUND, not_found.to_string()).into_response() } other => Internal::from(other).into_response(), } } }