use crate::constants::{SizeConstraint, StaticConstraints}; use crate::controllers::HttpResult; use crate::extractors::family_extractor::FamilyInPath; use crate::extractors::member_extractor::FamilyAndMemberInPath; use crate::models::{Member, MemberID, PhotoID, Sex}; use crate::services::{members_service, photos_service}; use crate::utils::countries_utils; use actix_multipart::form::tempfile::TempFile; use actix_multipart::form::MultipartForm; use actix_web::{web, HttpResponse}; serde_with::with_prefix!(prefix_birth "birth_"); serde_with::with_prefix!(prefix_death "death_"); #[derive(serde::Deserialize, Clone)] pub struct RequestDate { pub year: Option, pub month: Option, pub day: Option, } impl RequestDate { pub fn check(&self) -> bool { let c = StaticConstraints::default(); self.year.map(|y| c.date_year.validate(y)).unwrap_or(true) && self.month.map(|y| c.date_month.validate(y)).unwrap_or(true) && self.day.map(|y| c.date_day.validate(y)).unwrap_or(true) } } #[derive(serde::Deserialize, Clone)] pub struct MemberRequest { first_name: Option, last_name: Option, birth_last_name: Option, email: Option, phone: Option, address: Option, city: Option, postal_code: Option, country: Option, sex: Option, pub mother: Option, pub father: Option, #[serde(flatten, with = "prefix_birth")] birth: Option, #[serde(default)] dead: bool, #[serde(flatten, with = "prefix_death")] death: Option, note: Option, } #[derive(thiserror::Error, Debug)] enum MemberControllerErr { #[error("Malformed first name!")] MalformedFirstname, #[error("Malformed last name!")] MalformedLastname, #[error("Malformed birth last name!")] MalformedBirthLastname, #[error("Malformed email address!")] MalformedEmailAddress, #[error("Invalid email address!")] InvalidEmailAddress, #[error("Malformed phone number!")] MalformedPhoneNumber, #[error("Malformed address!")] MalformedAddress, #[error("Malformed city!")] MalformedCity, #[error("Malformed postal code!")] MalformedPostalCode, #[error("Malformed country!")] MalformedCountry, #[error("Invalid country code!")] InvalidCountryCode, #[error("Malformed date of birth!")] MalformedDateOfBirth, #[error("Malformed date of death!")] MalformedDateOfDeath, #[error("Malformed note!")] MalformedNote, #[error("Mother does not exists!")] MotherNotExisting, #[error("Father does not exists!")] FatherNotExisting, #[error("Mother and father can not be identical!")] MotherAndFatherIdentical, } fn check_opt_str_val( val: &Option, c: SizeConstraint, err: MemberControllerErr, ) -> anyhow::Result<()> { if let Some(v) = val { if !c.validate(v) { return Err(err.into()); } } Ok(()) } impl MemberRequest { pub async fn to_member(self, member: &mut Member) -> anyhow::Result<()> { let c = StaticConstraints::default(); check_opt_str_val( &self.first_name, c.member_first_name, MemberControllerErr::MalformedFirstname, )?; check_opt_str_val( &self.last_name, c.member_last_name, MemberControllerErr::MalformedLastname, )?; check_opt_str_val( &self.birth_last_name, c.member_birth_last_name, MemberControllerErr::MalformedBirthLastname, )?; check_opt_str_val( &self.email, c.member_email, MemberControllerErr::MalformedEmailAddress, )?; if let Some(mail) = &self.email { if !mailchecker::is_valid(mail) { return Err(MemberControllerErr::InvalidEmailAddress.into()); } } check_opt_str_val( &self.phone, c.member_phone, MemberControllerErr::MalformedPhoneNumber, )?; check_opt_str_val( &self.address, c.member_address, MemberControllerErr::MalformedAddress, )?; check_opt_str_val( &self.city, c.member_city, MemberControllerErr::MalformedCity, )?; check_opt_str_val( &self.postal_code, c.member_postal_code, MemberControllerErr::MalformedPostalCode, )?; check_opt_str_val( &self.country, c.member_country, MemberControllerErr::MalformedCountry, )?; if let Some(c) = &self.country { if !countries_utils::is_code_valid(c) { return Err(MemberControllerErr::InvalidCountryCode.into()); } } if let Some(d) = &self.birth { if !d.check() { return Err(MemberControllerErr::MalformedDateOfBirth.into()); } } if let Some(d) = &self.death { if !d.check() { return Err(MemberControllerErr::MalformedDateOfDeath.into()); } } check_opt_str_val( &self.note, c.member_note, MemberControllerErr::MalformedNote, )?; if let Some(mother) = self.mother { if !members_service::exists(member.family_id(), mother).await? { return Err(MemberControllerErr::MotherNotExisting.into()); } if self.mother == self.father { return Err(MemberControllerErr::MotherAndFatherIdentical.into()); } } if let Some(father) = self.father { if !members_service::exists(member.family_id(), father).await? { return Err(MemberControllerErr::FatherNotExisting.into()); } } member.first_name = self.first_name; member.last_name = self.last_name; member.birth_last_name = self.birth_last_name; member.email = self.email; member.phone = self.phone; member.address = self.address; member.city = self.city; member.postal_code = self.postal_code; member.country = self.country; member.set_sex(self.sex); member.set_mother(self.mother); member.set_father(self.father); member.birth_year = self.birth.as_ref().map(|m| m.year).unwrap_or_default(); member.birth_month = self.birth.as_ref().map(|m| m.month).unwrap_or_default(); member.birth_day = self.birth.as_ref().map(|m| m.day).unwrap_or_default(); member.dead = self.dead; member.death_year = self.death.as_ref().map(|m| m.year).unwrap_or_default(); member.death_month = self.death.as_ref().map(|m| m.month).unwrap_or_default(); member.death_day = self.death.as_ref().map(|m| m.day).unwrap_or_default(); member.note = self.note; Ok(()) } } #[derive(serde::Serialize)] struct MemberAPI { #[serde(flatten)] member: Member, signed_photo_id: Option, } impl MemberAPI { pub fn new(member: Member) -> Self { Self { signed_photo_id: member.photo_id().as_ref().map(PhotoID::to_signed_hash), member, } } } /// Create a new family member pub async fn create(f: FamilyInPath, req: web::Json) -> HttpResult { let mut member = members_service::create(f.family_id()).await?; if let Err(e) = req.0.to_member(&mut member).await { log::error!("Failed to apply member information! {e}"); members_service::delete(&mut member).await?; return Ok(HttpResponse::BadRequest().body(e.to_string())); } if let Err(e) = members_service::update(&mut member).await { log::error!("Failed to update member information! {e}"); members_service::delete(&mut member).await?; return Ok(HttpResponse::InternalServerError().finish()); } Ok(HttpResponse::Ok().json(member)) } /// Get the entire list of members of the family pub async fn get_all(f: FamilyInPath) -> HttpResult { let members = members_service::get_all_of_family(f.family_id()).await?; Ok(HttpResponse::Ok().json(members.into_iter().map(MemberAPI::new).collect::>())) } /// Get the information of a single family member pub async fn get_single(m: FamilyAndMemberInPath) -> HttpResult { Ok(HttpResponse::Ok().json(MemberAPI::new(m.to_member()))) } /// Update a member information pub async fn update(m: FamilyAndMemberInPath, req: web::Json) -> HttpResult { let mut member = m.to_member(); if let Err(e) = req.0.to_member(&mut member).await { log::error!("Failed to parse member information {e}!"); return Ok(HttpResponse::BadRequest().body(e.to_string())); } // Check for potential loops let members = members_service::get_all_of_family(member.family_id()).await?; let mut members_ref = Vec::with_capacity(members.len()); members_ref.push(&member); for m in &members { if m.id() != member.id() { members_ref.push(m); } } if members_service::loop_detection::detect_loop(&members_ref) { log::warn!("Membership update rejected due to detected loop!"); return Ok(HttpResponse::NotAcceptable().json("Loop detected!")); } members_service::update(&mut member).await?; Ok(HttpResponse::Accepted().finish()) } /// Delete a member pub async fn delete(m: FamilyAndMemberInPath) -> HttpResult { members_service::delete(&mut m.to_member()).await?; Ok(HttpResponse::Ok().finish()) } #[derive(Debug, MultipartForm)] pub struct UploadPhotoForm { #[multipart(rename = "photo")] photo: TempFile, } /// Upload a new photo for a user pub async fn set_photo( m: FamilyAndMemberInPath, MultipartForm(form): MultipartForm, ) -> HttpResult { let photo = photos_service::finalize_upload(form.photo).await?; let mut member = m.to_member(); members_service::remove_photo(&mut member).await?; member.set_photo_id(Some(photo.id())); members_service::update(&mut member).await?; Ok(HttpResponse::Ok().finish()) } /// Remove a photo pub async fn remove_photo(m: FamilyAndMemberInPath) -> HttpResult { let mut member = m.to_member(); members_service::remove_photo(&mut member).await?; Ok(HttpResponse::Ok().finish()) }