Add booking dao implementations
This commit is contained in:
parent
4bca60a23c
commit
71c1432fd1
10 changed files with 225 additions and 13 deletions
|
|
@ -14,6 +14,7 @@ pub struct BookingEntity {
|
||||||
pub slot_id: Uuid,
|
pub slot_id: Uuid,
|
||||||
pub calendar_week: i32,
|
pub calendar_week: i32,
|
||||||
pub year: u32,
|
pub year: u32,
|
||||||
|
pub created: PrimitiveDateTime,
|
||||||
pub deleted: Option<PrimitiveDateTime>,
|
pub deleted: Option<PrimitiveDateTime>,
|
||||||
pub version: Uuid,
|
pub version: Uuid,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
109
dao_impl/src/booking.rs
Normal file
109
dao_impl/src/booking.rs
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::ResultDbErrorExt;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use dao::{
|
||||||
|
booking::{BookingDao, BookingEntity},
|
||||||
|
DaoError,
|
||||||
|
};
|
||||||
|
use sqlx::{query, query_as};
|
||||||
|
use time::{format_description::well_known::Iso8601, PrimitiveDateTime};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
struct BookingDb {
|
||||||
|
id: Vec<u8>,
|
||||||
|
sales_person_id: Vec<u8>,
|
||||||
|
slot_id: Vec<u8>,
|
||||||
|
calendar_week: i64,
|
||||||
|
year: i64,
|
||||||
|
created: String,
|
||||||
|
deleted: Option<String>,
|
||||||
|
update_version: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl TryFrom<&BookingDb> for BookingEntity {
|
||||||
|
type Error = DaoError;
|
||||||
|
fn try_from(booking: &BookingDb) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
|
id: Uuid::from_slice(booking.id.as_ref()).unwrap(),
|
||||||
|
sales_person_id: Uuid::from_slice(booking.sales_person_id.as_ref()).unwrap(),
|
||||||
|
slot_id: Uuid::from_slice(booking.slot_id.as_ref()).unwrap(),
|
||||||
|
calendar_week: booking.calendar_week as i32,
|
||||||
|
year: booking.year as u32,
|
||||||
|
created: PrimitiveDateTime::parse(&booking.created, &Iso8601::DATE_TIME)?,
|
||||||
|
deleted: booking
|
||||||
|
.deleted
|
||||||
|
.as_ref()
|
||||||
|
.map(|deleted| PrimitiveDateTime::parse(deleted, &Iso8601::DATE_TIME))
|
||||||
|
.transpose()?,
|
||||||
|
version: Uuid::from_slice(&booking.update_version).unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct BookingDaoImpl {
|
||||||
|
pub pool: Arc<sqlx::SqlitePool>,
|
||||||
|
}
|
||||||
|
impl BookingDaoImpl {
|
||||||
|
pub fn new(pool: Arc<sqlx::SqlitePool>) -> Self {
|
||||||
|
Self { pool }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl BookingDao for BookingDaoImpl {
|
||||||
|
async fn all(&self) -> Result<Arc<[BookingEntity]>, DaoError> {
|
||||||
|
Ok(query_as!(
|
||||||
|
BookingDb,
|
||||||
|
"SELECT id, sales_person_id, slot_id, calendar_week, year, created, deleted, update_version FROM booking WHERE deleted IS NULL"
|
||||||
|
)
|
||||||
|
.fetch_all(self.pool.as_ref())
|
||||||
|
.await
|
||||||
|
.map_db_error()?
|
||||||
|
.iter()
|
||||||
|
.map(BookingEntity::try_from)
|
||||||
|
.collect::<Result<Arc<[BookingEntity]>, DaoError>>()?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
async fn find_by_id(&self, id: Uuid) -> Result<Option<BookingEntity>, DaoError> {
|
||||||
|
let id_vec = id.as_bytes().to_vec();
|
||||||
|
Ok(query_as!(
|
||||||
|
BookingDb,
|
||||||
|
"SELECT id, sales_person_id, slot_id, calendar_week, year, created, deleted, update_version FROM booking WHERE id = ?",
|
||||||
|
id_vec,
|
||||||
|
)
|
||||||
|
.fetch_optional(self.pool.as_ref())
|
||||||
|
.await
|
||||||
|
.map_db_error()?
|
||||||
|
.as_ref()
|
||||||
|
.map(BookingEntity::try_from)
|
||||||
|
.transpose()?)
|
||||||
|
}
|
||||||
|
async fn create(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> {
|
||||||
|
let id_vec = entity.id.as_bytes().to_vec();
|
||||||
|
let sales_person_id_vec = entity.sales_person_id.as_bytes().to_vec();
|
||||||
|
let slot_id_vec = entity.slot_id.as_bytes().to_vec();
|
||||||
|
let created = entity.created.to_string();
|
||||||
|
let deleted = entity.deleted.as_ref().map(|deleted| deleted.to_string());
|
||||||
|
let version_vec = entity.version.as_bytes().to_vec();
|
||||||
|
query!("INSERT INTO booking (id, sales_person_id, slot_id, calendar_week, year, created, deleted, update_version, update_process) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
|
id_vec, sales_person_id_vec, slot_id_vec, entity.calendar_week, entity.year, created, deleted, version_vec, process
|
||||||
|
).execute(self.pool.as_ref()).await.map_db_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn update(&self, entity: &BookingEntity, process: &str) -> Result<(), DaoError> {
|
||||||
|
let id_vec = entity.id.as_bytes().to_vec();
|
||||||
|
let version_vec = entity.version.as_bytes().to_vec();
|
||||||
|
let deleted = entity.deleted.as_ref().map(|deleted| deleted.to_string());
|
||||||
|
query!(
|
||||||
|
"UPDATE booking SET deleted = ?, update_version = ?, update_process = ? WHERE id = ?",
|
||||||
|
deleted,
|
||||||
|
version_vec,
|
||||||
|
process,
|
||||||
|
id_vec
|
||||||
|
)
|
||||||
|
.execute(self.pool.as_ref())
|
||||||
|
.await
|
||||||
|
.map_db_error()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,6 +4,7 @@ use async_trait::async_trait;
|
||||||
use dao::DaoError;
|
use dao::DaoError;
|
||||||
use sqlx::{query, query_as, SqlitePool};
|
use sqlx::{query, query_as, SqlitePool};
|
||||||
|
|
||||||
|
pub mod booking;
|
||||||
pub mod sales_person;
|
pub mod sales_person;
|
||||||
pub mod slot;
|
pub mod slot;
|
||||||
|
|
||||||
|
|
|
||||||
15
migrations/20240507063704_add-booking.sql
Normal file
15
migrations/20240507063704_add-booking.sql
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
-- Add migration script here
|
||||||
|
|
||||||
|
CREATE TABLE booking (
|
||||||
|
id blob(16) NOT NULL PRIMARY KEY,
|
||||||
|
sales_person_id blob(16) NOT NULL,
|
||||||
|
slot_id blob(16) NOT NULL,
|
||||||
|
calendar_week INTEGER NOT NULL,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
created TEXT NOT NULL,
|
||||||
|
deleted TEXT,
|
||||||
|
|
||||||
|
update_timestamp TEXT,
|
||||||
|
update_process TEXT NOT NULL,
|
||||||
|
update_version blob(16) NOT NULL
|
||||||
|
);
|
||||||
|
|
@ -5,6 +5,7 @@ mod sales_person;
|
||||||
mod slot;
|
mod slot;
|
||||||
|
|
||||||
use axum::{body::Body, response::Response, Router};
|
use axum::{body::Body, response::Response, Router};
|
||||||
|
use service::ServiceError;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
@ -124,6 +125,10 @@ fn error_handler(result: Result<Response, RestError>) -> Response {
|
||||||
.body(Body::new(err.to_string()))
|
.body(Body::new(err.to_string()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
Err(RestError::ServiceError(ServiceError::InternalError)) => Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(Body::new("Internal server error".to_string()))
|
||||||
|
.unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ pub struct Booking {
|
||||||
pub slot_id: Uuid,
|
pub slot_id: Uuid,
|
||||||
pub calendar_week: i32,
|
pub calendar_week: i32,
|
||||||
pub year: u32,
|
pub year: u32,
|
||||||
|
pub created: Option<PrimitiveDateTime>,
|
||||||
pub deleted: Option<PrimitiveDateTime>,
|
pub deleted: Option<PrimitiveDateTime>,
|
||||||
pub version: Uuid,
|
pub version: Uuid,
|
||||||
}
|
}
|
||||||
|
|
@ -25,23 +26,26 @@ impl From<&dao::booking::BookingEntity> for Booking {
|
||||||
slot_id: booking.slot_id,
|
slot_id: booking.slot_id,
|
||||||
calendar_week: booking.calendar_week,
|
calendar_week: booking.calendar_week,
|
||||||
year: booking.year,
|
year: booking.year,
|
||||||
|
created: Some(booking.created),
|
||||||
deleted: booking.deleted,
|
deleted: booking.deleted,
|
||||||
version: booking.version,
|
version: booking.version,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Booking> for dao::booking::BookingEntity {
|
impl TryFrom<&Booking> for dao::booking::BookingEntity {
|
||||||
fn from(booking: &Booking) -> Self {
|
type Error = ServiceError;
|
||||||
Self {
|
fn try_from(booking: &Booking) -> Result<Self, Self::Error> {
|
||||||
|
Ok(Self {
|
||||||
id: booking.id,
|
id: booking.id,
|
||||||
sales_person_id: booking.sales_person_id,
|
sales_person_id: booking.sales_person_id,
|
||||||
slot_id: booking.slot_id,
|
slot_id: booking.slot_id,
|
||||||
calendar_week: booking.calendar_week,
|
calendar_week: booking.calendar_week,
|
||||||
year: booking.year,
|
year: booking.year,
|
||||||
|
created: booking.created.ok_or_else(|| ServiceError::InternalError)?,
|
||||||
deleted: booking.deleted,
|
deleted: booking.deleted,
|
||||||
version: booking.version,
|
version: booking.version,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ pub use permission::User;
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum ValidationFailureItem {
|
pub enum ValidationFailureItem {
|
||||||
ModificationNotAllowed(Arc<str>),
|
ModificationNotAllowed(Arc<str>),
|
||||||
|
InvalidValue(Arc<str>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
|
@ -57,4 +58,7 @@ pub enum ServiceError {
|
||||||
|
|
||||||
#[error("Date order wrong. {0} must is not smaller or equal to {1}")]
|
#[error("Date order wrong. {0} must is not smaller or equal to {1}")]
|
||||||
DateOrderWrong(Date, Date),
|
DateOrderWrong(Date, Date),
|
||||||
|
|
||||||
|
#[error("Internal error")]
|
||||||
|
InternalError,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use dao::booking;
|
||||||
use service::{
|
use service::{
|
||||||
booking::{Booking, BookingService},
|
booking::{Booking, BookingService},
|
||||||
ServiceError,
|
ServiceError, ValidationFailureItem,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
@ -95,16 +96,40 @@ where
|
||||||
return Err(ServiceError::VersionSetOnCreate);
|
return Err(ServiceError::VersionSetOnCreate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut validation = Vec::with_capacity(8);
|
||||||
|
if booking.created.is_some() {
|
||||||
|
validation.push(ValidationFailureItem::InvalidValue("created".into()));
|
||||||
|
}
|
||||||
|
if booking.sales_person_id == Uuid::nil() {
|
||||||
|
validation.push(ValidationFailureItem::InvalidValue(
|
||||||
|
"sales_person_id".into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if booking.slot_id == Uuid::nil() {
|
||||||
|
validation.push(ValidationFailureItem::InvalidValue("slot_id".into()));
|
||||||
|
}
|
||||||
|
if booking.calendar_week <= 0 {
|
||||||
|
validation.push(ValidationFailureItem::InvalidValue("calendar_week".into()));
|
||||||
|
}
|
||||||
|
if booking.calendar_week > 53 {
|
||||||
|
validation.push(ValidationFailureItem::InvalidValue("calendar_week".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validation.is_empty() {
|
||||||
|
return Err(ServiceError::ValidationError(validation.into()));
|
||||||
|
}
|
||||||
|
|
||||||
let new_id = self.uuid_service.new_uuid("booking-id");
|
let new_id = self.uuid_service.new_uuid("booking-id");
|
||||||
let new_version = self.uuid_service.new_uuid("booking-version");
|
let new_version = self.uuid_service.new_uuid("booking-version");
|
||||||
let new_booking = Booking {
|
let new_booking = Booking {
|
||||||
id: new_id,
|
id: new_id,
|
||||||
version: new_version,
|
version: new_version,
|
||||||
|
created: Some(self.clock_service.date_time_now()),
|
||||||
..booking.clone()
|
..booking.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.booking_dao
|
self.booking_dao
|
||||||
.create(&(&new_booking).into(), BOOKING_SERVICE_PROCESS)
|
.create(&(&new_booking).try_into()?, BOOKING_SERVICE_PROCESS)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(new_booking)
|
Ok(new_booking)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,8 @@ use crate::test::error_test::*;
|
||||||
use dao::booking::{BookingEntity, MockBookingDao};
|
use dao::booking::{BookingEntity, MockBookingDao};
|
||||||
use mockall::predicate::eq;
|
use mockall::predicate::eq;
|
||||||
use service::{
|
use service::{
|
||||||
booking::Booking, clock::MockClockService, uuid_service::MockUuidService, MockPermissionService,
|
booking::Booking, clock::MockClockService, uuid_service::MockUuidService,
|
||||||
|
MockPermissionService, ValidationFailureItem,
|
||||||
};
|
};
|
||||||
use time::{Date, Month, PrimitiveDateTime, Time};
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||||
use uuid::{uuid, Uuid};
|
use uuid::{uuid, Uuid};
|
||||||
|
|
@ -36,6 +37,10 @@ pub fn default_booking() -> Booking {
|
||||||
slot_id: default_slot_id(),
|
slot_id: default_slot_id(),
|
||||||
calendar_week: 3,
|
calendar_week: 3,
|
||||||
year: 2024,
|
year: 2024,
|
||||||
|
created: Some(PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2024, Month::January, 1).unwrap(),
|
||||||
|
Time::from_hms(0, 0, 0).unwrap(),
|
||||||
|
)),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
version: default_version(),
|
version: default_version(),
|
||||||
}
|
}
|
||||||
|
|
@ -48,6 +53,10 @@ pub fn default_booking_entity() -> BookingEntity {
|
||||||
slot_id: default_slot_id(),
|
slot_id: default_slot_id(),
|
||||||
calendar_week: 3,
|
calendar_week: 3,
|
||||||
year: 2024,
|
year: 2024,
|
||||||
|
created: PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2024, Month::January, 1).unwrap(),
|
||||||
|
Time::from_hms(0, 0, 0).unwrap(),
|
||||||
|
),
|
||||||
deleted: None,
|
deleted: None,
|
||||||
version: default_version(),
|
version: default_version(),
|
||||||
}
|
}
|
||||||
|
|
@ -186,7 +195,13 @@ async fn test_create() {
|
||||||
let mut deps = build_dependencies(true, "hr");
|
let mut deps = build_dependencies(true, "hr");
|
||||||
deps.booking_dao
|
deps.booking_dao
|
||||||
.expect_create()
|
.expect_create()
|
||||||
.with(eq(default_booking_entity()), eq("booking-service"))
|
.with(
|
||||||
|
eq(BookingEntity {
|
||||||
|
created: generate_default_datetime(),
|
||||||
|
..default_booking_entity()
|
||||||
|
}),
|
||||||
|
eq("booking-service"),
|
||||||
|
)
|
||||||
.returning(|_, _| Ok(()));
|
.returning(|_, _| Ok(()));
|
||||||
deps.uuid_service
|
deps.uuid_service
|
||||||
.expect_new_uuid()
|
.expect_new_uuid()
|
||||||
|
|
@ -202,13 +217,20 @@ async fn test_create() {
|
||||||
&Booking {
|
&Booking {
|
||||||
id: Uuid::nil(),
|
id: Uuid::nil(),
|
||||||
version: Uuid::nil(),
|
version: Uuid::nil(),
|
||||||
|
created: None,
|
||||||
..default_booking()
|
..default_booking()
|
||||||
},
|
},
|
||||||
(),
|
(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
assert_eq!(result.unwrap(), default_booking());
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
Booking {
|
||||||
|
created: Some(generate_default_datetime()),
|
||||||
|
..default_booking()
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
@ -260,6 +282,27 @@ async fn test_create_with_version() {
|
||||||
test_zero_version_error(&result);
|
test_zero_version_error(&result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_with_created_fail() {
|
||||||
|
let deps = build_dependencies(true, "hr");
|
||||||
|
let service = deps.build_service();
|
||||||
|
let result = service
|
||||||
|
.create(
|
||||||
|
&Booking {
|
||||||
|
id: Uuid::nil(),
|
||||||
|
version: Uuid::nil(),
|
||||||
|
..default_booking()
|
||||||
|
},
|
||||||
|
(),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
test_validation_error(
|
||||||
|
&result,
|
||||||
|
&ValidationFailureItem::InvalidValue("created".into()),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_delete_no_permission() {
|
async fn test_delete_no_permission() {
|
||||||
let deps = build_dependencies(false, "hr");
|
let deps = build_dependencies(false, "hr");
|
||||||
|
|
@ -291,10 +334,7 @@ async fn test_delete() {
|
||||||
.expect_update()
|
.expect_update()
|
||||||
.with(
|
.with(
|
||||||
eq(BookingEntity {
|
eq(BookingEntity {
|
||||||
deleted: Some(PrimitiveDateTime::new(
|
deleted: Some(generate_default_datetime()),
|
||||||
Date::from_calendar_date(2063, Month::April, 5).unwrap(),
|
|
||||||
Time::from_hms(23, 42, 0).unwrap(),
|
|
||||||
)),
|
|
||||||
version: alternate_version(),
|
version: alternate_version(),
|
||||||
..default_booking_entity()
|
..default_booking_entity()
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
use service::ValidationFailureItem;
|
use service::ValidationFailureItem;
|
||||||
|
use time::{Date, Month, PrimitiveDateTime, Time};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub fn test_forbidden<T>(result: &Result<T, service::ServiceError>) {
|
pub fn test_forbidden<T>(result: &Result<T, service::ServiceError>) {
|
||||||
|
|
@ -106,3 +107,10 @@ pub fn test_validation_error<T>(
|
||||||
panic!("Expected validation error");
|
panic!("Expected validation error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_default_datetime() -> PrimitiveDateTime {
|
||||||
|
PrimitiveDateTime::new(
|
||||||
|
Date::from_calendar_date(2063, Month::April, 5).unwrap(),
|
||||||
|
Time::from_hms(23, 42, 0).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue