feat: add cargobike structure
This commit is contained in:
parent
7e958b89c6
commit
1ae4ba3a58
4 changed files with 144 additions and 5 deletions
|
|
@ -2,18 +2,57 @@
|
|||
//! cargo bikes can be picked.
|
||||
|
||||
use aide::{
|
||||
axum::{ApiRouter, routing::get_with},
|
||||
axum::{
|
||||
ApiRouter,
|
||||
routing::{get_with, put_with},
|
||||
},
|
||||
transform::TransformOperation,
|
||||
};
|
||||
use axum::{Json, http::StatusCode};
|
||||
use axum::{Json, extract::Path, http::StatusCode};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
api::helpers::unexpected_error,
|
||||
core::{controller::AnonAppController, models::bike::Bike},
|
||||
api::helpers::{admin, admin_desc, unexpected_error},
|
||||
core::{
|
||||
controller::{AnonAppController, AppController},
|
||||
models::bike::{Bike, BikeId, BikeStatus},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn routes() -> ApiRouter {
|
||||
ApiRouter::new().api_route("/", get_with(get_bikes, get_bikes_docs))
|
||||
ApiRouter::new()
|
||||
.api_route("/", get_with(get_bikes, get_bikes_docs))
|
||||
.api_route("/{id}/status", put_with(set_status, set_status_docs))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SetStatusForm {
|
||||
status: BikeStatus,
|
||||
}
|
||||
|
||||
/// Takes a bike in or out of the fleet. Deleting is not offered: a bike that
|
||||
/// has been booked must stay, for the history.
|
||||
#[axum::debug_handler]
|
||||
async fn set_status(
|
||||
ac: AppController,
|
||||
Path(id): Path<BikeId>,
|
||||
Json(SetStatusForm { status }): Json<SetStatusForm>,
|
||||
) -> Result<(), (StatusCode, String)> {
|
||||
match admin(ac)?.set_bike_status(id, status).await {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) if err.is_not_found() => {
|
||||
Err((StatusCode::NOT_FOUND, "No such bike".to_owned()))
|
||||
}
|
||||
Err(err) => unexpected_error("set_bike_status", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_status_docs(op: TransformOperation) -> TransformOperation {
|
||||
op.tag("Bikes")
|
||||
.summary("Put a bike in or out of service")
|
||||
.response_with::<403, (), _>(admin_desc)
|
||||
.response::<404, ()>()
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ pub fn api_docs_metadata(api: TransformOpenApi) -> TransformOpenApi {
|
|||
name: "Bikes".to_owned(),
|
||||
..Default::default()
|
||||
})
|
||||
.tag(Tag {
|
||||
name: "Reservations".to_owned(),
|
||||
..Default::default()
|
||||
})
|
||||
.tag(Tag {
|
||||
name: "misc".to_owned(),
|
||||
..Default::default()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ mod auth;
|
|||
mod bikes;
|
||||
mod docs;
|
||||
mod helpers;
|
||||
mod reservations;
|
||||
|
||||
pub fn get_router(aac: AnonAppController) -> Router {
|
||||
aide::generate::on_error(|err| error!("aide generated error: {err}"));
|
||||
|
|
@ -66,6 +67,7 @@ pub fn get_router(aac: AnonAppController) -> Router {
|
|||
// `auth` carries its own `/api/...` paths, so it is merged, not nested
|
||||
.merge(auth::routes())
|
||||
.nest_api_service("/api/bikes", bikes::routes())
|
||||
.nest_api_service("/api/reservations", reservations::routes())
|
||||
.nest_api_service("/api/docs", docs::routes())
|
||||
.finish_api_with(&mut api, docs::api_docs_metadata)
|
||||
.layer(Extension(aac))
|
||||
|
|
|
|||
94
src/api/reservations.rs
Normal file
94
src/api/reservations.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//! Reservations, from the administration side.
|
||||
//!
|
||||
//! Reading the whole list is an admin action for now; changing a status is
|
||||
//! reserved to the unit the reservation belongs to (an admin manages every
|
||||
//! unit), which is why the handler resolves the unit before narrowing the
|
||||
//! controller down.
|
||||
|
||||
use aide::{
|
||||
axum::{ApiRouter, routing::get_with},
|
||||
transform::TransformOperation,
|
||||
};
|
||||
use axum::{
|
||||
Json,
|
||||
extract::Path,
|
||||
http::StatusCode,
|
||||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
api::helpers::{admin, admin_desc, manager, manager_desc, unexpected_error},
|
||||
core::{
|
||||
controller::{AppController, ControllerError, reservations::ReservationsControllerError},
|
||||
models::reservation::{Reservation, ReservationId, ReservationStatus},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn routes() -> ApiRouter {
|
||||
ApiRouter::new()
|
||||
.api_route("/", get_with(get_reservations, get_reservations_docs))
|
||||
.api_route(
|
||||
"/{id}/status",
|
||||
aide::axum::routing::put_with(set_status, set_status_docs),
|
||||
)
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn get_reservations(
|
||||
ac: AppController,
|
||||
) -> Result<Json<Vec<Reservation>>, (StatusCode, String)> {
|
||||
match admin(ac)?.get_reservations().await {
|
||||
Ok(reservations) => Ok(Json(reservations)),
|
||||
Err(err) => unexpected_error("get_reservations", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reservations_docs(op: TransformOperation) -> TransformOperation {
|
||||
op.tag("Reservations")
|
||||
.summary("Get every reservation")
|
||||
.response_with::<403, (), _>(admin_desc)
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, JsonSchema)]
|
||||
struct SetStatusForm {
|
||||
status: ReservationStatus,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn set_status(
|
||||
ac: AppController,
|
||||
Path(id): Path<ReservationId>,
|
||||
Json(SetStatusForm { status }): Json<SetStatusForm>,
|
||||
) -> Result<(), (StatusCode, String)> {
|
||||
// The unit is not in the body: it is the reservation's own
|
||||
let reservation = match ac.get_reservation(id).await {
|
||||
Ok(reservation) => reservation,
|
||||
Err(err) if err.is_not_found() => {
|
||||
return Err((StatusCode::NOT_FOUND, "No such reservation".to_owned()));
|
||||
}
|
||||
Err(err) => return unexpected_error("set_status", err),
|
||||
};
|
||||
|
||||
match manager(ac, reservation.unit.id)?
|
||||
.set_reservation_status(id, status)
|
||||
.await
|
||||
{
|
||||
Ok(()) => Ok(()),
|
||||
Err(ControllerError::Reservation(err @ ReservationsControllerError::InvalidTransition(..))) => {
|
||||
Err((StatusCode::CONFLICT, err.to_string()))
|
||||
}
|
||||
Err(err) => unexpected_error("set_status", err),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_status_docs(op: TransformOperation) -> TransformOperation {
|
||||
op.tag("Reservations")
|
||||
.summary("Move a reservation through its state machine")
|
||||
.description(
|
||||
"Refuses a transition the state machine does not allow, with a 409.",
|
||||
)
|
||||
.response_with::<403, (), _>(manager_desc)
|
||||
.response::<404, ()>()
|
||||
.response::<409, ()>()
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue