Add REST endpoint for slot

This commit is contained in:
Simon Goller 2024-05-02 23:25:04 +02:00
parent 82e89baeeb
commit 8f378472ea
28 changed files with 1925 additions and 28 deletions

View file

@ -9,8 +9,8 @@ edition = "2021"
axum = "0.7.5"
bytes = "1.6.0"
http-body = "1.0.0"
serde = "1.0.198"
serde_json = "1.0.116"
time = { version = "0.3.36", features = ["serde-human-readable"] }
[dependencies.tokio]
version = "1.37.0"
@ -22,3 +22,10 @@ path = "../service"
[dependencies.uuid]
version = "1.8.0"
features = ["v4", "serde"]
[dependencies.serde]
version = "1.0.198"
features = ["derive", "std", "alloc", "rc"]
[dependencies.thiserror]
version = "1.0"

View file

@ -2,8 +2,11 @@ use std::{convert::Infallible, sync::Arc};
mod hello;
mod permission;
mod slot;
use axum::{body::Body, response::Response, routing::get, Router};
use thiserror::Error;
use uuid::Uuid;
pub struct RoString(Arc<str>, bool);
impl http_body::Body for RoString {
@ -39,31 +42,103 @@ impl From<RoString> for Response {
}
}
fn error_handler(result: Result<Response, service::ServiceError>) -> Response {
#[derive(Debug, Error)]
pub enum RestError {
#[error("Service error")]
ServiceError(#[from] service::ServiceError),
#[error("Inconsistent id. Got {0} in path but {1} in body")]
InconsistentId(Uuid, Uuid),
}
fn error_handler(result: Result<Response, RestError>) -> Response {
match result {
Ok(response) => response,
Err(service::ServiceError::Forbidden) => {
Err(err @ RestError::InconsistentId(_, _)) => Response::builder()
.status(400)
.body(Body::new(err.to_string()))
.unwrap(),
Err(RestError::ServiceError(service::ServiceError::Forbidden)) => {
Response::builder().status(403).body(Body::empty()).unwrap()
}
Err(service::ServiceError::DatabaseQueryError(e)) => Response::builder()
.status(500)
.body(Body::new(e.to_string()))
.unwrap(),
Err(RestError::ServiceError(service::ServiceError::DatabaseQueryError(e))) => {
Response::builder()
.status(500)
.body(Body::new(e.to_string()))
.unwrap()
}
Err(RestError::ServiceError(service::ServiceError::EntityAlreadyExists(id))) => {
Response::builder()
.status(409)
.body(Body::new(id.to_string()))
.unwrap()
}
Err(RestError::ServiceError(service::ServiceError::EntityNotFound(id))) => {
Response::builder()
.status(404)
.body(Body::new(id.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::EntityConflicts(_, _, _))) => {
Response::builder()
.status(409)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::ValidationError(_))) => {
Response::builder()
.status(422)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::IdSetOnCreate)) => {
Response::builder()
.status(422)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::VersionSetOnCreate)) => {
Response::builder()
.status(422)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::OverlappingTimeRange)) => {
Response::builder()
.status(409)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::TimeOrderWrong(_, _))) => {
Response::builder()
.status(422)
.body(Body::new(err.to_string()))
.unwrap()
}
Err(RestError::ServiceError(err @ service::ServiceError::DateOrderWrong(_, _))) => {
Response::builder()
.status(422)
.body(Body::new(err.to_string()))
.unwrap()
}
}
}
pub trait RestStateDef: Clone + Send + Sync + 'static {
type HelloService: service::HelloService + Send + Sync + 'static;
type PermissionService: service::PermissionService + Send + Sync + 'static;
type SlotService: service::slot::SlotService + Send + Sync + 'static;
fn hello_service(&self) -> Arc<Self::HelloService>;
fn permission_service(&self) -> Arc<Self::PermissionService>;
fn slot_service(&self) -> Arc<Self::SlotService>;
}
pub async fn start_server<RestState: RestStateDef>(rest_state: RestState) {
let app = Router::new()
.route("/", get(hello::hello::<RestState>))
.nest("/permission", permission::generate_route())
.nest("/slot", slot::generate_route())
.with_state(rest_state);
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
.await

View file

@ -13,10 +13,10 @@ use crate::{error_handler, RestStateDef};
use service::PermissionService;
#[derive(Debug, Serialize, Deserialize)]
pub struct User {
pub struct UserTO {
pub name: String,
}
impl From<&service::User> for User {
impl From<&service::User> for UserTO {
fn from(user: &service::User) -> Self {
Self {
name: user.name.to_string(),
@ -25,10 +25,10 @@ impl From<&service::User> for User {
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Role {
pub struct RoleTO {
pub name: String,
}
impl From<&service::Role> for Role {
impl From<&service::Role> for RoleTO {
fn from(role: &service::Role) -> Self {
Self {
name: role.name.to_string(),
@ -37,10 +37,10 @@ impl From<&service::Role> for Role {
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Privilege {
pub struct PrivilegeTO {
pub name: String,
}
impl From<&service::Privilege> for Privilege {
impl From<&service::Privilege> for PrivilegeTO {
fn from(privilege: &service::Privilege) -> Self {
Self {
name: privilege.name.to_string(),
@ -80,7 +80,7 @@ pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
pub async fn add_user<RestState: RestStateDef>(
rest_state: State<RestState>,
Json(user): Json<User>,
Json(user): Json<UserTO>,
) -> Response {
println!("Adding user: {:?}", user);
error_handler(
@ -117,7 +117,7 @@ pub async fn remove_user<RestState: RestStateDef>(
pub async fn add_role<RestState: RestStateDef>(
rest_state: State<RestState>,
Json(role): Json<Role>,
Json(role): Json<RoleTO>,
) -> Response {
error_handler(
(async {
@ -238,12 +238,12 @@ pub async fn remove_role_privilege<RestState: RestStateDef>(
pub async fn get_all_users<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
error_handler(
(async {
let users: Arc<[User]> = rest_state
let users: Arc<[UserTO]> = rest_state
.permission_service()
.get_all_users()
.await?
.iter()
.map(User::from)
.map(UserTO::from)
.collect();
Ok(Response::builder()
.status(200)
@ -257,12 +257,12 @@ pub async fn get_all_users<RestState: RestStateDef>(rest_state: State<RestState>
pub async fn get_all_roles<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
error_handler(
(async {
let roles: Arc<[Role]> = rest_state
let roles: Arc<[RoleTO]> = rest_state
.permission_service()
.get_all_roles()
.await?
.iter()
.map(Role::from)
.map(RoleTO::from)
.collect();
Ok(Response::builder()
.status(200)
@ -276,12 +276,12 @@ pub async fn get_all_roles<RestState: RestStateDef>(rest_state: State<RestState>
pub async fn get_all_privileges<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
error_handler(
(async {
let privileges: Arc<[Privilege]> = rest_state
let privileges: Arc<[PrivilegeTO]> = rest_state
.permission_service()
.get_all_privileges()
.await?
.iter()
.map(Privilege::from)
.map(PrivilegeTO::from)
.collect();
Ok(Response::builder()
.status(200)

182
rest/src/slot.rs Normal file
View file

@ -0,0 +1,182 @@
use std::sync::Arc;
use axum::{
body::Body,
extract::{Path, State},
response::Response,
routing::{get, post, put},
Json, Router,
};
use serde::{Deserialize, Serialize};
use service::slot::SlotService;
use uuid::Uuid;
use crate::{error_handler, RestError, RestStateDef};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum DayOfWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
impl From<service::slot::DayOfWeek> for DayOfWeek {
fn from(day_of_week: service::slot::DayOfWeek) -> Self {
match day_of_week {
service::slot::DayOfWeek::Monday => Self::Monday,
service::slot::DayOfWeek::Tuesday => Self::Tuesday,
service::slot::DayOfWeek::Wednesday => Self::Wednesday,
service::slot::DayOfWeek::Thursday => Self::Thursday,
service::slot::DayOfWeek::Friday => Self::Friday,
service::slot::DayOfWeek::Saturday => Self::Saturday,
service::slot::DayOfWeek::Sunday => Self::Sunday,
}
}
}
impl From<DayOfWeek> for service::slot::DayOfWeek {
fn from(day_of_week: DayOfWeek) -> Self {
match day_of_week {
DayOfWeek::Monday => Self::Monday,
DayOfWeek::Tuesday => Self::Tuesday,
DayOfWeek::Wednesday => Self::Wednesday,
DayOfWeek::Thursday => Self::Thursday,
DayOfWeek::Friday => Self::Friday,
DayOfWeek::Saturday => Self::Saturday,
DayOfWeek::Sunday => Self::Sunday,
}
}
}
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SlotTO {
#[serde(default)]
pub id: Uuid,
pub day_of_week: DayOfWeek,
pub from: time::Time,
pub to: time::Time,
pub valid_from: time::Date,
pub valid_to: Option<time::Date>,
#[serde(default)]
pub deleted: Option<time::PrimitiveDateTime>,
#[serde(rename = "$version")]
#[serde(default)]
pub version: Uuid,
}
impl From<&service::slot::Slot> for SlotTO {
fn from(slot: &service::slot::Slot) -> Self {
Self {
id: slot.id,
day_of_week: slot.day_of_week.into(),
from: slot.from,
to: slot.to,
valid_from: slot.valid_from,
valid_to: slot.valid_to,
deleted: slot.deleted,
version: slot.version,
}
}
}
impl From<&SlotTO> for service::slot::Slot {
fn from(slot: &SlotTO) -> Self {
Self {
id: slot.id,
day_of_week: slot.day_of_week.into(),
from: slot.from,
to: slot.to,
valid_from: slot.valid_from,
valid_to: slot.valid_to,
deleted: slot.deleted,
version: slot.version,
}
}
}
pub fn generate_route<RestState: RestStateDef>() -> Router<RestState> {
Router::new()
.route("/", get(get_all_slots::<RestState>))
.route("/:id", get(get_slot::<RestState>))
.route("/", post(create_slot::<RestState>))
.route("/:id", put(update_slot::<RestState>))
}
pub async fn get_all_slots<RestState: RestStateDef>(rest_state: State<RestState>) -> Response {
error_handler(
(async {
let slots: Arc<[SlotTO]> = rest_state
.slot_service()
.get_slots()
.await?
.iter()
.map(SlotTO::from)
.collect();
Ok(Response::builder()
.status(200)
.body(Body::new(serde_json::to_string(&slots).unwrap()))
.unwrap())
})
.await,
)
}
pub async fn get_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Path(slot_id): Path<Uuid>,
) -> Response {
error_handler(
(async {
let slot = SlotTO::from(&rest_state.slot_service().get_slot(&slot_id).await?.into());
Ok(Response::builder()
.status(200)
.body(Body::new(serde_json::to_string(&slot).unwrap()))
.unwrap())
})
.await,
)
}
pub async fn create_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Json(slot): Json<SlotTO>,
) -> Response {
error_handler(
(async {
let slot = SlotTO::from(
&rest_state
.slot_service()
.create_slot(&(&slot).into())
.await?,
);
Ok(Response::builder()
.status(200)
.body(Body::new(serde_json::to_string(&slot).unwrap()))
.unwrap())
})
.await,
)
}
pub async fn update_slot<RestState: RestStateDef>(
rest_state: State<RestState>,
Path(slot_id): Path<Uuid>,
Json(slot): Json<SlotTO>,
) -> Response {
error_handler(
(async {
if slot_id != slot.id {
return Err(RestError::InconsistentId(slot_id, slot.id));
}
rest_state
.slot_service()
.update_slot(&(&slot).into())
.await?;
Ok(Response::builder()
.status(200)
.body(Body::new(serde_json::to_string(&slot).unwrap()))
.unwrap())
})
.await,
)
}