GeneIT/geneit_backend/src/controllers/members_controller.rs

346 lines
10 KiB
Rust

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<i16>,
pub month: Option<i16>,
pub day: Option<i16>,
}
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<String>,
last_name: Option<String>,
birth_last_name: Option<String>,
email: Option<String>,
phone: Option<String>,
address: Option<String>,
city: Option<String>,
postal_code: Option<String>,
country: Option<String>,
sex: Option<Sex>,
pub mother: Option<MemberID>,
pub father: Option<MemberID>,
#[serde(flatten, with = "prefix_birth")]
birth: Option<RequestDate>,
#[serde(default)]
dead: bool,
#[serde(flatten, with = "prefix_death")]
death: Option<RequestDate>,
note: Option<String>,
}
#[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<String>,
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<String>,
}
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<MemberRequest>) -> 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::<Vec<_>>()))
}
/// 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<MemberRequest>) -> 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<UploadPhotoForm>,
) -> 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())
}