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 dafe0b9..75a40ff 100644 --- a/api/src/routes/candidate.rs +++ b/api/src/routes/candidate.rs @@ -86,7 +86,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)?; @@ -103,7 +103,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); @@ -284,21 +284,27 @@ 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\" + }, + \"parents\": [ + { + \"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..049b96b 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,17 +18,17 @@ 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())); + 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) + 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 787b85c..01368f5 100644 --- a/core/src/database/query/parent.rs +++ b/core/src/database/query/parent.rs @@ -1,18 +1,33 @@ +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, + 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?? + pub async fn find_candidate_parents( + db: &DbConn, + candidate: candidate::Model, + ) -> Result, DbErr> { + + candidate.find_related(parent::Entity) + .all(db) + .await } } 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.rs b/core/src/models/candidate.rs index 0f7688c..14c4082 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,11 +42,24 @@ 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 parents: Vec, } /// CSV export (admin endpoint) diff --git a/core/src/models/candidate_details.rs b/core/src/models/candidate_details.rs index f0de1fb..3efcbaa 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)] +#[derive(Debug, Clone)] pub struct EncryptedString(String); -#[derive(Clone)] -pub struct EncryptedApplicationDetails { - // Candidate +#[derive(Debug, Clone)] +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(Debug, Clone)] +pub struct EncryptedParentDetails { + pub name: EncryptedString, + pub surname: EncryptedString, + pub telephone: EncryptedString, + pub email: EncryptedString, +} +#[derive(Debug, Clone)] +pub struct EncryptedApplicationDetails { + pub candidate: EncryptedCandidateDetails, + pub parents: Vec, } impl EncryptedString { @@ -86,11 +94,11 @@ impl TryFrom> for EncryptedString { } } -impl EncryptedApplicationDetails { +impl EncryptedCandidateDetails { pub async fn new( - form: &ApplicationDetails, + form: &CandidateDetails, recipients: Vec, - ) -> Result { + ) -> Result { let birthdate_str = form.birthdate.format(NAIVE_DATE_FMT).to_string(); let d = tokio::try_join!( EncryptedString::new(&form.name, &recipients), @@ -103,34 +111,26 @@ impl EncryptedApplicationDetails { EncryptedString::new(&form.email, &recipients), EncryptedString::new(&form.sex, &recipients), EncryptedString::new(&form.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), )?; - 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(), - - parent_name: d.10, - parent_surname: d.11, - parent_telephone: d.12, - parent_email: d.13, - }) + Ok( + 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.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.name.decrypt(&priv_key), // 0 self.surname.decrypt(&priv_key), // 1 @@ -142,56 +142,153 @@ impl EncryptedApplicationDetails { 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 )?; - 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, + Ok(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.study, + } + ) + } +} +impl TryFrom for EncryptedCandidateDetails { + type Error = ServiceError; - parent_name: d.10, - parent_surname: d.11, - parent_telephone: d.12, - parent_email: d.13, + fn try_from( + candidate: candidate::Model, + ) -> Result { + Ok( + 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)?, + } + ) + } +} + +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 = EncryptedCandidateDetails::new(&form.candidate, recipients.clone()).await?; + let mut enc_parents= vec![]; + for parent in form.parents.iter() { + enc_parents.push( + EncryptedParentDetails::new(parent, recipients.clone()).await? + ); + } + Ok( + EncryptedApplicationDetails { + candidate, + parents: enc_parents, + } + ) + } + + pub async fn decrypt(self, priv_key: String) -> Result { + let candidate = self.candidate.decrypt(priv_key.clone()).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: parents, }) } } -impl TryFrom<(candidate::Model, parent::Model)> for EncryptedApplicationDetails { +// TODO: use different metehod for this +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 { - 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_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)?, + candidate: EncryptedCandidateDetails::try_from(candidate)?, + parents: enc_parents, }) } } @@ -203,22 +300,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)?, + }, + 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)?, + }] - 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 +341,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 +350,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(), + }, + parents: vec![ParentDetails { + name: "parent_name".to_string(), + surname: "parent_surname".to_string(), + telephone: "parent_telephone".to_string(), + email: "parent_email".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.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] @@ -295,19 +400,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..88a737a 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? @@ -30,39 +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_parent_by_id(db, application) - .await? - .ok_or(ServiceError::ParentNotFound)?; - - 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.clone()), - ParentService::add_parent_details(db, parent, enc_details.clone()) - )? + ( + 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_parent_by_id(db, application_id).await? - .ok_or(ServiceError::ParentNotFound)?; - let enc_details = EncryptedApplicationDetails::try_from((candidate, parent))?; + 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 cdd7c3e..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}, + 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,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 parents = Query::find_candidate_parents(db, candidate.clone()).await?; // TODO let new_password_plain = crypto::random_8_char_string(); @@ -125,12 +124,12 @@ impl CandidateService { .await?; let enc_details_opt = EncryptedApplicationDetails::try_from( - (candidate, parent) + (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( @@ -150,9 +149,11 @@ impl CandidateService { pub(in crate::services) async fn add_candidate_details( db: &DbConn, candidate: candidate::Model, - enc_details: EncryptedApplicationDetails, + details: &CandidateDetails, ) -> Result { - let model = Mutation::add_candidate_details(db, candidate, enc_details.clone()).await?; + 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) } @@ -373,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( @@ -383,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 3372201..f25664d 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::EncryptedApplicationDetails}; +use crate::{error::ServiceError, Mutation, models::{candidate_details::{EncryptedParentDetails}, candidate::ParentDetails}, Query, utils::db::get_recipients}; pub struct ParentService; @@ -16,14 +16,140 @@ impl ParentService { Ok(parent) } - pub async fn add_parent_details( + /* 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?; 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 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() { + 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(); + } + + #[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 diff --git a/core/src/utils/csv.rs b/core/src/utils/csv.rs index 54cc898..c741628 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.parents[0].clone(); 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), } } } 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, 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,