From 1262eda871f5d0c423ba5a8b9df4b4d547fdf4f7 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 15:21:30 +0100 Subject: [PATCH 1/7] feat: application details structure --- api/src/routes/candidate.rs | 34 +-- core/src/database/mutation/candidate.rs | 28 +-- core/src/database/mutation/parent.rs | 14 +- core/src/models/candidate.rs | 52 +++- core/src/models/candidate_details.rs | 287 +++++++++++++---------- core/src/services/application_service.rs | 12 +- core/src/services/candidate_service.rs | 6 +- core/src/services/parent_service.rs | 4 +- core/src/utils/csv.rs | 32 +-- 9 files changed, 267 insertions(+), 202 deletions(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index fa04a09..aefc98b 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -240,21 +240,25 @@ mod tests { } const CANDIDATE_DETAILS: &'static str = "{ - \"name\": \"idk\", - \"surname\": \"idk\", - \"birthplace\": \"Praha 1\", - \"birthdate\": \"2015-09-18\", - \"address\": \"Stefanikova jidelna\", - \"telephone\": \"000111222333\", - \"citizenship\": \"Czech Republic\", - \"email\": \"magor@magor.cz\", - \"sex\": \"MALE\", - \"personalIdNumber\": \"0000000000\", - \"study\": \"KB\", - \"parentName\": \"maminka\", - \"parentSurname\": \"chad\", - \"parentTelephone\": \"420111222333\", - \"parentEmail\": \"maminka@centrum.cz\" + \"candidate\": { + \"name\": \"idk\", + \"surname\": \"idk\", + \"birthplace\": \"Praha 1\", + \"birthdate\": \"2015-09-18\", + \"address\": \"Stefanikova jidelna\", + \"telephone\": \"000111222333\", + \"citizenship\": \"Czech Republic\", + \"email\": \"magor@magor.cz\", + \"sex\": \"MALE\", + \"personalIdNumber\": \"0000000000\", + \"study\": \"KB\" + }, + \"parent\": { + \"name\": \"maminka\", + \"surname\": \"chad\", + \"telephone\": \"420111222333\", + \"email\": \"maminka@centrum.cz\" + } }"; #[test] diff --git a/core/src/database/mutation/candidate.rs b/core/src/database/mutation/candidate.rs index 04f90e8..b64a029 100644 --- a/core/src/database/mutation/candidate.rs +++ b/core/src/database/mutation/candidate.rs @@ -1,4 +1,4 @@ -use crate::{Mutation, models::candidate_details::EncryptedApplicationDetails}; +use crate::{Mutation, models::candidate_details::{EncryptedCandidateDetails}}; use ::entity::candidate::{self}; use sea_orm::*; @@ -44,20 +44,20 @@ impl Mutation { pub async fn add_candidate_details( db: &DbConn, user: candidate::Model, - enc_details: EncryptedApplicationDetails, + enc_candidate: EncryptedCandidateDetails, ) -> Result { let mut user: candidate::ActiveModel = user.into(); - user.name = Set(Some(enc_details.name.into())); - user.surname = Set(Some(enc_details.surname.into())); - user.birthplace = Set(Some(enc_details.birthplace.into())); - user.birthdate = Set(Some(enc_details.birthdate.into())); - user.address = Set(Some(enc_details.address.into())); - user.telephone = Set(Some(enc_details.telephone.into())); - user.citizenship = Set(Some(enc_details.citizenship.into())); - user.email = Set(Some(enc_details.email.into())); - user.sex = Set(Some(enc_details.sex.into())); - user.personal_identification_number = Set(enc_details.personal_id_number.into()); // TODO: do not set this here, it is already set in the create_candidate mutation??? - user.study = Set(Some(enc_details.study.into())); + user.name = Set(Some(enc_candidate.name.into())); + user.surname = Set(Some(enc_candidate.surname.into())); + user.birthplace = Set(Some(enc_candidate.birthplace.into())); + user.birthdate = Set(Some(enc_candidate.birthdate.into())); + user.address = Set(Some(enc_candidate.address.into())); + user.telephone = Set(Some(enc_candidate.telephone.into())); + user.citizenship = Set(Some(enc_candidate.citizenship.into())); + user.email = Set(Some(enc_candidate.email.into())); + user.sex = Set(Some(enc_candidate.sex.into())); + user.personal_identification_number = Set(enc_candidate.personal_id_number.into()); // TODO: do not set this here, it is already set in the create_candidate mutation??? + user.study = Set(Some(enc_candidate.study.into())); user.updated_at = Set(chrono::offset::Local::now().naive_local()); @@ -117,7 +117,7 @@ mod tests { vec!["age1u889gp407hsz309wn09kxx9anl6uns30m27lfwnctfyq9tq4qpus8tzmq5".to_string()], ).await.unwrap(); - Mutation::add_candidate_details(&db, candidate, encrypted_details).await.unwrap(); + Mutation::add_candidate_details(&db, candidate, encrypted_details.candidate).await.unwrap(); let candidate = Query::find_candidate_by_id(&db, APPLICATION_ID) .await diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs index 0001700..37f4631 100644 --- a/core/src/database/mutation/parent.rs +++ b/core/src/database/mutation/parent.rs @@ -1,4 +1,4 @@ -use crate::{Mutation, models::candidate_details::EncryptedApplicationDetails}; +use crate::{Mutation, models::candidate_details::{EncryptedParentDetails}}; use ::entity::parent::{self, Model}; use sea_orm::*; @@ -18,13 +18,13 @@ impl Mutation { pub async fn add_parent_details( db: &DbConn, parent: Model, - enc_details: EncryptedApplicationDetails, // TODO: use seperate struct?? + enc_parent: EncryptedParentDetails, ) -> Result { let mut user: parent::ActiveModel = parent.into(); - user.name = Set(Some(enc_details.parent_name.into())); - user.surname = Set(Some(enc_details.parent_surname.into())); - user.telephone = Set(Some(enc_details.parent_telephone.into())); - user.email = Set(Some(enc_details.parent_email.into())); + user.name = Set(Some(enc_parent.name.into())); + user.surname = Set(Some(enc_parent.surname.into())); + user.telephone = Set(Some(enc_parent.telephone.into())); + user.email = Set(Some(enc_parent.email.into())); user.updated_at = Set(chrono::offset::Local::now().naive_local()); @@ -88,7 +88,7 @@ mod tests { .await .unwrap(); - Mutation::add_parent_details(&db, parent, encrypted_details) + Mutation::add_parent_details(&db, parent, encrypted_details.parent) .await .unwrap(); diff --git a/core/src/models/candidate.rs b/core/src/models/candidate.rs index 0f7688c..ffa8b6a 100644 --- a/core/src/models/candidate.rs +++ b/core/src/models/candidate.rs @@ -27,11 +27,10 @@ pub struct BaseCandidateResponse { pub submitted: bool, } -/// Candidate details (admin and candidate endpoints) #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] #[serde(rename_all = "camelCase")] -pub struct ApplicationDetails { - // Candidate +pub struct CandidateDetails { + // pub application_id: i32, pub name: String, pub surname: String, pub birthplace: String, @@ -43,12 +42,49 @@ pub struct ApplicationDetails { pub sex: String, pub study: String, pub personal_id_number: String, - // Parent - pub parent_name: String, - pub parent_surname: String, - pub parent_telephone: String, - pub parent_email: String, } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ParentDetails { + // pub application_id: i32, + pub name: String, + pub surname: String, + pub telephone: String, + pub email: String, +} + +/// Candidate details (admin and candidate endpoints) +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ApplicationDetails { + // Candidate + pub candidate: CandidateDetails, + pub parent: ParentDetails, + // pub opt_parent2: Option, +} +/* impl ApplicationDetails { + pub fn new_one_parent( + candidate: CandidateDetails, + parent: ParentDetails, + ) -> Self { + Self { + candidate, + parent, + opt_parent2: None, + } + } + pub fn new_two_parents( + candidate: CandidateDetails, + parent: ParentDetails, + opt_parent2: ParentDetails, + ) -> Self { + Self { + candidate, + parent, + opt_parent2: Some(opt_parent2), + } + } +} */ /// CSV export (admin endpoint) #[derive(FromQueryResult, Serialize, Default)] diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index f0de1fb..a9dc902 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -4,14 +4,15 @@ use entity::{candidate, parent}; use crate::{crypto, models::candidate::{CandidateWithParent, ApplicationDetails}, error::ServiceError}; +use super::candidate::{CandidateDetails, ParentDetails}; + pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d"; #[derive(Clone)] pub struct EncryptedString(String); #[derive(Clone)] -pub struct EncryptedApplicationDetails { - // Candidate +pub struct EncryptedCandidateDetails { pub name: EncryptedString, pub surname: EncryptedString, pub birthplace: EncryptedString, @@ -23,12 +24,19 @@ pub struct EncryptedApplicationDetails { pub sex: EncryptedString, pub personal_id_number: EncryptedString, pub study: String, +} - // Parent - pub parent_name: EncryptedString, - pub parent_surname: EncryptedString, - pub parent_telephone: EncryptedString, - pub parent_email: EncryptedString, +#[derive(Clone)] +pub struct EncryptedParentDetails { + pub name: EncryptedString, + pub surname: EncryptedString, + pub telephone: EncryptedString, + pub email: EncryptedString, +} +#[derive(Clone)] +pub struct EncryptedApplicationDetails { + pub candidate: EncryptedCandidateDetails, + pub parent: EncryptedParentDetails, } impl EncryptedString { @@ -91,80 +99,89 @@ impl EncryptedApplicationDetails { form: &ApplicationDetails, recipients: Vec, ) -> Result { - let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); + let birthdate_str = form.candidate.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( - EncryptedString::new(&form.name, &recipients), - EncryptedString::new(&form.surname, &recipients), - EncryptedString::new(&form.birthplace, &recipients), + EncryptedString::new(&form.candidate.name, &recipients), + EncryptedString::new(&form.candidate.surname, &recipients), + EncryptedString::new(&form.candidate.birthplace, &recipients), EncryptedString::new(&birthdate_str, &recipients), - EncryptedString::new(&form.address, &recipients), - EncryptedString::new(&form.telephone, &recipients), - EncryptedString::new(&form.citizenship, &recipients), - EncryptedString::new(&form.email, &recipients), - EncryptedString::new(&form.sex, &recipients), - EncryptedString::new(&form.personal_id_number, &recipients), + EncryptedString::new(&form.candidate.address, &recipients), + EncryptedString::new(&form.candidate.telephone, &recipients), + EncryptedString::new(&form.candidate.citizenship, &recipients), + EncryptedString::new(&form.candidate.email, &recipients), + EncryptedString::new(&form.candidate.sex, &recipients), + EncryptedString::new(&form.candidate.personal_id_number, &recipients), - EncryptedString::new(&form.parent_name, &recipients), - EncryptedString::new(&form.parent_surname, &recipients), - EncryptedString::new(&form.parent_telephone, &recipients), - EncryptedString::new(&form.parent_email, &recipients), + EncryptedString::new(&form.parent.name, &recipients), + EncryptedString::new(&form.parent.surname, &recipients), + EncryptedString::new(&form.parent.telephone, &recipients), + EncryptedString::new(&form.parent.email, &recipients), )?; Ok(EncryptedApplicationDetails { - name: d.0, - surname: d.1, - birthplace: d.2, - birthdate: d.3, - address: d.4, - telephone: d.5, - citizenship: d.6, - email: d.7, - sex: d.8, - personal_id_number: d.9, - study: form.study.clone(), + candidate: EncryptedCandidateDetails { + name: d.0, + surname: d.1, + birthplace: d.2, + birthdate: d.3, + address: d.4, + telephone: d.5, + citizenship: d.6, + email: d.7, + sex: d.8, + personal_id_number: d.9, + study: form.candidate.study.clone(), + }, + parent: EncryptedParentDetails { + name: d.10, + surname: d.11, + telephone: d.12, + email: d.13, + } - parent_name: d.10, - parent_surname: d.11, - parent_telephone: d.12, - parent_email: d.13, }) } pub async fn decrypt(self, priv_key: String) -> Result { let d = tokio::try_join!( - self.name.decrypt(&priv_key), // 0 - self.surname.decrypt(&priv_key), // 1 - self.birthplace.decrypt(&priv_key), // 2 - self.birthdate.decrypt(&priv_key), // 3 - self.address.decrypt(&priv_key), // 4 - self.telephone.decrypt(&priv_key), // 5 - self.citizenship.decrypt(&priv_key), // 6 - self.email.decrypt(&priv_key), // 7 - self.sex.decrypt(&priv_key), // 8 - self.personal_id_number.decrypt(&priv_key),// 9 - self.parent_name.decrypt(&priv_key), // 10 - self.parent_surname.decrypt(&priv_key), // 11 - self.parent_telephone.decrypt(&priv_key), // 12 - self.parent_email.decrypt(&priv_key), // 13 + self.candidate.name.decrypt(&priv_key), // 0 + self.candidate.surname.decrypt(&priv_key), // 1 + self.candidate.birthplace.decrypt(&priv_key), // 2 + self.candidate.birthdate.decrypt(&priv_key), // 3 + self.candidate.address.decrypt(&priv_key), // 4 + self.candidate.telephone.decrypt(&priv_key), // 5 + self.candidate.citizenship.decrypt(&priv_key), // 6 + self.candidate.email.decrypt(&priv_key), // 7 + self.candidate.sex.decrypt(&priv_key), // 8 + self.candidate.personal_id_number.decrypt(&priv_key),// 9 + self.parent.name.decrypt(&priv_key), // 10 + self.parent.surname.decrypt(&priv_key), // 11 + self.parent.telephone.decrypt(&priv_key), // 12 + self.parent.email.decrypt(&priv_key), // 13 )?; Ok(ApplicationDetails { - name: d.0, - surname: d.1, - birthplace: d.2, - birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO - address: d.4, - telephone: d.5, - citizenship: d.6, - email: d.7, - sex: d.8, - personal_id_number: d.9, - study: self.study, + candidate: CandidateDetails { + name: d.0, + surname: d.1, + birthplace: d.2, + birthdate: NaiveDate::parse_from_str(&d.3, NAIVE_DATE_FMT).unwrap(), // TODO + address: d.4, + telephone: d.5, + citizenship: d.6, + email: d.7, + sex: d.8, + personal_id_number: d.9, + study: self.candidate.study, + }, + parent: ParentDetails { + + name: d.10, + surname: d.11, + telephone: d.12, + email: d.13, + } - parent_name: d.10, - parent_surname: d.11, - parent_telephone: d.12, - parent_email: d.13, }) } } @@ -176,22 +193,26 @@ impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails (candidate, parent): (candidate::Model, parent::Model), ) -> Result { Ok(EncryptedApplicationDetails { - name: EncryptedString::try_from(candidate.name)?, - surname: EncryptedString::try_from(candidate.surname)?, - birthplace: EncryptedString::try_from(candidate.birthplace)?, - birthdate: EncryptedString::try_from(candidate.birthdate)?, - address: EncryptedString::try_from(candidate.address)?, - telephone: EncryptedString::try_from(candidate.telephone)?, - citizenship: EncryptedString::try_from(candidate.citizenship)?, - email: EncryptedString::try_from(candidate.email)?, - sex: EncryptedString::try_from(candidate.sex)?, - personal_id_number: EncryptedString::from(candidate.personal_identification_number), - study: candidate.study.ok_or(ServiceError::CandidateDetailsNotSet)?, + candidate: EncryptedCandidateDetails { + name: EncryptedString::try_from(candidate.name)?, + surname: EncryptedString::try_from(candidate.surname)?, + birthplace: EncryptedString::try_from(candidate.birthplace)?, + birthdate: EncryptedString::try_from(candidate.birthdate)?, + address: EncryptedString::try_from(candidate.address)?, + telephone: EncryptedString::try_from(candidate.telephone)?, + citizenship: EncryptedString::try_from(candidate.citizenship)?, + email: EncryptedString::try_from(candidate.email)?, + sex: EncryptedString::try_from(candidate.sex)?, + personal_id_number: EncryptedString::from(candidate.personal_identification_number), + study: candidate.study.ok_or(ServiceError::CandidateDetailsNotSet)?, + }, + parent: EncryptedParentDetails { + name: EncryptedString::try_from(parent.name)?, + surname: EncryptedString::try_from(parent.surname)?, + telephone: EncryptedString::try_from(parent.telephone)?, + email: EncryptedString::try_from(parent.email)?, + } - parent_name: EncryptedString::try_from(parent.name)?, - parent_surname: EncryptedString::try_from(parent.surname)?, - parent_telephone: EncryptedString::try_from(parent.telephone)?, - parent_email: EncryptedString::try_from(parent.email)?, }) } } @@ -203,22 +224,26 @@ impl TryFrom for EncryptedApplicationDetails { cp: CandidateWithParent, ) -> Result { Ok(EncryptedApplicationDetails { - name: EncryptedString::try_from(cp.name)?, - surname: EncryptedString::try_from(cp.surname)?, - birthplace: EncryptedString::try_from(cp.birthplace)?, - birthdate: EncryptedString::try_from(cp.birthdate)?, - address: EncryptedString::try_from(cp.address)?, - telephone: EncryptedString::try_from(cp.telephone)?, - citizenship: EncryptedString::try_from(cp.citizenship)?, - email: EncryptedString::try_from(cp.email)?, - sex: EncryptedString::try_from(cp.sex)?, - personal_id_number: EncryptedString::try_from(cp.personal_identification_number)?, - study: cp.study.ok_or(ServiceError::CandidateDetailsNotSet)?, + candidate: EncryptedCandidateDetails { + name: EncryptedString::try_from(cp.name)?, + surname: EncryptedString::try_from(cp.surname)?, + birthplace: EncryptedString::try_from(cp.birthplace)?, + birthdate: EncryptedString::try_from(cp.birthdate)?, + address: EncryptedString::try_from(cp.address)?, + telephone: EncryptedString::try_from(cp.telephone)?, + citizenship: EncryptedString::try_from(cp.citizenship)?, + email: EncryptedString::try_from(cp.email)?, + sex: EncryptedString::try_from(cp.sex)?, + personal_id_number: EncryptedString::try_from(cp.personal_identification_number)?, + study: cp.study.ok_or(ServiceError::CandidateDetailsNotSet)?, + }, + parent: EncryptedParentDetails { + name: EncryptedString::try_from(cp.parent_name)?, + surname: EncryptedString::try_from(cp.parent_surname)?, + telephone: EncryptedString::try_from(cp.parent_telephone)?, + email: EncryptedString::try_from(cp.parent_email)?, + } - parent_name: EncryptedString::try_from(cp.parent_name)?, - parent_surname: EncryptedString::try_from(cp.parent_surname)?, - parent_telephone: EncryptedString::try_from(cp.parent_telephone)?, - parent_email: EncryptedString::try_from(cp.parent_email)?, }) } } @@ -240,7 +265,7 @@ pub mod tests { use once_cell::sync::Lazy; - use crate::crypto; + use crate::{crypto, models::candidate::{CandidateDetails, ParentDetails}}; use super::{ApplicationDetails, EncryptedApplicationDetails, EncryptedString}; @@ -249,40 +274,44 @@ pub mod tests { pub static APPLICATION_DETAILS: Lazy> = Lazy::new(|| Mutex::new(ApplicationDetails { - name: "name".to_string(), - surname: "surname".to_string(), - birthplace: "birthplace".to_string(), - birthdate: chrono::NaiveDate::from_ymd(2000, 1, 1), - address: "address".to_string(), - telephone: "telephone".to_string(), - citizenship: "citizenship".to_string(), - email: "email".to_string(), - sex: "sex".to_string(), - personal_id_number: "personal_id_number".to_string(), - study: "study".to_string(), - parent_email: "parent_email".to_string(), - parent_name: "parent_name".to_string(), - parent_surname: "parent_surname".to_string(), - parent_telephone: "parent_telephone".to_string() + candidate: CandidateDetails { + name: "name".to_string(), + surname: "surname".to_string(), + birthplace: "birthplace".to_string(), + birthdate: chrono::NaiveDate::from_ymd(2000, 1, 1), + address: "address".to_string(), + telephone: "telephone".to_string(), + citizenship: "citizenship".to_string(), + email: "email".to_string(), + sex: "sex".to_string(), + personal_id_number: "personal_id_number".to_string(), + study: "study".to_string(), + }, + parent: ParentDetails { + email: "parent_email".to_string(), + name: "parent_name".to_string(), + surname: "parent_surname".to_string(), + telephone: "parent_telephone".to_string() + } }) ); pub fn assert_all_application_details(details: &ApplicationDetails) { - assert_eq!(details.name, "name"); - assert_eq!(details.surname, "surname"); - assert_eq!(details.birthplace, "birthplace"); - assert_eq!(details.birthdate, chrono::NaiveDate::from_ymd(2000, 1, 1)); - assert_eq!(details.address, "address"); - assert_eq!(details.telephone, "telephone"); - assert_eq!(details.citizenship, "citizenship"); - assert_eq!(details.email, "email"); - assert_eq!(details.sex, "sex"); - assert_eq!(details.study, "study"); - assert_eq!(details.personal_id_number, "personal_id_number"); - assert_eq!(details.parent_name, "parent_name"); - assert_eq!(details.parent_surname, "parent_surname"); - assert_eq!(details.parent_telephone, "parent_telephone"); - assert_eq!(details.parent_email, "parent_email"); + assert_eq!(details.candidate.name, "name"); + assert_eq!(details.candidate.surname, "surname"); + assert_eq!(details.candidate.birthplace, "birthplace"); + assert_eq!(details.candidate.birthdate, chrono::NaiveDate::from_ymd(2000, 1, 1)); + assert_eq!(details.candidate.address, "address"); + assert_eq!(details.candidate.telephone, "telephone"); + assert_eq!(details.candidate.citizenship, "citizenship"); + assert_eq!(details.candidate.email, "email"); + assert_eq!(details.candidate.sex, "sex"); + assert_eq!(details.candidate.study, "study"); + assert_eq!(details.candidate.personal_id_number, "personal_id_number"); + assert_eq!(details.parent.name, "parent_name"); + assert_eq!(details.parent.surname, "parent_surname"); + assert_eq!(details.parent.telephone, "parent_telephone"); + assert_eq!(details.parent.email, "parent_email"); } #[tokio::test] @@ -295,19 +324,19 @@ pub mod tests { .unwrap(); assert_eq!( - crypto::decrypt_password_with_private_key(&encrypted_details.name.0, PRIVATE_KEY) + crypto::decrypt_password_with_private_key(&encrypted_details.candidate.name.0, PRIVATE_KEY) .await .unwrap(), "name" ); assert_eq!( - crypto::decrypt_password_with_private_key(&encrypted_details.email.0, PRIVATE_KEY) + crypto::decrypt_password_with_private_key(&encrypted_details.candidate.email.0, PRIVATE_KEY) .await .unwrap(), "email" ); assert_eq!( - crypto::decrypt_password_with_private_key(&encrypted_details.sex.0, PRIVATE_KEY) + crypto::decrypt_password_with_private_key(&encrypted_details.candidate.sex.0, PRIVATE_KEY) .await .unwrap(), "sex" diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index d8ad37f..cc81e59 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -14,13 +14,7 @@ impl ApplicationService { plain_text_password: &String, personal_id_number: String, ) -> Result<(candidate::Model, parent::Model), ServiceError> { - Ok( - /* tokio::try_join!( // TODO: try_join! is not working - CandidateService::create(db, application_id, plain_text_password, personal_id_number), - ParentService::create(db, application_id) - )? */ - - + Ok( ( CandidateService::create(db, application_id, plain_text_password, personal_id_number).await?, ParentService::create(db, application_id).await? @@ -47,8 +41,8 @@ impl ApplicationService { Ok( tokio::try_join!( - CandidateService::add_candidate_details(db, candidate, enc_details.clone()), - ParentService::add_parent_details(db, parent, enc_details.clone()) + CandidateService::add_candidate_details(db, candidate, enc_details.candidate), + ParentService::add_parent_details(db, parent, enc_details.parent) )? ) } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index cdd7c3e..19eea64 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -2,7 +2,7 @@ use entity::candidate; use sea_orm::{prelude::Uuid, DbConn}; use crate::{ - models::candidate_details::{EncryptedApplicationDetails, EncryptedString}, + models::candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, crypto::{self, hash_password}, error::ServiceError, Mutation, Query, models::candidate::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients, @@ -150,9 +150,9 @@ impl CandidateService { pub(in crate::services) async fn add_candidate_details( db: &DbConn, candidate: candidate::Model, - enc_details: EncryptedApplicationDetails, + enc_details: EncryptedCandidateDetails, ) -> Result { - let model = Mutation::add_candidate_details(db, candidate, enc_details.clone()).await?; + let model = Mutation::add_candidate_details(db, candidate, enc_details).await?; Ok(model) } diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index 3372201..970c69f 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -1,7 +1,7 @@ use entity::{parent}; use sea_orm::DbConn; -use crate::{error::ServiceError, Mutation, models::candidate_details::EncryptedApplicationDetails}; +use crate::{error::ServiceError, Mutation, models::candidate_details::{EncryptedParentDetails}}; pub struct ParentService; @@ -19,7 +19,7 @@ impl ParentService { pub async fn add_parent_details( db: &DbConn, parent: parent::Model, - enc_details: EncryptedApplicationDetails, + enc_details: EncryptedParentDetails, ) -> Result { let parent = Mutation::add_parent_details(db, parent, enc_details) .await?; diff --git a/core/src/utils/csv.rs b/core/src/utils/csv.rs index 54cc898..e441009 100644 --- a/core/src/utils/csv.rs +++ b/core/src/utils/csv.rs @@ -6,24 +6,26 @@ type Row = CandidateWithParent; impl From<(i32, ApplicationDetails)> for Row { fn from((application, d): (i32, ApplicationDetails)) -> Self { + let c = d.candidate; + let p = d.parent; Self { application, - name: Some(d.name), - surname: Some(d.surname), - birthplace: Some(d.birthplace), - birthdate: Some(d.birthdate.to_string()), - address: Some(d.address), - telephone: Some(d.telephone), - citizenship: Some(d.citizenship), - email: Some(d.email), - sex: Some(d.sex), - study: Some(d.study), - personal_identification_number: Some(d.personal_id_number), + name: Some(c.name), + surname: Some(c.surname), + birthplace: Some(c.birthplace), + birthdate: Some(c.birthdate.to_string()), + address: Some(c.address), + telephone: Some(c.telephone), + citizenship: Some(c.citizenship), + email: Some(c.email), + sex: Some(c.sex), + study: Some(c.study), + personal_identification_number: Some(c.personal_id_number), - parent_name: Some(d.parent_name), - parent_surname: Some(d.parent_surname), - parent_telephone: Some(d.parent_telephone), - parent_email: Some(d.parent_email), + parent_name: Some(p.name), + parent_surname: Some(p.surname), + parent_telephone: Some(p.telephone), + parent_email: Some(p.email), } } } From 1467f0b8c6e2c97396602d46aca2b7a257fd9b6e Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 15:42:59 +0100 Subject: [PATCH 2/7] feat: seperate candidate and parent --- core/src/models/candidate_details.rs | 191 ++++++++++++++++++--------- 1 file changed, 128 insertions(+), 63 deletions(-) diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index a9dc902..c7a6cbb 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -94,32 +94,27 @@ impl TryFrom> for EncryptedString { } } -impl EncryptedApplicationDetails { +impl EncryptedCandidateDetails { pub async fn new( - form: &ApplicationDetails, + form: &CandidateDetails, recipients: Vec, - ) -> Result { - let birthdate_str = form.candidate.birthdate.format(NAIVE_DATE_FMT).to_string(); + ) -> Result { + let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( - EncryptedString::new(&form.candidate.name, &recipients), - EncryptedString::new(&form.candidate.surname, &recipients), - EncryptedString::new(&form.candidate.birthplace, &recipients), + EncryptedString::new(&form.name, &recipients), + EncryptedString::new(&form.surname, &recipients), + EncryptedString::new(&form.birthplace, &recipients), EncryptedString::new(&birthdate_str, &recipients), - EncryptedString::new(&form.candidate.address, &recipients), - EncryptedString::new(&form.candidate.telephone, &recipients), - EncryptedString::new(&form.candidate.citizenship, &recipients), - EncryptedString::new(&form.candidate.email, &recipients), - EncryptedString::new(&form.candidate.sex, &recipients), - EncryptedString::new(&form.candidate.personal_id_number, &recipients), - - EncryptedString::new(&form.parent.name, &recipients), - EncryptedString::new(&form.parent.surname, &recipients), - EncryptedString::new(&form.parent.telephone, &recipients), - EncryptedString::new(&form.parent.email, &recipients), + EncryptedString::new(&form.address, &recipients), + EncryptedString::new(&form.telephone, &recipients), + EncryptedString::new(&form.citizenship, &recipients), + EncryptedString::new(&form.email, &recipients), + EncryptedString::new(&form.sex, &recipients), + EncryptedString::new(&form.personal_id_number, &recipients), )?; - Ok(EncryptedApplicationDetails { - candidate: EncryptedCandidateDetails { + Ok( + EncryptedCandidateDetails { name: d.0, surname: d.1, birthplace: d.2, @@ -130,38 +125,26 @@ impl EncryptedApplicationDetails { email: d.7, sex: d.8, personal_id_number: d.9, - study: form.candidate.study.clone(), - }, - parent: EncryptedParentDetails { - name: d.10, - surname: d.11, - telephone: d.12, - email: d.13, + study: form.study.clone(), } - - }) + ) } - pub async fn decrypt(self, priv_key: String) -> Result { + pub async fn decrypt(self, priv_key: String) -> Result { let d = tokio::try_join!( - self.candidate.name.decrypt(&priv_key), // 0 - self.candidate.surname.decrypt(&priv_key), // 1 - self.candidate.birthplace.decrypt(&priv_key), // 2 - self.candidate.birthdate.decrypt(&priv_key), // 3 - self.candidate.address.decrypt(&priv_key), // 4 - self.candidate.telephone.decrypt(&priv_key), // 5 - self.candidate.citizenship.decrypt(&priv_key), // 6 - self.candidate.email.decrypt(&priv_key), // 7 - self.candidate.sex.decrypt(&priv_key), // 8 - self.candidate.personal_id_number.decrypt(&priv_key),// 9 - self.parent.name.decrypt(&priv_key), // 10 - self.parent.surname.decrypt(&priv_key), // 11 - self.parent.telephone.decrypt(&priv_key), // 12 - self.parent.email.decrypt(&priv_key), // 13 + self.name.decrypt(&priv_key), // 0 + self.surname.decrypt(&priv_key), // 1 + self.birthplace.decrypt(&priv_key), // 2 + self.birthdate.decrypt(&priv_key), // 3 + self.address.decrypt(&priv_key), // 4 + self.telephone.decrypt(&priv_key), // 5 + self.citizenship.decrypt(&priv_key), // 6 + self.email.decrypt(&priv_key), // 7 + self.sex.decrypt(&priv_key), // 8 + self.personal_id_number.decrypt(&priv_key),// 9 )?; - Ok(ApplicationDetails { - candidate: CandidateDetails { + Ok(CandidateDetails { name: d.0, surname: d.1, birthplace: d.2, @@ -172,28 +155,19 @@ impl EncryptedApplicationDetails { email: d.7, sex: d.8, personal_id_number: d.9, - study: self.candidate.study, - }, - parent: ParentDetails { - - name: d.10, - surname: d.11, - telephone: d.12, - email: d.13, + study: self.study, } - - }) + ) } } - -impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails { +impl TryFrom for EncryptedCandidateDetails { type Error = ServiceError; fn try_from( - (candidate, parent): (candidate::Model, parent::Model), + candidate: candidate::Model, ) -> Result { - Ok(EncryptedApplicationDetails { - candidate: EncryptedCandidateDetails { + Ok( + EncryptedCandidateDetails { name: EncryptedString::try_from(candidate.name)?, surname: EncryptedString::try_from(candidate.surname)?, birthplace: EncryptedString::try_from(candidate.birthplace)?, @@ -205,14 +179,105 @@ impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails sex: EncryptedString::try_from(candidate.sex)?, personal_id_number: EncryptedString::from(candidate.personal_identification_number), study: candidate.study.ok_or(ServiceError::CandidateDetailsNotSet)?, - }, - parent: EncryptedParentDetails { + } + ) + } +} + +impl EncryptedParentDetails { + pub async fn new( + form: &ParentDetails, + recipients: Vec, + ) -> Result { + let d = tokio::try_join!( + EncryptedString::new(&form.name, &recipients), + EncryptedString::new(&form.surname, &recipients), + EncryptedString::new(&form.telephone, &recipients), + EncryptedString::new(&form.email, &recipients), + )?; + + Ok( + EncryptedParentDetails { + name: d.0, + surname: d.1, + telephone: d.2, + email: d.3, + } + ) + } + + pub async fn decrypt(self, priv_key: String) -> Result { + let d = tokio::try_join!( + self.name.decrypt(&priv_key), + self.surname.decrypt(&priv_key), + self.telephone.decrypt(&priv_key), + self.email.decrypt(&priv_key), + )?; + + Ok(ParentDetails { + name: d.0, + surname: d.1, + telephone: d.2, + email: d.3, + } + ) + } +} +impl TryFrom for EncryptedParentDetails { + type Error = ServiceError; + + fn try_from( + parent: parent::Model, + ) -> Result { + Ok(EncryptedParentDetails { name: EncryptedString::try_from(parent.name)?, surname: EncryptedString::try_from(parent.surname)?, telephone: EncryptedString::try_from(parent.telephone)?, email: EncryptedString::try_from(parent.email)?, } + ) + } +} +impl EncryptedApplicationDetails { + pub async fn new( + form: &ApplicationDetails, + recipients: Vec, + ) -> Result { + let (candidate, parent) = tokio::try_join!( + EncryptedCandidateDetails::new(&form.candidate, recipients.clone()), + EncryptedParentDetails::new(&form.parent, recipients), + )?; + Ok( + EncryptedApplicationDetails { + candidate, + parent, + } + ) + } + + pub async fn decrypt(self, priv_key: String) -> Result { + let (candidate, parent) = tokio::try_join!( + self.candidate.decrypt(priv_key.clone()), + self.parent.decrypt(priv_key), + )?; + Ok(ApplicationDetails { + candidate, + parent, + }) + } +} + +// TODO: use different metehod for this +impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails { + type Error = ServiceError; + + fn try_from( + (candidate, parent): (candidate::Model, parent::Model), + ) -> Result { + Ok(EncryptedApplicationDetails { + candidate: EncryptedCandidateDetails::try_from(candidate)?, + parent: EncryptedParentDetails::try_from(parent)?, }) } } From 617cddd6980c3bb1fe0f9479b10e8fe7aff9b1fb Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 15:57:50 +0100 Subject: [PATCH 3/7] feat: parents as vector --- api/src/routes/candidate.rs | 14 +++++---- core/src/database/mutation/parent.rs | 2 +- core/src/models/candidate.rs | 26 +-------------- core/src/models/candidate_details.rs | 40 ++++++++++++------------ core/src/services/application_service.rs | 2 +- core/src/utils/csv.rs | 2 +- 6 files changed, 32 insertions(+), 54 deletions(-) diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index aefc98b..f7408a0 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -253,12 +253,14 @@ mod tests { \"personalIdNumber\": \"0000000000\", \"study\": \"KB\" }, - \"parent\": { - \"name\": \"maminka\", - \"surname\": \"chad\", - \"telephone\": \"420111222333\", - \"email\": \"maminka@centrum.cz\" - } + \"parents\": [ + { + \"name\": \"maminka\", + \"surname\": \"chad\", + \"telephone\": \"420111222333\", + \"email\": \"maminka@centrum.cz\" + } + ] }"; #[test] diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs index 37f4631..7d96ddb 100644 --- a/core/src/database/mutation/parent.rs +++ b/core/src/database/mutation/parent.rs @@ -88,7 +88,7 @@ mod tests { .await .unwrap(); - Mutation::add_parent_details(&db, parent, encrypted_details.parent) + Mutation::add_parent_details(&db, parent, encrypted_details.parents[0].clone()) .await .unwrap(); diff --git a/core/src/models/candidate.rs b/core/src/models/candidate.rs index ffa8b6a..14c4082 100644 --- a/core/src/models/candidate.rs +++ b/core/src/models/candidate.rs @@ -59,32 +59,8 @@ pub struct ParentDetails { pub struct ApplicationDetails { // Candidate pub candidate: CandidateDetails, - pub parent: ParentDetails, - // pub opt_parent2: Option, + pub parents: Vec, } -/* impl ApplicationDetails { - pub fn new_one_parent( - candidate: CandidateDetails, - parent: ParentDetails, - ) -> Self { - Self { - candidate, - parent, - opt_parent2: None, - } - } - pub fn new_two_parents( - candidate: CandidateDetails, - parent: ParentDetails, - opt_parent2: ParentDetails, - ) -> Self { - Self { - candidate, - parent, - opt_parent2: Some(opt_parent2), - } - } -} */ /// CSV export (admin endpoint) #[derive(FromQueryResult, Serialize, Default)] diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index c7a6cbb..c5093b6 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -36,7 +36,7 @@ pub struct EncryptedParentDetails { #[derive(Clone)] pub struct EncryptedApplicationDetails { pub candidate: EncryptedCandidateDetails, - pub parent: EncryptedParentDetails, + pub parents: Vec, } impl EncryptedString { @@ -244,26 +244,26 @@ impl EncryptedApplicationDetails { form: &ApplicationDetails, recipients: Vec, ) -> Result { - let (candidate, parent) = tokio::try_join!( - EncryptedCandidateDetails::new(&form.candidate, recipients.clone()), - EncryptedParentDetails::new(&form.parent, recipients), - )?; + let candidate = EncryptedCandidateDetails::new(&form.candidate, recipients.clone()).await?; + let parent = EncryptedParentDetails::new(&form.parents[0], recipients.clone()).await?; // TODO async Ok( EncryptedApplicationDetails { candidate, - parent, + parents: vec![parent], } ) } pub async fn decrypt(self, priv_key: String) -> Result { - let (candidate, parent) = tokio::try_join!( - self.candidate.decrypt(priv_key.clone()), - self.parent.decrypt(priv_key), - )?; + /* let (candidate, parent) = tokio::try_join!( + &self.candidate.decrypt(priv_key.clone()), + self.parents[0].decrypt(priv_key), + )?; */ + let candidate = self.candidate.decrypt(priv_key.clone()).await?; + let parent = self.parents[0].clone().decrypt(priv_key).await?; Ok(ApplicationDetails { candidate, - parent, + parents: vec![parent], }) } } @@ -277,7 +277,7 @@ impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails ) -> Result { Ok(EncryptedApplicationDetails { candidate: EncryptedCandidateDetails::try_from(candidate)?, - parent: EncryptedParentDetails::try_from(parent)?, + parents: vec![EncryptedParentDetails::try_from(parent)?], }) } } @@ -302,12 +302,12 @@ impl TryFrom for EncryptedApplicationDetails { personal_id_number: EncryptedString::try_from(cp.personal_identification_number)?, study: cp.study.ok_or(ServiceError::CandidateDetailsNotSet)?, }, - parent: EncryptedParentDetails { + parents: vec![EncryptedParentDetails { name: EncryptedString::try_from(cp.parent_name)?, surname: EncryptedString::try_from(cp.parent_surname)?, telephone: EncryptedString::try_from(cp.parent_telephone)?, email: EncryptedString::try_from(cp.parent_email)?, - } + }] }) } @@ -352,12 +352,12 @@ pub mod tests { personal_id_number: "personal_id_number".to_string(), study: "study".to_string(), }, - parent: ParentDetails { + parents: vec![ParentDetails { email: "parent_email".to_string(), name: "parent_name".to_string(), surname: "parent_surname".to_string(), telephone: "parent_telephone".to_string() - } + }] }) ); @@ -373,10 +373,10 @@ pub mod tests { assert_eq!(details.candidate.sex, "sex"); assert_eq!(details.candidate.study, "study"); assert_eq!(details.candidate.personal_id_number, "personal_id_number"); - assert_eq!(details.parent.name, "parent_name"); - assert_eq!(details.parent.surname, "parent_surname"); - assert_eq!(details.parent.telephone, "parent_telephone"); - assert_eq!(details.parent.email, "parent_email"); + assert_eq!(details.parents[0].name, "parent_name"); + assert_eq!(details.parents[0].surname, "parent_surname"); + assert_eq!(details.parents[0].telephone, "parent_telephone"); + assert_eq!(details.parents[0].email, "parent_email"); } #[tokio::test] diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index cc81e59..29db72e 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -42,7 +42,7 @@ impl ApplicationService { Ok( tokio::try_join!( CandidateService::add_candidate_details(db, candidate, enc_details.candidate), - ParentService::add_parent_details(db, parent, enc_details.parent) + ParentService::add_parent_details(db, parent, enc_details.parents[0].clone()) )? ) } diff --git a/core/src/utils/csv.rs b/core/src/utils/csv.rs index e441009..c741628 100644 --- a/core/src/utils/csv.rs +++ b/core/src/utils/csv.rs @@ -7,7 +7,7 @@ type Row = CandidateWithParent; impl From<(i32, ApplicationDetails)> for Row { fn from((application, d): (i32, ApplicationDetails)) -> Self { let c = d.candidate; - let p = d.parent; + let p = d.parents[0].clone(); Self { application, name: Some(c.name), From a453117e8950461f18ce1f131c269e9b3f9ea0af Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 16:07:11 +0100 Subject: [PATCH 4/7] feat: decrypt all parents details --- core/src/models/candidate_details.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index c5093b6..f611e89 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -206,7 +206,7 @@ impl EncryptedParentDetails { ) } - pub async fn decrypt(self, priv_key: String) -> Result { + pub async fn decrypt(&self, priv_key: String) -> Result { let d = tokio::try_join!( self.name.decrypt(&priv_key), self.surname.decrypt(&priv_key), @@ -245,25 +245,30 @@ impl EncryptedApplicationDetails { recipients: Vec, ) -> Result { let candidate = EncryptedCandidateDetails::new(&form.candidate, recipients.clone()).await?; - let parent = EncryptedParentDetails::new(&form.parents[0], recipients.clone()).await?; // TODO async + let mut enc_parents= vec![]; + for parent in form.parents.iter() { + enc_parents.push( + EncryptedParentDetails::new(parent, recipients.clone()).await? + ); + } Ok( EncryptedApplicationDetails { candidate, - parents: vec![parent], + parents: enc_parents, } ) } pub async fn decrypt(self, priv_key: String) -> Result { - /* let (candidate, parent) = tokio::try_join!( - &self.candidate.decrypt(priv_key.clone()), - self.parents[0].decrypt(priv_key), - )?; */ let candidate = self.candidate.decrypt(priv_key.clone()).await?; - let parent = self.parents[0].clone().decrypt(priv_key).await?; + let mut parents = vec![]; + for parent in self.parents.iter() { + let dec = parent.decrypt(priv_key.clone()).await?; + parents.push(dec); + } Ok(ApplicationDetails { candidate, - parents: vec![parent], + parents: parents, }) } } From fa9af8f8ee0d4f25ad1bc61f31f58f10a37b5b8e Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 16:20:52 +0100 Subject: [PATCH 5/7] feat: multiple related parents in database --- core/src/database/query/parent.rs | 15 +++++++++++++++ core/src/services/application_service.rs | 12 +++++------- core/src/services/candidate_service.rs | 5 ++--- migration/src/m20221024_124701_create_parent.rs | 12 +++++++++--- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs index 787b85c..7b74127 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -1,12 +1,16 @@ +use entity::candidate; +use entity::parent; use entity::parent::Model; use entity::parent::Entity; +use sea_orm::ModelTrait; use sea_orm::{DbConn, DbErr}; use sea_orm::EntityTrait; use crate::Query; impl Query { + #[deprecated(note = "Use find_candidate_parents instead")] pub async fn find_parent_by_id( db: &DbConn, application_id: i32, @@ -14,6 +18,17 @@ impl Query { Entity::find_by_id(application_id).one(db).await } + + // TODO limit to two parents?? + pub async fn find_candidate_parents( + db: &DbConn, + candidate: candidate::Model, + ) -> Result, DbErr> { + + candidate.find_related(parent::Entity) + .all(db) + .await + } } #[cfg(test)] diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 29db72e..9f18b4f 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -31,9 +31,8 @@ impl ApplicationService { .await? .ok_or(ServiceError::CandidateNotFound)?; - let parent = Query::find_parent_by_id(db, application) - .await? - .ok_or(ServiceError::ParentNotFound)?; + let parent = Query::find_candidate_parents(db, candidate.clone()) + .await?; let recipients = get_recipients(db, &candidate.public_key).await?; @@ -42,7 +41,7 @@ impl ApplicationService { Ok( tokio::try_join!( CandidateService::add_candidate_details(db, candidate, enc_details.candidate), - ParentService::add_parent_details(db, parent, enc_details.parents[0].clone()) + ParentService::add_parent_details(db, parent[0].clone(), enc_details.parents[0].clone()) // TODO )? ) } @@ -54,9 +53,8 @@ impl ApplicationService { ) -> Result { let candidate = Query::find_candidate_by_id(db, application_id).await? .ok_or(ServiceError::CandidateNotFound)?; - let parent = Query::find_parent_by_id(db, application_id).await? - .ok_or(ServiceError::ParentNotFound)?; - let enc_details = EncryptedApplicationDetails::try_from((candidate, parent))?; + let parent = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO + let enc_details = EncryptedApplicationDetails::try_from((candidate, parent[0].clone()))?; enc_details.decrypt(private_key).await } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index 19eea64..e4862b3 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -102,8 +102,7 @@ impl CandidateService { ) -> Result { let candidate = Query::find_candidate_by_id(db, id).await? .ok_or(ServiceError::CandidateNotFound)?; - let parent = Query::find_parent_by_id(db, id).await? - .ok_or(ServiceError::CandidateNotFound)?; + let parent = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO let new_password_plain = crypto::random_8_char_string(); @@ -125,7 +124,7 @@ impl CandidateService { .await?; let enc_details_opt = EncryptedApplicationDetails::try_from( - (candidate, parent) + (candidate, parent[0].clone()) ); if let Ok(enc_details) = enc_details_opt { diff --git a/migration/src/m20221024_124701_create_parent.rs b/migration/src/m20221024_124701_create_parent.rs index 99a0b14..00d18ae 100644 --- a/migration/src/m20221024_124701_create_parent.rs +++ b/migration/src/m20221024_124701_create_parent.rs @@ -11,12 +11,17 @@ impl MigrationTrait for Migration { Table::create() .table(Parent::Table) .if_not_exists() + .col( + ColumnDef::new(Parent::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) .col( ColumnDef::new(Parent::Application) .integer() .not_null() - .primary_key() - .unique_key(), ) .col(ColumnDef::new(Parent::Name).string()) .col(ColumnDef::new(Parent::Surname).string()) @@ -26,7 +31,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Parent::UpdatedAt).date_time().not_null()) .to_owned(), ) - .await + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { @@ -38,6 +43,7 @@ impl MigrationTrait for Migration { #[derive(Iden)] pub enum Parent { + Id, Table, Application, Name, From 99d964cae49614a02f850c4c91f275a30ea3a9e4 Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Mon, 5 Dec 2022 22:22:11 +0100 Subject: [PATCH 6/7] feat: two parents --- api/src/routes/admin.rs | 9 +++-- api/src/routes/candidate.rs | 4 +-- core/src/database/mutation/parent.rs | 22 ++++++------ core/src/database/query/parent.rs | 4 +-- core/src/error.rs | 3 ++ core/src/models/candidate_details.rs | 24 ++++++++----- core/src/services/application_service.rs | 31 ++++++----------- core/src/services/candidate_service.rs | 35 +++++++++++-------- core/src/services/parent_service.rs | 43 ++++++++++++++++++++++-- entity/src/parent.rs | 3 +- 10 files changed, 113 insertions(+), 65 deletions(-) diff --git a/api/src/routes/admin.rs b/api/src/routes/admin.rs index 2d7d6f8..b6008dc 100644 --- a/api/src/routes/admin.rs +++ b/api/src/routes/admin.rs @@ -2,7 +2,7 @@ use std::net::{SocketAddr, IpAddr, Ipv4Addr}; use portfolio_core::{ crypto::random_8_char_string, - services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::candidate::{BaseCandidateResponse, CreateCandidateResponse, ApplicationDetails}, sea_orm::prelude::Uuid, + services::{admin_service::AdminService, candidate_service::CandidateService, application_service::ApplicationService, portfolio_service::PortfolioService}, models::candidate::{BaseCandidateResponse, CreateCandidateResponse, ApplicationDetails}, sea_orm::prelude::Uuid, Query, error::ServiceError, }; use requests::{AdminLoginRequest, RegisterRequest}; use rocket::http::{Cookie, Status, CookieJar}; @@ -146,10 +146,15 @@ pub async fn get_candidate( let db = conn.into_inner(); let private_key = session.get_private_key(); + let candidate = Query::find_candidate_by_id(db, id) + .await + .map_err(|e| to_custom_error(ServiceError::Forbidden))? // TODO better error handling + .ok_or(to_custom_error(ServiceError::CandidateNotFound))?; + let details = ApplicationService::decrypt_all_details( private_key, db, - id + candidate ) .await .map_err(to_custom_error)?; diff --git a/api/src/routes/candidate.rs b/api/src/routes/candidate.rs index f7408a0..2bb59d0 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -78,7 +78,7 @@ pub async fn post_details( let form = details.into_inner(); let candidate: entity::candidate::Model = session.into(); - let _candidate_parent = ApplicationService::add_all_details(db, candidate.application, &form) + let _candidate_parent = ApplicationService::add_all_details(db, candidate, &form) .await .map_err(to_custom_error)?; @@ -97,7 +97,7 @@ pub async fn get_details( let candidate: entity::candidate::Model = session.into(); // let handle = tokio::spawn(async move { - let details = ApplicationService::decrypt_all_details(private_key, db, candidate.application) + let details = ApplicationService::decrypt_all_details(private_key, db, candidate) .await .map(|x| Json(x)) .map_err(to_custom_error); diff --git a/core/src/database/mutation/parent.rs b/core/src/database/mutation/parent.rs index 7d96ddb..049b96b 100644 --- a/core/src/database/mutation/parent.rs +++ b/core/src/database/mutation/parent.rs @@ -20,15 +20,15 @@ impl Mutation { parent: Model, enc_parent: EncryptedParentDetails, ) -> Result { - let mut user: parent::ActiveModel = parent.into(); - user.name = Set(Some(enc_parent.name.into())); - user.surname = Set(Some(enc_parent.surname.into())); - user.telephone = Set(Some(enc_parent.telephone.into())); - user.email = Set(Some(enc_parent.email.into())); + let mut parent: parent::ActiveModel = parent.into(); + parent.name = Set(Some(enc_parent.name.into())); + parent.surname = Set(Some(enc_parent.surname.into())); + parent.telephone = Set(Some(enc_parent.telephone.into())); + parent.email = Set(Some(enc_parent.email.into())); - user.updated_at = Set(chrono::offset::Local::now().naive_local()); + parent.updated_at = Set(chrono::offset::Local::now().naive_local()); - user.update(db).await + parent.update(db).await } } @@ -56,9 +56,9 @@ mod tests { .await .unwrap(); - Mutation::create_parent(&db, APPLICATION_ID).await.unwrap(); + let new_parent = Mutation::create_parent(&db, APPLICATION_ID).await.unwrap(); - let parent = Query::find_parent_by_id(&db, APPLICATION_ID).await.unwrap(); + let parent = Query::find_parent_by_id(&db, new_parent.id).await.unwrap(); assert!(parent.is_some()); } @@ -88,11 +88,11 @@ mod tests { .await .unwrap(); - Mutation::add_parent_details(&db, parent, encrypted_details.parents[0].clone()) + Mutation::add_parent_details(&db, parent.clone(), encrypted_details.parents[0].clone()) .await .unwrap(); - let parent = Query::find_parent_by_id(&db, APPLICATION_ID) + let parent = Query::find_parent_by_id(&db, parent.id) .await .unwrap() .unwrap(); diff --git a/core/src/database/query/parent.rs b/core/src/database/query/parent.rs index 7b74127..01368f5 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -13,10 +13,10 @@ impl Query { #[deprecated(note = "Use find_candidate_parents instead")] pub async fn find_parent_by_id( db: &DbConn, - application_id: i32, + id: i32, ) -> Result, DbErr> { - Entity::find_by_id(application_id).one(db).await + Entity::find_by_id(id).one(db).await } // TODO limit to two parents?? diff --git a/core/src/error.rs b/core/src/error.rs index be230b0..5949ec9 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -23,6 +23,8 @@ pub enum ServiceError { #[error("Parrent not found")] ParentNotFound, #[error("Database error")] + ParentOverflow, + #[error("Too many parents")] DbError(#[from] sea_orm::DbErr), #[error("User not found, please contact technical support")] UserNotFoundByJwtId, @@ -78,6 +80,7 @@ impl ServiceError { ServiceError::UserAlreadyExists => 409, ServiceError::CandidateNotFound => 404, ServiceError::ParentNotFound => 500, + ServiceError::ParentOverflow => 400, // TODO: correct error code ServiceError::DbError(_) => 500, ServiceError::UserNotFoundByJwtId => 500, ServiceError::UserNotFoundBySessionId => 500, diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index f611e89..3efcbaa 100644 --- a/core/src/models/candidate_details.rs +++ b/core/src/models/candidate_details.rs @@ -8,10 +8,10 @@ use super::candidate::{CandidateDetails, ParentDetails}; pub const NAIVE_DATE_FMT: &str = "%Y-%m-%d"; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EncryptedString(String); -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EncryptedCandidateDetails { pub name: EncryptedString, pub surname: EncryptedString, @@ -26,14 +26,14 @@ pub struct EncryptedCandidateDetails { pub study: String, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EncryptedParentDetails { pub name: EncryptedString, pub surname: EncryptedString, pub telephone: EncryptedString, pub email: EncryptedString, } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct EncryptedApplicationDetails { pub candidate: EncryptedCandidateDetails, pub parents: Vec, @@ -274,15 +274,21 @@ impl EncryptedApplicationDetails { } // TODO: use different metehod for this -impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails { +impl TryFrom<(candidate::Model, Vec)> for EncryptedApplicationDetails { type Error = ServiceError; fn try_from( - (candidate, parent): (candidate::Model, parent::Model), + (candidate, parents): (candidate::Model, Vec), ) -> Result { + let mut enc_parents = vec![]; + for parent in parents.iter() { + enc_parents.push( + EncryptedParentDetails::try_from(parent.clone())? + ); + } Ok(EncryptedApplicationDetails { candidate: EncryptedCandidateDetails::try_from(candidate)?, - parents: vec![EncryptedParentDetails::try_from(parent)?], + parents: enc_parents, }) } } @@ -358,10 +364,10 @@ pub mod tests { study: "study".to_string(), }, parents: vec![ParentDetails { - email: "parent_email".to_string(), name: "parent_name".to_string(), surname: "parent_surname".to_string(), - telephone: "parent_telephone".to_string() + telephone: "parent_telephone".to_string(), + email: "parent_email".to_string(), }] }) ); diff --git a/core/src/services/application_service.rs b/core/src/services/application_service.rs index 9f18b4f..88a737a 100644 --- a/core/src/services/application_service.rs +++ b/core/src/services/application_service.rs @@ -24,37 +24,26 @@ impl ApplicationService { pub async fn add_all_details( db: &DbConn, - application: i32, + candidate: candidate::Model, form: &ApplicationDetails, - ) -> Result<(candidate::Model, parent::Model), ServiceError> { - let candidate = Query::find_candidate_by_id(db, application) - .await? - .ok_or(ServiceError::CandidateNotFound)?; - - let parent = Query::find_candidate_parents(db, candidate.clone()) - .await?; - - let recipients = get_recipients(db, &candidate.public_key).await?; - - let enc_details = EncryptedApplicationDetails::new(form, recipients).await?; + ) -> Result<(candidate::Model, Vec), ServiceError> { // TODO: is this service needed? Ok( - tokio::try_join!( - CandidateService::add_candidate_details(db, candidate, enc_details.candidate), - ParentService::add_parent_details(db, parent[0].clone(), enc_details.parents[0].clone()) // TODO - )? + ( + CandidateService::add_candidate_details(db, candidate.clone(), &form.candidate).await?, + ParentService::add_parents_details(db, candidate, &form.parents).await? + ) ) } pub async fn decrypt_all_details( private_key: String, db: &DbConn, - application_id: i32, + candidate: candidate::Model, + // parents: Vec, ) -> Result { - let candidate = Query::find_candidate_by_id(db, application_id).await? - .ok_or(ServiceError::CandidateNotFound)?; - let parent = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO - let enc_details = EncryptedApplicationDetails::try_from((candidate, parent[0].clone()))?; + let parents = Query::find_candidate_parents(db, candidate.clone()).await?; + let enc_details = EncryptedApplicationDetails::try_from((candidate, parents))?; enc_details.decrypt(private_key).await } diff --git a/core/src/services/candidate_service.rs b/core/src/services/candidate_service.rs index e4862b3..b15fea8 100644 --- a/core/src/services/candidate_service.rs +++ b/core/src/services/candidate_service.rs @@ -2,7 +2,7 @@ use entity::candidate; use sea_orm::{prelude::Uuid, DbConn}; use crate::{ - models::candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, + models::{candidate_details::{EncryptedApplicationDetails, EncryptedString, EncryptedCandidateDetails}, candidate::CandidateDetails}, crypto::{self, hash_password}, error::ServiceError, Mutation, Query, models::candidate::{BaseCandidateResponse, CreateCandidateResponse}, utils::db::get_recipients, @@ -102,7 +102,7 @@ impl CandidateService { ) -> Result { let candidate = Query::find_candidate_by_id(db, id).await? .ok_or(ServiceError::CandidateNotFound)?; - let parent = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO + let parents = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO let new_password_plain = crypto::random_8_char_string(); @@ -124,12 +124,12 @@ impl CandidateService { .await?; let enc_details_opt = EncryptedApplicationDetails::try_from( - (candidate, parent[0].clone()) + (candidate.clone(), parents) ); if let Ok(enc_details) = enc_details_opt { let application_details = enc_details.decrypt(admin_private_key).await?; - ApplicationService::add_all_details(db, id, &application_details).await?; + ApplicationService::add_all_details(db, candidate, &application_details).await?; } Ok( @@ -149,8 +149,10 @@ impl CandidateService { pub(in crate::services) async fn add_candidate_details( db: &DbConn, candidate: candidate::Model, - enc_details: EncryptedCandidateDetails, + details: &CandidateDetails, ) -> Result { + let recipients = get_recipients(db, &candidate.public_key).await?; + let enc_details = EncryptedCandidateDetails::new(&details, recipients).await?; let model = Mutation::add_candidate_details(db, candidate, enc_details).await?; Ok(model) } @@ -372,8 +374,8 @@ pub mod tests { } #[cfg(test)] - pub async fn put_user_data(db: &DbConn) -> (candidate::Model, parent::Model) { - use crate::models::candidate_details::tests::APPLICATION_DETAILS; + pub async fn put_user_data(db: &DbConn) -> (candidate::Model, Vec) { + use crate::{models::candidate_details::tests::APPLICATION_DETAILS, Query}; let plain_text_password = "test".to_string(); let (candidate, _parent) = ApplicationService::create_candidate_with_parent( @@ -382,23 +384,28 @@ pub mod tests { &plain_text_password, "".to_string(), ) - .await - .ok() - .unwrap(); + .await + .ok() + .unwrap(); let form = APPLICATION_DETAILS.lock().unwrap().clone(); - ApplicationService::add_all_details(&db, candidate.application, &form) + let (candidate, parents) = ApplicationService::add_all_details(&db, candidate.clone(), &form) .await - .unwrap() + .unwrap(); + + ( + candidate, + parents, + ) } #[tokio::test] async fn test_put_user_data() { let db = get_memory_sqlite_connection().await; - let (candidate, parent) = put_user_data(&db).await; + let (candidate, parents) = put_user_data(&db).await; assert!(candidate.name.is_some()); - assert!(parent.name.is_some()); + assert!(parents[0].name.is_some()); } #[tokio::test] diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index 970c69f..c08ce2d 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -1,7 +1,7 @@ -use entity::{parent}; +use entity::{parent, candidate}; use sea_orm::DbConn; -use crate::{error::ServiceError, Mutation, models::candidate_details::{EncryptedParentDetails}}; +use crate::{error::ServiceError, Mutation, models::{candidate_details::{EncryptedParentDetails}, candidate::ParentDetails}, Query, utils::db::get_recipients}; pub struct ParentService; @@ -16,7 +16,7 @@ impl ParentService { Ok(parent) } - pub async fn add_parent_details( + /* pub async fn add_parent_details( db: &DbConn, parent: parent::Model, enc_details: EncryptedParentDetails, @@ -25,5 +25,42 @@ impl ParentService { .await?; Ok(parent) + } */ + pub async fn add_parents_details( + db: &DbConn, + ref_candidate: candidate::Model, + parents_details: &Vec, + ) -> Result, ServiceError> { + let found_parents = Query::find_candidate_parents(db, ref_candidate.clone()).await?; + if found_parents.len() > 2 { + return Err(ServiceError::ParentOverflow); + } + + let mut result = vec![]; + for i in 0..parents_details.len() { + let found_parent = match found_parents.get(i) { + Some(parent) => parent.clone(), + None => ParentService::create(db, ref_candidate.application).await?, + }; + let recipients = get_recipients(db, &ref_candidate.public_key).await?; + let enc_details = EncryptedParentDetails::new(&parents_details[i], recipients).await?; + let parent = Mutation::add_parent_details(db, found_parent.clone(), enc_details.clone()).await?; + result.push(parent); + } + + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use crate::{utils::db::get_memory_sqlite_connection, models::{candidate::{ParentDetails, ApplicationDetails}, candidate_details::{tests::APPLICATION_DETAILS, EncryptedParentDetails, EncryptedApplicationDetails}}, services::{candidate_service::CandidateService, application_service::ApplicationService}}; + + #[tokio::test] + async fn create_parent_test() { + let db = get_memory_sqlite_connection().await; + CandidateService::create(&db, 103100, &"test".to_string(), "".to_string()).await.unwrap(); + super::ParentService::create(&db, 103100).await.unwrap(); + super::ParentService::create(&db, 103100).await.unwrap(); } } \ No newline at end of file diff --git a/entity/src/parent.rs b/entity/src/parent.rs index f748482..1ac42c0 100644 --- a/entity/src/parent.rs +++ b/entity/src/parent.rs @@ -5,7 +5,8 @@ use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] #[sea_orm(table_name = "parent")] pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] + #[sea_orm(primary_key)] + pub id: i32, pub application: i32, pub name: Option, pub surname: Option, From b227155962391914afecca397b307b0d33b1e0bc Mon Sep 17 00:00:00 2001 From: Sebastian Pravda Date: Tue, 6 Dec 2022 00:09:31 +0100 Subject: [PATCH 7/7] feat: multiple parents test --- core/src/services/parent_service.rs | 91 ++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/core/src/services/parent_service.rs b/core/src/services/parent_service.rs index c08ce2d..f25664d 100644 --- a/core/src/services/parent_service.rs +++ b/core/src/services/parent_service.rs @@ -54,7 +54,43 @@ impl ParentService { #[cfg(test)] mod tests { - use crate::{utils::db::get_memory_sqlite_connection, models::{candidate::{ParentDetails, ApplicationDetails}, candidate_details::{tests::APPLICATION_DETAILS, EncryptedParentDetails, EncryptedApplicationDetails}}, services::{candidate_service::CandidateService, application_service::ApplicationService}}; + use std::sync::Mutex; + + use once_cell::sync::Lazy; + + use crate::{utils::db::get_memory_sqlite_connection, models::{candidate::{ParentDetails, ApplicationDetails, CandidateDetails}, candidate_details::{tests::APPLICATION_DETAILS, EncryptedParentDetails, EncryptedApplicationDetails}}, services::{candidate_service::CandidateService, application_service::ApplicationService}, crypto}; + + use super::ParentService; + + pub static APPLICATION_DETAILS_TWO_PARENTS: Lazy> = Lazy::new(|| + Mutex::new(ApplicationDetails { + candidate: CandidateDetails { + name: "name".to_string(), + surname: "surname".to_string(), + birthplace: "birthplace".to_string(), + birthdate: chrono::NaiveDate::from_ymd(2000, 1, 1), + address: "address".to_string(), + telephone: "telephone".to_string(), + citizenship: "citizenship".to_string(), + email: "email".to_string(), + sex: "sex".to_string(), + personal_id_number: "personal_id_number".to_string(), + study: "study".to_string(), + }, + parents: vec![ParentDetails { + name: "parent_name".to_string(), + surname: "parent_surname".to_string(), + telephone: "parent_telephone".to_string(), + email: "parent_email".to_string(), + }, + ParentDetails { + name: "parent_name2".to_string(), + surname: "parent_surname2".to_string(), + telephone: "parent_telephone2".to_string(), + email: "parent_email2".to_string(), + }], + }) + ); #[tokio::test] async fn create_parent_test() { @@ -63,4 +99,57 @@ mod tests { super::ParentService::create(&db, 103100).await.unwrap(); super::ParentService::create(&db, 103100).await.unwrap(); } + + #[tokio::test] + async fn add_parent_details_test() { + let db = get_memory_sqlite_connection().await; + let plain_text_password = "test".to_string(); + let (candidate, _parent) = ApplicationService::create_candidate_with_parent( + &db, + 103101, + &plain_text_password, + "".to_string(), + ) + .await + .ok() + .unwrap(); + + ParentService::create(&db, 103101).await.unwrap(); + + let form = APPLICATION_DETAILS_TWO_PARENTS.lock().unwrap().clone(); + + let (candidate, parents) = ApplicationService::add_all_details(&db, candidate.clone(), &form) + .await + .unwrap(); + + let priv_key = crypto::decrypt_password(candidate.private_key.clone(), plain_text_password).await.unwrap(); + let dec_details = EncryptedApplicationDetails::try_from((candidate, parents)) + .unwrap() + .decrypt(priv_key) + .await + .unwrap(); + + assert_eq!(dec_details.candidate.name, form.candidate.name); + assert_eq!(dec_details.candidate.surname, form.candidate.surname); + assert_eq!(dec_details.candidate.birthplace, form.candidate.birthplace); + assert_eq!(dec_details.candidate.birthdate, form.candidate.birthdate); + assert_eq!(dec_details.candidate.address, form.candidate.address); + assert_eq!(dec_details.candidate.telephone, form.candidate.telephone); + assert_eq!(dec_details.candidate.citizenship, form.candidate.citizenship); + assert_eq!(dec_details.candidate.email, form.candidate.email); + assert_eq!(dec_details.candidate.sex, form.candidate.sex); + assert_eq!(dec_details.candidate.personal_id_number, form.candidate.personal_id_number); + assert_eq!(dec_details.candidate.study, form.candidate.study); + + assert_eq!(dec_details.parents.len(), form.parents.len()); + for i in 0..dec_details.parents.len() { + assert_eq!(dec_details.parents[i].name, form.parents[i].name); + assert_eq!(dec_details.parents[i].surname, form.parents[i].surname); + assert_eq!(dec_details.parents[i].telephone, form.parents[i].telephone); + assert_eq!(dec_details.parents[i].email, form.parents[i].email); + } + + + + } } \ No newline at end of file