346 lines
10 KiB
Rust
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())
|
|
}
|