diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts index 03a464c..2238e8d 100644 --- a/geneit_app/src/api/ServerApi.ts +++ b/geneit_app/src/api/ServerApi.ts @@ -32,6 +32,8 @@ interface Constraints { member_country: LenConstraint; member_sex: LenConstraint; member_note: LenConstraint; + accomodation_name_len: LenConstraint; + accomodation_description_len: LenConstraint; } interface OIDCProvider { diff --git a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/down.sql b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/down.sql index ab5440a..3512fca 100644 --- a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/down.sql +++ b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/down.sql @@ -1,5 +1,5 @@ ALTER TABLE public.families DROP COLUMN enable_accommodations; -DROP TABLE IF EXISTS accomodations_reservations; -DROP TABLE IF EXISTS accomodations_list; \ No newline at end of file +DROP TABLE IF EXISTS accommodations_reservations; +DROP TABLE IF EXISTS accommodations_list; \ No newline at end of file diff --git a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql index 0262ead..6ce2823 100644 --- a/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql +++ b/geneit_backend/migrations/2024-05-23-163128_accommodation_module/up.sql @@ -8,13 +8,14 @@ COMMENT -- Create tables CREATE TABLE IF NOT EXISTS accommodations_list ( - id SERIAL PRIMARY KEY, - family_id integer NOT NULL REFERENCES families, - time_create BIGINT NOT NULL, - time_update BIGINT NOT NULL, - need_validation BOOLEAN, - description text NULL, - open_to_reservation BOOLEAN + id SERIAL PRIMARY KEY, + family_id integer NOT NULL REFERENCES families, + time_create BIGINT NOT NULL, + time_update BIGINT NOT NULL, + name VARCHAR(50) NOT NULL, + need_validation BOOLEAN NOT NULL DEFAULT true, + description text NULL, + open_to_reservations BOOLEAN NOT NULL DEFAULT false ); CREATE TABLE IF NOT EXISTS accommodations_reservations @@ -27,5 +28,5 @@ CREATE TABLE IF NOT EXISTS accommodations_reservations time_update BIGINT NOT NULL, reservation_start BIGINT NOT NULL, reservation_end BIGINT NOT NULL, - validated BOOLEAN + validated BOOLEAN NULL ); \ No newline at end of file diff --git a/geneit_backend/src/constants.rs b/geneit_backend/src/constants.rs index f1edc26..c8aca30 100644 --- a/geneit_backend/src/constants.rs +++ b/geneit_backend/src/constants.rs @@ -60,6 +60,9 @@ pub struct StaticConstraints { pub member_country: SizeConstraint, pub member_sex: SizeConstraint, pub member_note: SizeConstraint, + + pub accomodation_name_len: SizeConstraint, + pub accomodation_description_len: SizeConstraint, } impl Default for StaticConstraints { @@ -91,6 +94,9 @@ impl Default for StaticConstraints { member_country: SizeConstraint::new(0, 2), member_sex: SizeConstraint::new(0, 1), member_note: SizeConstraint::new(0, 35000), + + accomodation_name_len: SizeConstraint::new(1, 50), + accomodation_description_len: SizeConstraint::new(0, 500), } } } diff --git a/geneit_backend/src/controllers/accommodations_list_controller.rs b/geneit_backend/src/controllers/accommodations_list_controller.rs new file mode 100644 index 0000000..06cabc7 --- /dev/null +++ b/geneit_backend/src/controllers/accommodations_list_controller.rs @@ -0,0 +1,75 @@ +use crate::constants::StaticConstraints; +use crate::controllers::HttpResult; +use crate::extractors::family_extractor::FamilyInPathWithAdminMembership; +use crate::models::{Accommodation, FamilyID}; +use crate::services::accommodations_list_service; +use crate::services::couples_service::{delete, get_all_of_family}; +use actix_web::{web, HttpResponse}; + +#[derive(thiserror::Error, Debug)] +enum AccommodationListControllerErr { + #[error("Malformed name!")] + MalformedName, + #[error("Malformed description!")] + MalformedDescription, +} + +#[derive(serde::Deserialize, Clone)] +pub struct AccommodationRequest { + pub name: String, + pub need_validation: bool, + pub description: Option, + pub open_to_reservations: bool, +} + +impl AccommodationRequest { + pub async fn to_accommodation(self, accommodation: &mut Accommodation) -> anyhow::Result<()> { + let c = StaticConstraints::default(); + + if !c.accomodation_name_len.validate(&self.name) { + return Err(AccommodationListControllerErr::MalformedName.into()); + } + accommodation.name = self.name; + + if let Some(d) = &self.description { + if !c.accomodation_description_len.validate(d) { + return Err(AccommodationListControllerErr::MalformedDescription.into()); + } + } + accommodation.description.clone_from(&self.description); + + accommodation.need_validation = self.need_validation; + accommodation.open_to_reservations = self.open_to_reservations; + Ok(()) + } +} + +/// Create a new accommodation +pub async fn create( + m: FamilyInPathWithAdminMembership, + req: web::Json, +) -> HttpResult { + let mut accommodation = accommodations_list_service::create(m.family_id()).await?; + + if let Err(e) = req.0.to_accommodation(&mut accommodation).await { + log::error!("Failed to apply accommodation information! {e}"); + accommodations_list_service::delete(&mut accommodation).await?; + return Ok(HttpResponse::BadRequest().body(e.to_string())); + } + + if let Err(e) = accommodations_list_service::update(&mut accommodation).await { + log::error!("Failed to update accommodation information! {e}"); + accommodations_list_service::delete(&mut accommodation).await?; + return Ok(HttpResponse::InternalServerError().finish()); + } + + Ok(HttpResponse::Ok().json(accommodation)) +} + +/// Delete all the accommodations of a family +pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> { + for mut m in get_all_of_family(family_id).await? { + delete(&mut m).await?; + } + Ok(()) +} diff --git a/geneit_backend/src/controllers/mod.rs b/geneit_backend/src/controllers/mod.rs index 0d22c59..4cd4c7d 100644 --- a/geneit_backend/src/controllers/mod.rs +++ b/geneit_backend/src/controllers/mod.rs @@ -5,6 +5,7 @@ use actix_web::HttpResponse; use std::fmt::{Debug, Display, Formatter}; use zip::result::ZipError; +pub mod accommodations_list_controller; pub mod auth_controller; pub mod couples_controller; pub mod data_controller; diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index 9fa9c26..e44f561 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -6,8 +6,9 @@ use actix_web::{web, App, HttpServer}; use geneit_backend::app_config::AppConfig; use geneit_backend::connections::{db_connection, s3_connection}; use geneit_backend::controllers::{ - auth_controller, couples_controller, data_controller, families_controller, members_controller, - photos_controller, server_controller, users_controller, + accommodations_list_controller, auth_controller, couples_controller, data_controller, + families_controller, members_controller, photos_controller, server_controller, + users_controller, }; #[actix_web::main] @@ -205,7 +206,10 @@ async fn main() -> std::io::Result<()> { web::put().to(data_controller::import_family), ) // [ACCOMODATIONS] List controller - // TODO : create + .route( + "/family/{id}/accommodations/list/create", + web::post().to(accommodations_list_controller::create), + ) // TODO : update // TODO : delete // TODO : list diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index d1dd017..7055c4f 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -1,5 +1,5 @@ use crate::app_config::AppConfig; -use crate::schema::{couples, families, members, memberships, photos, users}; +use crate::schema::{accommodations_list, couples, families, members, memberships, photos, users}; use crate::utils::crypt_utils::sha256; use diesel::prelude::*; @@ -309,7 +309,7 @@ pub struct NewMember { pub time_update: i64, } -/// Member ID holder +/// Couple ID holder #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] pub struct CoupleID(pub i32); @@ -442,3 +442,34 @@ pub struct NewCouple { pub time_create: i64, pub time_update: i64, } + +/// Accommodation ID holder +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash)] +pub struct AccommodationID(pub i32); + +#[derive(Queryable, Debug, serde::Serialize)] +pub struct Accommodation { + id: i32, + family_id: i32, + time_create: i64, + pub time_update: i64, + pub name: String, + pub need_validation: bool, + pub description: Option, + pub open_to_reservations: bool, +} + +impl Accommodation { + pub fn id(&self) -> AccommodationID { + AccommodationID(self.id) + } +} + +#[derive(Insertable)] +#[diesel(table_name = accommodations_list)] +pub struct NewAccommodation { + pub family_id: i32, + pub name: String, + pub time_create: i64, + pub time_update: i64, +} diff --git a/geneit_backend/src/schema.rs b/geneit_backend/src/schema.rs index 7c4d774..14ad1a2 100644 --- a/geneit_backend/src/schema.rs +++ b/geneit_backend/src/schema.rs @@ -6,9 +6,11 @@ diesel::table! { family_id -> Int4, time_create -> Int8, time_update -> Int8, - need_validation -> Nullable, + #[max_length = 50] + name -> Varchar, + need_validation -> Bool, description -> Nullable, - open_to_reservation -> Nullable, + open_to_reservations -> Bool, } } diff --git a/geneit_backend/src/services/accommodations_list_service.rs b/geneit_backend/src/services/accommodations_list_service.rs new file mode 100644 index 0000000..3ebbebc --- /dev/null +++ b/geneit_backend/src/services/accommodations_list_service.rs @@ -0,0 +1,102 @@ +use crate::connections::db_connection; +use crate::models::{Accommodation, AccommodationID, FamilyID, NewAccommodation}; +use crate::schema::accommodations_list; +use crate::utils::time_utils::time; +use diesel::prelude::*; + +/// Create a new accommodation +pub async fn create(family_id: FamilyID) -> anyhow::Result { + db_connection::execute(|conn| { + let res: Accommodation = diesel::insert_into(accommodations_list::table) + .values(&NewAccommodation { + family_id: family_id.0, + name: "".to_string(), + time_create: time() as i64, + time_update: time() as i64, + }) + .get_result(conn)?; + + Ok(res) + }) +} + +/// Get the information of an accommodation +pub async fn get_by_id(id: AccommodationID) -> anyhow::Result { + db_connection::execute(|conn| { + accommodations_list::table + .filter(accommodations_list::dsl::id.eq(id.0)) + .first(conn) + }) +} + +/// Get all the couples of an accommodation +pub async fn get_all_of_family(id: FamilyID) -> anyhow::Result> { + db_connection::execute(|conn| { + accommodations_list::table + .filter(accommodations_list::dsl::family_id.eq(id.0)) + .get_results(conn) + }) +} + +/// Check whether accommodation with a given id exists or not +pub async fn exists( + family_id: FamilyID, + accommodation_id: AccommodationID, +) -> anyhow::Result { + db_connection::execute(|conn| { + let count: i64 = accommodations_list::table + .filter( + accommodations_list::id + .eq(accommodation_id.0) + .and(accommodations_list::family_id.eq(family_id.0)), + ) + .count() + .get_result(conn)?; + + Ok(count != 0) + }) +} + +/// Update the information of a couple +pub async fn update(accommodation: &mut Accommodation) -> anyhow::Result<()> { + accommodation.time_update = time() as i64; + + db_connection::execute(|conn| { + diesel::update( + accommodations_list::dsl::accommodations_list + .filter(accommodations_list::dsl::id.eq(accommodation.id().0)), + ) + .set(( + accommodations_list::dsl::time_update.eq(accommodation.time_update), + accommodations_list::dsl::name.eq(accommodation.name.to_string()), + accommodations_list::dsl::need_validation.eq(accommodation.need_validation), + accommodations_list::dsl::description.eq(accommodation.description.clone()), + accommodations_list::dsl::open_to_reservations.eq(accommodation.open_to_reservations), + )) + .execute(conn) + })?; + + Ok(()) +} + +/// Delete an accommodation +pub async fn delete(accommodation: &mut Accommodation) -> anyhow::Result<()> { + // Remove the accommodation + db_connection::execute(|conn| { + diesel::delete( + accommodations_list::dsl::accommodations_list + .filter(accommodations_list::dsl::id.eq(accommodation.id().0)), + ) + .execute(conn) + })?; + + Ok(()) +} + +/// Delete all the accommodations of a family +pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> { + for mut m in get_all_of_family(family_id).await? { + delete(&mut m).await?; + } + Ok(()) +} diff --git a/geneit_backend/src/services/families_service.rs b/geneit_backend/src/services/families_service.rs index a1fc6e7..6bfdccb 100644 --- a/geneit_backend/src/services/families_service.rs +++ b/geneit_backend/src/services/families_service.rs @@ -5,7 +5,9 @@ use crate::models::{ Family, FamilyID, FamilyMembership, Membership, NewFamily, NewMembership, UserID, }; use crate::schema::{families, memberships}; -use crate::services::{couples_service, members_service, users_service}; +use crate::services::{ + accommodations_list_service, couples_service, members_service, users_service, +}; use crate::utils::string_utils::rand_str; use crate::utils::time_utils::time; use diesel::prelude::*; @@ -186,6 +188,9 @@ pub async fn update_family(family: &Family) -> anyhow::Result<()> { /// Delete a family pub async fn delete_family(family_id: FamilyID) -> anyhow::Result<()> { + // Delete all family accommodations + accommodations_list_service::delete_all_family(family_id).await?; + // Delete all family couples couples_service::delete_all_family(family_id).await?; diff --git a/geneit_backend/src/services/mod.rs b/geneit_backend/src/services/mod.rs index 935229e..b5db75a 100644 --- a/geneit_backend/src/services/mod.rs +++ b/geneit_backend/src/services/mod.rs @@ -1,5 +1,6 @@ //! # Backend services +pub mod accommodations_list_service; pub mod couples_service; pub mod families_service; pub mod login_token_service;