Add REST endpoint for slot
This commit is contained in:
parent
82e89baeeb
commit
8f378472ea
28 changed files with 1925 additions and 28 deletions
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
182
rest/src/slot.rs
Normal 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,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue