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,